upclaw-cli 1.0.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.
Files changed (53) hide show
  1. package/README.md +188 -0
  2. package/dist/commands/info.d.ts +2 -0
  3. package/dist/commands/info.d.ts.map +1 -0
  4. package/dist/commands/info.js +44 -0
  5. package/dist/commands/info.js.map +1 -0
  6. package/dist/commands/install.d.ts +4 -0
  7. package/dist/commands/install.d.ts.map +1 -0
  8. package/dist/commands/install.js +100 -0
  9. package/dist/commands/install.js.map +1 -0
  10. package/dist/commands/list.d.ts +4 -0
  11. package/dist/commands/list.d.ts.map +1 -0
  12. package/dist/commands/list.js +43 -0
  13. package/dist/commands/list.js.map +1 -0
  14. package/dist/commands/login.d.ts +5 -0
  15. package/dist/commands/login.d.ts.map +1 -0
  16. package/dist/commands/login.js +111 -0
  17. package/dist/commands/login.js.map +1 -0
  18. package/dist/commands/publish.d.ts +10 -0
  19. package/dist/commands/publish.d.ts.map +1 -0
  20. package/dist/commands/publish.js +163 -0
  21. package/dist/commands/publish.js.map +1 -0
  22. package/dist/commands/search.d.ts +9 -0
  23. package/dist/commands/search.d.ts.map +1 -0
  24. package/dist/commands/search.js +47 -0
  25. package/dist/commands/search.js.map +1 -0
  26. package/dist/commands/whoami.d.ts +2 -0
  27. package/dist/commands/whoami.d.ts.map +1 -0
  28. package/dist/commands/whoami.js +22 -0
  29. package/dist/commands/whoami.js.map +1 -0
  30. package/dist/config.d.ts +10 -0
  31. package/dist/config.d.ts.map +1 -0
  32. package/dist/config.js +13 -0
  33. package/dist/config.js.map +1 -0
  34. package/dist/index.d.ts +3 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +59 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/utils/api.d.ts +5 -0
  39. package/dist/utils/api.d.ts.map +1 -0
  40. package/dist/utils/api.js +31 -0
  41. package/dist/utils/api.js.map +1 -0
  42. package/package.json +49 -0
  43. package/src/commands/info.ts +42 -0
  44. package/src/commands/install.ts +73 -0
  45. package/src/commands/list.ts +47 -0
  46. package/src/commands/login.ts +80 -0
  47. package/src/commands/publish.ts +152 -0
  48. package/src/commands/search.ts +56 -0
  49. package/src/commands/whoami.ts +18 -0
  50. package/src/config.ts +14 -0
  51. package/src/index.ts +67 -0
  52. package/src/utils/api.ts +26 -0
  53. package/tsconfig.json +21 -0
