From 20d34bf6e728ed11269788b52c1c371e3215c2a9 Mon Sep 17 00:00:00 2001 From: Space-Banane Date: Wed, 18 Mar 2026 22:35:18 +0100 Subject: [PATCH] add: Implement storage management commands including create, delete, list, and item manipulation --- OpenclawSkill.md | 8 ++++ package.json | 2 +- src/commands/storage/clear-items.ts | 35 +++++++++++++++++ src/commands/storage/create.ts | 41 ++++++++++++++++++++ src/commands/storage/delete-item.ts | 36 ++++++++++++++++++ src/commands/storage/delete.ts | 35 +++++++++++++++++ src/commands/storage/get-items.ts | 42 ++++++++++++++++++++ src/commands/storage/list.ts | 40 +++++++++++++++++++ src/commands/storage/set-item.ts | 59 +++++++++++++++++++++++++++++ 9 files changed, 297 insertions(+), 1 deletion(-) create mode 100644 src/commands/storage/clear-items.ts create mode 100644 src/commands/storage/create.ts create mode 100644 src/commands/storage/delete-item.ts create mode 100644 src/commands/storage/delete.ts create mode 100644 src/commands/storage/get-items.ts create mode 100644 src/commands/storage/list.ts create mode 100644 src/commands/storage/set-item.ts diff --git a/OpenclawSkill.md b/OpenclawSkill.md index c1f938a..1340efa 100644 --- a/OpenclawSkill.md +++ b/OpenclawSkill.md @@ -34,6 +34,14 @@ this will check the health, and if not setup, it will prompt you to set up the C - `shsf get namespace `: Get details of a specific namespace by its ID - `shsf get trigger `: Get details of a specific trigger from a function. +- `shsf storage create --name --purpose `: Create a new storage. +- `shsf storage delete --name `: Delete a storage. +- `shsf storage list`: List all storages. +- `shsf storage get-items --name `: List all items in a storage. +- `shsf storage set-item --name --key --value [--expires ]`: Set a storage item (value can be JSON). +- `shsf storage delete-item --name --key `: Delete a storage item. +- `shsf storage clear-items --name `: Clear all items from a storage. + - `shsf update function `: Update a specific serverless function by its ID. (use `shsf update function -h` first) - `shsf update namespace `: Update a specific namespace by its ID. (use `shsf update namespace -h` first) - `shsf update trigger `: Update a specific trigger from a function. (use `shsf update trigger -h` first) diff --git a/package.json b/package.json index 201c736..77ae35a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "shsf-cli", - "version": "2.0.4", + "version": "2.1.0", "description": "", "type": "module", "files": [ diff --git a/src/commands/storage/clear-items.ts b/src/commands/storage/clear-items.ts new file mode 100644 index 0000000..5a1f2b5 --- /dev/null +++ b/src/commands/storage/clear-items.ts @@ -0,0 +1,35 @@ +import chalk from "chalk"; +import { getApiClient } from "../../api.js"; + +export const clearItemsDefinition = { + name: "clear-items", + description: "Clear all items from a storage.", + options: [ + { name: "--name ", description: "Storage name", required: true }, + ], + action: async (options: any) => { + const client = await getApiClient(); + + try { + const response = await client.delete(`/api/storage/${options.name}/items`); + + if (response.status === 200) { + console.log( + `${chalk.green("✓")} All items cleared from storage ${chalk.cyan(options.name)}!`, + ); + } else { + console.log( + `${chalk.yellow("!")} Unexpected response from server: ${response.status}`, + ); + } + } catch (error: any) { + if (error.response) { + console.error( + `${chalk.red("✗")} Failed to clear items: ${chalk.yellow(error.response.data.message || "Unknown error")}`, + ); + } else { + console.error(`${chalk.red("✗")} Error: ${error.message}`); + } + } + }, +}; diff --git a/src/commands/storage/create.ts b/src/commands/storage/create.ts new file mode 100644 index 0000000..414cb7c --- /dev/null +++ b/src/commands/storage/create.ts @@ -0,0 +1,41 @@ +import chalk from "chalk"; +import { getApiClient } from "../../api.js"; + +export const createStorageDefinition = { + name: "create", + description: "Create a new storage.", + options: [ + { name: "--name ", description: "Storage name", required: true }, + { name: "--purpose ", description: "Storage purpose", required: true }, + ], + action: async (options: any) => { + const data = { + name: options.name, + purpose: options.purpose, + }; + + const client = await getApiClient(); + + try { + const response = await client.post("/api/storage", data); + + if (response.status === 200 || response.status === 201) { + console.log( + `${chalk.green("✓")} Storage ${chalk.cyan(data.name)} created successfully!`, + ); + } else { + console.log( + `${chalk.yellow("!")} Unexpected response from server: ${response.status}`, + ); + } + } catch (error: any) { + if (error.response) { + console.error( + `${chalk.red("✗")} Failed to create storage: ${chalk.yellow(error.response.data.message || "Unknown error")}`, + ); + } else { + console.error(`${chalk.red("✗")} Error: ${error.message}`); + } + } + }, +}; diff --git a/src/commands/storage/delete-item.ts b/src/commands/storage/delete-item.ts new file mode 100644 index 0000000..ba367f1 --- /dev/null +++ b/src/commands/storage/delete-item.ts @@ -0,0 +1,36 @@ +import chalk from "chalk"; +import { getApiClient } from "../../api.js"; + +export const deleteItemDefinition = { + name: "delete-item", + description: "Delete a storage item.", + options: [ + { name: "--name ", description: "Storage name", required: true }, + { name: "--key ", description: "Item key", required: true }, + ], + action: async (options: any) => { + const client = await getApiClient(); + + try { + const response = await client.delete(`/api/storage/${options.name}/item/${options.key}`); + + if (response.status === 200) { + console.log( + `${chalk.green("✓")} Item ${chalk.cyan(options.key)} deleted from ${chalk.cyan(options.name)}!`, + ); + } else { + console.log( + `${chalk.yellow("!")} Unexpected response from server: ${response.status}`, + ); + } + } catch (error: any) { + if (error.response) { + console.error( + `${chalk.red("✗")} Failed to delete item: ${chalk.yellow(error.response.data.message || "Unknown error")}`, + ); + } else { + console.error(`${chalk.red("✗")} Error: ${error.message}`); + } + } + }, +}; diff --git a/src/commands/storage/delete.ts b/src/commands/storage/delete.ts new file mode 100644 index 0000000..7567e02 --- /dev/null +++ b/src/commands/storage/delete.ts @@ -0,0 +1,35 @@ +import chalk from "chalk"; +import { getApiClient } from "../../api.js"; + +export const deleteStorageDefinition = { + name: "delete", + description: "Delete a storage.", + options: [ + { name: "--name ", description: "Storage name", required: true }, + ], + action: async (options: any) => { + const client = await getApiClient(); + + try { + const response = await client.delete(`/api/storage/${options.name}`); + + if (response.status === 200) { + console.log( + `${chalk.green("✓")} Storage ${chalk.cyan(options.name)} deleted successfully!`, + ); + } else { + console.log( + `${chalk.yellow("!")} Unexpected response from server: ${response.status}`, + ); + } + } catch (error: any) { + if (error.response) { + console.error( + `${chalk.red("✗")} Failed to delete storage: ${chalk.yellow(error.response.data.message || "Unknown error")}`, + ); + } else { + console.error(`${chalk.red("✗")} Error: ${error.message}`); + } + } + }, +}; diff --git a/src/commands/storage/get-items.ts b/src/commands/storage/get-items.ts new file mode 100644 index 0000000..62e1650 --- /dev/null +++ b/src/commands/storage/get-items.ts @@ -0,0 +1,42 @@ +import chalk from "chalk"; +import { getApiClient } from "../../api.js"; + +export const listItemsDefinition = { + name: "get-items", + description: "List all items in a storage.", + options: [ + { name: "--name ", description: "Storage name", required: true }, + ], + action: async (options: any) => { + const client = await getApiClient(); + + try { + const response = await client.get(`/api/storage/${options.name}/items`); + + if (response.status === 200 && response.data.data) { + const items = response.data.data; + if (items.length === 0) { + console.log(`${chalk.yellow("!")} Storage ${chalk.cyan(options.name)} is empty.`); + return; + } + + console.log(chalk.blue(`Items in ${chalk.cyan(options.name)}:`)); + items.forEach((item: any) => { + console.log(`- ${chalk.cyan(item.key)}: ${JSON.stringify(item.value)}`); + }); + } else { + console.log( + `${chalk.yellow("!")} Unexpected response from server: ${response.status}`, + ); + } + } catch (error: any) { + if (error.response) { + console.error( + `${chalk.red("✗")} Failed to list items: ${chalk.yellow(error.response.data.message || "Unknown error")}`, + ); + } else { + console.error(`${chalk.red("✗")} Error: ${error.message}`); + } + } + }, +}; diff --git a/src/commands/storage/list.ts b/src/commands/storage/list.ts new file mode 100644 index 0000000..ed3c943 --- /dev/null +++ b/src/commands/storage/list.ts @@ -0,0 +1,40 @@ +import chalk from "chalk"; +import { getApiClient } from "../../api.js"; + +export const listStoragesDefinition = { + name: "list", + description: "List all storages.", + action: async () => { + const client = await getApiClient(); + + try { + const response = await client.get("/api/storage"); + + if (response.status === 200 && response.data.data) { + const storages = response.data.data; + if (storages.length === 0) { + console.log(`${chalk.yellow("!")} No storages found.`); + return; + } + + console.log(chalk.blue("Storages:")); + storages.forEach((s: any) => { + const purposeInfo = s.purpose ? chalk.gray(` - ${s.purpose}`) : ""; + console.log(`- ${chalk.cyan(s.name)}${purposeInfo}`); + }); + } else { + console.log( + `${chalk.yellow("!")} Unexpected response from server: ${response.status}`, + ); + } + } catch (error: any) { + if (error.response) { + console.error( + `${chalk.red("✗")} Failed to list storages: ${chalk.yellow(error.response.data.message || "Unknown error")}`, + ); + } else { + console.error(`${chalk.red("✗")} Error: ${error.message}`); + } + } + }, +}; diff --git a/src/commands/storage/set-item.ts b/src/commands/storage/set-item.ts new file mode 100644 index 0000000..fd0898c --- /dev/null +++ b/src/commands/storage/set-item.ts @@ -0,0 +1,59 @@ +import chalk from "chalk"; +import { getApiClient } from "../../api.js"; + +export const setItemDefinition = { + name: "set-item", + description: "Set a storage item.", + options: [ + { name: "--name ", description: "Storage name", required: true }, + { name: "--key ", description: "Item key", required: true }, + { name: "--value ", description: "Item value (JSON string)", required: true }, + { name: "--expires ", description: "Expiration (ISO string or hours)" }, + ], + action: async (options: any) => { + let value; + try { + value = JSON.parse(options.value); + } catch (e) { + // If not valid JSON, treat as string + value = options.value; + } + + const data: any = { + key: options.key, + value: value, + }; + + if (options.expires) { + if (!isNaN(Number(options.expires))) { + data.expiresAt = Number(options.expires); + } else { + data.expiresAt = options.expires; + } + } + + const client = await getApiClient(); + + try { + const response = await client.post(`/api/storage/${options.name}/item`, data); + + if (response.status === 200 || response.status === 201) { + console.log( + `${chalk.green("✓")} Item ${chalk.cyan(options.key)} set successfully in ${chalk.cyan(options.name)}!`, + ); + } else { + console.log( + `${chalk.yellow("!")} Unexpected response from server: ${response.status}`, + ); + } + } catch (error: any) { + if (error.response) { + console.error( + `${chalk.red("✗")} Failed to set item: ${chalk.yellow(error.response.data.message || "Unknown error")}`, + ); + } else { + console.error(`${chalk.red("✗")} Error: ${error.message}`); + } + } + }, +};