stow-cli 2.2.0 → 2.2.1
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/backfill-JCNPLFJU.js +67 -0
- package/dist/backfill-VAORMLMY.js +67 -0
- package/dist/buckets-AFNX7FV3.js +137 -0
- package/dist/buckets-VYI2QVLO.js +137 -0
- package/dist/chunk-5BVMPHKH.js +147 -0
- package/dist/chunk-5IX3ASXH.js +153 -0
- package/dist/cli.js +40 -38
- package/dist/describe-CU5FBHZS.js +79 -0
- package/dist/describe-HSEHMJVD.js +79 -0
- package/dist/files-CFOTEASC.js +206 -0
- package/dist/files-XU6MDPP4.js +206 -0
- package/dist/health-SH6T6DZS.js +61 -0
- package/dist/health-YLNNKAFP.js +61 -0
- package/dist/jobs-RMRGXLAA.js +90 -0
- package/dist/jobs-ROJFRPMR.js +90 -0
- package/dist/jobs-TND5AHCL.js +102 -0
- package/dist/jobs-TOLVGN6K.js +102 -0
- package/dist/maintenance-6XNJ56LL.js +79 -0
- package/dist/maintenance-V2TXPXQE.js +79 -0
- package/dist/profiles-MB3TZQE4.js +53 -0
- package/dist/profiles-NVCJCYXR.js +53 -0
- package/dist/queues-AUGTAFBT.js +61 -0
- package/dist/queues-NR25TGT7.js +61 -0
- package/dist/search-ETC2EXKM.js +135 -0
- package/dist/search-ICJO264J.js +135 -0
- package/dist/tags-75SSHS26.js +90 -0
- package/dist/tags-TBFPDHIQ.js +90 -0
- package/package.json +12 -12
- package/LICENSE +0 -21
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import {
|
|
2
|
+
adminRequest
|
|
3
|
+
} from "./chunk-QF7PVPWQ.js";
|
|
4
|
+
import {
|
|
5
|
+
output
|
|
6
|
+
} from "./chunk-5BVMPHKH.js";
|
|
7
|
+
import "./chunk-TOADDO2F.js";
|
|
8
|
+
|
|
9
|
+
// src/commands/admin/backfill.ts
|
|
10
|
+
async function runBackfill(type, options) {
|
|
11
|
+
const parsedLimit = options.limit ? Number.parseInt(options.limit, 10) : null;
|
|
12
|
+
const body = {};
|
|
13
|
+
if (options.bucket) {
|
|
14
|
+
body.bucketId = options.bucket;
|
|
15
|
+
}
|
|
16
|
+
if (parsedLimit && parsedLimit > 0) {
|
|
17
|
+
body.limit = parsedLimit;
|
|
18
|
+
}
|
|
19
|
+
if (options.dryRun) {
|
|
20
|
+
body.dryRun = true;
|
|
21
|
+
}
|
|
22
|
+
const result = await adminRequest({
|
|
23
|
+
method: "POST",
|
|
24
|
+
path: `/internal/files/${type}/backfill`,
|
|
25
|
+
body
|
|
26
|
+
});
|
|
27
|
+
output(
|
|
28
|
+
result,
|
|
29
|
+
() => {
|
|
30
|
+
const lines = [];
|
|
31
|
+
if (result.dryRun) {
|
|
32
|
+
lines.push(
|
|
33
|
+
`[dry run] ${result.remaining} files need ${type} processing`
|
|
34
|
+
);
|
|
35
|
+
return lines.join("\n");
|
|
36
|
+
}
|
|
37
|
+
lines.push(`Enqueued: ${result.enqueued ?? 0}`);
|
|
38
|
+
lines.push(`Remaining: ${result.remaining}`);
|
|
39
|
+
if (result.errors && result.errors.length > 0) {
|
|
40
|
+
lines.push(`
|
|
41
|
+
Errors (${result.errors.length}):`);
|
|
42
|
+
for (const err of result.errors) {
|
|
43
|
+
lines.push(` ${err.fileId}: ${err.error}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (result.nextCursor) {
|
|
47
|
+
lines.push("\n(more files available -- run again to continue)");
|
|
48
|
+
}
|
|
49
|
+
return lines.join("\n");
|
|
50
|
+
},
|
|
51
|
+
{ json: options.json }
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
async function backfillDimensions(options) {
|
|
55
|
+
await runBackfill("dimensions", options);
|
|
56
|
+
}
|
|
57
|
+
async function backfillColors(options) {
|
|
58
|
+
await runBackfill("colors", options);
|
|
59
|
+
}
|
|
60
|
+
async function backfillEmbeddings(options) {
|
|
61
|
+
await runBackfill("embeddings", options);
|
|
62
|
+
}
|
|
63
|
+
export {
|
|
64
|
+
backfillColors,
|
|
65
|
+
backfillDimensions,
|
|
66
|
+
backfillEmbeddings
|
|
67
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import {
|
|
2
|
+
adminRequest
|
|
3
|
+
} from "./chunk-QF7PVPWQ.js";
|
|
4
|
+
import {
|
|
5
|
+
output
|
|
6
|
+
} from "./chunk-5IX3ASXH.js";
|
|
7
|
+
import "./chunk-TOADDO2F.js";
|
|
8
|
+
|
|
9
|
+
// src/commands/admin/backfill.ts
|
|
10
|
+
async function runBackfill(type, options) {
|
|
11
|
+
const parsedLimit = options.limit ? Number.parseInt(options.limit, 10) : null;
|
|
12
|
+
const body = {};
|
|
13
|
+
if (options.bucket) {
|
|
14
|
+
body.bucketId = options.bucket;
|
|
15
|
+
}
|
|
16
|
+
if (parsedLimit && parsedLimit > 0) {
|
|
17
|
+
body.limit = parsedLimit;
|
|
18
|
+
}
|
|
19
|
+
if (options.dryRun) {
|
|
20
|
+
body.dryRun = true;
|
|
21
|
+
}
|
|
22
|
+
const result = await adminRequest({
|
|
23
|
+
method: "POST",
|
|
24
|
+
path: `/internal/files/${type}/backfill`,
|
|
25
|
+
body
|
|
26
|
+
});
|
|
27
|
+
output(
|
|
28
|
+
result,
|
|
29
|
+
() => {
|
|
30
|
+
const lines = [];
|
|
31
|
+
if (result.dryRun) {
|
|
32
|
+
lines.push(
|
|
33
|
+
`[dry run] ${result.remaining} files need ${type} processing`
|
|
34
|
+
);
|
|
35
|
+
return lines.join("\n");
|
|
36
|
+
}
|
|
37
|
+
lines.push(`Enqueued: ${result.enqueued ?? 0}`);
|
|
38
|
+
lines.push(`Remaining: ${result.remaining}`);
|
|
39
|
+
if (result.errors && result.errors.length > 0) {
|
|
40
|
+
lines.push(`
|
|
41
|
+
Errors (${result.errors.length}):`);
|
|
42
|
+
for (const err of result.errors) {
|
|
43
|
+
lines.push(` ${err.fileId}: ${err.error}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (result.nextCursor) {
|
|
47
|
+
lines.push("\n(more files available -- run again to continue)");
|
|
48
|
+
}
|
|
49
|
+
return lines.join("\n");
|
|
50
|
+
},
|
|
51
|
+
{ json: options.json }
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
async function backfillDimensions(options) {
|
|
55
|
+
await runBackfill("dimensions", options);
|
|
56
|
+
}
|
|
57
|
+
async function backfillColors(options) {
|
|
58
|
+
await runBackfill("colors", options);
|
|
59
|
+
}
|
|
60
|
+
async function backfillEmbeddings(options) {
|
|
61
|
+
await runBackfill("embeddings", options);
|
|
62
|
+
}
|
|
63
|
+
export {
|
|
64
|
+
backfillColors,
|
|
65
|
+
backfillDimensions,
|
|
66
|
+
backfillEmbeddings
|
|
67
|
+
};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parseJsonInput
|
|
3
|
+
} from "./chunk-3BLL5SQJ.js";
|
|
4
|
+
import {
|
|
5
|
+
validateBucketName
|
|
6
|
+
} from "./chunk-PLZFHPLC.js";
|
|
7
|
+
import {
|
|
8
|
+
isJsonOutput,
|
|
9
|
+
output
|
|
10
|
+
} from "./chunk-5IX3ASXH.js";
|
|
11
|
+
import {
|
|
12
|
+
formatBytes,
|
|
13
|
+
formatTable
|
|
14
|
+
} from "./chunk-FZGOTXTE.js";
|
|
15
|
+
import {
|
|
16
|
+
createStow
|
|
17
|
+
} from "./chunk-5LU25QZK.js";
|
|
18
|
+
import "./chunk-TOADDO2F.js";
|
|
19
|
+
|
|
20
|
+
// src/commands/buckets.ts
|
|
21
|
+
async function listBuckets() {
|
|
22
|
+
const stow = createStow();
|
|
23
|
+
const data = await stow.listBuckets();
|
|
24
|
+
if (data.buckets.length === 0) {
|
|
25
|
+
if (isJsonOutput()) {
|
|
26
|
+
output(data);
|
|
27
|
+
} else {
|
|
28
|
+
console.log(
|
|
29
|
+
"No buckets yet. Create one with: stow buckets create <name>"
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
output(data, () => {
|
|
35
|
+
const rows = data.buckets.map((b) => [
|
|
36
|
+
b.name,
|
|
37
|
+
b.isPublic ? "public" : "private",
|
|
38
|
+
b.searchable ? "yes" : "no",
|
|
39
|
+
`${b.fileCount ?? 0} files`,
|
|
40
|
+
formatBytes(b.usageBytes ?? 0),
|
|
41
|
+
b.description || ""
|
|
42
|
+
]);
|
|
43
|
+
return formatTable(
|
|
44
|
+
["Name", "Access", "Search", "Files", "Size", "Description"],
|
|
45
|
+
rows
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
async function createBucket(name, options) {
|
|
50
|
+
const input = parseJsonInput(options.inputJson, {
|
|
51
|
+
name,
|
|
52
|
+
description: options.description,
|
|
53
|
+
public: options.public
|
|
54
|
+
});
|
|
55
|
+
validateBucketName(input.name);
|
|
56
|
+
if (options.dryRun) {
|
|
57
|
+
console.log(
|
|
58
|
+
JSON.stringify(
|
|
59
|
+
{
|
|
60
|
+
dryRun: true,
|
|
61
|
+
action: "createBucket",
|
|
62
|
+
details: {
|
|
63
|
+
name: input.name,
|
|
64
|
+
description: input.description ?? null,
|
|
65
|
+
isPublic: input.public ?? false
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
null,
|
|
69
|
+
2
|
|
70
|
+
)
|
|
71
|
+
);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const stow = createStow();
|
|
75
|
+
const bucket = await stow.createBucket({
|
|
76
|
+
name: input.name,
|
|
77
|
+
...input.description ? { description: input.description } : {},
|
|
78
|
+
...input.public ? { isPublic: true } : {}
|
|
79
|
+
});
|
|
80
|
+
console.log(`Created bucket: ${bucket.name}`);
|
|
81
|
+
}
|
|
82
|
+
async function renameBucket(name, newName, options) {
|
|
83
|
+
const input = parseJsonInput(
|
|
84
|
+
options.inputJson,
|
|
85
|
+
{ name, newName }
|
|
86
|
+
);
|
|
87
|
+
validateBucketName(input.name);
|
|
88
|
+
validateBucketName(input.newName);
|
|
89
|
+
if (options.dryRun) {
|
|
90
|
+
console.log(
|
|
91
|
+
JSON.stringify(
|
|
92
|
+
{
|
|
93
|
+
dryRun: true,
|
|
94
|
+
action: "renameBucket",
|
|
95
|
+
details: { name: input.name, newName: input.newName }
|
|
96
|
+
},
|
|
97
|
+
null,
|
|
98
|
+
2
|
|
99
|
+
)
|
|
100
|
+
);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (!options.yes) {
|
|
104
|
+
console.error(
|
|
105
|
+
"Warning: Renaming a bucket will break any existing URLs using the old name."
|
|
106
|
+
);
|
|
107
|
+
console.error("Use --yes to skip this warning.");
|
|
108
|
+
}
|
|
109
|
+
const stow = createStow();
|
|
110
|
+
const bucket = await stow.renameBucket(input.name, input.newName);
|
|
111
|
+
console.log(`Renamed bucket: ${input.name} \u2192 ${bucket.name}`);
|
|
112
|
+
}
|
|
113
|
+
async function deleteBucket(id, options = {}) {
|
|
114
|
+
if (options.dryRun) {
|
|
115
|
+
console.log(
|
|
116
|
+
JSON.stringify(
|
|
117
|
+
{
|
|
118
|
+
dryRun: true,
|
|
119
|
+
action: "deleteBucket",
|
|
120
|
+
details: { id }
|
|
121
|
+
},
|
|
122
|
+
null,
|
|
123
|
+
2
|
|
124
|
+
)
|
|
125
|
+
);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const stow = createStow();
|
|
129
|
+
await stow.deleteBucket(id);
|
|
130
|
+
console.log(`Deleted bucket: ${id}`);
|
|
131
|
+
}
|
|
132
|
+
export {
|
|
133
|
+
createBucket,
|
|
134
|
+
deleteBucket,
|
|
135
|
+
listBuckets,
|
|
136
|
+
renameBucket
|
|
137
|
+
};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parseJsonInput
|
|
3
|
+
} from "./chunk-3BLL5SQJ.js";
|
|
4
|
+
import {
|
|
5
|
+
validateBucketName
|
|
6
|
+
} from "./chunk-PLZFHPLC.js";
|
|
7
|
+
import {
|
|
8
|
+
isJsonOutput,
|
|
9
|
+
output
|
|
10
|
+
} from "./chunk-5BVMPHKH.js";
|
|
11
|
+
import {
|
|
12
|
+
formatBytes,
|
|
13
|
+
formatTable
|
|
14
|
+
} from "./chunk-FZGOTXTE.js";
|
|
15
|
+
import {
|
|
16
|
+
createStow
|
|
17
|
+
} from "./chunk-5LU25QZK.js";
|
|
18
|
+
import "./chunk-TOADDO2F.js";
|
|
19
|
+
|
|
20
|
+
// src/commands/buckets.ts
|
|
21
|
+
async function listBuckets() {
|
|
22
|
+
const stow = createStow();
|
|
23
|
+
const data = await stow.listBuckets();
|
|
24
|
+
if (data.buckets.length === 0) {
|
|
25
|
+
if (isJsonOutput()) {
|
|
26
|
+
output(data);
|
|
27
|
+
} else {
|
|
28
|
+
console.log(
|
|
29
|
+
"No buckets yet. Create one with: stow buckets create <name>"
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
output(data, () => {
|
|
35
|
+
const rows = data.buckets.map((b) => [
|
|
36
|
+
b.name,
|
|
37
|
+
b.isPublic ? "public" : "private",
|
|
38
|
+
b.searchable ? "yes" : "no",
|
|
39
|
+
`${b.fileCount ?? 0} files`,
|
|
40
|
+
formatBytes(b.usageBytes ?? 0),
|
|
41
|
+
b.description || ""
|
|
42
|
+
]);
|
|
43
|
+
return formatTable(
|
|
44
|
+
["Name", "Access", "Search", "Files", "Size", "Description"],
|
|
45
|
+
rows
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
async function createBucket(name, options) {
|
|
50
|
+
const input = parseJsonInput(options.inputJson, {
|
|
51
|
+
name,
|
|
52
|
+
description: options.description,
|
|
53
|
+
public: options.public
|
|
54
|
+
});
|
|
55
|
+
validateBucketName(input.name);
|
|
56
|
+
if (options.dryRun) {
|
|
57
|
+
console.log(
|
|
58
|
+
JSON.stringify(
|
|
59
|
+
{
|
|
60
|
+
dryRun: true,
|
|
61
|
+
action: "createBucket",
|
|
62
|
+
details: {
|
|
63
|
+
name: input.name,
|
|
64
|
+
description: input.description ?? null,
|
|
65
|
+
isPublic: input.public ?? false
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
null,
|
|
69
|
+
2
|
|
70
|
+
)
|
|
71
|
+
);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const stow = createStow();
|
|
75
|
+
const bucket = await stow.createBucket({
|
|
76
|
+
name: input.name,
|
|
77
|
+
...input.description ? { description: input.description } : {},
|
|
78
|
+
...input.public ? { isPublic: true } : {}
|
|
79
|
+
});
|
|
80
|
+
console.log(`Created bucket: ${bucket.name}`);
|
|
81
|
+
}
|
|
82
|
+
async function renameBucket(name, newName, options) {
|
|
83
|
+
const input = parseJsonInput(
|
|
84
|
+
options.inputJson,
|
|
85
|
+
{ name, newName }
|
|
86
|
+
);
|
|
87
|
+
validateBucketName(input.name);
|
|
88
|
+
validateBucketName(input.newName);
|
|
89
|
+
if (options.dryRun) {
|
|
90
|
+
console.log(
|
|
91
|
+
JSON.stringify(
|
|
92
|
+
{
|
|
93
|
+
dryRun: true,
|
|
94
|
+
action: "renameBucket",
|
|
95
|
+
details: { name: input.name, newName: input.newName }
|
|
96
|
+
},
|
|
97
|
+
null,
|
|
98
|
+
2
|
|
99
|
+
)
|
|
100
|
+
);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (!options.yes) {
|
|
104
|
+
console.error(
|
|
105
|
+
"Warning: Renaming a bucket will break any existing URLs using the old name."
|
|
106
|
+
);
|
|
107
|
+
console.error("Use --yes to skip this warning.");
|
|
108
|
+
}
|
|
109
|
+
const stow = createStow();
|
|
110
|
+
const bucket = await stow.renameBucket(input.name, input.newName);
|
|
111
|
+
console.log(`Renamed bucket: ${input.name} \u2192 ${bucket.name}`);
|
|
112
|
+
}
|
|
113
|
+
async function deleteBucket(id, options = {}) {
|
|
114
|
+
if (options.dryRun) {
|
|
115
|
+
console.log(
|
|
116
|
+
JSON.stringify(
|
|
117
|
+
{
|
|
118
|
+
dryRun: true,
|
|
119
|
+
action: "deleteBucket",
|
|
120
|
+
details: { id }
|
|
121
|
+
},
|
|
122
|
+
null,
|
|
123
|
+
2
|
|
124
|
+
)
|
|
125
|
+
);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const stow = createStow();
|
|
129
|
+
await stow.deleteBucket(id);
|
|
130
|
+
console.log(`Deleted bucket: ${id}`);
|
|
131
|
+
}
|
|
132
|
+
export {
|
|
133
|
+
createBucket,
|
|
134
|
+
deleteBucket,
|
|
135
|
+
listBuckets,
|
|
136
|
+
renameBucket
|
|
137
|
+
};
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// src/lib/sanitize-response.ts
|
|
2
|
+
var INJECTION_PATTERNS = [
|
|
3
|
+
// Direct instruction injection
|
|
4
|
+
/\b(?:ignore|disregard|forget)\b.*\b(?:previous|above|prior)\b.*\b(?:instructions?|rules?|context)\b/i,
|
|
5
|
+
// System prompt extraction attempts
|
|
6
|
+
/\b(?:reveal|show|print|output|display)\b.*\b(?:system\s*prompt|instructions?|rules?)\b/i,
|
|
7
|
+
// Role hijacking
|
|
8
|
+
/\byou\s+are\s+(?:now|a)\b/i,
|
|
9
|
+
// Tool/action injection
|
|
10
|
+
/\b(?:execute|run|call)\b.*\b(?:command|tool|function|bash|shell)\b/i,
|
|
11
|
+
// Markdown/XML injection that could affect agent parsing
|
|
12
|
+
/<\/?(?:system|user|assistant|tool_use|tool_result)\b/i
|
|
13
|
+
];
|
|
14
|
+
var USER_CONTENT_FIELDS = /* @__PURE__ */ new Set([
|
|
15
|
+
"originalFilename",
|
|
16
|
+
"filename",
|
|
17
|
+
"name",
|
|
18
|
+
"description",
|
|
19
|
+
"label",
|
|
20
|
+
"text",
|
|
21
|
+
"slug",
|
|
22
|
+
"webhookUrl"
|
|
23
|
+
]);
|
|
24
|
+
function detectInjection(value) {
|
|
25
|
+
return INJECTION_PATTERNS.some((pattern) => pattern.test(value));
|
|
26
|
+
}
|
|
27
|
+
function sanitizeValue(value) {
|
|
28
|
+
if (detectInjection(value)) {
|
|
29
|
+
return `[FLAGGED: potential prompt injection] ${value}`;
|
|
30
|
+
}
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
function sanitizeResponse(data) {
|
|
34
|
+
if (data === null || data === void 0) return data;
|
|
35
|
+
if (typeof data === "string") {
|
|
36
|
+
return sanitizeValue(data);
|
|
37
|
+
}
|
|
38
|
+
if (Array.isArray(data)) {
|
|
39
|
+
return data.map((item) => sanitizeResponse(item));
|
|
40
|
+
}
|
|
41
|
+
if (typeof data === "object") {
|
|
42
|
+
const result = {};
|
|
43
|
+
for (const [key, value] of Object.entries(
|
|
44
|
+
data
|
|
45
|
+
)) {
|
|
46
|
+
if (USER_CONTENT_FIELDS.has(key) && typeof value === "string") {
|
|
47
|
+
result[key] = sanitizeValue(value);
|
|
48
|
+
} else if (typeof value === "object" && value !== null) {
|
|
49
|
+
result[key] = sanitizeResponse(value);
|
|
50
|
+
} else {
|
|
51
|
+
result[key] = value;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
return data;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// src/lib/output.ts
|
|
60
|
+
var _forceHuman = false;
|
|
61
|
+
var _globalFields;
|
|
62
|
+
var _globalNdjson = false;
|
|
63
|
+
function setForceHuman(value) {
|
|
64
|
+
_forceHuman = value;
|
|
65
|
+
}
|
|
66
|
+
function setGlobalFields(fields) {
|
|
67
|
+
_globalFields = fields;
|
|
68
|
+
}
|
|
69
|
+
function setGlobalNdjson(value) {
|
|
70
|
+
_globalNdjson = value;
|
|
71
|
+
}
|
|
72
|
+
function isJsonOutput() {
|
|
73
|
+
if (_forceHuman) return false;
|
|
74
|
+
return !process.stdout.isTTY;
|
|
75
|
+
}
|
|
76
|
+
function output(data, humanFormatter, options) {
|
|
77
|
+
const sanitized = sanitizeResponse(data);
|
|
78
|
+
const unwrapped = _globalFields || _globalNdjson ? unwrapArray(sanitized) : sanitized;
|
|
79
|
+
const masked = applyFieldMask(unwrapped, _globalFields);
|
|
80
|
+
if (_globalNdjson && Array.isArray(masked)) {
|
|
81
|
+
outputNdjson(masked);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (options?.json || isJsonOutput()) {
|
|
85
|
+
console.log(JSON.stringify(masked, null, 2));
|
|
86
|
+
} else if (humanFormatter && !_globalFields) {
|
|
87
|
+
console.log(humanFormatter());
|
|
88
|
+
} else {
|
|
89
|
+
console.log(JSON.stringify(masked, null, 2));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function outputError(error, code, details) {
|
|
93
|
+
if (isJsonOutput()) {
|
|
94
|
+
console.error(
|
|
95
|
+
JSON.stringify({ error, ...code ? { code } : {}, ...details })
|
|
96
|
+
);
|
|
97
|
+
} else {
|
|
98
|
+
console.error(`Error: ${error}`);
|
|
99
|
+
}
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
function outputNdjson(items) {
|
|
103
|
+
for (const item of items) {
|
|
104
|
+
const sanitized = sanitizeResponse(item);
|
|
105
|
+
console.log(JSON.stringify(sanitized));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function applyFieldMask(data, fields) {
|
|
109
|
+
if (!fields) return data;
|
|
110
|
+
const fieldSet = new Set(fields.split(",").map((f) => f.trim()));
|
|
111
|
+
if (Array.isArray(data)) {
|
|
112
|
+
return data.map((item) => pickFields(item, fieldSet));
|
|
113
|
+
}
|
|
114
|
+
if (typeof data === "object" && data !== null) {
|
|
115
|
+
return pickFields(data, fieldSet);
|
|
116
|
+
}
|
|
117
|
+
return data;
|
|
118
|
+
}
|
|
119
|
+
function unwrapArray(data) {
|
|
120
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
121
|
+
return data;
|
|
122
|
+
}
|
|
123
|
+
const entries = Object.entries(data);
|
|
124
|
+
if (entries.length === 1 && Array.isArray(entries[0][1])) {
|
|
125
|
+
return entries[0][1];
|
|
126
|
+
}
|
|
127
|
+
return data;
|
|
128
|
+
}
|
|
129
|
+
function pickFields(obj, fieldSet) {
|
|
130
|
+
if (typeof obj !== "object" || obj === null) return {};
|
|
131
|
+
const result = {};
|
|
132
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
133
|
+
if (fieldSet.has(key)) {
|
|
134
|
+
result[key] = value;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export {
|
|
141
|
+
setForceHuman,
|
|
142
|
+
setGlobalFields,
|
|
143
|
+
setGlobalNdjson,
|
|
144
|
+
isJsonOutput,
|
|
145
|
+
output,
|
|
146
|
+
outputError
|
|
147
|
+
};
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// src/lib/sanitize-response.ts
|
|
2
|
+
var INJECTION_PATTERNS = [
|
|
3
|
+
// Direct instruction injection
|
|
4
|
+
/\b(?:ignore|disregard|forget)\b.*\b(?:previous|above|prior)\b.*\b(?:instructions?|rules?|context)\b/i,
|
|
5
|
+
// System prompt extraction attempts
|
|
6
|
+
/\b(?:reveal|show|print|output|display)\b.*\b(?:system\s*prompt|instructions?|rules?)\b/i,
|
|
7
|
+
// Role hijacking
|
|
8
|
+
/\byou\s+are\s+(?:now|a)\b/i,
|
|
9
|
+
// Tool/action injection
|
|
10
|
+
/\b(?:execute|run|call)\b.*\b(?:command|tool|function|bash|shell)\b/i,
|
|
11
|
+
// Markdown/XML injection that could affect agent parsing
|
|
12
|
+
/<\/?(?:system|user|assistant|tool_use|tool_result)\b/i
|
|
13
|
+
];
|
|
14
|
+
var USER_CONTENT_FIELDS = /* @__PURE__ */ new Set([
|
|
15
|
+
"originalFilename",
|
|
16
|
+
"filename",
|
|
17
|
+
"name",
|
|
18
|
+
"description",
|
|
19
|
+
"label",
|
|
20
|
+
"text",
|
|
21
|
+
"slug",
|
|
22
|
+
"webhookUrl"
|
|
23
|
+
]);
|
|
24
|
+
function detectInjection(value) {
|
|
25
|
+
return INJECTION_PATTERNS.some((pattern) => pattern.test(value));
|
|
26
|
+
}
|
|
27
|
+
function sanitizeValue(value) {
|
|
28
|
+
if (detectInjection(value)) {
|
|
29
|
+
return `[FLAGGED: potential prompt injection] ${value}`;
|
|
30
|
+
}
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
function sanitizeResponse(data) {
|
|
34
|
+
if (data === null || data === void 0) return data;
|
|
35
|
+
if (typeof data === "string") {
|
|
36
|
+
return sanitizeValue(data);
|
|
37
|
+
}
|
|
38
|
+
if (Array.isArray(data)) {
|
|
39
|
+
return data.map((item) => sanitizeResponse(item));
|
|
40
|
+
}
|
|
41
|
+
if (typeof data === "object") {
|
|
42
|
+
const result = {};
|
|
43
|
+
for (const [key, value] of Object.entries(
|
|
44
|
+
data
|
|
45
|
+
)) {
|
|
46
|
+
if (USER_CONTENT_FIELDS.has(key) && typeof value === "string") {
|
|
47
|
+
result[key] = sanitizeValue(value);
|
|
48
|
+
} else if (typeof value === "object" && value !== null) {
|
|
49
|
+
result[key] = sanitizeResponse(value);
|
|
50
|
+
} else {
|
|
51
|
+
result[key] = value;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
return data;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// src/lib/output.ts
|
|
60
|
+
var _forceHuman = false;
|
|
61
|
+
var _globalFields;
|
|
62
|
+
var _globalNdjson = false;
|
|
63
|
+
function setForceHuman(value) {
|
|
64
|
+
_forceHuman = value;
|
|
65
|
+
}
|
|
66
|
+
function setGlobalFields(fields) {
|
|
67
|
+
_globalFields = fields;
|
|
68
|
+
}
|
|
69
|
+
function setGlobalNdjson(value) {
|
|
70
|
+
_globalNdjson = value;
|
|
71
|
+
}
|
|
72
|
+
function isJsonOutput() {
|
|
73
|
+
if (_forceHuman) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
return !process.stdout.isTTY;
|
|
77
|
+
}
|
|
78
|
+
function output(data, humanFormatter, options) {
|
|
79
|
+
const sanitized = sanitizeResponse(data);
|
|
80
|
+
const unwrapped = _globalFields || _globalNdjson ? unwrapArray(sanitized) : sanitized;
|
|
81
|
+
const masked = applyFieldMask(unwrapped, _globalFields);
|
|
82
|
+
if (_globalNdjson && Array.isArray(masked)) {
|
|
83
|
+
outputNdjson(masked);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (options?.json || isJsonOutput()) {
|
|
87
|
+
console.log(JSON.stringify(masked, null, 2));
|
|
88
|
+
} else if (humanFormatter && !_globalFields) {
|
|
89
|
+
console.log(humanFormatter());
|
|
90
|
+
} else {
|
|
91
|
+
console.log(JSON.stringify(masked, null, 2));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function outputError(error, code, details) {
|
|
95
|
+
if (isJsonOutput()) {
|
|
96
|
+
console.error(
|
|
97
|
+
JSON.stringify({ error, ...code ? { code } : {}, ...details })
|
|
98
|
+
);
|
|
99
|
+
} else {
|
|
100
|
+
console.error(`Error: ${error}`);
|
|
101
|
+
}
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
function outputNdjson(items) {
|
|
105
|
+
for (const item of items) {
|
|
106
|
+
const sanitized = sanitizeResponse(item);
|
|
107
|
+
console.log(JSON.stringify(sanitized));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function applyFieldMask(data, fields) {
|
|
111
|
+
if (!fields) {
|
|
112
|
+
return data;
|
|
113
|
+
}
|
|
114
|
+
const fieldSet = new Set(fields.split(",").map((f) => f.trim()));
|
|
115
|
+
if (Array.isArray(data)) {
|
|
116
|
+
return data.map((item) => pickFields(item, fieldSet));
|
|
117
|
+
}
|
|
118
|
+
if (typeof data === "object" && data !== null) {
|
|
119
|
+
return pickFields(data, fieldSet);
|
|
120
|
+
}
|
|
121
|
+
return data;
|
|
122
|
+
}
|
|
123
|
+
function unwrapArray(data) {
|
|
124
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
125
|
+
return data;
|
|
126
|
+
}
|
|
127
|
+
const entries = Object.entries(data);
|
|
128
|
+
if (entries.length === 1 && Array.isArray(entries[0][1])) {
|
|
129
|
+
return entries[0][1];
|
|
130
|
+
}
|
|
131
|
+
return data;
|
|
132
|
+
}
|
|
133
|
+
function pickFields(obj, fieldSet) {
|
|
134
|
+
if (typeof obj !== "object" || obj === null) {
|
|
135
|
+
return {};
|
|
136
|
+
}
|
|
137
|
+
const result = {};
|
|
138
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
139
|
+
if (fieldSet.has(key)) {
|
|
140
|
+
result[key] = value;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export {
|
|
147
|
+
setForceHuman,
|
|
148
|
+
setGlobalFields,
|
|
149
|
+
setGlobalNdjson,
|
|
150
|
+
isJsonOutput,
|
|
151
|
+
output,
|
|
152
|
+
outputError
|
|
153
|
+
};
|