@@ -0,0 +1,80 @@
1
+ import { createWalletClient, http, parseEther } from "viem";
2
+ import { privateKeyToAccount } from "viem/accounts";
3
+ import { baseSepolia } from "viem/chains";
4
+ import inquirer from "inquirer";
5
+ import chalk from "chalk";
6
+ import ora from "ora";
7
+ import { config } from "../config";
8
+ import { createAPIClient } from "../utils/api";
9
+ import * as fs from "fs-extra";
10
+
11
+ export async function loginCommand(options: { privateKey?: string; keystore?: string }) {
12
+ let privateKey: string;
13
+
14
+ // Get private key from various sources
15
+ if (options.privateKey) {
16
+ privateKey = options.privateKey;
17
+ } else if (options.keystore) {
18
+ // Load from keystore file
19
+ const keystoreData = await fs.readJSON(options.keystore);
20
+ privateKey = keystoreData.privateKey;
21
+ } else {
22
+ // Prompt for private key
23
+ const answers = await inquirer.prompt([
24
+ {
25
+ type: "password",
26
+ name: "privateKey",
27
+ message: "Enter your wallet private key (0x...):",
28
+ validate: (input) => {
29
+ if (!input.startsWith("0x") || input.length !== 66) {
30
+ return "Invalid private key format. Must be 0x followed by 64 hex characters.";
31
+ }
32
+ return true;
33
+ },
34
+ },
35
+ ]);
36
+ privateKey = answers.privateKey;
37
+ }
38
+
39
+ const spinner = ora("Authenticating...").start();
40
+
41
+ try {
42
+ // Create account from private key
43
+ const account = privateKeyToAccount(privateKey as `0x${string}`);
44
+ const address = account.address;
45
+
46
+ // Sign a message to prove ownership
47
+ const message = `Sign in to UpClaw CLI\nTimestamp: ${Date.now()}`;
48
+ const signature = await account.signMessage({ message });
49
+
50
+ // Authenticate with API
51
+ const api = createAPIClient();
52
+ const response = await api.post("/cli/auth", {
53
+ address,
54
+ signature,
55
+ message,
56
+ });
57
+
58
+ const { token, user } = response.data;
59
+
60
+ // Store credentials
61
+ config.set("token", token);
62
+ config.set("address", user.address);
63
+ config.set("username", user.username);
64
+
65
+ spinner.succeed(
66
+ chalk.green(`āœ“ Successfully logged in as ${chalk.bold(user.username)} (${user.address})`)
67
+ );
68
+ } catch (error: any) {
69
+ spinner.fail(chalk.red("Authentication failed"));
70
+
71
+ if (error.response?.status === 404) {
72
+ console.error(chalk.yellow("\nāš ļø User not found. Please register at https://upclaw.ai first."));
73
+ } else if (error.response?.data?.error) {
74
+ console.error(chalk.red(`\nError: ${error.response.data.error}`));
75
+ } else {
76
+ console.error(chalk.red(`\nError: ${error.message}`));
77
+ }
78
+ process.exit(1);
79
+ }
80
+ }
@@ -0,0 +1,152 @@
1
+ import chalk from "chalk";
2
+ import ora from "ora";
3
+ import inquirer from "inquirer";
4
+ import * as fs from "fs-extra";
5
+ import * as path from "path";
6
+ import archiver from "archiver";
7
+ import FormData from "form-data";
8
+ import { createAPIClient, requireAuth } from "../utils/api";
9
+
10
+ interface PublishOptions {
11
+ name?: string;
12
+ description?: string;
13
+ category?: string;
14
+ price?: string;
15
+ version?: string;
16
+ }
17
+
18
+ export async function publishCommand(options: PublishOptions) {
19
+ requireAuth();
20
+
21
+ console.log(chalk.bold.cyan("\nšŸ“¤ Publishing Skill to UpClaw\n"));
22
+
23
+ // Check for required files
24
+ const requiredFiles = ["SKILL.md", "package.json"];
25
+ const missingFiles = requiredFiles.filter((file) => !fs.existsSync(file));
26
+
27
+ if (missingFiles.length > 0) {
28
+ console.error(chalk.red(`āŒ Missing required files: ${missingFiles.join(", ")}\n`));
29
+ console.log(chalk.gray("Your skill must include:"));
30
+ console.log(chalk.gray(" - SKILL.md (documentation)"));
31
+ console.log(chalk.gray(" - package.json (dependencies)\n"));
32
+ process.exit(1);
33
+ }
34
+
35
+ // Get metadata from user if not provided
36
+ let metadata = options;
37
+
38
+ if (!metadata.name || !metadata.description || !metadata.category || !metadata.price) {
39
+ const packageJson = await fs.readJSON("package.json");
40
+
41
+ const answers = await inquirer.prompt([
42
+ {
43
+ type: "input",
44
+ name: "name",
45
+ message: "Skill name:",
46
+ default: metadata.name || packageJson.name,
47
+ when: !metadata.name,
48
+ },
49
+ {
50
+ type: "input",
51
+ name: "description",
52
+ message: "Description:",
53
+ default: metadata.description || packageJson.description,
54
+ when: !metadata.description,
55
+ },
56
+ {
57
+ type: "list",
58
+ name: "category",
59
+ message: "Category:",
60
+ choices: ["automation", "data", "communication", "web", "ai", "utility", "other"],
61
+ when: !metadata.category,
62
+ },
63
+ {
64
+ type: "input",
65
+ name: "price",
66
+ message: "Price in USDC:",
67
+ validate: (input) => {
68
+ const num = parseFloat(input);
69
+ if (isNaN(num) || num < 0) {
70
+ return "Please enter a valid price (e.g., 25.00)";
71
+ }
72
+ return true;
73
+ },
74
+ when: !metadata.price,
75
+ },
76
+ {
77
+ type: "input",
78
+ name: "version",
79
+ message: "Version:",
80
+ default: metadata.version || packageJson.version || "1.0.0",
81
+ when: !metadata.version,
82
+ },
83
+ ]);
84
+
85
+ metadata = { ...metadata, ...answers };
86
+ }
87
+
88
+ const spinner = ora("Creating skill bundle...").start();
89
+
90
+ try {
91
+ // Create a zip of the current directory
92
+ const zipPath = path.join(process.cwd(), "skill-bundle.zip");
93
+ const output = fs.createWriteStream(zipPath);
94
+ const archive = archiver("zip", { zlib: { level: 9 } });
95
+
96
+ archive.pipe(output);
97
+ archive.glob("**/*", {
98
+ ignore: ["node_modules/**", "*.zip", ".git/**", "dist/**", ".env*"],
99
+ });
100
+
101
+ await archive.finalize();
102
+
103
+ await new Promise((resolve) => output.on("close", () => resolve(undefined)));
104
+
105
+ spinner.text = "Uploading to IPFS...";
106
+
107
+ // Upload to marketplace API
108
+ const api = createAPIClient();
109
+ const formData = new FormData();
110
+
111
+ formData.append("name", metadata.name!);
112
+ formData.append("description", metadata.description!);
113
+ formData.append("category", metadata.category!);
114
+ formData.append("price", metadata.price!);
115
+ formData.append("version", metadata.version!);
116
+ formData.append("files", fs.createReadStream(zipPath));
117
+
118
+ const response = await api.post("/cli/skills/publish", formData, {
119
+ headers: formData.getHeaders(),
120
+ maxContentLength: Infinity,
121
+ maxBodyLength: Infinity,
122
+ });
123
+
124
+ // Clean up
125
+ await fs.remove(zipPath);
126
+
127
+ spinner.succeed(chalk.green("āœ“ Skill uploaded successfully!"));
128
+
129
+ const { skill, message, next_steps } = response.data;
130
+
131
+ console.log(chalk.bold(`\n✨ ${metadata.name} is ready!\n`));
132
+ console.log(`${chalk.gray("Skill ID:")} ${skill.id}`);
133
+ console.log(`${chalk.gray("IPFS CID:")} ${skill.ipfs_cid}`);
134
+ console.log(`${chalk.gray("Price:")} ${chalk.green("$" + skill.price + " USDC")}\n`);
135
+
136
+ console.log(chalk.yellow(message + "\n"));
137
+ console.log(chalk.bold("⚔ Next Steps:\n"));
138
+ next_steps.forEach((step: string, i: number) => {
139
+ console.log(` ${i + 1}. ${step}`);
140
+ });
141
+ console.log();
142
+ } catch (error: any) {
143
+ spinner.fail(chalk.red("Publication failed"));
144
+
145
+ if (error.response?.data?.error) {
146
+ console.error(chalk.red(`\nError: ${error.response.data.error}\n`));
147
+ } else {
148
+ console.error(chalk.red(`\nError: ${error.message}\n`));
149
+ }
150
+ process.exit(1);
151
+ }
152
+ }
@@ -0,0 +1,56 @@
1
+ import chalk from "chalk";
2
+ import ora from "ora";
3
+ import { createAPIClient } from "../utils/api";
4
+
5
+ interface SearchOptions {
6
+ category?: string;
7
+ minPrice?: string;
8
+ maxPrice?: string;
9
+ limit?: string;
10
+ }
11
+
12
+ export async function searchCommand(query: string, options: SearchOptions) {
13
+ const spinner = ora("Searching marketplace...").start();
14
+
15
+ try {
16
+ const api = createAPIClient();
17
+ const params = new URLSearchParams({
18
+ q: query,
19
+ limit: options.limit || "20",
20
+ });
21
+
22
+ if (options.category) params.append("category", options.category);
23
+ if (options.minPrice) params.append("minPrice", options.minPrice);
24
+ if (options.maxPrice) params.append("maxPrice", options.maxPrice);
25
+
26
+ const response = await api.get(`/cli/skills/search?${params.toString()}`);
27
+ const { skills, count } = response.data;
28
+
29
+ spinner.stop();
30
+
31
+ if (skills.length === 0) {
32
+ console.log(chalk.yellow("\nāš ļø No skills found matching your query.\n"));
33
+ return;
34
+ }
35
+
36
+ console.log(chalk.bold(`\nšŸ” Found ${count} skill${count !== 1 ? "s" : ""}\n`));
37
+
38
+ skills.forEach((skill: any) => {
39
+ console.log(chalk.bold.cyan(`• ${skill.name}`));
40
+ console.log(` ${skill.description}`);
41
+ console.log(
42
+ ` ${chalk.gray("Category:")} ${skill.category} ${chalk.gray("Price:")} ${chalk.green(
43
+ "$" + skill.price_usdc
44
+ )} ${chalk.gray("⬇")} ${skill.downloads}`
45
+ );
46
+ console.log(` ${chalk.gray("ID:")} ${skill.id}\n`);
47
+ });
48
+
49
+ console.log(chalk.gray(`Run 'upclaw info <skill-name>' to see more details`));
50
+ console.log(chalk.gray(`Run 'upclaw install <skill-name>' to purchase and install\n`));
51
+ } catch (error: any) {
52
+ spinner.fail(chalk.red("Search failed"));
53
+ console.error(chalk.red(`Error: ${error.message}\n`));
54
+ process.exit(1);
55
+ }
56
+ }
@@ -0,0 +1,18 @@
1
+ import chalk from "chalk";
2
+ import { config } from "../config";
3
+
4
+ export async function whoamiCommand() {
5
+ const token = config.get("token");
6
+ const address = config.get("address");
7
+ const username = config.get("username");
8
+
9
+ if (!token) {
10
+ console.log(chalk.yellow("Not authenticated. Run 'upclaw login' to sign in."));
11
+ return;
12
+ }
13
+
14
+ console.log(chalk.bold("\nšŸ” Authentication Status\n"));
15
+ console.log(`Username: ${chalk.cyan(username)}`);
16
+ console.log(`Address: ${chalk.cyan(address)}`);
17
+ console.log(`Status: ${chalk.green("āœ“ Authenticated")}\n`);
18
+ }
package/src/config.ts ADDED
@@ -0,0 +1,14 @@
1
+ import Conf from "conf";
2
+
3
+ interface UpClawConfig {
4
+ token?: string;
5
+ address?: string;
6
+ username?: string;
7
+ }
8
+
9
+ export const config = new Conf<UpClawConfig>({
10
+ projectName: "upclaw-cli",
11
+ defaults: {},
12
+ });
13
+
14
+ export const API_BASE_URL = process.env.UPCLAW_API_URL || "https://upclaw.ai/api";
package/src/index.ts ADDED
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from "commander";
4
+ import { searchCommand } from "./commands/search";
5
+ import { installCommand } from "./commands/install";
6
+ import { publishCommand } from "./commands/publish";
7
+ import { loginCommand } from "./commands/login";
8
+ import { whoamiCommand } from "./commands/whoami";
9
+ import { listCommand } from "./commands/list";
10
+ import { infoCommand } from "./commands/info";
11
+
12
+ const program = new Command();
13
+
14
+ program
15
+ .name("upclaw")
16
+ .description("CLI tool for UpClaw marketplace - buy and sell OpenClaw agent skills")
17
+ .version("1.0.0");
18
+
19
+ program
20
+ .command("login")
21
+ .description("Authenticate with your wallet")
22
+ .option("-p, --private-key <key>", "Wallet private key (0x...)")
23
+ .option("-k, --keystore <file>", "Path to keystore JSON file")
24
+ .action(loginCommand);
25
+
26
+ program
27
+ .command("whoami")
28
+ .description("Show current authentication status")
29
+ .action(whoamiCommand);
30
+
31
+ program
32
+ .command("search <query>")
33
+ .description("Search for skills in the marketplace")
34
+ .option("-c, --category <category>", "Filter by category")
35
+ .option("--min-price <price>", "Minimum price in USDC")
36
+ .option("--max-price <price>", "Maximum price in USDC")
37
+ .option("-l, --limit <number>", "Number of results to show", "20")
38
+ .action(searchCommand);
39
+
40
+ program
41
+ .command("info <skill-name>")
42
+ .description("View detailed information about a skill")
43
+ .action(infoCommand);
44
+
45
+ program
46
+ .command("install <skill-name>")
47
+ .description("Purchase and install a skill")
48
+ .option("-o, --output <directory>", "Installation directory", process.env.HOME + "/.upclaw/skills")
49
+ .action(installCommand);
50
+
51
+ program
52
+ .command("list")
53
+ .description("List your owned skills")
54
+ .option("-f, --format <format>", "Output format (table|json)", "table")
55
+ .action(listCommand);
56
+
57
+ program
58
+ .command("publish")
59
+ .description("Publish current directory as a skill")
60
+ .option("-n, --name <name>", "Skill name")
61
+ .option("-d, --description <description>", "Skill description")
62
+ .option("-c, --category <category>", "Category (automation|data|communication|etc)")
63
+ .option("-p, --price <price>", "Price in USDC")
64
+ .option("-v, --version <version>", "Version number", "1.0.0")
65
+ .action(publishCommand);
66
+
67
+ program.parse();
@@ -0,0 +1,26 @@
1
+ import axios, { AxiosInstance } from "axios";
2
+ import { config, API_BASE_URL } from "../config";
3
+
4
+ export function createAPIClient(): AxiosInstance {
5
+ const token = config.get("token");
6
+
7
+ return axios.create({
8
+ baseURL: API_BASE_URL,
9
+ headers: token
10
+ ? {
11
+ Authorization: `Bearer ${token}`,
12
+ }
13
+ : {},
14
+ });
15
+ }
16
+
17
+ export function isAuthenticated(): boolean {
18
+ return !!config.get("token");
19
+ }
20
+
21
+ export function requireAuth() {
22
+ if (!isAuthenticated()) {
23
+ console.error("āŒ Not authenticated. Please run 'upclaw login' first.");
24
+ process.exit(1);
25
+ }
26
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "commonjs",
5
+ "lib": ["ES2022"],
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": false,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "resolveJsonModule": true,
13
+ "declaration": true,
14
+ "declarationMap": true,
15
+ "sourceMap": true,
16
+ "moduleResolution": "node",
17
+ "types": ["node"]
18
+ },
19
+ "include": ["src/**/*"],
20
+ "exclude": ["node_modules", "dist", "../node_modules"]
21
+ }