@riddledc/openclaw-riddledc 0.5.6 → 0.6.0
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/CHECKSUMS.txt +2 -2
- package/dist/index.cjs +107 -0
- package/dist/index.js +108 -1
- package/openclaw.plugin.json +7 -4
- package/package.json +1 -1
package/CHECKSUMS.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
876e2b78ca5f38ca4da27097761bcdb99882a5724f025d5befd5ba78710c5832 dist/index.cjs
|
|
2
2
|
94ce04f0e2d84bf64dd68f0500dfdd2f951287a3deccec87f197261961927f6f dist/index.d.cts
|
|
3
3
|
94ce04f0e2d84bf64dd68f0500dfdd2f951287a3deccec87f197261961927f6f dist/index.d.ts
|
|
4
|
-
|
|
4
|
+
c481cef6e2fd6aca8d7aef042bc7b8cb5fc12274afe9c50ba10947e398c566ce dist/index.js
|
package/dist/index.cjs
CHANGED
|
@@ -26,6 +26,9 @@ module.exports = __toCommonJS(index_exports);
|
|
|
26
26
|
var import_typebox = require("@sinclair/typebox");
|
|
27
27
|
var import_promises = require("fs/promises");
|
|
28
28
|
var import_node_path = require("path");
|
|
29
|
+
var import_node_child_process = require("child_process");
|
|
30
|
+
var import_node_util = require("util");
|
|
31
|
+
var execFile = (0, import_node_util.promisify)(import_node_child_process.execFile);
|
|
29
32
|
var INLINE_CAP = 50 * 1024;
|
|
30
33
|
function getCfg(api) {
|
|
31
34
|
const cfg = api?.config ?? {};
|
|
@@ -753,4 +756,108 @@ function register(api) {
|
|
|
753
756
|
},
|
|
754
757
|
{ optional: true }
|
|
755
758
|
);
|
|
759
|
+
api.registerTool(
|
|
760
|
+
{
|
|
761
|
+
name: "riddle_preview",
|
|
762
|
+
description: "Deploy a local build directory as an ephemeral preview site. Tars the directory, uploads to Riddle, and returns a live URL at preview.riddledc.com that can be screenshotted with other riddle_* tools. Previews auto-expire after 24 hours.",
|
|
763
|
+
parameters: import_typebox.Type.Object({
|
|
764
|
+
directory: import_typebox.Type.String({ description: "Absolute path to the build output directory (e.g. /path/to/build or /path/to/dist)" }),
|
|
765
|
+
framework: import_typebox.Type.Optional(import_typebox.Type.String({ description: "Framework hint: 'spa' (default) or 'static'" }))
|
|
766
|
+
}),
|
|
767
|
+
async execute(_id, params) {
|
|
768
|
+
const { apiKey, baseUrl } = getCfg(api);
|
|
769
|
+
if (!apiKey) {
|
|
770
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: "Missing Riddle API key." }, null, 2) }] };
|
|
771
|
+
}
|
|
772
|
+
assertAllowedBaseUrl(baseUrl);
|
|
773
|
+
const dir = params.directory;
|
|
774
|
+
if (!dir || typeof dir !== "string") throw new Error("directory must be an absolute path");
|
|
775
|
+
try {
|
|
776
|
+
const st = await (0, import_promises.stat)(dir);
|
|
777
|
+
if (!st.isDirectory()) throw new Error(`Not a directory: ${dir}`);
|
|
778
|
+
} catch (e) {
|
|
779
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Cannot access directory: ${e.message}` }, null, 2) }] };
|
|
780
|
+
}
|
|
781
|
+
const endpoint = baseUrl.replace(/\/$/, "");
|
|
782
|
+
const createRes = await fetch(`${endpoint}/v1/preview`, {
|
|
783
|
+
method: "POST",
|
|
784
|
+
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
785
|
+
body: JSON.stringify({ framework: params.framework || "spa" })
|
|
786
|
+
});
|
|
787
|
+
if (!createRes.ok) {
|
|
788
|
+
const err = await createRes.text();
|
|
789
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Create failed: HTTP ${createRes.status} ${err}` }, null, 2) }] };
|
|
790
|
+
}
|
|
791
|
+
const created = await createRes.json();
|
|
792
|
+
const tarball = `/tmp/riddle-preview-${created.id}.tar.gz`;
|
|
793
|
+
try {
|
|
794
|
+
await execFile("tar", ["czf", tarball, "-C", dir, "."], { timeout: 6e4 });
|
|
795
|
+
const tarData = await (0, import_promises.readFile)(tarball);
|
|
796
|
+
const uploadRes = await fetch(created.upload_url, {
|
|
797
|
+
method: "PUT",
|
|
798
|
+
headers: { "Content-Type": "application/gzip" },
|
|
799
|
+
body: tarData
|
|
800
|
+
});
|
|
801
|
+
if (!uploadRes.ok) {
|
|
802
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, id: created.id, error: `Upload failed: HTTP ${uploadRes.status}` }, null, 2) }] };
|
|
803
|
+
}
|
|
804
|
+
} finally {
|
|
805
|
+
try {
|
|
806
|
+
await (0, import_promises.rm)(tarball, { force: true });
|
|
807
|
+
} catch {
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
const publishRes = await fetch(`${endpoint}/v1/preview/${created.id}/publish`, {
|
|
811
|
+
method: "POST",
|
|
812
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
813
|
+
});
|
|
814
|
+
if (!publishRes.ok) {
|
|
815
|
+
const err = await publishRes.text();
|
|
816
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, id: created.id, error: `Publish failed: HTTP ${publishRes.status} ${err}` }, null, 2) }] };
|
|
817
|
+
}
|
|
818
|
+
const published = await publishRes.json();
|
|
819
|
+
return {
|
|
820
|
+
content: [{
|
|
821
|
+
type: "text",
|
|
822
|
+
text: JSON.stringify({
|
|
823
|
+
ok: true,
|
|
824
|
+
id: published.id,
|
|
825
|
+
preview_url: published.preview_url,
|
|
826
|
+
file_count: published.file_count,
|
|
827
|
+
total_bytes: published.total_bytes,
|
|
828
|
+
expires_at: created.expires_at
|
|
829
|
+
}, null, 2)
|
|
830
|
+
}]
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
},
|
|
834
|
+
{ optional: true }
|
|
835
|
+
);
|
|
836
|
+
api.registerTool(
|
|
837
|
+
{
|
|
838
|
+
name: "riddle_preview_delete",
|
|
839
|
+
description: "Delete an ephemeral preview site created by riddle_preview. Removes all files and frees the preview ID immediately instead of waiting for auto-expiry.",
|
|
840
|
+
parameters: import_typebox.Type.Object({
|
|
841
|
+
id: import_typebox.Type.String({ description: "Preview ID (e.g. pv_a1b2c3d4)" })
|
|
842
|
+
}),
|
|
843
|
+
async execute(_id, params) {
|
|
844
|
+
const { apiKey, baseUrl } = getCfg(api);
|
|
845
|
+
if (!apiKey) {
|
|
846
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: "Missing Riddle API key." }, null, 2) }] };
|
|
847
|
+
}
|
|
848
|
+
assertAllowedBaseUrl(baseUrl);
|
|
849
|
+
const res = await fetch(`${baseUrl.replace(/\/$/, "")}/v1/preview/${params.id}`, {
|
|
850
|
+
method: "DELETE",
|
|
851
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
852
|
+
});
|
|
853
|
+
if (!res.ok) {
|
|
854
|
+
const err = await res.text();
|
|
855
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Delete failed: HTTP ${res.status} ${err}` }, null, 2) }] };
|
|
856
|
+
}
|
|
857
|
+
const data = await res.json();
|
|
858
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: true, deleted: true, files_removed: data.files_removed }, null, 2) }] };
|
|
859
|
+
}
|
|
860
|
+
},
|
|
861
|
+
{ optional: true }
|
|
862
|
+
);
|
|
756
863
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { Type } from "@sinclair/typebox";
|
|
3
|
-
import { writeFile, mkdir } from "fs/promises";
|
|
3
|
+
import { writeFile, mkdir, readFile, stat, rm } from "fs/promises";
|
|
4
4
|
import { join } from "path";
|
|
5
|
+
import { execFile as execFileCb } from "child_process";
|
|
6
|
+
import { promisify } from "util";
|
|
7
|
+
var execFile = promisify(execFileCb);
|
|
5
8
|
var INLINE_CAP = 50 * 1024;
|
|
6
9
|
function getCfg(api) {
|
|
7
10
|
const cfg = api?.config ?? {};
|
|
@@ -729,6 +732,110 @@ function register(api) {
|
|
|
729
732
|
},
|
|
730
733
|
{ optional: true }
|
|
731
734
|
);
|
|
735
|
+
api.registerTool(
|
|
736
|
+
{
|
|
737
|
+
name: "riddle_preview",
|
|
738
|
+
description: "Deploy a local build directory as an ephemeral preview site. Tars the directory, uploads to Riddle, and returns a live URL at preview.riddledc.com that can be screenshotted with other riddle_* tools. Previews auto-expire after 24 hours.",
|
|
739
|
+
parameters: Type.Object({
|
|
740
|
+
directory: Type.String({ description: "Absolute path to the build output directory (e.g. /path/to/build or /path/to/dist)" }),
|
|
741
|
+
framework: Type.Optional(Type.String({ description: "Framework hint: 'spa' (default) or 'static'" }))
|
|
742
|
+
}),
|
|
743
|
+
async execute(_id, params) {
|
|
744
|
+
const { apiKey, baseUrl } = getCfg(api);
|
|
745
|
+
if (!apiKey) {
|
|
746
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: "Missing Riddle API key." }, null, 2) }] };
|
|
747
|
+
}
|
|
748
|
+
assertAllowedBaseUrl(baseUrl);
|
|
749
|
+
const dir = params.directory;
|
|
750
|
+
if (!dir || typeof dir !== "string") throw new Error("directory must be an absolute path");
|
|
751
|
+
try {
|
|
752
|
+
const st = await stat(dir);
|
|
753
|
+
if (!st.isDirectory()) throw new Error(`Not a directory: ${dir}`);
|
|
754
|
+
} catch (e) {
|
|
755
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Cannot access directory: ${e.message}` }, null, 2) }] };
|
|
756
|
+
}
|
|
757
|
+
const endpoint = baseUrl.replace(/\/$/, "");
|
|
758
|
+
const createRes = await fetch(`${endpoint}/v1/preview`, {
|
|
759
|
+
method: "POST",
|
|
760
|
+
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
761
|
+
body: JSON.stringify({ framework: params.framework || "spa" })
|
|
762
|
+
});
|
|
763
|
+
if (!createRes.ok) {
|
|
764
|
+
const err = await createRes.text();
|
|
765
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Create failed: HTTP ${createRes.status} ${err}` }, null, 2) }] };
|
|
766
|
+
}
|
|
767
|
+
const created = await createRes.json();
|
|
768
|
+
const tarball = `/tmp/riddle-preview-${created.id}.tar.gz`;
|
|
769
|
+
try {
|
|
770
|
+
await execFile("tar", ["czf", tarball, "-C", dir, "."], { timeout: 6e4 });
|
|
771
|
+
const tarData = await readFile(tarball);
|
|
772
|
+
const uploadRes = await fetch(created.upload_url, {
|
|
773
|
+
method: "PUT",
|
|
774
|
+
headers: { "Content-Type": "application/gzip" },
|
|
775
|
+
body: tarData
|
|
776
|
+
});
|
|
777
|
+
if (!uploadRes.ok) {
|
|
778
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, id: created.id, error: `Upload failed: HTTP ${uploadRes.status}` }, null, 2) }] };
|
|
779
|
+
}
|
|
780
|
+
} finally {
|
|
781
|
+
try {
|
|
782
|
+
await rm(tarball, { force: true });
|
|
783
|
+
} catch {
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
const publishRes = await fetch(`${endpoint}/v1/preview/${created.id}/publish`, {
|
|
787
|
+
method: "POST",
|
|
788
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
789
|
+
});
|
|
790
|
+
if (!publishRes.ok) {
|
|
791
|
+
const err = await publishRes.text();
|
|
792
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, id: created.id, error: `Publish failed: HTTP ${publishRes.status} ${err}` }, null, 2) }] };
|
|
793
|
+
}
|
|
794
|
+
const published = await publishRes.json();
|
|
795
|
+
return {
|
|
796
|
+
content: [{
|
|
797
|
+
type: "text",
|
|
798
|
+
text: JSON.stringify({
|
|
799
|
+
ok: true,
|
|
800
|
+
id: published.id,
|
|
801
|
+
preview_url: published.preview_url,
|
|
802
|
+
file_count: published.file_count,
|
|
803
|
+
total_bytes: published.total_bytes,
|
|
804
|
+
expires_at: created.expires_at
|
|
805
|
+
}, null, 2)
|
|
806
|
+
}]
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
},
|
|
810
|
+
{ optional: true }
|
|
811
|
+
);
|
|
812
|
+
api.registerTool(
|
|
813
|
+
{
|
|
814
|
+
name: "riddle_preview_delete",
|
|
815
|
+
description: "Delete an ephemeral preview site created by riddle_preview. Removes all files and frees the preview ID immediately instead of waiting for auto-expiry.",
|
|
816
|
+
parameters: Type.Object({
|
|
817
|
+
id: Type.String({ description: "Preview ID (e.g. pv_a1b2c3d4)" })
|
|
818
|
+
}),
|
|
819
|
+
async execute(_id, params) {
|
|
820
|
+
const { apiKey, baseUrl } = getCfg(api);
|
|
821
|
+
if (!apiKey) {
|
|
822
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: "Missing Riddle API key." }, null, 2) }] };
|
|
823
|
+
}
|
|
824
|
+
assertAllowedBaseUrl(baseUrl);
|
|
825
|
+
const res = await fetch(`${baseUrl.replace(/\/$/, "")}/v1/preview/${params.id}`, {
|
|
826
|
+
method: "DELETE",
|
|
827
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
828
|
+
});
|
|
829
|
+
if (!res.ok) {
|
|
830
|
+
const err = await res.text();
|
|
831
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `Delete failed: HTTP ${res.status} ${err}` }, null, 2) }] };
|
|
832
|
+
}
|
|
833
|
+
const data = await res.json();
|
|
834
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: true, deleted: true, files_removed: data.files_removed }, null, 2) }] };
|
|
835
|
+
}
|
|
836
|
+
},
|
|
837
|
+
{ optional: true }
|
|
838
|
+
);
|
|
732
839
|
}
|
|
733
840
|
export {
|
|
734
841
|
register as default
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
"id": "openclaw-riddledc",
|
|
3
3
|
"name": "Riddle",
|
|
4
4
|
"description": "Riddle (riddledc.com) hosted browser API tools for OpenClaw agents.",
|
|
5
|
-
"version": "0.
|
|
6
|
-
"notes": "0.
|
|
5
|
+
"version": "0.6.0",
|
|
6
|
+
"notes": "0.6.0: Added riddle_preview and riddle_preview_delete tools for ephemeral preview hosting at preview.riddledc.com.",
|
|
7
7
|
"type": "plugin",
|
|
8
8
|
"bundledSkills": [],
|
|
9
9
|
"capabilities": {
|
|
10
10
|
"network": {
|
|
11
11
|
"egress": [
|
|
12
|
-
"api.riddledc.com"
|
|
12
|
+
"api.riddledc.com",
|
|
13
|
+
"preview.riddledc.com"
|
|
13
14
|
],
|
|
14
15
|
"enforced": true,
|
|
15
16
|
"note": "Hardcoded allowlist in assertAllowedBaseUrl() - cannot be overridden by config"
|
|
@@ -37,7 +38,9 @@
|
|
|
37
38
|
"riddle_scrape",
|
|
38
39
|
"riddle_map",
|
|
39
40
|
"riddle_crawl",
|
|
40
|
-
"riddle_visual_diff"
|
|
41
|
+
"riddle_visual_diff",
|
|
42
|
+
"riddle_preview",
|
|
43
|
+
"riddle_preview_delete"
|
|
41
44
|
],
|
|
42
45
|
"invokes": [],
|
|
43
46
|
"note": "Provides tools for agent use; does not invoke other agent tools"
|