lua-cli 2.5.4 → 2.5.6

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.
@@ -0,0 +1,54 @@
1
+ import { HttpClient } from "../common/http.client.js";
2
+ import { ApiResponse } from "../interfaces/common.js";
3
+ /**
4
+ * Environment variables response structure from the API
5
+ * The actual environment variables are nested in the 'data' property
6
+ */
7
+ interface EnvironmentVariablesResponse {
8
+ data: Record<string, string>;
9
+ _id?: string;
10
+ agentId?: string;
11
+ createdAt?: string;
12
+ updatedAt?: string;
13
+ __v?: number;
14
+ }
15
+ /**
16
+ * Developer API calls for agent management and configuration
17
+ */
18
+ export default class DeveloperApi extends HttpClient {
19
+ private apiKey;
20
+ private agentId;
21
+ /**
22
+ * Creates an instance of DeveloperApi
23
+ * @param baseUrl - The base URL for the API
24
+ * @param apiKey - The API key for authentication
25
+ * @param agentId - The unique identifier of the agent
26
+ */
27
+ constructor(baseUrl: string, apiKey: string, agentId: string);
28
+ /**
29
+ * Retrieves all environment variables for the agent in production
30
+ * The response includes metadata (_id, agentId, timestamps) and the actual env vars in the 'data' property
31
+ * @returns Promise resolving to an ApiResponse containing environment variables and metadata
32
+ * @throws Error if the API request fails or the agent is not found
33
+ */
34
+ getEnvironmentVariables(): Promise<ApiResponse<EnvironmentVariablesResponse>>;
35
+ /**
36
+ * Updates all environment variables for the agent in production
37
+ * This operation replaces all existing environment variables with the provided set
38
+ * @param envData - Object containing environment variable key-value pairs
39
+ * @returns Promise resolving to an ApiResponse with the updated environment variables and metadata
40
+ * @throws Error if the API request fails or the agent is not found
41
+ */
42
+ updateEnvironmentVariables(envData: Record<string, string>): Promise<ApiResponse<EnvironmentVariablesResponse>>;
43
+ /**
44
+ * Deletes a specific environment variable by key
45
+ * @param key - The environment variable key to delete
46
+ * @returns Promise resolving to an ApiResponse with confirmation
47
+ * @throws Error if the API request fails, the agent is not found, or the key doesn't exist
48
+ */
49
+ deleteEnvironmentVariable(key: string): Promise<ApiResponse<{
50
+ message: string;
51
+ key: string;
52
+ }>>;
53
+ }
54
+ export {};
@@ -0,0 +1,51 @@
1
+ import { HttpClient } from "../common/http.client.js";
2
+ /**
3
+ * Developer API calls for agent management and configuration
4
+ */
5
+ export default class DeveloperApi extends HttpClient {
6
+ /**
7
+ * Creates an instance of DeveloperApi
8
+ * @param baseUrl - The base URL for the API
9
+ * @param apiKey - The API key for authentication
10
+ * @param agentId - The unique identifier of the agent
11
+ */
12
+ constructor(baseUrl, apiKey, agentId) {
13
+ super(baseUrl);
14
+ this.apiKey = apiKey;
15
+ this.agentId = agentId;
16
+ }
17
+ /**
18
+ * Retrieves all environment variables for the agent in production
19
+ * The response includes metadata (_id, agentId, timestamps) and the actual env vars in the 'data' property
20
+ * @returns Promise resolving to an ApiResponse containing environment variables and metadata
21
+ * @throws Error if the API request fails or the agent is not found
22
+ */
23
+ async getEnvironmentVariables() {
24
+ return this.httpGet(`/developer/agents/${this.agentId}/env`, {
25
+ Authorization: `Bearer ${this.apiKey}`,
26
+ });
27
+ }
28
+ /**
29
+ * Updates all environment variables for the agent in production
30
+ * This operation replaces all existing environment variables with the provided set
31
+ * @param envData - Object containing environment variable key-value pairs
32
+ * @returns Promise resolving to an ApiResponse with the updated environment variables and metadata
33
+ * @throws Error if the API request fails or the agent is not found
34
+ */
35
+ async updateEnvironmentVariables(envData) {
36
+ return this.httpPost(`/developer/agents/${this.agentId}/env`, envData, {
37
+ Authorization: `Bearer ${this.apiKey}`,
38
+ });
39
+ }
40
+ /**
41
+ * Deletes a specific environment variable by key
42
+ * @param key - The environment variable key to delete
43
+ * @returns Promise resolving to an ApiResponse with confirmation
44
+ * @throws Error if the API request fails, the agent is not found, or the key doesn't exist
45
+ */
46
+ async deleteEnvironmentVariable(key) {
47
+ return this.httpDelete(`/developer/agents/${this.agentId}/env/${key}`, {
48
+ Authorization: `Bearer ${this.apiKey}`,
49
+ });
50
+ }
51
+ }
@@ -1,6 +1,7 @@
1
1
  import { DevVersionResponse, UpdateDevVersionResponse } from "../interfaces/dev.js";
