lua-cli 1.2.1 → 1.3.0-alpha.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.
@@ -1,80 +1,105 @@
1
1
  import inquirer from "inquirer";
2
2
  import { saveApiKey, checkApiKey, requestEmailOTP, verifyOTPAndGetToken, generateApiKey } from "../services/auth.js";
3
+ import { withErrorHandling, clearPromptLines, writeProgress, writeSuccess } from "../utils/cli.js";
3
4
  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([
5
+ return withErrorHandling(async () => {
6
+ // Choose authentication method
7
+ const { authMethod } = await inquirer.prompt([
19
8
  {
20
- type: "password",
21
- name: "apiKey",
22
- message: "Enter your API key",
23
- mask: "*",
24
- },
9
+ type: "list",
10
+ name: "authMethod",
11
+ message: "Choose authentication method:",
12
+ choices: [
13
+ { name: "API Key", value: "api-key" },
14
+ { name: "Email", value: "email" }
15
+ ]
16
+ }
25
17
  ]);
26
- const data = await checkApiKey(answers.apiKey);
27
- if (!data) {
28
- console.error("❌ Invalid API key");
29
- process.exit(1);
18
+ // Clear the authentication method prompt lines
19
+ clearPromptLines(2);
20
+ if (authMethod === "api-key") {
21
+ // Existing API key flow
22
+ const answers = await inquirer.prompt([
23
+ {
24
+ type: "password",
25
+ name: "apiKey",
26
+ message: "Enter your API key",
27
+ mask: "*",
28
+ },
29
+ ]);
30
+ const data = await checkApiKey(answers.apiKey);
31
+ if (!data) {
32
+ console.error("❌ Invalid API key");
33
+ process.exit(1);
34
+ }
35
+ await saveApiKey(answers.apiKey);
36
+ writeSuccess("✅ API key saved securely.");
30
37
  }
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";
38
+ else if (authMethod === "email") {
39
+ // Email authentication flow
40
+ const { email } = await inquirer.prompt([
41
+ {
42
+ type: "input",
43
+ name: "email",
44
+ message: "Enter your email address:",
45
+ validate: (input) => {
46
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
47
+ return emailRegex.test(input) || "Please enter a valid email address";
48
+ }
44
49
  }
50
+ ]);
51
+ // Clear the email prompt lines
52
+ clearPromptLines(2);
53
+ writeProgress("📧 Sending OTP to your email...");
54
+ const otpSent = await requestEmailOTP(email);
55
+ if (!otpSent) {
56
+ console.error("❌ Failed to send OTP. Please try again.");
57
+ process.exit(1);
45
58
  }
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";
59
+ writeProgress("✅ OTP sent successfully!");
60
+ // OTP verification with retry logic
61
+ let otpVerified = false;
62
+ let attempts = 0;
63
+ let signInToken = null;
64
+ const maxAttempts = 3;
65
+ while (!otpVerified && attempts < maxAttempts) {
66
+ const { pin } = await inquirer.prompt([
67
+ {
68
+ type: "input",
69
+ name: "pin",
70
+ message: "Enter the OTP code:",
71
+ validate: (input) => {
72
+ return input.length === 6 || "OTP must be 6 digits";
73
+ }
74
+ }
75
+ ]);
76
+ // Clear the OTP prompt lines
77
+ clearPromptLines(2);
78
+ writeProgress("🔐 Verifying OTP...");
79
+ signInToken = await verifyOTPAndGetToken(email, pin);
80
+ if (signInToken) {
81
+ otpVerified = true;
82
+ writeProgress("✅ OTP verified successfully!");
83
+ writeProgress("🔑 Generating API key...");
84
+ }
85
+ else {
86
+ attempts++;
87
+ if (attempts < maxAttempts) {
88
+ console.log(`❌ Invalid OTP. ${maxAttempts - attempts} attempts remaining.`);
89
+ }
90
+ else {
91
+ console.error("❌ Invalid OTP. Maximum attempts reached. Please try again later.");
92
+ process.exit(1);
93
+ }
61
94
  }
62
95
  }
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);
96
+ const apiKey = await generateApiKey(signInToken);
97
+ if (!apiKey) {
98
+ console.error("❌ Failed to generate API key. Please try again.");
99
+ process.exit(1);
100
+ }
101
+ await saveApiKey(apiKey);
102
+ writeSuccess("✅ API key generated and saved securely.");
76
103
  }
77
- await saveApiKey(apiKey);
78
- console.log("✅ API key generated and saved securely.");
79
- }
104
+ }, "configuration");
80
105
  }
