lua-cli 1.0.0 → 1.1.1
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/CHANGELOG.md +65 -0
- package/README.md +22 -9
- package/dist/commands/agents.js +20 -0
- package/dist/commands/apiKey.js +24 -0
- package/dist/commands/configure.js +80 -0
- package/dist/commands/destroy.js +29 -0
- package/dist/commands/index.js +5 -0
- package/dist/commands/init.js +69 -0
- package/dist/index.js +6 -329
- package/dist/services/auth.js +91 -0
- package/dist/types/index.js +1 -0
- package/dist/utils/files.js +27 -0
- package/package.json +4 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.1.0] - 2024-01-XX
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Email Authentication**: New email-based OTP authentication method in `lua configure`
|
|
12
|
+
- **API Key Display**: New `lua apiKey` command to view stored API key
|
|
13
|
+
- **Modular Architecture**: Complete codebase refactoring into organized modules
|
|
14
|
+
- **TypeScript Types**: Comprehensive type definitions for all API responses
|
|
15
|
+
- **Command Structure**: Individual command files for better maintainability
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- **Project Structure**: Reorganized codebase into logical directories:
|
|
19
|
+
- `src/commands/` - Individual command implementations
|
|
20
|
+
- `src/services/` - API and authentication services
|
|
21
|
+
- `src/types/` - TypeScript interface definitions
|
|
22
|
+
- `src/utils/` - Utility functions
|
|
23
|
+
- **Main File**: Reduced `src/index.ts` from 392 lines to 39 lines
|
|
24
|
+
- **Authentication Flow**: Enhanced `lua configure` with dual authentication methods
|
|
25
|
+
- **Error Handling**: Improved error messages and validation throughout
|
|
26
|
+
|
|
27
|
+
### Technical Improvements
|
|
28
|
+
- **Code Organization**: Separated concerns for better maintainability
|
|
29
|
+
- **Type Safety**: Added proper TypeScript interfaces for all API responses
|
|
30
|
+
- **Reusability**: Services can now be shared across multiple commands
|
|
31
|
+
- **Scalability**: Easy to add new commands without cluttering main file
|
|
32
|
+
- **Testing Ready**: Modular structure enables independent testing
|
|
33
|
+
|
|
34
|
+
### Security
|
|
35
|
+
- **OTP Verification**: Secure email-based authentication with 6-digit codes
|
|
36
|
+
- **Token Management**: Proper handling of sign-in tokens and API key generation
|
|
37
|
+
- **Confirmation Prompts**: Added safety confirmations for sensitive operations
|
|
38
|
+
|
|
39
|
+
## [1.0.0] - 2024-01-XX
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
- **Initial Release**: First version of Lua CLI
|
|
43
|
+
- **API Key Authentication**: Direct API key input and validation
|
|
44
|
+
- **Project Initialization**: `lua init` command for creating new skill projects
|
|
45
|
+
- **Agent Management**: `lua agents` command to fetch and display agents
|
|
46
|
+
- **Credential Management**: `lua destroy` command to delete stored API keys
|
|
47
|
+
- **Template System**: Automatic template file copying for new projects
|
|
48
|
+
- **Secure Storage**: API keys stored using system credential manager
|
|
49
|
+
- **Interactive Prompts**: User-friendly command-line interface with inquirer
|
|
50
|
+
- **Organization Selection**: Choose from available organizations
|
|
51
|
+
- **Agent Selection**: Select agents within chosen organizations
|
|
52
|
+
- **TOML Configuration**: Generate `lua.skill.toml` configuration files
|
|
53
|
+
|
|
54
|
+
### Features
|
|
55
|
+
- **Cross-platform**: Works on Windows, macOS, and Linux
|
|
56
|
+
- **Secure**: Uses keytar for secure credential storage
|
|
57
|
+
- **User-friendly**: Interactive prompts and clear error messages
|
|
58
|
+
- **Template-based**: Quick project setup with pre-configured templates
|
|
59
|
+
- **Validation**: Input validation for email addresses and OTP codes
|
|
60
|
+
|
|
61
|
+
### Technical Details
|
|
62
|
+
- **Node.js**: Requires Node.js 16.0.0 or higher
|
|
63
|
+
- **TypeScript**: Built with TypeScript for type safety
|
|
64
|
+
- **Dependencies**: Uses commander, inquirer, keytar, and node-fetch
|
|
65
|
+
- **ES Modules**: Modern ES module syntax throughout
|
package/README.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
A command-line interface for the Lua AI platform that helps you manage agents, organizations, and skills with ease.
|
|
4
4
|
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g lua-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
After installation, you can use the `lua` command:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
lua --help
|
|
17
|
+
```
|
|
18
|
+
|
|
5
19
|
## Features
|
|
6
20
|
|
|
7
21
|
- 🔐 **Secure Authentication**: Support for both API key and email-based OTP authentication
|
|
@@ -11,14 +25,6 @@ A command-line interface for the Lua AI platform that helps you manage agents, o
|
|
|
11
25
|
- 🔑 **API Key Management**: Securely store, view, and manage your API keys
|
|
12
26
|
- 📦 **Template System**: Quick project setup with pre-configured templates
|
|
13
27
|
|
|
14
|
-
## Installation
|
|
15
|
-
|
|
16
|
-
Install the Lua CLI globally using npm:
|
|
17
|
-
|
|
18
|
-
```bash
|
|
19
|
-
npm install -g lua-cli
|
|
20
|
-
```
|
|
21
|
-
|
|
22
28
|
## Quick Start
|
|
23
29
|
|
|
24
30
|
1. **Configure your authentication:**
|
|
@@ -168,9 +174,16 @@ For support and questions:
|
|
|
168
174
|
|
|
169
175
|
## Changelog
|
|
170
176
|
|
|
177
|
+
### 1.1.0
|
|
178
|
+
- **Major Refactoring**: Complete codebase reorganization into modular structure
|
|
179
|
+
- **Email Authentication**: Added OTP-based email authentication
|
|
180
|
+
- **API Key Display**: New `lua apiKey` command
|
|
181
|
+
- **TypeScript Types**: Comprehensive type definitions
|
|
182
|
+
- **Improved Architecture**: Separated commands, services, and utilities
|
|
183
|
+
|
|
171
184
|
### 1.0.0
|
|
172
185
|
- Initial release
|
|
173
|
-
- API key
|
|
186
|
+
- API key authentication
|
|
174
187
|
- Organization and agent management
|
|
175
188
|
- Skill project initialization
|
|
176
189
|
- Secure credential storage
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import fetch from "node-fetch";
|
|
2
|
+
import { loadApiKey } from "../services/auth.js";
|
|
3
|
+
export async function agentsCommand() {
|
|
4
|
+
const apiKey = await loadApiKey();
|
|
5
|
+
if (!apiKey) {
|
|
6
|
+
console.error("❌ No API key found. Run `lua configure` first.");
|
|
7
|
+
process.exit(1);
|
|
8
|
+
}
|
|
9
|
+
const response = await fetch("https://api.heylua.ai/admin", {
|
|
10
|
+
headers: {
|
|
11
|
+
Authorization: `Bearer ${apiKey}`,
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
if (!response.ok) {
|
|
15
|
+
console.error(`❌ Error ${response.status}: ${await response.text()}`);
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
const data = await response.json();
|
|
19
|
+
console.log("✅ Agents:", JSON.stringify(data, null, 2));
|
|
20
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import inquirer from "inquirer";
|
|
2
|
+
import { loadApiKey } from "../services/auth.js";
|
|
3
|
+
export async function apiKeyCommand() {
|
|
4
|
+
const apiKey = await loadApiKey();
|
|
5
|
+
if (!apiKey) {
|
|
6
|
+
console.log("ℹ️ No API key found. Run `lua configure` first.");
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
const { confirm } = await inquirer.prompt([
|
|
10
|
+
{
|
|
11
|
+
type: "confirm",
|
|
12
|
+
name: "confirm",
|
|
13
|
+
message: "This will display your API key. Are you sure you want to continue?",
|
|
14
|
+
default: false
|
|
15
|
+
}
|
|
16
|
+
]);
|
|
17
|
+
if (confirm) {
|
|
18
|
+
console.log("🔑 Your API key:");
|
|
19
|
+
console.log(apiKey);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
console.log("ℹ️ API key display cancelled.");
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import inquirer from "inquirer";
|
|
2
|
+
import { saveApiKey, checkApiKey, requestEmailOTP, verifyOTPAndGetToken, generateApiKey } from "../services/auth.js";
|
|
3
|
+
export async function configureCommand() {
|
|
4
|
+
// Choose authentication method
|
|
5
|
+
const { authMethod } = await inquirer.prompt([
|
|
6
|
+
{
|
|
7
|
+
type: "list",
|
|
8
|
+
name: "authMethod",
|
|
9
|
+
message: "Choose authentication method:",
|
|
10
|
+
choices: [
|
|
11
|
+
{ name: "API Key", value: "api-key" },
|
|
12
|
+
{ name: "Email", value: "email" }
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
]);
|
|
16
|
+
if (authMethod === "api-key") {
|
|
17
|
+
// Existing API key flow
|
|
18
|
+
const answers = await inquirer.prompt([
|
|
19
|
+
{
|
|
20
|
+
type: "password",
|
|
21
|
+
name: "apiKey",
|
|
22
|
+
message: "Enter your API key",
|
|
23
|
+
mask: "*",
|
|
24
|
+
},
|
|
25
|
+
]);
|
|
26
|
+
const data = await checkApiKey(answers.apiKey);
|
|
27
|
+
if (!data) {
|
|
28
|
+
console.error("❌ Invalid API key");
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
await saveApiKey(answers.apiKey);
|
|
32
|
+
console.log("✅ API key saved securely.");
|
|
33
|
+
}
|
|
34
|
+
else if (authMethod === "email") {
|
|
35
|
+
// Email authentication flow
|
|
36
|
+
const { email } = await inquirer.prompt([
|
|
37
|
+
{
|
|
38
|
+
type: "input",
|
|
39
|
+
name: "email",
|
|
40
|
+
message: "Enter your email address:",
|
|
41
|
+
validate: (input) => {
|
|
42
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
43
|
+
return emailRegex.test(input) || "Please enter a valid email address";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
]);
|
|
47
|
+
console.log("📧 Sending OTP to your email...");
|
|
48
|
+
const otpSent = await requestEmailOTP(email);
|
|
49
|
+
if (!otpSent) {
|
|
50
|
+
console.error("❌ Failed to send OTP. Please try again.");
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
console.log("✅ OTP sent successfully!");
|
|
54
|
+
const { pin } = await inquirer.prompt([
|
|
55
|
+
{
|
|
56
|
+
type: "input",
|
|
57
|
+
name: "pin",
|
|
58
|
+
message: "Enter the OTP code:",
|
|
59
|
+
validate: (input) => {
|
|
60
|
+
return input.length === 6 || "OTP must be 6 digits";
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
]);
|
|
64
|
+
console.log("🔐 Verifying OTP...");
|
|
65
|
+
const signInToken = await verifyOTPAndGetToken(email, pin);
|
|
66
|
+
if (!signInToken) {
|
|
67
|
+
console.error("❌ Invalid OTP. Please try again.");
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
console.log("✅ OTP verified successfully!");
|
|
71
|
+
console.log("🔑 Generating API key...");
|
|
72
|
+
const apiKey = await generateApiKey(signInToken);
|
|
73
|
+
if (!apiKey) {
|
|
74
|
+
console.error("❌ Failed to generate API key. Please try again.");
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
await saveApiKey(apiKey);
|
|
78
|
+
console.log("✅ API key generated and saved securely.");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import inquirer from "inquirer";
|
|
2
|
+
import { loadApiKey, deleteApiKey } from "../services/auth.js";
|
|
3
|
+
export async function destroyCommand() {
|
|
4
|
+
const apiKey = await loadApiKey();
|
|
5
|
+
if (!apiKey) {
|
|
6
|
+
console.log("ℹ️ No API key found to delete.");
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
const { confirm } = await inquirer.prompt([
|
|
10
|
+
{
|
|
11
|
+
type: "confirm",
|
|
12
|
+
name: "confirm",
|
|
13
|
+
message: "Are you sure you want to delete your API key? This action cannot be undone.",
|
|
14
|
+
default: false
|
|
15
|
+
}
|
|
16
|
+
]);
|
|
17
|
+
if (confirm) {
|
|
18
|
+
const deleted = await deleteApiKey();
|
|
19
|
+
if (deleted) {
|
|
20
|
+
console.log("✅ API key deleted successfully.");
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
console.log("❌ Failed to delete API key.");
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
console.log("ℹ️ API key deletion cancelled.");
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import inquirer from "inquirer";
|
|
2
|
+
import { fileURLToPath } from "url";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { loadApiKey, checkApiKey } from "../services/auth.js";
|
|
5
|
+
import { copyTemplateFiles, createSkillToml } from "../utils/files.js";
|
|
6
|
+
export async function initCommand() {
|
|
7
|
+
const apiKey = await loadApiKey();
|
|
8
|
+
if (!apiKey) {
|
|
9
|
+
console.error("❌ No API key found. Run `lua configure` first.");
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
// Get user data from API
|
|
13
|
+
const userData = await checkApiKey(apiKey);
|
|
14
|
+
// Extract organizations and create choices for selection
|
|
15
|
+
const orgs = userData.admin.orgs;
|
|
16
|
+
const orgChoices = orgs.map((org) => ({
|
|
17
|
+
name: org.registeredName,
|
|
18
|
+
value: org
|
|
19
|
+
}));
|
|
20
|
+
// Select organization
|
|
21
|
+
const { selectedOrg } = await inquirer.prompt([
|
|
22
|
+
{
|
|
23
|
+
type: "list",
|
|
24
|
+
name: "selectedOrg",
|
|
25
|
+
message: "Select an organization:",
|
|
26
|
+
choices: orgChoices
|
|
27
|
+
}
|
|
28
|
+
]);
|
|
29
|
+
// Extract agents from selected organization
|
|
30
|
+
const agentChoices = selectedOrg.agents.map((agent) => ({
|
|
31
|
+
name: agent.name,
|
|
32
|
+
value: agent
|
|
33
|
+
}));
|
|
34
|
+
// Select agent
|
|
35
|
+
const { selectedAgent } = await inquirer.prompt([
|
|
36
|
+
{
|
|
37
|
+
type: "list",
|
|
38
|
+
name: "selectedAgent",
|
|
39
|
+
message: "Select an agent:",
|
|
40
|
+
choices: agentChoices
|
|
41
|
+
}
|
|
42
|
+
]);
|
|
43
|
+
// Get skill details
|
|
44
|
+
const { skillName, skillDescription } = await inquirer.prompt([
|
|
45
|
+
{
|
|
46
|
+
type: "input",
|
|
47
|
+
name: "skillName",
|
|
48
|
+
message: "Enter a name for your skill:",
|
|
49
|
+
default: "My Lua Skill"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
type: "input",
|
|
53
|
+
name: "skillDescription",
|
|
54
|
+
message: "Describe your skill:",
|
|
55
|
+
default: "A Lua skill for automation"
|
|
56
|
+
}
|
|
57
|
+
]);
|
|
58
|
+
// Create lua.skill.toml file
|
|
59
|
+
createSkillToml(selectedAgent.agentId, selectedOrg.id, skillName, skillDescription);
|
|
60
|
+
console.log("✅ Created lua.skill.toml");
|
|
61
|
+
// Copy template files
|
|
62
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
63
|
+
const __dirname = path.dirname(__filename);
|
|
64
|
+
const templateDir = path.join(__dirname, "..", "..", "template");
|
|
65
|
+
const currentDir = process.cwd();
|
|
66
|
+
copyTemplateFiles(templateDir, currentDir);
|
|
67
|
+
console.log("✅ Copied template files");
|
|
68
|
+
console.log("🎉 Lua skill project initialized successfully!");
|
|
69
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,348 +1,25 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from "commander";
|
|
3
|
-
import
|
|
4
|
-
import keytar from "keytar";
|
|
5
|
-
import fetch from "node-fetch";
|
|
6
|
-
import fs from "fs";
|
|
7
|
-
import path from "path";
|
|
8
|
-
import { fileURLToPath } from "url";
|
|
3
|
+
import { configureCommand, initCommand, destroyCommand, apiKeyCommand, agentsCommand } from "./commands/index.js";
|
|
9
4
|
const program = new Command();
|
|
10
|
-
const SERVICE = "lua-cli";
|
|
11
|
-
const ACCOUNT = "api-key";
|
|
12
|
-
async function saveApiKey(apiKey) {
|
|
13
|
-
await keytar.setPassword(SERVICE, ACCOUNT, apiKey);
|
|
14
|
-
}
|
|
15
|
-
async function loadApiKey() {
|
|
16
|
-
return keytar.getPassword(SERVICE, ACCOUNT);
|
|
17
|
-
}
|
|
18
|
-
async function deleteApiKey() {
|
|
19
|
-
return keytar.deletePassword(SERVICE, ACCOUNT);
|
|
20
|
-
}
|
|
21
|
-
async function requestEmailOTP(email) {
|
|
22
|
-
try {
|
|
23
|
-
const response = await fetch("https://auth.heylua.ai/otp", {
|
|
24
|
-
method: "POST",
|
|
25
|
-
headers: {
|
|
26
|
-
"accept": "application/json",
|
|
27
|
-
"Content-Type": "application/json"
|
|
28
|
-
},
|
|
29
|
-
body: JSON.stringify({
|
|
30
|
-
type: "email",
|
|
31
|
-
email: email
|
|
32
|
-
})
|
|
33
|
-
});
|
|
34
|
-
return response.ok;
|
|
35
|
-
}
|
|
36
|
-
catch (error) {
|
|
37
|
-
console.error("❌ Error requesting OTP:", error);
|
|
38
|
-
return false;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
async function verifyOTPAndGetToken(email, pin) {
|
|
42
|
-
try {
|
|
43
|
-
const response = await fetch("https://auth.heylua.ai/otp/verify", {
|
|
44
|
-
method: "POST",
|
|
45
|
-
headers: {
|
|
46
|
-
"accept": "application/json",
|
|
47
|
-
"Content-Type": "application/json"
|
|
48
|
-
},
|
|
49
|
-
body: JSON.stringify({
|
|
50
|
-
pin: pin,
|
|
51
|
-
type: "email",
|
|
52
|
-
email: email
|
|
53
|
-
})
|
|
54
|
-
});
|
|
55
|
-
if (!response.ok) {
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
const data = await response.json();
|
|
59
|
-
return data.signInToken;
|
|
60
|
-
}
|
|
61
|
-
catch (error) {
|
|
62
|
-
console.error("❌ Error verifying OTP:", error);
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
async function generateApiKey(signInToken) {
|
|
67
|
-
try {
|
|
68
|
-
const response = await fetch("https://auth.heylua.ai/profile/apiKey", {
|
|
69
|
-
method: "POST",
|
|
70
|
-
headers: {
|
|
71
|
-
"Authorization": `Bearer ${signInToken}`,
|
|
72
|
-
"Content-Type": "application/json"
|
|
73
|
-
},
|
|
74
|
-
body: ""
|
|
75
|
-
});
|
|
76
|
-
if (!response.ok) {
|
|
77
|
-
return null;
|
|
78
|
-
}
|
|
79
|
-
const data = await response.json();
|
|
80
|
-
return data.apiKey;
|
|
81
|
-
}
|
|
82
|
-
catch (error) {
|
|
83
|
-
console.error("❌ Error generating API key:", error);
|
|
84
|
-
return null;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
async function checkApiKey(apiKey) {
|
|
88
|
-
const response = await fetch("https://api.heylua.ai/admin", {
|
|
89
|
-
headers: {
|
|
90
|
-
Authorization: `Bearer ${apiKey}`,
|
|
91
|
-
},
|
|
92
|
-
});
|
|
93
|
-
if (!response.ok) {
|
|
94
|
-
console.error(`❌ Invalid API key`);
|
|
95
|
-
process.exit(1);
|
|
96
|
-
}
|
|
97
|
-
return await response.json();
|
|
98
|
-
}
|
|
99
|
-
function copyTemplateFiles(templateDir, targetDir) {
|
|
100
|
-
const files = fs.readdirSync(templateDir);
|
|
101
|
-
for (const file of files) {
|
|
102
|
-
const srcPath = path.join(templateDir, file);
|
|
103
|
-
const destPath = path.join(targetDir, file);
|
|
104
|
-
if (fs.statSync(srcPath).isDirectory()) {
|
|
105
|
-
fs.mkdirSync(destPath, { recursive: true });
|
|
106
|
-
copyTemplateFiles(srcPath, destPath);
|
|
107
|
-
}
|
|
108
|
-
else {
|
|
109
|
-
fs.copyFileSync(srcPath, destPath);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
function createSkillToml(agentId, orgId, skillName, skillDescription) {
|
|
114
|
-
const tomlContent = `[agent]
|
|
115
|
-
agentId = "${agentId}"
|
|
116
|
-
orgId = "${orgId}"
|
|
117
|
-
|
|
118
|
-
[skill]
|
|
119
|
-
name = "${skillName}"
|
|
120
|
-
description = "${skillDescription}"
|
|
121
|
-
`;
|
|
122
|
-
fs.writeFileSync("lua.skill.toml", tomlContent);
|
|
123
|
-
}
|
|
124
5
|
program
|
|
125
6
|
.command("init")
|
|
126
7
|
.description("Initialize a new Lua skill project")
|
|
127
|
-
.action(
|
|
128
|
-
const apiKey = await loadApiKey();
|
|
129
|
-
if (!apiKey) {
|
|
130
|
-
console.error("❌ No API key found. Run `lua configure` first.");
|
|
131
|
-
process.exit(1);
|
|
132
|
-
}
|
|
133
|
-
// Get user data from API
|
|
134
|
-
const userData = await checkApiKey(apiKey);
|
|
135
|
-
// Extract organizations and create choices for selection
|
|
136
|
-
const orgs = userData.admin.orgs;
|
|
137
|
-
const orgChoices = orgs.map((org) => ({
|
|
138
|
-
name: org.registeredName,
|
|
139
|
-
value: org
|
|
140
|
-
}));
|
|
141
|
-
// Select organization
|
|
142
|
-
const { selectedOrg } = await inquirer.prompt([
|
|
143
|
-
{
|
|
144
|
-
type: "list",
|
|
145
|
-
name: "selectedOrg",
|
|
146
|
-
message: "Select an organization:",
|
|
147
|
-
choices: orgChoices
|
|
148
|
-
}
|
|
149
|
-
]);
|
|
150
|
-
// Extract agents from selected organization
|
|
151
|
-
const agentChoices = selectedOrg.agents.map((agent) => ({
|
|
152
|
-
name: agent.name,
|
|
153
|
-
value: agent
|
|
154
|
-
}));
|
|
155
|
-
// Select agent
|
|
156
|
-
const { selectedAgent } = await inquirer.prompt([
|
|
157
|
-
{
|
|
158
|
-
type: "list",
|
|
159
|
-
name: "selectedAgent",
|
|
160
|
-
message: "Select an agent:",
|
|
161
|
-
choices: agentChoices
|
|
162
|
-
}
|
|
163
|
-
]);
|
|
164
|
-
// Get skill details
|
|
165
|
-
const { skillName, skillDescription } = await inquirer.prompt([
|
|
166
|
-
{
|
|
167
|
-
type: "input",
|
|
168
|
-
name: "skillName",
|
|
169
|
-
message: "Enter a name for your skill:",
|
|
170
|
-
default: "My Lua Skill"
|
|
171
|
-
},
|
|
172
|
-
{
|
|
173
|
-
type: "input",
|
|
174
|
-
name: "skillDescription",
|
|
175
|
-
message: "Describe your skill:",
|
|
176
|
-
default: "A Lua skill for automation"
|
|
177
|
-
}
|
|
178
|
-
]);
|
|
179
|
-
// Create lua.skill.toml file
|
|
180
|
-
createSkillToml(selectedAgent.agentId, selectedOrg.id, skillName, skillDescription);
|
|
181
|
-
console.log("✅ Created lua.skill.toml");
|
|
182
|
-
// Copy template files
|
|
183
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
184
|
-
const __dirname = path.dirname(__filename);
|
|
185
|
-
const templateDir = path.join(__dirname, "..", "template");
|
|
186
|
-
const currentDir = process.cwd();
|
|
187
|
-
copyTemplateFiles(templateDir, currentDir);
|
|
188
|
-
console.log("✅ Copied template files");
|
|
189
|
-
console.log("🎉 Lua skill project initialized successfully!");
|
|
190
|
-
});
|
|
8
|
+
.action(initCommand);
|
|
191
9
|
program
|
|
192
10
|
.command("destroy")
|
|
193
11
|
.description("Delete your stored API key")
|
|
194
|
-
.action(
|
|
195
|
-
const apiKey = await loadApiKey();
|
|
196
|
-
if (!apiKey) {
|
|
197
|
-
console.log("ℹ️ No API key found to delete.");
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
const { confirm } = await inquirer.prompt([
|
|
201
|
-
{
|
|
202
|
-
type: "confirm",
|
|
203
|
-
name: "confirm",
|
|
204
|
-
message: "Are you sure you want to delete your API key? This action cannot be undone.",
|
|
205
|
-
default: false
|
|
206
|
-
}
|
|
207
|
-
]);
|
|
208
|
-
if (confirm) {
|
|
209
|
-
const deleted = await deleteApiKey();
|
|
210
|
-
if (deleted) {
|
|
211
|
-
console.log("✅ API key deleted successfully.");
|
|
212
|
-
}
|
|
213
|
-
else {
|
|
214
|
-
console.log("❌ Failed to delete API key.");
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
else {
|
|
218
|
-
console.log("ℹ️ API key deletion cancelled.");
|
|
219
|
-
}
|
|
220
|
-
});
|
|
12
|
+
.action(destroyCommand);
|
|
221
13
|
program
|
|
222
14
|
.command("configure")
|
|
223
15
|
.description("Set up your API key")
|
|
224
|
-
.action(
|
|
225
|
-
// Choose authentication method
|
|
226
|
-
const { authMethod } = await inquirer.prompt([
|
|
227
|
-
{
|
|
228
|
-
type: "list",
|
|
229
|
-
name: "authMethod",
|
|
230
|
-
message: "Choose authentication method:",
|
|
231
|
-
choices: [
|
|
232
|
-
{ name: "API Key", value: "api-key" },
|
|
233
|
-
{ name: "Email", value: "email" }
|
|
234
|
-
]
|
|
235
|
-
}
|
|
236
|
-
]);
|
|
237
|
-
if (authMethod === "api-key") {
|
|
238
|
-
// Existing API key flow
|
|
239
|
-
const answers = await inquirer.prompt([
|
|
240
|
-
{
|
|
241
|
-
type: "password",
|
|
242
|
-
name: "apiKey",
|
|
243
|
-
message: "Enter your API key",
|
|
244
|
-
mask: "*",
|
|
245
|
-
},
|
|
246
|
-
]);
|
|
247
|
-
const data = await checkApiKey(answers.apiKey);
|
|
248
|
-
if (!data) {
|
|
249
|
-
console.error("❌ Invalid API key");
|
|
250
|
-
process.exit(1);
|
|
251
|
-
}
|
|
252
|
-
await saveApiKey(answers.apiKey);
|
|
253
|
-
console.log("✅ API key saved securely.");
|
|
254
|
-
}
|
|
255
|
-
else if (authMethod === "email") {
|
|
256
|
-
// Email authentication flow
|
|
257
|
-
const { email } = await inquirer.prompt([
|
|
258
|
-
{
|
|
259
|
-
type: "input",
|
|
260
|
-
name: "email",
|
|
261
|
-
message: "Enter your email address:",
|
|
262
|
-
validate: (input) => {
|
|
263
|
-
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
264
|
-
return emailRegex.test(input) || "Please enter a valid email address";
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
]);
|
|
268
|
-
console.log("📧 Sending OTP to your email...");
|
|
269
|
-
const otpSent = await requestEmailOTP(email);
|
|
270
|
-
if (!otpSent) {
|
|
271
|
-
console.error("❌ Failed to send OTP. Please try again.");
|
|
272
|
-
process.exit(1);
|
|
273
|
-
}
|
|
274
|
-
console.log("✅ OTP sent successfully!");
|
|
275
|
-
const { pin } = await inquirer.prompt([
|
|
276
|
-
{
|
|
277
|
-
type: "input",
|
|
278
|
-
name: "pin",
|
|
279
|
-
message: "Enter the OTP code:",
|
|
280
|
-
validate: (input) => {
|
|
281
|
-
return input.length === 6 || "OTP must be 6 digits";
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
]);
|
|
285
|
-
console.log("🔐 Verifying OTP...");
|
|
286
|
-
const signInToken = await verifyOTPAndGetToken(email, pin);
|
|
287
|
-
if (!signInToken) {
|
|
288
|
-
console.error("❌ Invalid OTP. Please try again.");
|
|
289
|
-
process.exit(1);
|
|
290
|
-
}
|
|
291
|
-
console.log("✅ OTP verified successfully!");
|
|
292
|
-
console.log("🔑 Generating API key...");
|
|
293
|
-
const apiKey = await generateApiKey(signInToken);
|
|
294
|
-
if (!apiKey) {
|
|
295
|
-
console.error("❌ Failed to generate API key. Please try again.");
|
|
296
|
-
process.exit(1);
|
|
297
|
-
}
|
|
298
|
-
await saveApiKey(apiKey);
|
|
299
|
-
console.log("✅ API key generated and saved securely.");
|
|
300
|
-
}
|
|
301
|
-
});
|
|
16
|
+
.action(configureCommand);
|
|
302
17
|
program
|
|
303
18
|
.command("apiKey")
|
|
304
19
|
.description("Display your stored API key")
|
|
305
|
-
.action(
|
|
306
|
-
const apiKey = await loadApiKey();
|
|
307
|
-
if (!apiKey) {
|
|
308
|
-
console.log("ℹ️ No API key found. Run `lua configure` first.");
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
311
|
-
const { confirm } = await inquirer.prompt([
|
|
312
|
-
{
|
|
313
|
-
type: "confirm",
|
|
314
|
-
name: "confirm",
|
|
315
|
-
message: "This will display your API key. Are you sure you want to continue?",
|
|
316
|
-
default: false
|
|
317
|
-
}
|
|
318
|
-
]);
|
|
319
|
-
if (confirm) {
|
|
320
|
-
console.log("🔑 Your API key:");
|
|
321
|
-
console.log(apiKey);
|
|
322
|
-
}
|
|
323
|
-
else {
|
|
324
|
-
console.log("ℹ️ API key display cancelled.");
|
|
325
|
-
}
|
|
326
|
-
});
|
|
20
|
+
.action(apiKeyCommand);
|
|
327
21
|
program
|
|
328
22
|
.command("agents")
|
|
329
23
|
.description("Fetch agents from HeyLua API")
|
|
330
|
-
.action(
|
|
331
|
-
const apiKey = await loadApiKey();
|
|
332
|
-
if (!apiKey) {
|
|
333
|
-
console.error("❌ No API key found. Run `lua configure` first.");
|
|
334
|
-
process.exit(1);
|
|
335
|
-
}
|
|
336
|
-
const response = await fetch("https://api.heylua.ai/admin", {
|
|
337
|
-
headers: {
|
|
338
|
-
Authorization: `Bearer ${apiKey}`,
|
|
339
|
-
},
|
|
340
|
-
});
|
|
341
|
-
if (!response.ok) {
|
|
342
|
-
console.error(`❌ Error ${response.status}: ${await response.text()}`);
|
|
343
|
-
process.exit(1);
|
|
344
|
-
}
|
|
345
|
-
const data = await response.json();
|
|
346
|
-
console.log("✅ Agents:", JSON.stringify(data, null, 2));
|
|
347
|
-
});
|
|
24
|
+
.action(agentsCommand);
|
|
348
25
|
program.parse(process.argv);
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import keytar from "keytar";
|
|
2
|
+
import fetch from "node-fetch";
|
|
3
|
+
const SERVICE = "lua-cli";
|
|
4
|
+
const ACCOUNT = "api-key";
|
|
5
|
+
export async function saveApiKey(apiKey) {
|
|
6
|
+
await keytar.setPassword(SERVICE, ACCOUNT, apiKey);
|
|
7
|
+
}
|
|
8
|
+
export async function loadApiKey() {
|
|
9
|
+
return keytar.getPassword(SERVICE, ACCOUNT);
|
|
10
|
+
}
|
|
11
|
+
export async function deleteApiKey() {
|
|
12
|
+
return keytar.deletePassword(SERVICE, ACCOUNT);
|
|
13
|
+
}
|
|
14
|
+
export async function checkApiKey(apiKey) {
|
|
15
|
+
const response = await fetch("https://api.heylua.ai/admin", {
|
|
16
|
+
headers: {
|
|
17
|
+
Authorization: `Bearer ${apiKey}`,
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
if (!response.ok) {
|
|
21
|
+
console.error(`❌ Invalid API key`);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
return await response.json();
|
|
25
|
+
}
|
|
26
|
+
export async function requestEmailOTP(email) {
|
|
27
|
+
try {
|
|
28
|
+
const response = await fetch("https://auth.heylua.ai/otp", {
|
|
29
|
+
method: "POST",
|
|
30
|
+
headers: {
|
|
31
|
+
"accept": "application/json",
|
|
32
|
+
"Content-Type": "application/json"
|
|
33
|
+
},
|
|
34
|
+
body: JSON.stringify({
|
|
35
|
+
type: "email",
|
|
36
|
+
email: email
|
|
37
|
+
})
|
|
38
|
+
});
|
|
39
|
+
return response.ok;
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
console.error("❌ Error requesting OTP:", error);
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export async function verifyOTPAndGetToken(email, pin) {
|
|
47
|
+
try {
|
|
48
|
+
const response = await fetch("https://auth.heylua.ai/otp/verify", {
|
|
49
|
+
method: "POST",
|
|
50
|
+
headers: {
|
|
51
|
+
"accept": "application/json",
|
|
52
|
+
"Content-Type": "application/json"
|
|
53
|
+
},
|
|
54
|
+
body: JSON.stringify({
|
|
55
|
+
pin: pin,
|
|
56
|
+
type: "email",
|
|
57
|
+
email: email
|
|
58
|
+
})
|
|
59
|
+
});
|
|
60
|
+
if (!response.ok) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
const data = await response.json();
|
|
64
|
+
return data.signInToken;
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
console.error("❌ Error verifying OTP:", error);
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
export async function generateApiKey(signInToken) {
|
|
72
|
+
try {
|
|
73
|
+
const response = await fetch("https://auth.heylua.ai/profile/apiKey", {
|
|
74
|
+
method: "POST",
|
|
75
|
+
headers: {
|
|
76
|
+
"Authorization": `Bearer ${signInToken}`,
|
|
77
|
+
"Content-Type": "application/json"
|
|
78
|
+
},
|
|
79
|
+
body: ""
|
|
80
|
+
});
|
|
81
|
+
if (!response.ok) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
const data = await response.json();
|
|
85
|
+
return data.apiKey;
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
console.error("❌ Error generating API key:", error);
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
export function copyTemplateFiles(templateDir, targetDir) {
|
|
4
|
+
const files = fs.readdirSync(templateDir);
|
|
5
|
+
for (const file of files) {
|
|
6
|
+
const srcPath = path.join(templateDir, file);
|
|
7
|
+
const destPath = path.join(targetDir, file);
|
|
8
|
+
if (fs.statSync(srcPath).isDirectory()) {
|
|
9
|
+
fs.mkdirSync(destPath, { recursive: true });
|
|
10
|
+
copyTemplateFiles(srcPath, destPath);
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
fs.copyFileSync(srcPath, destPath);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function createSkillToml(agentId, orgId, skillName, skillDescription) {
|
|
18
|
+
const tomlContent = `[agent]
|
|
19
|
+
agentId = "${agentId}"
|
|
20
|
+
orgId = "${orgId}"
|
|
21
|
+
|
|
22
|
+
[skill]
|
|
23
|
+
name = "${skillName}"
|
|
24
|
+
description = "${skillDescription}"
|
|
25
|
+
`;
|
|
26
|
+
fs.writeFileSync("lua.skill.toml", tomlContent);
|
|
27
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lua-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Command-line interface for Lua AI platform - manage agents, organizations, and skills",
|
|
5
|
+
"readmeFilename": "README.md",
|
|
5
6
|
"main": "dist/index.js",
|
|
6
7
|
"scripts": {
|
|
7
8
|
"build": "tsc",
|
|
@@ -26,6 +27,7 @@
|
|
|
26
27
|
"url": "https://github.com/heylua/lua-cli.git"
|
|
27
28
|
},
|
|
28
29
|
"homepage": "https://github.com/heylua/lua-cli#readme",
|
|
30
|
+
"readme": "README.md",
|
|
29
31
|
"bugs": {
|
|
30
32
|
"url": "https://github.com/heylua/lua-cli/issues"
|
|
31
33
|
},
|
|
@@ -36,6 +38,7 @@
|
|
|
36
38
|
"dist/**/*",
|
|
37
39
|
"template/**/*",
|
|
38
40
|
"README.md",
|
|
41
|
+
"CHANGELOG.md",
|
|
39
42
|
"LICENSE"
|
|
40
43
|
],
|
|
41
44
|
"dependencies": {
|