2
2
  import { HttpClient } from "../common/http.client.js";
3
3
  import { ApiResponse } from "../interfaces/common.js";
4
+ import { GetSkillsResponse, DeleteSkillResponse } from "../interfaces/skills.js";
4
5
  /**
5
6
  * Skill API calls
6
7
  */
@@ -14,6 +15,12 @@ export default class SkillApi extends HttpClient {
14
15
  * @param agentId - The unique identifier of the agent
15
16
  */
16
17
  constructor(baseUrl: string, apiKey: string, agentId: string);
18
+ /**
19
+ * Retrieves all skills for the agent
20
+ * @returns Promise resolving to an ApiResponse containing an array of skills with their versions and tools
21
+ * @throws Error if the API request fails or the agent is not found
22
+ */
23
+ getSkills(): Promise<ApiResponse<GetSkillsResponse>>;
17
24
  /**
18
25
  * Creates a new skill for the agent
19
26
  * @param skillData - The skill data including name, description, and optional context
@@ -74,4 +81,13 @@ export default class SkillApi extends HttpClient {
74
81
  activeVersionId: string;
75
82
  publishedAt: string;
76
83
  }>>;
84
+ /**
85
+ * Deletes a skill and all its versions, or deactivates it if it has versions
86
+ * @param skillId - The unique identifier of the skill to delete
87
+ * @returns Promise resolving to an ApiResponse with deletion status
88
+ * - If deleted is true: skill was successfully deleted
89
+ * - If deleted is false and deactivated is true: skill has versions and was deactivated instead
90
+ * @throws Error if the skill is not found or the delete operation fails
91
+ */
92
+ deleteSkill(skillId: string): Promise<ApiResponse<DeleteSkillResponse>>;
77
93
  }
@@ -14,6 +14,16 @@ export default class SkillApi extends HttpClient {
14
14
  this.apiKey = apiKey;
15
15
  this.agentId = agentId;
16
16
  }
17
+ /**
18
+ * Retrieves all skills for the agent
19
+ * @returns Promise resolving to an ApiResponse containing an array of skills with their versions and tools
20
+ * @throws Error if the API request fails or the agent is not found
21
+ */
22
+ async getSkills() {
23
+ return this.httpGet(`/developer/skills/${this.agentId}`, {
24
+ Authorization: `Bearer ${this.apiKey}`,
25
+ });
26
+ }
17
27
  /**
18
28
  * Creates a new skill for the agent
19
29
  * @param skillData - The skill data including name, description, and optional context
@@ -85,4 +95,17 @@ export default class SkillApi extends HttpClient {
85
95
  Authorization: `Bearer ${this.apiKey}`,
86
96
  });
87
97
  }
98
+ /**
99
+ * Deletes a skill and all its versions, or deactivates it if it has versions
100
+ * @param skillId - The unique identifier of the skill to delete
101
+ * @returns Promise resolving to an ApiResponse with deletion status
102
+ * - If deleted is true: skill was successfully deleted
103
+ * - If deleted is false and deactivated is true: skill has versions and was deactivated instead
104
+ * @throws Error if the skill is not found or the delete operation fails
105
+ */
106
+ async deleteSkill(skillId) {
107
+ return this.httpDelete(`/developer/skills/${this.agentId}/${skillId}`, {
108
+ Authorization: `Bearer ${this.apiKey}`,
109
+ });
110
+ }
88
111
  }