@@ -0,0 +1,20 @@
1
+ export interface VersionInfo {
2
+ version: string;
3
+ createdDate: string;
4
+ createdBy: string;
5
+ isCurrent: boolean;
6
+ createdByEmail: string;
7
+ createdByFullName: string;
8
+ }
9
+ export interface VersionsResponse {
10
+ versions: VersionInfo[];
11
+ }
12
+ export interface PublishResponse {
13
+ message: string;
14
+ skillId: string;
15
+ activeVersionId: string;
16
+ publishedAt: string;
17
+ }
18
+ export declare function fetchVersions(apiKey: string, agentId: string, skillId: string): Promise<VersionsResponse>;
19
+ export declare function publishVersion(apiKey: string, agentId: string, skillId: string, version: string): Promise<PublishResponse>;
20
+ export declare function deployCommand(): Promise<void>;
@@ -0,0 +1,128 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import inquirer from 'inquirer';
4
+ import { loadApiKey, checkApiKey } from '../services/auth.js';
5
+ export async function fetchVersions(apiKey, agentId, skillId) {
6
+ try {
7
+ const response = await fetch(`https://api.lua.dev/developer/skills/${agentId}/${skillId}/versions`, {
8
+ method: "GET",
9
+ headers: {
10
+ "accept": "application/json",
11
+ "Authorization": `Bearer ${apiKey}`
12
+ }
13
+ });
14
+ if (!response.ok) {
15
+ throw new Error(`Failed to fetch versions: ${response.status} ${response.statusText}`);
16
+ }
17
+ return await response.json();
18
+ }
19
+ catch (error) {
20
+ console.error("❌ Error fetching versions:", error);
21
+ throw error;
22
+ }
23
+ }
24
+ export async function publishVersion(apiKey, agentId, skillId, version) {
25
+ try {
26
+ const response = await fetch(`https://api.lua.dev/developer/skills/${agentId}/${skillId}/${version}/publish`, {
27
+ method: "PUT",
28
+ headers: {
29
+ "accept": "application/json",
30
+ "Authorization": `Bearer ${apiKey}`
31
+ }
32
+ });
33
+ if (!response.ok) {
34
+ throw new Error(`Failed to publish version: ${response.status} ${response.statusText}`);
35
+ }
36
+ return await response.json();
37
+ }
38
+ catch (error) {
39
+ console.error("❌ Error publishing version:", error);
40
+ throw error;
41
+ }
42
+ }
43
+ function readTomlData() {
44
+ const tomlPath = path.join(process.cwd(), 'lua.skill.toml');
45
+ if (!fs.existsSync(tomlPath)) {
46
+ return null;
47
+ }
48
+ const tomlContent = fs.readFileSync(tomlPath, 'utf8');
49
+ const agentIdMatch = tomlContent.match(/agentId\s*=\s*["']([^"']+)["']/);
50
+ const skillIdMatch = tomlContent.match(/skillId\s*=\s*["']([^"']+)["']/);
51
+ if (!agentIdMatch || !skillIdMatch) {
52
+ return null;
53
+ }
54
+ return {
55
+ agentId: agentIdMatch[1],
56
+ skillId: skillIdMatch[1]
57
+ };
58
+ }
59
+ function formatVersionChoice(version) {
60
+ const currentIndicator = version.isCurrent ? ' (CURRENT)' : '';
61
+ const date = new Date(version.createdDate).toLocaleDateString();
62
+ return `${version.version}${currentIndicator} - Created: ${date} by ${version.createdByEmail}`;
63
+ }
64
+ export async function deployCommand() {
65
+ try {
66
+ // Check if we're in a skill directory
67
+ const tomlData = readTomlData();
68
+ if (!tomlData) {
69
+ console.error("❌ No lua.skill.toml found or missing agentId/skillId. Please run this command from a skill directory.");
70
+ process.exit(1);
71
+ }
72
+ // Load API key
73
+ const apiKey = await loadApiKey();
74
+ if (!apiKey) {
75
+ console.error("❌ No API key found. Please run 'lua configure' to set up your API key.");
76
+ process.exit(1);
77
+ }
78
+ // Validate API key
79
+ const userData = await checkApiKey(apiKey);
80
+ console.log(`✅ Authenticated as ${userData.email}`);
81
+ // Fetch available versions
82
+ console.log("🔄 Fetching available versions...");
83
+ const versionsResponse = await fetchVersions(apiKey, tomlData.agentId, tomlData.skillId);
84
+ if (!versionsResponse.versions || versionsResponse.versions.length === 0) {
85
+ console.log("❌ No versions found for this skill.");
86
+ process.exit(1);
87
+ }
88
+ // Sort versions by creation date (newest first)
89
+ const sortedVersions = versionsResponse.versions.sort((a, b) => new Date(b.createdDate).getTime() - new Date(a.createdDate).getTime());
90
+ // Let user select a version
91
+ const { selectedVersion } = await inquirer.prompt([
92
+ {
93
+ type: "list",
94
+ name: "selectedVersion",
95
+ message: "Select a version to deploy:",
96
+ choices: sortedVersions.map(version => ({
97
+ name: formatVersionChoice(version),
98
+ value: version.version
99
+ }))
100
+ }
101
+ ]);
102
+ // Show warning and confirm deployment
103
+ const { confirmed } = await inquirer.prompt([
104
+ {
105
+ type: "confirm",
106
+ name: "confirmed",
107
+ message: "⚠️ Warning: This version will be deployed to all users. Do you want to proceed?",
108
+ default: false
109
+ }
110
+ ]);
111
+ if (!confirmed) {
112
+ console.log("❌ Deployment cancelled.");
113
+ process.exit(0);
114
+ }
115
+ // Publish the selected version
116
+ console.log(`🔄 Publishing version ${selectedVersion}...`);
117
+ const publishResponse = await publishVersion(apiKey, tomlData.agentId, tomlData.skillId, selectedVersion);
118
+ console.log("✅ Deployment successful!");
119
+ console.log(`📦 Version: ${publishResponse.activeVersionId}`);
120
+ console.log(`🆔 Skill ID: ${publishResponse.skillId}`);
121
+ console.log(`📅 Published at: ${new Date(publishResponse.publishedAt).toLocaleString()}`);
122
+ console.log(`💬 ${publishResponse.message}`);
123
+ }
124
+ catch (error) {
125
+ console.error("❌ Error during deployment:", error.message);
126
+ process.exit(1);
127
+ }
128
+ }
@@ -1 +1,20 @@
1
+ export interface VersionInfo {
2
+ version: string;
3
+ createdDate: string;
4
+ createdBy: string;
5
+ isCurrent: boolean;
6
+ createdByEmail: string;
7
+ createdByFullName: string;
8
+ }
9
+ export interface VersionsResponse {
10
+ versions: VersionInfo[];
11
+ }
12
+ export interface PublishResponse {
13
+ message: string;
14
+ skillId: string;
15
+ activeVersionId: string;
16
+ publishedAt: string;
17
+ }
18
+ export declare function fetchVersions(apiKey: string, agentId: string, skillId: string): Promise<VersionsResponse>;
19
+ export declare function publishVersion(apiKey: string, agentId: string, skillId: string, version: string): Promise<PublishResponse>;
1
20
  export declare function deployCommand(): Promise<void>;