diff --git a/OpenclawSkill.md b/OpenclawSkill.md index 404327b..c446d90 100644 --- a/OpenclawSkill.md +++ b/OpenclawSkill.md @@ -50,8 +50,8 @@ this will check the health, and if not setup, it will prompt you to set up the C - `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) -- `shsf remote --id --into `: Pull files from a function into a local directory. -- `shsf remote --id --from [--force]`: Push files from a local directory to a function. +- `shsf remote pull --id --into [--force]`: Pull files from a function into a local directory. +- `shsf remote push --id --from [--force]`: Push files from a local directory to a function. ## 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. diff --git a/src/__tests__/remote.test.js b/src/__tests__/remote.test.js deleted file mode 100644 index ff10260..0000000 --- a/src/__tests__/remote.test.js +++ /dev/null @@ -1,12 +0,0 @@ -import { describe, it, expect, vi } from 'vitest'; -import { action } from '../commands/remote.js'; -import axios from 'axios'; - -vi.mock('axios'); - -describe('remote command', () => { - it('should validate inputs', async () => { - // Simple test for now - await expect(action({})).resolves.not.toThrow(); - }); -}); diff --git a/src/commands/remote.ts b/src/commands/remote.ts deleted file mode 100644 index 3c137db..0000000 --- a/src/commands/remote.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { Command } from 'commander'; -import axios from 'axios'; -import { loadConfig } from '../config.js'; -import chalk from 'chalk'; -import fs from 'fs'; -import path from 'path'; - -export async function action(options: any) { - const config = await loadConfig(); - const api = axios.create({ - baseURL: config.apiBaseUrl, - headers: { 'Authorization': `Bearer ${config.token}` } - }); - - if (options.into) { - // PULL - try { - const response = await api.get(`/functions/${options.id}/files`); - const files = response.data; // Assuming { filename: string, content: string }[] - - if (!fs.existsSync(options.into)) { - fs.mkdirSync(options.into, { recursive: true }); - } else if (fs.readdirSync(options.into).length > 0 && !options.force) { - console.error(chalk.red(`Directory ${options.into} is not empty. Use --force to overwrite.`)); - return; - } - - for (const file of files) { - fs.writeFileSync(path.join(options.into, file.filename), file.content); - } - console.log(chalk.green(`Successfully pulled files into ${options.into}`)); - } catch (e) { - console.error(chalk.red('Failed to pull files'), e); - } - } else if (options.from) { - // PUSH - if (!fs.existsSync(options.from)) { - console.error(chalk.red(`Source ${options.from} does not exist.`)); - return; - } - - const files = fs.readdirSync(options.from).map(file => ({ - filename: file, - content: fs.readFileSync(path.join(options.from, file), 'utf-8') - })); - - if (!options.force) { - console.log(chalk.yellow(`Pushing ${files.length} files. Review: ${files.map(f => f.filename).join(', ')}`)); - - if (files.length > 5) { - console.log(chalk.yellow('Too many modifications. Please use --force to confirm.')); - return; - } - } - - try { - await api.post(`/functions/${options.id}/files`, { files }); - console.log(chalk.green('Successfully pushed files.')); - } catch (e) { - console.error(chalk.red('Failed to push files'), e); - } - } else { - console.error(chalk.red('Must specify --into or --from')); - } -} - -export default { - name: 'remote', - description: 'sync remote functions to/from local storage', - options: [ - { name: '--id ', description: 'function id', required: true }, - { name: '--into ', description: 'target directory for pull' }, - { name: '--from ', description: 'source directory for push' }, - { name: '--force', description: 'force push/pull without confirmation' } - ], - action -}; diff --git a/src/commands/remote/pull.ts b/src/commands/remote/pull.ts new file mode 100644 index 0000000..eb2c893 --- /dev/null +++ b/src/commands/remote/pull.ts @@ -0,0 +1,36 @@ +import chalk from "chalk"; +import { getApiClient } from "../../api.js"; +import fs from "fs"; +import path from "path"; + +export const pullDefinition = { + name: "pull", + description: "Pull files from a remote function.", + options: [ + { name: "--id ", description: "Function ID", required: true }, + { name: "--into ", description: "Target directory", required: true }, + { name: "--force", description: "Force overwrite" }, + ], + action: async (options: any) => { + const client = await getApiClient(); + + try { + const response = await client.get(`/functions/${options.id}/files`); + const files = response.data; // Expecting { filename: string, content: string }[] + + if (!fs.existsSync(options.into)) { + fs.mkdirSync(options.into, { recursive: true }); + } else if (fs.readdirSync(options.into).length > 0 && !options.force) { + console.error(chalk.red(`Directory ${options.into} is not empty. Use --force to overwrite.`)); + return; + } + + for (const file of files) { + fs.writeFileSync(path.join(options.into, file.filename), file.content); + } + console.log(chalk.green(`Successfully pulled ${files.length} files into ${options.into}`)); + } catch (error: any) { + console.error(chalk.red(`Failed to pull files: ${error.message}`)); + } + }, +}; diff --git a/src/commands/remote/push.ts b/src/commands/remote/push.ts new file mode 100644 index 0000000..0e6e6e0 --- /dev/null +++ b/src/commands/remote/push.ts @@ -0,0 +1,39 @@ +import chalk from "chalk"; +import { getApiClient } from "../../api.js"; +import fs from "fs"; +import path from "path"; + +export const pushDefinition = { + name: "push", + description: "Push files to a remote function.", + options: [ + { name: "--id ", description: "Function ID", required: true }, + { name: "--from ", description: "Source directory", required: true }, + { name: "--force", description: "Force overwrite" }, + ], + action: async (options: any) => { + const client = await getApiClient(); + + if (!fs.existsSync(options.from)) { + console.error(chalk.red(`Source ${options.from} does not exist.`)); + return; + } + + const files = fs.readdirSync(options.from).map(file => ({ + filename: file, + content: fs.readFileSync(path.join(options.from, file), 'utf-8') + })); + + if (!options.force && files.length > 5) { + console.log(chalk.yellow('Too many modifications. Please use --force to confirm.')); + return; + } + + try { + await client.post(`/functions/${options.id}/files`, { files }); + console.log(chalk.green(`Successfully pushed ${files.length} files to function ${options.id}`)); + } catch (error: any) { + console.error(chalk.red(`Failed to push files: ${error.message}`)); + } + }, +};