@@ -12,6 +12,8 @@
12
12
  * 4. Extracts execute code and schemas from bundled tools
13
13
  * 5. Bundles the main index file
14
14
  * 6. Creates deployment data in both new and legacy formats
15
+ * 7. Syncs lua.skill.yaml with deploy.json (removes deleted skills from YAML)
16
+ * 8. Syncs server with YAML (deletes/deactivates skills not in YAML)
15
17
  *
16
18
  * Output:
17
19
  * - dist/deployment.json - New deployment format with tool references
@@ -19,6 +21,11 @@
19
21
  * - dist/index.js - Main skill entry point
20
22
  * - .lua/deploy.json - Legacy deployment format with compressed tools
21
23
  * - .lua/*.js - Individual uncompressed tool files for debugging
24
+ * - lua.skill.yaml - Updated to match compiled skills
25
+ *
26
+ * Server Sync:
27
+ * - Skills deleted from code are removed from YAML
28
+ * - Skills not in YAML are deleted from server (or deactivated if they have versions)
22
29
  *
23
30
  * @returns Promise that resolves when compilation is complete
24
31
  */
@@ -10,6 +10,8 @@ import { findIndexFile } from '../utils/compile.js';
10
10
  import { detectTools } from '../utils/tool-detection.js';
11
11
  import { bundleTool, bundleMainIndex, extractExecuteCode } from '../utils/bundling.js';
12
12
  import { createDeploymentData, createLegacyDeploymentData } from '../utils/deployment.js';
13
+ import { syncYamlWithDeployJson, syncServerSkillsWithYaml } from '../utils/skill-management.js';
14
+ import { readSkillConfig } from '../utils/files.js';
13
15
  import { COMPILE_DIRS, COMPILE_FILES } from '../config/compile.constants.js';
14
16
  /**
15
17
  * Main compile command - orchestrates the entire skill compilation process.
@@ -21,6 +23,8 @@ import { COMPILE_DIRS, COMPILE_FILES } from '../config/compile.constants.js';
21
23
  * 4. Extracts execute code and schemas from bundled tools
22
24
  * 5. Bundles the main index file
23
25
  * 6. Creates deployment data in both new and legacy formats
26
+ * 7. Syncs lua.skill.yaml with deploy.json (removes deleted skills from YAML)
27
+ * 8. Syncs server with YAML (deletes/deactivates skills not in YAML)
24
28
  *
25
29
  * Output:
26
30
  * - dist/deployment.json - New deployment format with tool references
@@ -28,6 +32,11 @@ import { COMPILE_DIRS, COMPILE_FILES } from '../config/compile.constants.js';
28
32
  * - dist/index.js - Main skill entry point
29
33
  * - .lua/deploy.json - Legacy deployment format with compressed tools
30
34
  * - .lua/*.js - Individual uncompressed tool files for debugging
35
+ * - lua.skill.yaml - Updated to match compiled skills
36
+ *
37
+ * Server Sync:
38
+ * - Skills deleted from code are removed from YAML
39
+ * - Skills not in YAML are deleted from server (or deactivated if they have versions)
31
40
  *
32
41
  * @returns Promise that resolves when compilation is complete
33
42
  */
@@ -52,6 +61,15 @@ export async function compileCommand() {
52
61
  // Step 5: Create deployment data in both formats
53
62
  await createDeploymentData(tools, distDir);
54
63
  await createLegacyDeploymentData(tools, luaDir, indexFile);
64
+ // Step 6: Sync YAML with deploy.json
65
+ writeProgress("šŸ”„ Syncing YAML with deploy.json...");
66
+ const deployJsonPath = path.join(luaDir, COMPILE_FILES.DEPLOY_JSON);
67
+ const config = readSkillConfig();
68
+ await syncYamlWithDeployJson(deployJsonPath, config);
69
+ // Step 7: Sync server with YAML (delete skills not in YAML)
70
+ writeProgress("šŸ”„ Syncing server with YAML...");
71
+ const updatedConfig = readSkillConfig(); // Re-read config after YAML sync
72
+ await syncServerSkillsWithYaml(updatedConfig);
55
73
  writeSuccess(`āœ… Skill compiled successfully - ${tools.length} tools bundled`);
56
74
  }, "compilation");
