10 Commits

34 changed files with 1682 additions and 6 deletions
+3
View File
@@ -24,3 +24,6 @@
- **New Commands**: To add a command, create a new file in `src/commands/` (or a subfolder). It must export a definition object (default or named) with `name`, `description`, and `action`. - **New Commands**: To add a command, create a new file in `src/commands/` (or a subfolder). It must export a definition object (default or named) with `name`, `description`, and `action`.
- **Error Handling**: Follow the pattern in [src/commands/health.ts](src/commands/health.ts) for handling Axios errors (check for `error.response`, `error.request`, etc.). - **Error Handling**: Follow the pattern in [src/commands/health.ts](src/commands/health.ts) for handling Axios errors (check for `error.response`, `error.request`, etc.).
- **No Global Scope**: Keep command logic within the `action` function or extracted to utility modules to maintain testability. - **No Global Scope**: Keep command logic within the `action` function or extracted to utility modules to maintain testability.
## Versioning
- **Versioning**: Always increment the version number in package.json whenever a change is merged into main. The version format is major.minor.patch.
+14 -2
View File
@@ -4,9 +4,15 @@ on:
push: push:
branches: branches:
- '**' - '**'
paths:
- '**/*.ts'
- '**/*.json'
pull_request: pull_request:
branches: branches:
- main - main
paths:
- '**/*.ts'
- '**/*.json'
jobs: jobs:
build: build:
@@ -74,12 +80,18 @@ jobs:
with: with:
name: dist-artifact name: dist-artifact
- name: Get version from package.json
id: package_version
run: |
pkg_version=$(node -p "require('./package.json').version")
echo "VERSION=$pkg_version" >> $GITHUB_OUTPUT
- name: Create Release - name: Create Release
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2
with: with:
files: dist.zip files: dist.zip
tag_name: v${{ github.run_number }} tag_name: v${{ steps.package_version.outputs.VERSION }}
name: Release v${{ github.run_number }} name: Release v${{ steps.package_version.outputs.VERSION }}
draft: false draft: false
prerelease: false prerelease: false
env: env:
+112
View File
@@ -0,0 +1,112 @@
---
name: shsf
description: Interact with the users shsf instance to manage serverless functions, namespaces, triggers, schedules, and more via the CLI.
---
# SHSF - Selfhostable Serverless Functions; THE CLI
The `shsf` CLI tool allows you to interact with your shsf instance to manage serverless functions, namespaces, triggers, schedules, and more. Below are the available commands and their descriptions.
## Setup
To get started, you need to set up the `shsf` CLI tool. Follow the instructions below to install and configure it.
1. Run this
```bash
shsf health
```
this will check the health, and if not setup, it will prompt you to set up the CLI.
## Commands
- `shsf count functions`: Count your functions. Add `--full` to list them.
- `shsf count namespaces`: Count your namespaces. Add `--full` to list them.
- `shsf count storages`: Count your storages. Add `--full` to list them.
- `shsf count triggers`: Count your triggers. Add `--full` to list them.
- `shsf create function`: Create a new function. (use `shsf create function -h` first)
- `shsf create namespace`: Create a new namespace. (use `shsf create namespace -h` first)
- `shsf create trigger`: Create a new trigger. (use `shsf create trigger -h` first)
- `shsf delete function <id>`: Deletes a specific serverless function by its ID.
- `shsf delete namespace <id>`: Deletes a namespace and all its functions by ID.
- `shsf delete trigger <functionId> <triggerId>`: Deletes a specific trigger from a function.
- `shsf get function <id>`: Get details of a specific function by its ID.
- `shsf get namespace <id>`: Get details of a specific namespace by its ID
- `shsf get trigger <functionId> <triggerId>`: Get details of a specific trigger from a function.
- `shsf storage create --name <name> --purpose <purpose>`: Create a new storage.
- `shsf storage delete --name <name>`: Delete a storage.
- `shsf storage list`: List all storages.
- `shsf storage get-items --name <name>`: List all items in a storage.
- `shsf storage set-item --name <name> --key <key> --value <value> [--expires <expires>]`: Set a storage item (value can be JSON).
- `shsf storage delete-item --name <name> --key <key>`: Delete a storage item.
- `shsf storage clear-items --name <name>`: Clear all items from a storage.
- `shsf update function <id>`: Update a specific serverless function by its ID. (use `shsf update function -h` first)
- `shsf update namespace <id>`: Update a specific namespace by its ID. (use `shsf update namespace -h` first)
- `shsf update trigger <functionId> <triggerId>`: Update a specific trigger from a function. (use `shsf update trigger -h` first)
- `shsf file create`: Create a file in a storage. (use `shsf file create -h` first)
- `shsf file overwrite`: Overwrite an existing file in a storage. (use `shsf file overwrite -h` first)
- `shsf file delete`: Delete a file from a storage. (use `shsf file delete -h` first)
- `shsf file list`: List files in a storage. (use `shsf file list -h` first)
## Instructions
Use these commands for when you need to interact with shsf from the command line. Its faster than using the ui for almost all ops.
## Ui Links
Get the ui url with:
```bash
shsf uiurl
```
## Available UI Routes
Below are the main routes you can use in the SHSF web UI. These are not clickable links, but you can navigate to them in your browser.
**General**
- `/`: Home Page
**Documentation**
- `/docs`: Documentation
**Account**
- `/account`: Account Settings
- `/login`: Login
- `/register`: Register
**Functions**
- `/functions`: Shows your functions, grouped by their respective namespaces (requires auth).
- `/functions/:id`: View details for a specific function (requires auth).
**Other**
- `/admin`: Admin dashboard (admin only, requires authentication)
- `/storage`: Storage management (requires auth)
- `/cron-jobs`: Cron job management (requires auth)
- `/access-tokens`: Access token management (requires auth)
- `/guest-users`: Guest user management (requires auth)
- `/guest-access`: Guest access portal
### Example
After creating a function and receiving an ID (for example, 81), you can share the following URL with your human so they can view the function in the UI:
```
[UI_URL]/functions/[ID]
```
## Update
Update with your package manager of choice. Preferably pnpm:
```bash
pnpm add -g shsf-cli
```
Note that new features are often released and a user might tell you to do things that are only in the latest version. So its a good idea to always keep it updated.
It is also good practice to update this skill every now and then, as it might get new features that are not documented here yet.
Download the latest version with:
```
curl https://raw.githubusercontent.com/Space-Banane/shsf-cli/refs/heads/main/OpenclawSkill.md > /tmp/OpenclawSkill.md
```
and then replace the old one with the new one using `mv`.
## Missing a feature?
Features might be not documented yet, as this skill document rarely updates.
Use the shsf -h command to see options and features.
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "shsf-cli", "name": "shsf-cli",
"version": "2.0.1", "version": "2.1.5",
"description": "", "description": "",
"type": "module", "type": "module",
"files": [ "files": [
+14 -3
View File
@@ -55,10 +55,21 @@ export async function resolveCommands() {
const definition = module.default || Object.values(module).find((val: any) => val && val.name && val.action); const definition = module.default || Object.values(module).find((val: any) => val && val.name && val.action);
if (definition && definition.name && definition.action) { if (definition && definition.name && definition.action) {
currentParent const command = currentParent
.command(definition.name) .command(definition.name)
.description(definition.description || '') .description(definition.description || '');
.action(definition.action);
if (definition.options) {
definition.options.forEach((opt: any) => {
if (opt.required) {
command.requiredOption(opt.name, opt.description);
} else {
command.option(opt.name, opt.description);
}
});
}
command.action(definition.action);
} }
} }
} }
+62
View File
@@ -0,0 +1,62 @@
import chalk from "chalk";
import { getApiClient } from "../../api.js";
export const functionsCountDefinition = {
name: "functions",
description: "Counts the amount of functions the current user has.",
options: [
{ name: "--full", description: "List all functions instead of just the count", type: "boolean" },
],
action: async (options: any) => {
await countFunctions(options?.full);
},
};
async function countFunctions(full: boolean = false) {
const client = await getApiClient();
try {
const response = await client.get("/api/functions");
if (response.status === 200 && response.data.data) {
const functions = response.data.data;
const count = functions.length;
if (full && count > 0) {
console.log(chalk.blue("Functions List:"));
functions.forEach((f: any) => {
const namespaceInfo = f.namespace ? chalk.gray(` [NS: ${f.namespace.id}]`) : "";
console.log(`- ${chalk.cyan(f.name)} ${chalk.gray(`(${f.id})`)}${namespaceInfo}`);
if (f.description) {
console.log(` ${chalk.italic.gray(f.description)}`);
}
});
console.log("");
}
console.log(
`${chalk.green("✓")} You have ${chalk.bgGreen.black(` ${count} `)} functions.`,
);
} else {
console.log(
`${chalk.yellow("!")} Unexpected response from server.`,
);
console.log(`Status Code: ${chalk.blue(response.status)}`);
}
} catch (error: any) {
if (error.response) {
console.error(
`${chalk.red("✗")} Failed to fetch function count.`,
);
console.error(`Status Code: ${chalk.red(error.response.status)}`);
console.error(
`Message: ${chalk.yellow(error.response.data.message || "Unknown error from server")}`,
);
} else {
console.error(
`${chalk.red("✗")} Local error occurred.`,
);
console.error(`Error: ${chalk.yellow(error.message)}`);
}
}
}
+57
View File
@@ -0,0 +1,57 @@
import chalk from "chalk";
import { getApiClient } from "../../api.js";
export const namespacesCountDefinition = {
name: "namespaces",
description: "Counts the amount of namespaces the current user has.",
options: [
{ name: "--full", description: "List all namespaces instead of just the count", type: "boolean" },
],
action: async (options: any) => {
await countNamespaces(options?.full);
},
};
async function countNamespaces(full: boolean = false) {
const client = await getApiClient();
try {
const response = await client.get("/api/namespaces");
if (response.status === 200 && response.data.data) {
const namespaces = response.data.data;
const count = namespaces.length;
if (full && count > 0) {
console.log(chalk.blue("Namespaces List:"));
namespaces.forEach((ns: any) => {
console.log(`- ${chalk.cyan(ns.name)} ${chalk.gray(`(${ns.id})`)}`);
});
console.log("");
}
console.log(
`${chalk.green("✓")} You have ${chalk.bgGreen.black(` ${count} `)} namespaces.`,
);
} else {
console.log(
`${chalk.yellow("!")} Unexpected response from server.`,
);
console.log(`Status Code: ${chalk.blue(response.status)}`);
}
} catch (error: any) {
if (error.response) {
console.error(
`${chalk.red("✗")} Failed to fetch namespace count.`,
);
console.error(`Status Code: ${chalk.red(error.response.status)}`);
console.error(
`Message: ${chalk.yellow(error.response.data.message || "Unknown error from server")}`,
);
} else if (error.request) {
console.error(`${chalk.red("✗")} No response received from server.`);
} else {
console.error(`${chalk.red("✗")} Error:`, error.message);
}
}
}
+58
View File
@@ -0,0 +1,58 @@
import chalk from "chalk";
import { getApiClient } from "../../api.js";
export const storagesCountDefinition = {
name: "storages",
description: "Counts the amount of storages the current user has.",
options: [
{ name: "--full", description: "List all storages instead of just the count", type: "boolean" },
],
action: async (options: any) => {
await countStorages(options?.full);
},
};
async function countStorages(full: boolean = false) {
const client = await getApiClient();
try {
const response = await client.get("/api/storage");
if (response.status === 200 && response.data.data) {
const storages = response.data.data;
const count = storages.length;
if (full && count > 0) {
console.log(chalk.blue("Storages List:"));
storages.forEach((s: any) => {
const purposeInfo = s.purpose ? chalk.gray(` - ${s.purpose}`) : "";
console.log(`- ${chalk.cyan(s.name)}${purposeInfo}`);
});
console.log("");
}
console.log(
`${chalk.green("✓")} You have ${chalk.bgGreen.black(` ${count} `)} storages.`,
);
} else {
console.log(
`${chalk.yellow("!")} Unexpected response from server.`,
);
console.log(`Status Code: ${chalk.blue(response.status)}`);
}
} catch (error: any) {
if (error.response) {
console.error(
`${chalk.red("✗")} Failed to fetch storage count.`,
);
console.error(`Status Code: ${chalk.red(error.response.status)}`);
console.error(
`Message: ${chalk.yellow(error.response.data.message || "Unknown error from server")}`,
);
} else if (error.request) {
console.error(`${chalk.red("✗")} No response received from server.`);
} else {
console.error(`${chalk.red("✗")} Error:`, error.message);
}
}
}
+62
View File
@@ -0,0 +1,62 @@
import chalk from "chalk";
import { getApiClient } from "../../api.js";
export const triggersCountDefinition = {
name: "triggers",
description: "Counts the amount of triggers the current user has.",
options: [
{ name: "--full", description: "List all triggers instead of just the count", type: "boolean" },
],
action: async (options: any) => {
await countTriggers(options?.full);
},
};
async function countTriggers(full: boolean = false) {
const client = await getApiClient();
try {
const response = await client.get("/api/triggers");
if (response.status === 200 && response.data.data) {
const triggers = response.data.data;
const count = triggers.length;
if (full && count > 0) {
console.log(chalk.blue("Triggers List:"));
triggers.forEach((t: any) => {
const functionInfo = t.function ? chalk.gray(` [Func: ${t.function.name}]`) : "";
const cronInfo = t.cron ? chalk.yellow(` (Cron: ${t.cron})`) : "";
console.log(`- ${chalk.cyan(t.name)} ${chalk.gray(`(${t.id})`)}${functionInfo}${cronInfo}`);
if (t.description) {
console.log(` ${chalk.italic.gray(t.description)}`);
}
});
console.log("");
}
console.log(
`${chalk.green("✓")} You have ${chalk.bgGreen.black(` ${count} `)} triggers.`,
);
} else {
console.log(
`${chalk.yellow("!")} Unexpected response from server.`,
);
console.log(`Status Code: ${chalk.blue(response.status)}`);
}
} catch (error: any) {
if (error.response) {
console.error(
`${chalk.red("✗")} Failed to fetch trigger count.`,
);
console.error(`Status Code: ${chalk.red(error.response.status)}`);
console.error(
`Message: ${chalk.yellow(error.response.data.message || "Unknown error from server")}`,
);
} else if (error.request) {
console.error(`${chalk.red("✗")} No response received from server.`);
} else {
console.error(`${chalk.red("✗")} Error:`, error.message);
}
}
}
+75
View File
@@ -0,0 +1,75 @@
import chalk from "chalk";
import { getApiClient } from "../../api.js";
export const createFunctionDefinition = {
name: "function",
description: "Create a new serverless function.",
options: [
{ name: "--name <name>", description: "Function name", required: true },
{ name: "--description <description>", description: "Function description", required: true },
{ name: "--image <image>", description: "Docker image tag", required: true },
{ name: "--startup-file <file>", description: "Startup file name", required: true },
{ name: "--namespace-id <id>", description: "Namespace ID", required: true },
{ name: "--execution-alias <alias>", description: "Custom execution alias" },
{ name: "--docker-mount", description: "Enable Docker mount", type: "boolean" },
{ name: "--ffmpeg-install", description: "Install ffmpeg in container", type: "boolean" },
{ name: "--imported", description: "Marks the function as imported", type: "boolean" },
{ name: "--cache-enabled <enabled>", description: "Enable response caching (true/false)" },
{ name: "--cache-ttl <seconds>", description: "Cache TTL in seconds" },
],
action: async (options: any) => {
let parsedCacheEnabled: boolean | undefined;
if (options.cacheEnabled !== undefined) {
const normalized = String(options.cacheEnabled).toLowerCase();
if (normalized !== "true" && normalized !== "false") {
console.error(`${chalk.red("✗")} Invalid value for --cache-enabled. Use true or false.`);
return;
}
parsedCacheEnabled = normalized === "true";
}
const parsedCacheTtl = options.cacheTtl !== undefined ? Number(options.cacheTtl) : undefined;
if (parsedCacheTtl !== undefined && Number.isNaN(parsedCacheTtl)) {
console.error(`${chalk.red("✗")} Invalid value for --cache-ttl. Please provide a number.`);
return;
}
const data = {
name: options.name,
description: options.description,
image: options.image,
startup_file: options.startupFile,
namespaceId: parseInt(options.namespaceId),
executionAlias: options.executionAlias,
docker_mount: !!options.dockerMount,
ffmpeg_install: !!options.ffmpegInstall,
imported: !!options.imported,
cache_enabled: parsedCacheEnabled,
cache_ttl: parsedCacheTtl,
};
const client = await getApiClient();
try {
const response = await client.post("/api/function", data);
if (response.status === 200) {
console.log(
`${chalk.green("✓")} Function ${chalk.cyan(data.name)} created successfully! ID: ${chalk.bgGreen.black(` ${response.data.data.id} `)}`,
);
} else {
console.log(
`${chalk.yellow("!")} Unexpected response from server: ${response.status}`,
);
}
} catch (error: any) {
if (error.response) {
console.error(
`${chalk.red("✗")} Failed to create function: ${chalk.yellow(error.response.data.message || "Unknown error")}`,
);
} else {
console.error(`${chalk.red("✗")} Error: ${error.message}`);
}
}
},
};
+41
View File
@@ -0,0 +1,41 @@
import chalk from "chalk";
import { getApiClient } from "../../api.js";
export const createNamespaceDefinition = {
name: "namespace",
description: "Create a new namespace.",
options: [
{ name: "--name <name>", description: "Namespace name", required: true },
{ name: "--description <description>", description: "Namespace description", required: true },
],
action: async (options: any) => {
const data = {
name: options.name,
description: options.description,
};
const client = await getApiClient();
try {
const response = await client.post("/api/namespaces", data);
if (response.status === 200 || response.status === 201) {
console.log(
`${chalk.green("✓")} Namespace ${chalk.cyan(data.name)} created successfully! ID: ${chalk.bgGreen.black(` ${response.data.data.id} `)}`,
);
} else {
console.log(
`${chalk.yellow("!")} Unexpected response from server: ${response.status}`,
);
}
} catch (error: any) {
if (error.response) {
console.error(
`${chalk.red("✗")} Failed to create namespace: ${chalk.yellow(error.response.data.message || "Unknown error")}`,
);
} else {
console.error(`${chalk.red("✗")} Error: ${error.message}`);
}
}
},
};
+44
View File
@@ -0,0 +1,44 @@
import chalk from "chalk";
import { getApiClient } from "../../api.js";
export const createTriggerDefinition = {
name: "trigger",
description: "Create a new trigger for a function.",
options: [
{ name: "--function-id <id>", description: "The ID of the function to add the trigger to", required: true },
{ name: "--name <name>", description: "Trigger name", required: true },
{ name: "--description <description>", description: "Trigger description", required: true },
],
action: async (options: any) => {
const { functionId, name, description } = options;
const data = {
name,
description,
};
const client = await getApiClient();
try {
const response = await client.post(`/api/functions/${functionId}/triggers`, data);
if (response.status === 200 || response.status === 201) {
console.log(
`${chalk.green("✓")} Trigger ${chalk.cyan(name)} created successfully for function ID ${chalk.cyan(functionId)}! ID: ${chalk.bgGreen.black(` ${response.data.data.id} `)}`,
);
} else {
console.log(
`${chalk.yellow("!")} Unexpected response from server: ${response.status}`,
);
}
} catch (error: any) {
if (error.response) {
console.error(
`${chalk.red("✗")} Failed to create trigger: ${chalk.yellow(error.response.data.message || "Unknown error")}`,
);
} else {
console.error(`${chalk.red("✗")} Error: ${error.message}`);
}
}
},
};
+47
View File
@@ -0,0 +1,47 @@
import chalk from "chalk";
import { getApiClient } from "../../api.js";
export const deleteFunctionDefinition = {
name: "function <id>",
description: "Deletes a specific serverless function by its ID.",
action: async (id: string) => {
await deleteFunction(id);
},
};
async function deleteFunction(id: string) {
const client = await getApiClient();
try {
const response = await client.delete(`/api/function/${id}`);
if (response.status === 200) {
console.log(
`${chalk.green("✓")} Function with ID ${chalk.cyan(id)} has been deleted.`,
);
} else {
console.log(
`${chalk.yellow("!")} Unexpected response from server.`,
);
console.log(`Status Code: ${chalk.blue(response.status)}`);
}
} catch (error: any) {
if (error.response) {
if (error.response.status === 404) {
console.error(`${chalk.red("✗")} Function with ID ${chalk.yellow(id)} not found.`);
} else {
console.error(
`${chalk.red("✗")} Failed to delete function.`,
);
console.error(`Status Code: ${chalk.red(error.response.status)}`);
console.error(
`Message: ${chalk.yellow(error.response.data.message || "Unknown error from server")}`,
);
}
} else if (error.request) {
console.error(`${chalk.red("✗")} No response received from server.`);
} else {
console.error(`${chalk.red("✗")} Error:`, error.message);
}
}
}
+47
View File
@@ -0,0 +1,47 @@
import chalk from "chalk";
import { getApiClient } from "../../api.js";
export const deleteNamespaceDefinition = {
name: "namespace <id>",
description: "Deletes a namespace and all its functions by ID.",
action: async (id: string) => {
await deleteNamespace(id);
},
};
async function deleteNamespace(id: string) {
const client = await getApiClient();
try {
const response = await client.delete(`/api/namespace/${id}`);
if (response.status === 200) {
console.log(
`${chalk.green("✓")} Namespace with ID ${chalk.cyan(id)} has been deleted.`,
);
} else {
console.log(
`${chalk.yellow("!")} Unexpected response from server.`,
);
console.log(`Status Code: ${chalk.blue(response.status)}`);
}
} catch (error: any) {
if (error.response) {
if (error.response.status === 404) {
console.error(`${chalk.red("✗")} Namespace with ID ${chalk.yellow(id)} not found.`);
} else {
console.error(
`${chalk.red("✗")} Failed to delete namespace.`,
);
console.error(`Status Code: ${chalk.red(error.response.status)}`);
console.error(
`Message: ${chalk.yellow(error.response.data.message || "Unknown error from server")}`,
);
}
} else if (error.request) {
console.error(`${chalk.red("✗")} No response received from server.`);
} else {
console.error(`${chalk.red("✗")} Error:`, error.message);
}
}
}
+47
View File
@@ -0,0 +1,47 @@
import chalk from "chalk";
import { getApiClient } from "../../api.js";
export const deleteTriggerDefinition = {
name: "trigger <functionId> <triggerId>",
description: "Deletes a specific trigger from a function.",
action: async (functionId: string, triggerId: string) => {
await deleteTrigger(functionId, triggerId);
},
};
async function deleteTrigger(functionId: string, triggerId: string) {
const client = await getApiClient();
try {
const response = await client.delete(`/api/functions/${functionId}/triggers/${triggerId}`);
if (response.status === 200) {
console.log(
`${chalk.green("✓")} Trigger with ID ${chalk.cyan(triggerId)} from function ${chalk.cyan(functionId)} has been deleted.`,
);
} else {
console.log(
`${chalk.yellow("!")} Unexpected response from server.`,
);
console.log(`Status Code: ${chalk.blue(response.status)}`);
}
} catch (error: any) {
if (error.response) {
if (error.response.status === 404) {
console.error(`${chalk.red("✗")} Trigger or function not found.`);
} else {
console.error(
`${chalk.red("✗")} Failed to delete trigger.`,
);
console.error(`Status Code: ${chalk.red(error.response.status)}`);
console.error(
`Message: ${chalk.yellow(error.response.data.message || "Unknown error from server")}`,
);
}
} else if (error.request) {
console.error(`${chalk.red("✗")} No response received from server.`);
} else {
console.error(`${chalk.red("✗")} Error:`, error.message);
}
}
}
+63
View File
@@ -0,0 +1,63 @@
import chalk from "chalk";
import fs from "fs/promises";
import { getApiClient } from "../../api.js";
export const fileCreateDefinition = {
name: "create",
description: "Create a file in a storage.",
options: [
{ name: "--storage-id <id>", description: "Storage ID", required: true },
{ name: "--path <path>", description: "Remote file path", required: true },
{ name: "--content <content>", description: "Inline file content" },
{ name: "--source <source>", description: "Local source file path" },
],
action: async (options: any) => {
if (!options.content && !options.source) {
console.error(`${chalk.red("✗")} You must provide either --content or --source.`);
return;
}
if (options.content && options.source) {
console.error(`${chalk.red("✗")} Please provide only one of --content or --source.`);
return;
}
let content = options.content;
if (options.source) {
try {
content = await fs.readFile(options.source, "utf8");
} catch (error: any) {
console.error(`${chalk.red("✗")} Failed to read source file: ${chalk.yellow(error.message)}`);
return;
}
}
const client = await getApiClient();
const payload = {
path: options.path,
content,
};
try {
const response = await client.post(`/api/storage/${options.storageId}/files`, payload);
if (response.status === 200 || response.status === 201) {
console.log(
`${chalk.green("✓")} File ${chalk.cyan(options.path)} created successfully in storage ${chalk.cyan(options.storageId)}.`,
);
} else {
console.log(`${chalk.yellow("!")} Unexpected response from server: ${response.status}`);
}
} catch (error: any) {
if (error.response) {
console.error(
`${chalk.red("✗")} Failed to create file: ${chalk.yellow(error.response.data?.message || "Unknown error")}`,
);
} else if (error.request) {
console.error(`${chalk.red("✗")} No response received from server.`);
} else {
console.error(`${chalk.red("✗")} Error: ${error.message}`);
}
}
},
};
+42
View File
@@ -0,0 +1,42 @@
import chalk from "chalk";
import { getApiClient } from "../../api.js";
export const fileDeleteDefinition = {
name: "delete",
description: "Delete a file from a storage.",
options: [
{ name: "--storage-id <id>", description: "Storage ID", required: true },
{ name: "--path <path>", description: "Remote file path", required: true },
],
action: async (options: any) => {
const client = await getApiClient();
try {
const response = await client.delete(`/api/storage/${options.storageId}/files`, {
data: { path: options.path },
});
if (response.status === 200) {
console.log(
`${chalk.green("✓")} File ${chalk.cyan(options.path)} deleted from storage ${chalk.cyan(options.storageId)}.`,
);
} else {
console.log(`${chalk.yellow("!")} Unexpected response from server: ${response.status}`);
}
} catch (error: any) {
if (error.response) {
if (error.response.status === 404) {
console.error(`${chalk.red("✗")} File ${chalk.yellow(options.path)} not found.`);
} else {
console.error(
`${chalk.red("✗")} Failed to delete file: ${chalk.yellow(error.response.data?.message || "Unknown error")}`,
);
}
} else if (error.request) {
console.error(`${chalk.red("✗")} No response received from server.`);
} else {
console.error(`${chalk.red("✗")} Error: ${error.message}`);
}
}
},
};
+49
View File
@@ -0,0 +1,49 @@
import chalk from "chalk";
import { getApiClient } from "../../api.js";
export const fileListDefinition = {
name: "list",
description: "List files in a storage.",
options: [
{ name: "--storage-id <id>", description: "Storage ID", required: true },
{ name: "--prefix <prefix>", description: "Optional path prefix filter" },
],
action: async (options: any) => {
const client = await getApiClient();
try {
const response = await client.get(`/api/storage/${options.storageId}/files`, {
params: options.prefix ? { prefix: options.prefix } : undefined,
});
if (response.status === 200 && response.data.data) {
const files = response.data.data;
if (!Array.isArray(files) || files.length === 0) {
console.log(`${chalk.yellow("!")} No files found.`);
return;
}
console.log(chalk.blue("Files:"));
files.forEach((file: any) => {
const path = file.path || file.name || "(unknown)";
console.log(`- ${chalk.cyan(path)}`);
});
console.log(`\n${chalk.green("✓")} Found ${chalk.bgGreen.black(` ${files.length} `)} files.`);
} else {
console.log(`${chalk.yellow("!")} Unexpected response from server.`);
console.log(`Status Code: ${chalk.blue(response.status)}`);
}
} catch (error: any) {
if (error.response) {
console.error(
`${chalk.red("✗")} Failed to list files: ${chalk.yellow(error.response.data?.message || "Unknown error")}`,
);
} else if (error.request) {
console.error(`${chalk.red("✗")} No response received from server.`);
} else {
console.error(`${chalk.red("✗")} Error: ${error.message}`);
}
}
},
};
+67
View File
@@ -0,0 +1,67 @@
import chalk from "chalk";
import fs from "fs/promises";
import { getApiClient } from "../../api.js";
export const fileOverwriteDefinition = {
name: "overwrite",
description: "Overwrite an existing file in a storage.",
options: [
{ name: "--storage-id <id>", description: "Storage ID", required: true },
{ name: "--path <path>", description: "Remote file path", required: true },
{ name: "--content <content>", description: "Inline file content" },
{ name: "--source <source>", description: "Local source file path" },
],
action: async (options: any) => {
if (!options.content && !options.source) {
console.error(`${chalk.red("✗")} You must provide either --content or --source.`);
return;
}
if (options.content && options.source) {
console.error(`${chalk.red("✗")} Please provide only one of --content or --source.`);
return;
}
let content = options.content;
if (options.source) {
try {
content = await fs.readFile(options.source, "utf8");
} catch (error: any) {
console.error(`${chalk.red("✗")} Failed to read source file: ${chalk.yellow(error.message)}`);
return;
}
}
const client = await getApiClient();
const payload = {
path: options.path,
content,
};
try {
const response = await client.put(`/api/storage/${options.storageId}/files`, payload);
if (response.status === 200) {
console.log(
`${chalk.green("✓")} File ${chalk.cyan(options.path)} overwritten successfully in storage ${chalk.cyan(options.storageId)}.`,
);
} else {
console.log(`${chalk.yellow("!")} Unexpected response from server: ${response.status}`);
}
} catch (error: any) {
if (error.response) {
if (error.response.status === 404) {
console.error(`${chalk.red("✗")} File ${chalk.yellow(options.path)} not found.`);
} else {
console.error(
`${chalk.red("✗")} Failed to overwrite file: ${chalk.yellow(error.response.data?.message || "Unknown error")}`,
);
}
} else if (error.request) {
console.error(`${chalk.red("✗")} No response received from server.`);
} else {
console.error(`${chalk.red("✗")} Error: ${error.message}`);
}
}
},
};
+56
View File
@@ -0,0 +1,56 @@
import chalk from "chalk";
import { getApiClient } from "../../api.js";
export const pipInstallDefinition = {
name: "pipinstall",
description: "Install Python dependencies for a function using requirements.txt",
options: [
{
name: "--id <id>",
description: "The ID of the function.",
required: true,
},
],
action: async (options: { id: string }) => {
await pipInstallFunction(options.id);
},
};
async function pipInstallFunction(id: string) {
const client = await getApiClient();
try {
const response = await client.post(`/api/function/${id}/pip-install`);
if (response.status === 200) {
console.log(`${chalk.green("✓")} Dependencies installed successfully for function ${chalk.yellow(id)}.`);
if (response.data && response.data.status) {
console.log(`${chalk.gray("Status:")} ${response.data.status}`);
}
} else {
console.log(
`${chalk.yellow("!")} Unexpected response from server.`,
);
console.log(`Status Code: ${chalk.blue(response.status)}`);
}
} catch (error: any) {
if (error.response) {
console.error(
`${chalk.red("✗")} Failed to install dependencies.`,
);
console.error(`Status Code: ${chalk.red(error.response.status)}`);
console.error(
`Message: ${chalk.yellow(error.response.data?.message || "Unknown error from server")}`,
);
} else if (error.request) {
console.error(
`${chalk.red("✗")} Failed to install dependencies.`,
);
console.error(
`${chalk.yellow("Could not connect to the SHSF instance. Check your connection.")}`,
);
} else {
console.error(`${chalk.red("✗")} Error:`, error.message);
}
}
}
+64
View File
@@ -0,0 +1,64 @@
import chalk from "chalk";
import { getApiClient } from "../../api.js";
export const getFunctionDefinition = {
name: "function <id>",
description: "Get detailed information about a function by its ID.",
action: async (id: string) => {
await getFunction(id);
},
};
async function getFunction(id: string) {
const client = await getApiClient();
try {
const response = await client.get(`/api/function/${id}`);
if (response.status === 200 && response.data.data) {
const f = response.data.data;
console.log(chalk.blue.bold(`\nFunction Details: ${f.name} (${f.id})`));
console.log(`${chalk.gray("-------------------------------------------")}`);
console.log(`${chalk.yellow("Name:")} ${f.name}`);
console.log(`${chalk.yellow("ID:")} ${f.id}`);
console.log(`${chalk.yellow("Description:")} ${f.description || chalk.gray("No description")}`);
console.log(`${chalk.yellow("Image:")} ${f.image}`);
console.log(`${chalk.yellow("Startup File:")} ${f.startup_file}`);
if (f.namespace) {
console.log(`${chalk.yellow("Namespace:")} ${f.namespace.name} (${f.namespace.id})`);
}
if (f.executionAlias) {
console.log(`${chalk.yellow("Alias:")} ${f.executionAlias}`);
}
console.log(`${chalk.gray("-------------------------------------------")}\n`);
} else {
console.log(
`${chalk.yellow("!")} Unexpected response from server.`,
);
console.log(`Status Code: ${chalk.blue(response.status)}`);
}
} catch (error: any) {
if (error.response) {
if (error.response.status === 404) {
console.error(`${chalk.red("✗")} Function with ID ${chalk.yellow(id)} not found.`);
} else {
console.error(
`${chalk.red("✗")} Failed to fetch function details.`,
);
console.error(`Status Code: ${chalk.red(error.response.status)}`);
console.error(
`Message: ${chalk.yellow(error.response.data.message || "Unknown error from server")}`,
);
}
} else if (error.request) {
console.error(`${chalk.red("✗")} No response received from server.`);
} else {
console.error(`${chalk.red("✗")} Error:`, error.message);
}
}
}
+62
View File
@@ -0,0 +1,62 @@
import chalk from "chalk";
import { getApiClient } from "../../api.js";
export const getNamespaceDefinition = {
name: "namespace <id>",
description: "Get detailed information about a namespace by its ID.",
action: async (id: string) => {
await getNamespace(id);
},
};
async function getNamespace(id: string) {
const client = await getApiClient();
try {
const response = await client.get(`/api/namespace/${id}`);
if (response.status === 200 && response.data.data) {
const ns = response.data.data;
console.log(chalk.blue.bold(`\nNamespace Details: ${ns.name} (${ns.id})`));
console.log(`${chalk.gray("-------------------------------------------")}`);
console.log(`${chalk.yellow("Name:")} ${ns.name}`);
console.log(`${chalk.yellow("ID:")} ${ns.id}`);
if (ns.functions && ns.functions.length > 0) {
console.log(`${chalk.yellow("Functions:")}`);
ns.functions.forEach((f: any) => {
console.log(`- ${chalk.cyan(f.name)} ${chalk.gray(`(${f.id})`)}`);
});
} else {
console.log(`${chalk.yellow("Functions:")} ${chalk.gray("None")}`);
}
console.log(`${chalk.gray("-------------------------------------------")}\n`);
} else {
console.log(
`${chalk.yellow("!")} Unexpected response from server.`,
);
console.log(`Status Code: ${chalk.blue(response.status)}`);
}
} catch (error: any) {
if (error.response) {
if (error.response.status === 404) {
console.error(`${chalk.red("✗")} Namespace with ID ${chalk.yellow(id)} not found.`);
} else {
console.error(
`${chalk.red("✗")} Failed to fetch namespace details.`,
);
console.error(`Status Code: ${chalk.red(error.response.status)}`);
console.error(
`Message: ${chalk.yellow(error.response.data.message || "Unknown error from server")}`,
);
}
} else if (error.request) {
console.error(`${chalk.red("✗")} No response received from server.`);
} else {
console.error(`${chalk.red("✗")} Error:`, error.message);
}
}
}
+65
View File
@@ -0,0 +1,65 @@
import chalk from "chalk";
import { getApiClient } from "../../api.js";
export const getTriggerDefinition = {
name: "trigger <functionId> <triggerId>",
description: "Get detail of a specific trigger from a function.",
action: async (functionId: string, triggerId: string) => {
await getTrigger(functionId, triggerId);
},
};
async function getTrigger(functionId: string, triggerId: string) {
const client = await getApiClient();
try {
const response = await client.get(`/api/functions/${functionId}/triggers/${triggerId}`);
if (response.status === 200 && response.data.data) {
const t = response.data.data;
console.log(chalk.blue.bold(`\nTrigger Details: ${t.name} (${t.id})`));
console.log(`${chalk.gray("-------------------------------------------")}`);
console.log(`${chalk.yellow("Name:")} ${t.name}`);
console.log(`${chalk.yellow("ID:")} ${t.id}`);
console.log(`${chalk.yellow("Description:")} ${t.description || chalk.gray("No description")}`);
console.log(`${chalk.yellow("Cron:")} ${chalk.cyan(t.cron)}`);
console.log(`${chalk.yellow("Enabled:")} ${t.enabled ? chalk.green("Yes") : chalk.red("No")}`);
console.log(`${chalk.yellow("Function ID:")} ${t.functionId}`);
if (t.nextRun) {
console.log(`${chalk.yellow("Next Run:")} ${new Date(t.nextRun).toLocaleString()}`);
}
if (t.data) {
console.log(`${chalk.yellow("Data:")} ${t.data}`);
}
console.log(`${chalk.gray("-------------------------------------------")}\n`);
} else {
console.log(
`${chalk.yellow("!")} Unexpected response from server.`,
);
console.log(`Status Code: ${chalk.blue(response.status)}`);
}
} catch (error: any) {
if (error.response) {
if (error.response.status === 404) {
console.error(`${chalk.red("✗")} Trigger or function not found.`);
} else {
console.error(
`${chalk.red("✗")} Failed to fetch trigger details.`,
);
console.error(`Status Code: ${chalk.red(error.response.status)}`);
console.error(
`Message: ${chalk.yellow(error.response.data.message || "Unknown error from server")}`,
);
}
} else if (error.request) {
console.error(`${chalk.red("✗")} No response received from server.`);
} else {
console.error(`${chalk.red("✗")} Error:`, error.message);
}
}
}
+35
View File
@@ -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 <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}`);
}
}
},
};
+41
View File
@@ -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 <name>", description: "Storage name", required: true },
{ name: "--purpose <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}`);
}
}
},
};
+36
View File
@@ -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 <name>", description: "Storage name", required: true },
{ name: "--key <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}`);
}
}
},
};
+35
View File
@@ -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 <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}`);
}
}
},
};
+42
View File
@@ -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 <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}`);
}
}
},
};
+40
View File
@@ -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}`);
}
}
},
};
+59
View File
@@ -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 <name>", description: "Storage name", required: true },
{ name: "--key <key>", description: "Item key", required: true },
{ name: "--value <value>", description: "Item value (JSON string)", required: true },
{ name: "--expires <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}`);
}
}
},
};
+52
View File
@@ -0,0 +1,52 @@
import chalk from "chalk";
import { getApiClient } from "../api.js";
export const uiUrlDefinition = {
name: "uiurl",
description: "Get the URL for the SHSF UI.",
action: async () => {
await getUiURL();
},
};
async function getUiURL(): Promise<void> {
const client = await getApiClient();
try {
const response = await client.get<{ status: string; uiUrl: string }>("/api/global/uiUrl");
if (response.status === 200 && response.data.status === "OK") {
console.log(
`${chalk.green("✓")} SHSF UI URL: ${chalk.bgGreen.black(` ${response.data.uiUrl} `)}`
);
} else {
console.log(
`${chalk.yellow("!")} SHSF Status: ${chalk.bgYellow.black(" UNEXPECTED RESPONSE ")}`,
);
console.log(`Status Code: ${chalk.blue(response.status)}`);
console.log(`Response Data: ${JSON.stringify(response.data)}`);
}
} catch (error: any) {
if (error.response) {
console.error(
`${chalk.red("✗")} SHSF Status: ${chalk.bgRed.black(" UNHEALTHY ")}`,
);
console.error(`Status Code: ${chalk.red(error.response.status)}`);
console.error(
`Message: ${chalk.yellow(error.response.data.message || "Unknown error from server")}`,
);
} else if (error.request) {
console.error(
`${chalk.red("✗")} SHSF Status: ${chalk.bgRed.black(" DISCONNECTED ")}`,
);
console.error(
`Error: ${chalk.yellow("Could not connect to the SHSF instance. Check your connection or SHSF_INSTANCE URL.")}`,
);
} else {
console.error(
`${chalk.red("✗")} SHSF Status: ${chalk.bgRed.black(" LOCAL ERROR ")}`,
);
console.error(`Error: ${chalk.yellow(error.message)}`);
}
}
}
+76
View File
@@ -0,0 +1,76 @@
import chalk from "chalk";
import { getApiClient } from "../../api.js";
export const updateFunctionDefinition = {
name: "function <id>",
description: "Update an existing serverless function.",
options: [
{ name: "--name <name>", description: "Function name" },
{ name: "--description <description>", description: "Function description" },
{ name: "--startup-file <file>", description: "Startup file name" },
{ name: "--execution-alias <alias>", description: "Custom execution alias" },
{ name: "--docker-mount", description: "Enable Docker mount", type: "boolean" },
{ name: "--ffmpeg-install", description: "Install ffmpeg in container", type: "boolean" },
{ name: "--imported", description: "Marks the function as imported", type: "boolean" },
{ name: "--cache-enabled <enabled>", description: "Enable response caching (true/false)" },
{ name: "--cache-ttl <seconds>", description: "Cache TTL in seconds" },
],
action: async (id: string, options: any) => {
let parsedCacheEnabled: boolean | undefined;
if (options.cacheEnabled !== undefined) {
const normalized = String(options.cacheEnabled).toLowerCase();
if (normalized !== "true" && normalized !== "false") {
console.error(`${chalk.red("✗")} Invalid value for --cache-enabled. Use true or false.`);
return;
}
parsedCacheEnabled = normalized === "true";
}
const parsedCacheTtl = options.cacheTtl !== undefined ? Number(options.cacheTtl) : undefined;
if (parsedCacheTtl !== undefined && Number.isNaN(parsedCacheTtl)) {
console.error(`${chalk.red("✗")} Invalid value for --cache-ttl. Please provide a number.`);
return;
}
const data: Record<string, any> = {};
if (options.name !== undefined) data.name = options.name;
if (options.description !== undefined) data.description = options.description;
if (options.startupFile !== undefined) data.startup_file = options.startupFile;
if (options.executionAlias !== undefined) data.executionAlias = options.executionAlias;
if (options.dockerMount) data.docker_mount = true;
if (options.ffmpegInstall) data.ffmpeg_install = true;
if (options.imported) data.imported = true;
if (parsedCacheEnabled !== undefined) data.cache_enabled = parsedCacheEnabled;
if (parsedCacheTtl !== undefined) data.cache_ttl = parsedCacheTtl;
if (Object.keys(data).length === 0) {
console.error(`${chalk.red("✗")} No update fields provided. Use --help to see available options.`);
return;
}
const client = await getApiClient();
try {
const response = await client.patch(`/api/function/${id}`, data);
if (response.status === 200) {
console.log(`${chalk.green("✓")} Function with ID ${chalk.cyan(id)} updated successfully.`);
} else {
console.log(`${chalk.yellow("!")} Unexpected response from server: ${response.status}`);
}
} catch (error: any) {
if (error.response) {
if (error.response.status === 404) {
console.error(`${chalk.red("✗")} Function with ID ${chalk.yellow(id)} not found.`);
} else {
console.error(
`${chalk.red("✗")} Failed to update function: ${chalk.yellow(error.response.data.message || "Unknown error")}`,
);
}
} else {
console.error(`${chalk.red("✗")} Error: ${error.message}`);
}
}
},
};
+48
View File
@@ -0,0 +1,48 @@
import chalk from "chalk";
import { getApiClient } from "../../api.js";
export const updateNamespaceDefinition = {
name: "namespace <id>",
description: "Update an existing namespace.",
options: [
{ name: "--name <name>", description: "Namespace name" },
{ name: "--description <description>", description: "Namespace description" },
],
action: async (id: string, options: any) => {
const data: Record<string, any> = {};
if (options.name !== undefined) data.name = options.name;
if (options.description !== undefined) data.description = options.description;
if (Object.keys(data).length === 0) {
console.error(`${chalk.red("✗")} No update fields provided. Use --help to see available options.`);
return;
}
const client = await getApiClient();
try {
const response = await client.patch(`/api/namespace/${id}`, data);
if (response.status === 200) {
console.log(`${chalk.green("✓")} Namespace with ID ${chalk.cyan(id)} updated successfully.`);
} else {
console.log(`${chalk.yellow("!")} Unexpected response from server: ${response.status}`);
}
} catch (error: any) {
if (error.response) {
if (error.response.status === 404) {
console.error(`${chalk.red("✗")} Namespace with ID ${chalk.yellow(id)} not found.`);
} else {
console.error(
`${chalk.red("✗")} Failed to update namespace: ${chalk.yellow(error.response.data.message || "Unknown error")}`,
);
}
} else if (error.request) {
console.error(`${chalk.red("✗")} No response received from server.`);
} else {
console.error(`${chalk.red("✗")} Error: ${error.message}`);
}
}
},
};
+66
View File
@@ -0,0 +1,66 @@
import chalk from "chalk";
import { getApiClient } from "../../api.js";
export const updateTriggerDefinition = {
name: "trigger <functionId> <triggerId>",
description: "Update an existing trigger for a function.",
options: [
{ name: "--name <name>", description: "Trigger name" },
{ name: "--description <description>", description: "Trigger description" },
{ name: "--cron <cron>", description: "Cron expression" },
{ name: "--enabled <enabled>", description: "Whether the trigger is enabled (true/false)" },
{ name: "--data <data>", description: "Optional trigger payload data" },
],
action: async (functionId: string, triggerId: string, options: any) => {
let parsedEnabled: boolean | undefined;
if (options.enabled !== undefined) {
const normalized = String(options.enabled).toLowerCase();
if (normalized !== "true" && normalized !== "false") {
console.error(`${chalk.red("✗")} Invalid value for --enabled. Use true or false.`);
return;
}
parsedEnabled = normalized === "true";
}
const data: Record<string, any> = {};
if (options.name !== undefined) data.name = options.name;
if (options.description !== undefined) data.description = options.description;
if (options.cron !== undefined) data.cron = options.cron;
if (parsedEnabled !== undefined) data.enabled = parsedEnabled;
if (options.data !== undefined) data.data = options.data;
if (Object.keys(data).length === 0) {
console.error(`${chalk.red("✗")} No update fields provided. Use --help to see available options.`);
return;
}
const client = await getApiClient();
try {
const response = await client.patch(`/api/functions/${functionId}/triggers/${triggerId}`, data);
if (response.status === 200) {
console.log(
`${chalk.green("✓")} Trigger with ID ${chalk.cyan(triggerId)} for function ${chalk.cyan(functionId)} updated successfully.`,
);
} else {
console.log(`${chalk.yellow("!")} Unexpected response from server: ${response.status}`);
}
} catch (error: any) {
if (error.response) {
if (error.response.status === 404) {
console.error(`${chalk.red("✗")} Trigger or function not found.`);
} else {
console.error(
`${chalk.red("✗")} Failed to update trigger: ${chalk.yellow(error.response.data.message || "Unknown error")}`,
);
}
} else if (error.request) {
console.error(`${chalk.red("✗")} No response received from server.`);
} else {
console.error(`${chalk.red("✗")} Error: ${error.message}`);
}
}
},
};