57
75
  }
@@ -7,6 +7,7 @@ import { readSkillConfig } from '../utils/files.js';
7
7
  import { withErrorHandling, writeProgress, writeSuccess, writeInfo } from '../utils/cli.js';
8
8
  import { sortVersionsByDate, promptVersionSelection, confirmDeployment, validateDeployConfig, validateVersionsAvailable, promptSkillSelection, getAvailableSkills, } from '../utils/deploy-helpers.js';
9
9
  import { fetchVersions, publishVersion } from '../utils/deploy-api.js';
10
+ import { updateSkillVersionInYaml } from '../utils/push-helpers.js';
10
11
  /**
11
12
  * Main deploy command - deploys a skill version to production.
12
13
  *
@@ -82,6 +83,12 @@ export async function deployCommand() {
82
83
  // Step 7: Publish the selected version
83
84
  writeProgress("šŸ”„ Publishing version...");
84
85
  const publishResponse = await publishVersion(apiKey, config.agent.agentId, selectedSkill.skillId, selectedVersion);
85
- writeSuccess(`āœ… Version ${publishResponse.activeVersionId} of "${selectedSkill.name}" deployed successfully`);
86
+ const deployedVersion = publishResponse.activeVersionId;
87
+ writeSuccess(`āœ… Version ${deployedVersion} of "${selectedSkill.name}" deployed successfully`);
88
+ // Step 8: Update YAML with the deployed version
89
+ if (deployedVersion !== selectedSkill.version) {
90
+ writeInfo(`šŸ“ Updating YAML with deployed version: ${deployedVersion}`);
91
+ updateSkillVersionInYaml(selectedSkill.name, deployedVersion);
92
+ }
86
93
  }, "deployment");
87
94
  }
@@ -10,6 +10,7 @@ import { readSkillConfig } from '../utils/files.js';
10
10
  import { withErrorHandling, writeProgress, writeSuccess } from '../utils/cli.js';
11
11
  import { BASE_URLS } from '../config/constants.js';
12
12
  import { validateConfig, validateAgentConfig, } from '../utils/dev-helpers.js';
13
+ import DeveloperApi from '../api/developer.api.service.js';
13
14
  /**
14
15
  * Main env command - manages environment variables
15
16
  *
@@ -57,6 +58,7 @@ export async function envCommand() {
57
58
  }
58
59
  await checkApiKey(apiKey);
59
60
  context.apiKey = apiKey;
61
+ context.developerApi = new DeveloperApi(BASE_URLS.API, apiKey, agentId);
60
62
  writeProgress("āœ… Authenticated");
61
63
  }
62
64
  // Step 4: Start management loop
@@ -160,19 +162,22 @@ function loadSandboxEnvVariables() {
160
162
  */
161
163
  async function loadProductionEnvVariables(context) {
162
164
  try {
163
- const response = await fetch(`${BASE_URLS.API}/developer/agents/${context.agentId}/env`, {
164
- method: 'GET',
165
- headers: {
166
- 'accept': 'application/json',
167
- 'Authorization': `Bearer ${context.apiKey}`
168
- }
169
- });
170
- if (!response.ok) {
171
- throw new Error(`HTTP error! status: ${response.status}`);
165
+ if (!context.developerApi) {
166
+ throw new Error('Developer API not initialized');
167
+ }
168
+ const response = await context.developerApi.getEnvironmentVariables();
169
+ if (!response.success) {
170
+ throw new Error(response.error?.message || 'Failed to load environment variables');
172
171
  }
173
- const data = await response.json();
174
- if (data.data && typeof data.data === 'object') {
175
- return Object.entries(data.data).map(([key, value]) => ({
172
+ // The API returns environment variables nested in a 'data' property
173
+ // Response structure: { success: true, data: { data: { KEY: "value", ... }, _id: "...", ... } }
174
+ const envData = response.data?.data || response.data;
175
+ if (envData && typeof envData === 'object') {
176
+ // Filter out metadata fields like _id, agentId, createdAt, updatedAt, __v
177
+ const metadataKeys = ['_id', 'agentId', 'data', 'createdAt', 'updatedAt', '__v'];
178
+ return Object.entries(envData)
179
+ .filter(([key]) => !metadataKeys.includes(key))
180
+ .map(([key, value]) => ({
176
181
  key,
177
182
  value: String(value)
178
183
  }));
@@ -231,23 +236,18 @@ function saveSandboxEnvVariables(variables) {
231
236
  */
232
237
  async function saveProductionEnvVariables(context, variables) {
233
238
  try {
239
+ if (!context.developerApi) {
240
+ throw new Error('Developer API not initialized');
241
+ }
234
242
  const envData = {};
235
243
  variables.forEach(v => {
236
244
  if (v.key.trim()) {
237
245
  envData[v.key.trim()] = v.value;
238
246
  }
239
247
  });
240
- const response = await fetch(`${BASE_URLS.API}/developer/agents/${context.agentId}/env`, {
241
- method: 'POST',
242
- headers: {
243
- 'accept': 'application/json',
244
- 'Authorization': `Bearer ${context.apiKey}`,
245
- 'Content-Type': 'application/json'
246
- },
247
- body: JSON.stringify(envData)
248
- });
249
- if (!response.ok) {
250
- throw new Error(`HTTP error! status: ${response.status}`);
248
+ const response = await context.developerApi.updateEnvironmentVariables(envData);
249
+ if (!response.success) {
250
+ throw new Error(response.error?.message || 'Failed to save environment variables');
251
251
  }
252
252
  return true;
253
253
  }
@@ -383,14 +383,30 @@ async function deleteVariable(context, currentVariables) {
383
383
  console.log("\nā„¹ļø Deletion cancelled.\n");
384
384
  return;
385
385
  }
386
- const updatedVariables = currentVariables.filter(v => v.key !== selectedKey);
387
- writeProgress("šŸ”„ Saving...");
386
+ writeProgress("šŸ”„ Deleting...");
388
387
  let success = false;
389
388
  if (context.environment === 'sandbox') {
389
+ const updatedVariables = currentVariables.filter(v => v.key !== selectedKey);
390
390
  success = saveSandboxEnvVariables(updatedVariables);
391
391
  }
392
392
  else {
393
- success = await saveProductionEnvVariables(context, updatedVariables);
393
+ // Use the dedicated delete API endpoint for production
394
+ if (!context.developerApi) {
395
+ console.error("āŒ Developer API not initialized");
396
+ return;
397
+ }
398
+ try {
399
+ const response = await context.developerApi.deleteEnvironmentVariable(selectedKey);
400
+ if (response.success) {
401
+ success = true;
402
+ }
403
+ else {
404
+ console.error(`āŒ ${response.error?.message || 'Failed to delete variable'}`);
405
+ }
406
+ }
407
+ catch (error) {
408
+ console.error('āŒ Error deleting production env variable:', error);
409
+ }
394
410
  }
395
411
  if (success) {
396
412
  writeSuccess(`āœ… Variable "${selectedKey}" deleted successfully`);
@@ -2,11 +2,13 @@
2
2
  * Init Command
3
3
  * Orchestrates the initialization of a new Lua skill project
4
4
  */
5
+ import inquirer from 'inquirer';
5
6
  import { loadApiKey, checkApiKey } from "../services/auth.js";
6
- import { withErrorHandling, writeProgress, writeSuccess, } from "../utils/cli.js";
7
+ import { withErrorHandling, writeProgress, writeSuccess, writeInfo, writeError, } from "../utils/cli.js";
7
8
  import { promptAgentChoice, promptOrganizationSelection, promptAgentSelection, promptMetadataCollection, promptFeatureConfiguration, promptBusinessConfiguration, } from "../utils/init-prompts.js";
8
- import { fetchAgentTypes, selectBaseAgentType, createNewAgent, } from "../utils/init-agent.js";
9
+ import { fetchAgentTypes, selectBaseAgentType, createNewAgent, fetchExistingAgentDetails, } from "../utils/init-agent.js";
9
10
  import { initializeProject, installDependencies, clearLinesIfNeeded, } from "../utils/init-helpers.js";
11
+ import { readSkillYaml, updateYamlAgent } from "../utils/files.js";
10
12
  /**
11
13
  * Main init command - initializes a new Lua skill project.
12
14
  *
@@ -40,40 +42,114 @@ export async function initCommand() {
40
42
  }
41
43
  const userData = await checkApiKey(apiKey);
42
44
  writeProgress("āœ… Authenticated");
43
- // Step 2: Choose between existing or new agent
45
+ // Step 2: Check for existing YAML file
46
+ const existingYaml = readSkillYaml();
47
+ const yamlExists = existingYaml !== null;
48
+ if (yamlExists) {
49
+ const existingAgentId = existingYaml?.agent?.agentId;
50
+ if (existingAgentId) {
51
+ // Check if user has access to this agent
52
+ const hasAccess = checkUserHasAccessToAgent(userData, existingAgentId);
53
+ if (!hasAccess) {
54
+ writeError("\nāš ļø You don't have access to the agent in this project");
55
+ writeInfo(` Agent ID: ${existingAgentId}\n`);
56
+ const { action } = await inquirer.prompt([
57
+ {
58
+ type: 'list',
59
+ name: 'action',
60
+ message: 'What would you like to do?',
61
+ choices: [
62
+ { name: 'šŸ“§ Contact the agent admin to request access', value: 'contact' },
63
+ { name: 'šŸ”„ Switch to a different agent', value: 'switch' },
64
+ { name: 'āŒ Cancel', value: 'cancel' }
65
+ ]
66
+ }
67
+ ]);
68
+ if (action === 'contact') {
69
+ writeInfo("\nšŸ’” Please contact the agent administrator to grant you access.");
70
+ writeInfo(` Agent ID: ${existingAgentId}\n`);
71
+ process.exit(0);
72
+ }
73
+ if (action === 'cancel') {
74
+ writeInfo("\nāŒ Initialization cancelled.\n");
75
+ process.exit(0);
76
+ }
77
+ // User chose to switch agent - update existing YAML only
78
+ await handleAgentSwitch(userData, apiKey, existingYaml);
79
+ return;
80
+ }
81
+ else {
82
+ // User has access - ask if they want to switch anyway
83
+ writeInfo("\nšŸ“‹ Found existing project configuration");
84
+ writeInfo(` Current Agent ID: ${existingAgentId}\n`);
85
+ const { wantSwitch } = await inquirer.prompt([
86
+ {
87
+ type: 'confirm',
88
+ name: 'wantSwitch',
89
+ message: 'Do you want to switch this project to a different agent?',
90
+ default: false
91
+ }
92
+ ]);
93
+ if (wantSwitch) {
94
+ // User wants to switch agent - update existing YAML only
95
+ await handleAgentSwitch(userData, apiKey, existingYaml);
96
+ return;
97
+ }
98
+ else {
99
+ writeInfo("\nāœ… Keeping current agent configuration\n");
100
+ process.exit(0);
101
+ }
102
+ }
103
+ }
104
+ }
105
+ // Step 3: Choose between existing or new agent
44
106
  const agentChoice = await promptAgentChoice();
45
107
  let selectedAgent;
46
108
  let selectedOrg;
47
109
  let persona;
48
110
  let welcomeMessage;
111
+ let skipProjectInit = false;
49
112
  if (agentChoice === "existing") {
50
- // Step 3a: Select existing agent
51
- const result = await selectExistingAgent(userData);
113
+ // Step 4a: Select existing agent
114
+ const result = await selectExistingAgent(userData, apiKey);
52
115
  selectedAgent = result.agent;
53
116
  selectedOrg = result.org;
117
+ persona = result.persona;
118
+ welcomeMessage = result.welcomeMessage;
54
119
  }
55
120
  else {
56
- // Step 3b: Create new agent
121
+ // Step 4b: Create new agent
57
122
  const result = await createNewAgentFlow(apiKey);
58
123
  selectedAgent = result.agent;
59
124
  selectedOrg = result.org;
60
125
  persona = result.persona;
61
126
  welcomeMessage = result.welcomeMessage;
62
127
  }
63
- // Step 4: Initialize project
64
- const currentDir = initializeProject(selectedAgent.agentId, selectedOrg.id, persona, welcomeMessage);
65
- // Step 5: Install dependencies
66
- await installDependencies(currentDir);
67
- writeSuccess("āœ… Lua skill project initialized successfully!");
128
+ // Step 5: Initialize or update project
129
+ if (yamlExists) {
130
+ // Update existing YAML only
131
+ writeInfo("šŸ“ Updating existing lua.skill.yaml with new agent...");
132
+ updateYamlAgent(selectedAgent.agentId, selectedOrg.id, persona, welcomeMessage);
133
+ writeSuccess("āœ… lua.skill.yaml updated successfully!");
134
+ }
135
+ else {
136
+ // Full project initialization
137
+ const currentDir = initializeProject(selectedAgent.agentId, selectedOrg.id, persona, welcomeMessage);
138
+ // Step 6: Install dependencies
139
+ await installDependencies(currentDir);
140
+ writeSuccess("āœ… Lua skill project initialized successfully!");
141
+ }
68
142
  }, "initialization");
69
143
  }
70
144
  /**
71
145
  * Handles the flow for selecting an existing agent.
146
+ * Fetches the agent details from the server to get persona and welcomeMessage.
72
147
  *
73
148
  * @param userData - User's data
74
- * @returns Selected agent and organization
149
+ * @param apiKey - User's API key
150
+ * @returns Selected agent, organization, and optional persona/welcome message
75
151
  */
76
- async function selectExistingAgent(userData) {
152
+ async function selectExistingAgent(userData, apiKey) {
77
153
  // Extract organizations
78
154
  const orgs = userData.admin.orgs;
79
155
  if (!orgs || orgs.length === 0) {
@@ -83,7 +159,14 @@ async function selectExistingAgent(userData) {
83
159
  const selectedOrg = await promptOrganizationSelection(orgs);
84
160
  // Select agent from organization
85
161
  const selectedAgent = await promptAgentSelection(selectedOrg);
86
- return { agent: selectedAgent, org: selectedOrg };
162
+ // Fetch agent details to get persona and welcomeMessage
163
+ const agentDetails = await fetchExistingAgentDetails(apiKey, selectedAgent.agentId);
164
+ return {
165
+ agent: selectedAgent,
166
+ org: selectedOrg,
167
+ persona: agentDetails.persona,
168
+ welcomeMessage: agentDetails.welcomeMessage
169
+ };
87
170
  }
88
171
  /**
89
172
  * Handles the flow for creating a new agent.
@@ -115,3 +198,103 @@ async function createNewAgentFlow(apiKey) {
115
198
  const result = await createNewAgent(apiKey, selectedAgentType, businessConfig, metadata, features);
116
199
  return result;
117
200
  }
201
+ /**
202
+ * Check if user has access to a specific agent
203
+ */
204
+ function checkUserHasAccessToAgent(userData, agentId) {
205
+ const orgs = userData.admin.orgs;
206
+ for (const org of orgs) {
207
+ const agent = org.agents.find((a) => a.agentId === agentId);
208
+ if (agent) {
209
+ return true;
210
+ }
211
+ }
212
+ return false;
213
+ }
214
+ /**
215
+ * Handle switching to a different agent
216
+ */
217
+ async function handleAgentSwitch(userData, apiKey, existingYaml) {
218
+ writeInfo("\nšŸ”„ Let's switch to a different agent...\n");
219
+ // Prompt for agent choice
220
+ const agentChoice = await promptAgentChoice();
221
+ let selectedAgent;
222
+ let selectedOrg;
223
+ let persona;
224
+ let welcomeMessage;
225
+ if (agentChoice === "existing") {
226
+ // Select existing agent
227
+ const result = await selectExistingAgent(userData, apiKey);
228
+ selectedAgent = result.agent;
229
+ selectedOrg = result.org;
230
+ persona = result.persona;
231
+ welcomeMessage = result.welcomeMessage;
232
+ }
233
+ else {
234
+ // Create new agent
235
+ const result = await createNewAgentFlow(apiKey);
236
+ selectedAgent = result.agent;
237
+ selectedOrg = result.org;
238
+ persona = result.persona;
239
+ welcomeMessage = result.welcomeMessage;
240
+ }
241
+ // Ask about persona and welcome message
242
+ const finalPersona = await promptPersonaReplacement(existingYaml, persona);
243
+ const finalWelcomeMessage = await promptWelcomeMessageReplacement(existingYaml, welcomeMessage);
244
+ // Update existing YAML file with new agent
245
+ writeInfo("\nšŸ“ Updating lua.skill.yaml with new agent...");
246
+ updateYamlAgent(selectedAgent.agentId, selectedOrg.id, finalPersona, finalWelcomeMessage);
247
+ writeSuccess("āœ… lua.skill.yaml updated successfully!");
248
+ writeInfo("\nšŸ’” Your project now uses the new agent. Run 'lua compile' to update your skills.\n");
249
+ }
250
+ /**
251
+ * Prompt user about replacing existing persona
252
+ */
253
+ async function promptPersonaReplacement(existingYaml, newPersona) {
254
+ const existingPersona = existingYaml?.agent?.persona;
255
+ // If no existing persona, use new one
256
+ if (!existingPersona) {
257
+ return newPersona;
258
+ }
259
+ // If no new persona, keep existing
260
+ if (!newPersona) {
261
+ return existingPersona;
262
+ }
263
+ // Both exist - ask user
264
+ writeInfo("\nšŸ“ Persona Configuration:");
265
+ writeInfo(" Existing persona found in project");
266
+ const { replacePersona } = await inquirer.prompt([
267
+ {
268
+ type: 'confirm',
269
+ name: 'replacePersona',
270
+ message: 'Replace existing persona with the new agent\'s persona?',
271
+ default: false
272
+ }
273
+ ]);
274
+ return replacePersona ? newPersona : existingPersona;
275
+ }
276
+ /**
277
+ * Prompt user about replacing existing welcome message
278
+ */
279
+ async function promptWelcomeMessageReplacement(existingYaml, newWelcomeMessage) {
280
+ const existingWelcomeMessage = existingYaml?.agent?.welcomeMessage;
281
+ // If no existing welcome message, use new one
282
+ if (!existingWelcomeMessage) {
283
+ return newWelcomeMessage;
284
+ }
285
+ // If no new welcome message, keep existing
286
+ if (!newWelcomeMessage) {
287
+ return existingWelcomeMessage;
288
+ }
289
+ // Both exist - ask user
290
+ writeInfo(" Existing welcome message found in project");
291
+ const { replaceWelcomeMessage } = await inquirer.prompt([
292
+ {
293
+ type: 'confirm',
294
+ name: 'replaceWelcomeMessage',
295
+ message: 'Replace existing welcome message with the new agent\'s welcome message?',
296
+ default: false
297
+ }
298
+ ]);
299
+ return replaceWelcomeMessage ? newWelcomeMessage : existingWelcomeMessage;
300
+ }
@@ -160,7 +160,9 @@ function displayLogs(logs, pagination, title) {
160
160
  writeInfo("No logs found.");
161
161
  return;
162
162
  }
163
- logs.forEach((log, index) => {
163
+ // Reverse logs so latest appears at the bottom (oldest first, newest last)
164
+ const reversedLogs = [...logs].reverse();
165
+ reversedLogs.forEach((log, index) => {
164
166
  const timestamp = new Date(log.timestamp).toLocaleString();
165
167
  const icon = getLogIcon(log.subType);
166
168
  const color = getLogColor(log.subType);
@@ -179,7 +181,7 @@ function displayLogs(logs, pagination, title) {
179
181
  ? log.message.substring(0, 200) + '...'
180
182
  : log.message;
181
183
  console.log(` ${message}`);
182
- if (index < logs.length - 1) {
184
+ if (index < reversedLogs.length - 1) {
183
185
  console.log(chalk.gray(` ${'-'.repeat(78)}`));
184
186
  }
185
187
  });