lua-cli 2.5.8 → 3.0.0-alpha.10
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/dist/api/chat.api.service.d.ts +8 -0
- package/dist/api/chat.api.service.js +50 -0
- package/dist/api/job.api.service.d.ts +219 -0
- package/dist/api/job.api.service.js +216 -0
- package/dist/api/lazy-instances.d.ts +24 -0
- package/dist/api/lazy-instances.js +48 -0
- package/dist/api/postprocessor.api.service.d.ts +158 -0
- package/dist/api/postprocessor.api.service.js +111 -0
- package/dist/api/preprocessor.api.service.d.ts +158 -0
- package/dist/api/preprocessor.api.service.js +111 -0
- package/dist/api/user.data.api.service.d.ts +13 -0
- package/dist/api/user.data.api.service.js +20 -0
- package/dist/api/webhook.api.service.d.ts +151 -0
- package/dist/api/webhook.api.service.js +134 -0
- package/dist/api-exports.d.ts +176 -41
- package/dist/api-exports.js +195 -21
- package/dist/cli/command-definitions.js +85 -8
- package/dist/commands/chat.js +73 -36
- package/dist/commands/compile.js +140 -7
- package/dist/commands/dev.js +23 -2
- package/dist/commands/index.d.ts +4 -0
- package/dist/commands/index.js +4 -0
- package/dist/commands/init.js +53 -7
- package/dist/commands/jobs.d.ts +20 -0
- package/dist/commands/jobs.js +533 -0
- package/dist/commands/logs.js +2 -5
- package/dist/commands/postprocessors.d.ts +8 -0
- package/dist/commands/postprocessors.js +431 -0
- package/dist/commands/preprocessors.d.ts +8 -0
- package/dist/commands/preprocessors.js +431 -0
- package/dist/commands/push.d.ts +3 -2
- package/dist/commands/push.js +1216 -7
- package/dist/commands/test.d.ts +9 -18
- package/dist/commands/test.js +574 -82
- package/dist/commands/webhooks.d.ts +18 -0
- package/dist/commands/webhooks.js +424 -0
- package/dist/common/job.instance.d.ts +80 -0
- package/dist/common/job.instance.js +116 -0
- package/dist/common/user.instance.d.ts +1 -0
- package/dist/common/user.instance.js +9 -0
- package/dist/config/constants.d.ts +4 -3
- package/dist/config/constants.js +10 -8
- package/dist/interfaces/agent.d.ts +2 -1
- package/dist/interfaces/chat.d.ts +52 -1
- package/dist/interfaces/index.d.ts +10 -0
- package/dist/interfaces/index.js +7 -0
- package/dist/interfaces/jobs.d.ts +193 -0
- package/dist/interfaces/jobs.js +5 -0
- package/dist/interfaces/postprocessors.d.ts +35 -0
- package/dist/interfaces/postprocessors.js +4 -0
- package/dist/interfaces/preprocessors.d.ts +35 -0
- package/dist/interfaces/preprocessors.js +4 -0
- package/dist/interfaces/webhooks.d.ts +104 -0
- package/dist/interfaces/webhooks.js +5 -0
- package/dist/services/auth.d.ts +8 -2
- package/dist/services/auth.js +35 -3
- package/dist/types/api-contracts.d.ts +5 -0
- package/dist/types/compile.types.d.ts +49 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.js +1 -1
- package/dist/types/skill.d.ts +521 -0
- package/dist/types/skill.js +471 -0
- package/dist/utils/agent-management.d.ts +25 -0
- package/dist/utils/agent-management.js +67 -0
- package/dist/utils/bundling.d.ts +44 -5
- package/dist/utils/bundling.js +723 -23
- package/dist/utils/compile.d.ts +63 -0
- package/dist/utils/compile.js +712 -36
- package/dist/utils/deployment.d.ts +2 -1
- package/dist/utils/deployment.js +16 -2
- package/dist/utils/dev-api.d.ts +42 -2
- package/dist/utils/dev-api.js +177 -4
- package/dist/utils/dev-server.d.ts +1 -1
- package/dist/utils/dev-server.js +4 -4
- package/dist/utils/dynamic-job-bundler.d.ts +17 -0
- package/dist/utils/dynamic-job-bundler.js +143 -0
- package/dist/utils/init-agent.d.ts +3 -1
- package/dist/utils/init-agent.js +6 -4
- package/dist/utils/init-prompts.d.ts +2 -1
- package/dist/utils/init-prompts.js +14 -9
- package/dist/utils/job-management.d.ts +24 -0
- package/dist/utils/job-management.js +264 -0
- package/dist/utils/postprocessor-management.d.ts +9 -0
- package/dist/utils/postprocessor-management.js +118 -0
- package/dist/utils/pre-bundle-jobs.d.ts +26 -0
- package/dist/utils/pre-bundle-jobs.js +176 -0
- package/dist/utils/preprocessor-management.d.ts +9 -0
- package/dist/utils/preprocessor-management.js +118 -0
- package/dist/utils/sandbox-storage.d.ts +48 -0
- package/dist/utils/sandbox-storage.js +114 -0
- package/dist/utils/sandbox.d.ts +61 -1
- package/dist/utils/sandbox.js +299 -72
- package/dist/utils/tool-detection.d.ts +3 -2
- package/dist/utils/tool-detection.js +18 -4
- package/dist/utils/webhook-management.d.ts +24 -0
- package/dist/utils/webhook-management.js +256 -0
- package/package.json +1 -1
- package/template/README.md +30 -2
- package/template/env.example +5 -0
- package/template/lua.skill.yaml +47 -0
- package/template/package-lock.json +10505 -0
- package/template/package.json +2 -1
- package/template/src/index.ts +103 -2
- package/template/src/jobs/AbandonedBasketProcessorJob.ts +139 -0
- package/template/src/jobs/DailyCleanupJob.ts +100 -0
- package/template/src/jobs/DataMigrationJob.ts +133 -0
- package/template/src/jobs/HealthCheckJob.ts +87 -0
- package/template/src/tools/CreateInlineJob.ts +42 -0
- package/template/src/tools/GameScoreTrackerTool.ts +356 -0
- package/template/src/tools/SmartBasketTool.ts +188 -0
- package/template/src/webhooks/PaymentWebhook.ts +113 -0
- package/template/src/webhooks/UserEventWebhook.ts +77 -0
- package/API_REFERENCE.md +0 -1408
- package/CHANGELOG.md +0 -236
- package/CLI_REFERENCE.md +0 -908
- package/GETTING_STARTED.md +0 -1040
- package/INSTANCE_TYPES.md +0 -1158
- package/README.md +0 -865
- package/TEMPLATE_GUIDE.md +0 -1398
- package/USER_DATA_INSTANCE.md +0 -621
- package/template/TOOL_EXAMPLES.md +0 -655
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Job Management Utilities
|
|
3
|
+
* Handles job creation via API and YAML configuration management
|
|
4
|
+
*/
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import yaml from "js-yaml";
|
|
8
|
+
import JobApi from '../api/job.api.service.js';
|
|
9
|
+
import { BASE_URLS } from '../config/constants.js';
|
|
10
|
+
import { loadApiKey } from '../services/auth.js';
|
|
11
|
+
import { COMPILE_FILES, SKILL_DEFAULTS, YAML_FORMAT, } from '../config/compile.constants.js';
|
|
12
|
+
/**
|
|
13
|
+
* Ensures all detected jobs exist in the YAML config with valid job IDs.
|
|
14
|
+
* If a job doesn't exist or has no ID, creates it via the API.
|
|
15
|
+
*
|
|
16
|
+
* @param jobsArray - Array of jobs detected from source code
|
|
17
|
+
* @param config - The skill configuration from lua.skill.yaml
|
|
18
|
+
* @returns Updated jobs array with valid job IDs
|
|
19
|
+
*/
|
|
20
|
+
export async function ensureJobsExistInYaml(jobsArray, config) {
|
|
21
|
+
const updatedJobsArray = [];
|
|
22
|
+
let yamlUpdated = false;
|
|
23
|
+
const existingJobs = config?.jobs || [];
|
|
24
|
+
const existingJobsMap = createJobsMap(existingJobs);
|
|
25
|
+
// Process each detected job
|
|
26
|
+
for (const job of jobsArray) {
|
|
27
|
+
const existingJob = existingJobsMap.get(job.name);
|
|
28
|
+
if (existingJob && existingJob.jobId && existingJob.jobId !== '') {
|
|
29
|
+
// Job exists with valid jobId - reuse it
|
|
30
|
+
updatedJobsArray.push({
|
|
31
|
+
...job,
|
|
32
|
+
jobId: existingJob.jobId
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
// Job doesn't exist or missing jobId - create via API
|
|
37
|
+
const createdJob = await createJobViaApi(job, config, existingJobs, existingJob);
|
|
38
|
+
updatedJobsArray.push(createdJob);
|
|
39
|
+
yamlUpdated = true;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// Update YAML file if any changes were made
|
|
43
|
+
if (yamlUpdated) {
|
|
44
|
+
await updateYamlWithJobs(config);
|
|
45
|
+
}
|
|
46
|
+
return updatedJobsArray;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Creates a map of existing jobs for quick lookup by name.
|
|
50
|
+
*
|
|
51
|
+
* @param existingJobs - Array of jobs from YAML config
|
|
52
|
+
* @returns Map of job names to job objects
|
|
53
|
+
*/
|
|
54
|
+
function createJobsMap(existingJobs) {
|
|
55
|
+
const map = new Map();
|
|
56
|
+
existingJobs.forEach((job) => {
|
|
57
|
+
map.set(job.name, job);
|
|
58
|
+
});
|
|
59
|
+
return map;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Creates a new job via the Lua API and updates the config.
|
|
63
|
+
*
|
|
64
|
+
* @param job - The job to create
|
|
65
|
+
* @param config - The skill configuration
|
|
66
|
+
* @param existingJobs - Array of existing jobs (mutated)
|
|
67
|
+
* @param existingJob - The existing job entry if one exists (may be without ID)
|
|
68
|
+
* @returns The job with its new ID
|
|
69
|
+
*/
|
|
70
|
+
async function createJobViaApi(job, config, existingJobs, existingJob) {
|
|
71
|
+
try {
|
|
72
|
+
// Validate prerequisites
|
|
73
|
+
const apiKey = await loadApiKey();
|
|
74
|
+
if (!apiKey) {
|
|
75
|
+
throw new Error("No API key found. Run 'lua auth configure' first.");
|
|
76
|
+
}
|
|
77
|
+
const agentId = config?.agent?.agentId;
|
|
78
|
+
if (!agentId) {
|
|
79
|
+
throw new Error("No agent ID found in lua.skill.yaml. Run 'lua init' first.");
|
|
80
|
+
}
|
|
81
|
+
// Create job via API
|
|
82
|
+
const jobPayload = {
|
|
83
|
+
name: job.name,
|
|
84
|
+
description: job.description || `A Lua job for ${job.name}`,
|
|
85
|
+
context: job.context || '',
|
|
86
|
+
schedule: job.schedule || { type: 'cron', expression: '0 0 * * *' },
|
|
87
|
+
timeout: job.timeout,
|
|
88
|
+
retry: job.retry,
|
|
89
|
+
metadata: job.metadata
|
|
90
|
+
};
|
|
91
|
+
const jobApi = new JobApi(BASE_URLS.API, apiKey, agentId);
|
|
92
|
+
const result = await jobApi.createJob(jobPayload);
|
|
93
|
+
if (result.success && result.data && result.data.id) {
|
|
94
|
+
const newJobId = result.data.id;
|
|
95
|
+
// Update YAML config with new job ID
|
|
96
|
+
if (!existingJob) {
|
|
97
|
+
existingJobs.push({
|
|
98
|
+
name: job.name || '',
|
|
99
|
+
version: job.version || SKILL_DEFAULTS.VERSION,
|
|
100
|
+
jobId: newJobId,
|
|
101
|
+
schedule: job.schedule
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
existingJob.jobId = newJobId;
|
|
106
|
+
existingJob.schedule = job.schedule;
|
|
107
|
+
}
|
|
108
|
+
// Add jobs to config if not already present
|
|
109
|
+
if (!config.jobs) {
|
|
110
|
+
config.jobs = existingJobs;
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
...job,
|
|
114
|
+
jobId: newJobId
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
console.error(`❌ Failed to create job ${job.name}:`, result.error);
|
|
119
|
+
throw new Error(result.error?.message || 'Failed to create job - no ID returned from API');
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
console.error(`❌ Failed to create job ${job.name}:`, error);
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Updates the lua.skill.yaml file with the current jobs array.
|
|
129
|
+
* Ensures no undefined values are written to the YAML file.
|
|
130
|
+
*
|
|
131
|
+
* @param config - Current skill configuration to merge with
|
|
132
|
+
*/
|
|
133
|
+
async function updateYamlWithJobs(config) {
|
|
134
|
+
const jobs = config?.jobs || [];
|
|
135
|
+
// Clean jobs array to ensure no undefined values
|
|
136
|
+
const cleanedJobs = jobs.map(job => ({
|
|
137
|
+
name: job.name || '',
|
|
138
|
+
version: job.version || SKILL_DEFAULTS.VERSION,
|
|
139
|
+
jobId: job.jobId || '',
|
|
140
|
+
schedule: job.schedule || { type: 'cron', expression: '0 0 * * *' }
|
|
141
|
+
}));
|
|
142
|
+
// Update config with cleaned jobs array
|
|
143
|
+
const updatedConfig = {
|
|
144
|
+
...config,
|
|
145
|
+
jobs: cleanedJobs
|
|
146
|
+
};
|
|
147
|
+
// Write updated YAML with consistent formatting
|
|
148
|
+
const yamlPath = path.join(process.cwd(), COMPILE_FILES.LUA_SKILL_YAML);
|
|
149
|
+
const yamlContent = yaml.dump(updatedConfig, {
|
|
150
|
+
indent: YAML_FORMAT.INDENT,
|
|
151
|
+
lineWidth: YAML_FORMAT.LINE_WIDTH,
|
|
152
|
+
noRefs: YAML_FORMAT.NO_REFS,
|
|
153
|
+
replacer: (key, value) => {
|
|
154
|
+
// Replace undefined values with empty strings
|
|
155
|
+
return value === undefined ? '' : value;
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
fs.writeFileSync(yamlPath, yamlContent);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Syncs the server jobs with the YAML configuration.
|
|
162
|
+
* Performs a two-way sync:
|
|
163
|
+
* 1. Deletes/deactivates jobs from server that aren't in YAML
|
|
164
|
+
* 2. Updates YAML with active version numbers from server
|
|
165
|
+
*
|
|
166
|
+
* @param config - The skill configuration from lua.skill.yaml
|
|
167
|
+
* @returns Array of messages about sync operations
|
|
168
|
+
*/
|
|
169
|
+
export async function syncServerJobsWithYaml(config) {
|
|
170
|
+
const messages = [];
|
|
171
|
+
let yamlNeedsUpdate = false;
|
|
172
|
+
try {
|
|
173
|
+
// Validate prerequisites
|
|
174
|
+
const apiKey = await loadApiKey();
|
|
175
|
+
if (!apiKey) {
|
|
176
|
+
console.warn("⚠️ No API key found. Skipping server job sync.");
|
|
177
|
+
return messages;
|
|
178
|
+
}
|
|
179
|
+
const agentId = config?.agent?.agentId;
|
|
180
|
+
if (!agentId) {
|
|
181
|
+
console.warn("⚠️ No agent ID found in lua.skill.yaml. Skipping server job sync.");
|
|
182
|
+
return messages;
|
|
183
|
+
}
|
|
184
|
+
// Get jobs from server
|
|
185
|
+
const jobApi = new JobApi(BASE_URLS.API, apiKey, agentId);
|
|
186
|
+
const serverJobsResponse = await jobApi.getJobs();
|
|
187
|
+
if (!serverJobsResponse.success || !serverJobsResponse.data?.jobs) {
|
|
188
|
+
console.warn("⚠️ Could not retrieve server jobs. Skipping server job sync.");
|
|
189
|
+
return messages;
|
|
190
|
+
}
|
|
191
|
+
const serverJobs = serverJobsResponse.data.jobs;
|
|
192
|
+
const yamlJobs = config?.jobs || [];
|
|
193
|
+
// Create maps for efficient lookup
|
|
194
|
+
const yamlJobsMap = new Map(yamlJobs
|
|
195
|
+
.filter((job) => job.jobId)
|
|
196
|
+
.map((job) => [job.jobId, job]));
|
|
197
|
+
const serverJobsMap = new Map(serverJobs.map(job => [job.id, job]));
|
|
198
|
+
// Part 1: Delete jobs from server that aren't in YAML
|
|
199
|
+
const jobsToDelete = serverJobs.filter(serverJob => !yamlJobsMap.has(serverJob.id));
|
|
200
|
+
for (const job of jobsToDelete) {
|
|
201
|
+
try {
|
|
202
|
+
const deleteResponse = await jobApi.deleteJob(job.id);
|
|
203
|
+
if (deleteResponse.success && deleteResponse.data) {
|
|
204
|
+
if (deleteResponse.data.deleted) {
|
|
205
|
+
const msg = `✅ Deleted job "${job.name}" from server`;
|
|
206
|
+
messages.push(msg);
|
|
207
|
+
console.log(msg);
|
|
208
|
+
}
|
|
209
|
+
else if (deleteResponse.data.deactivated) {
|
|
210
|
+
const msg = `⚠️ Job "${job.name}" has versions and cannot be deleted. It has been deactivated instead.`;
|
|
211
|
+
messages.push(msg);
|
|
212
|
+
console.warn(msg);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
const msg = `❌ Failed to delete job "${job.name}": ${deleteResponse.error?.message || 'Unknown error'}`;
|
|
217
|
+
messages.push(msg);
|
|
218
|
+
console.error(msg);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
const msg = `❌ Error deleting job "${job.name}": ${error}`;
|
|
223
|
+
messages.push(msg);
|
|
224
|
+
console.error(msg);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// Part 2: Sync version numbers from server to YAML
|
|
228
|
+
const updatedYamlJobs = yamlJobs.map((yamlJob) => {
|
|
229
|
+
const serverJob = serverJobsMap.get(yamlJob.jobId);
|
|
230
|
+
if (serverJob && serverJob.versions && serverJob.versions.length > 0) {
|
|
231
|
+
// Find the active version on the server
|
|
232
|
+
const activeVersion = serverJob.versions.find((v) => v.isActive);
|
|
233
|
+
if (activeVersion && activeVersion.version !== yamlJob.version) {
|
|
234
|
+
const msg = `📝 Updated "${yamlJob.name}" job version in YAML: ${yamlJob.version} → ${activeVersion.version}`;
|
|
235
|
+
messages.push(msg);
|
|
236
|
+
console.log(msg);
|
|
237
|
+
yamlNeedsUpdate = true;
|
|
238
|
+
return {
|
|
239
|
+
...yamlJob,
|
|
240
|
+
version: activeVersion.version,
|
|
241
|
+
schedule: activeVersion.schedule || yamlJob.schedule
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return yamlJob;
|
|
246
|
+
});
|
|
247
|
+
// Update YAML file if versions changed
|
|
248
|
+
if (yamlNeedsUpdate) {
|
|
249
|
+
const updatedConfig = {
|
|
250
|
+
...config,
|
|
251
|
+
jobs: updatedYamlJobs
|
|
252
|
+
};
|
|
253
|
+
await updateYamlWithJobs(updatedConfig);
|
|
254
|
+
console.log("✅ YAML job versions synced with server");
|
|
255
|
+
}
|
|
256
|
+
if (jobsToDelete.length === 0 && !yamlNeedsUpdate) {
|
|
257
|
+
console.log("✅ Server jobs and YAML are fully in sync");
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
console.error("❌ Error syncing server jobs:", error);
|
|
262
|
+
}
|
|
263
|
+
return messages;
|
|
264
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostProcessor Management Utilities
|
|
3
|
+
*/
|
|
4
|
+
import { SkillConfig } from '../types/compile.types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Ensures all detected postprocessors exist in the YAML config with valid IDs.
|
|
7
|
+
*/
|
|
8
|
+
export declare function ensurePostProcessorsExistInYaml(postprocessorsArray: any[], config: SkillConfig): Promise<any[]>;
|
|
9
|
+
export declare function syncServerPostProcessorsWithYaml(config: SkillConfig): Promise<string[]>;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostProcessor Management Utilities
|
|
3
|
+
*/
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import yaml from "js-yaml";
|
|
7
|
+
import PostProcessorApi from '../api/postprocessor.api.service.js';
|
|
8
|
+
import { BASE_URLS } from '../config/constants.js';
|
|
9
|
+
import { loadApiKey } from '../services/auth.js';
|
|
10
|
+
import { SKILL_DEFAULTS, YAML_FORMAT } from '../config/compile.constants.js';
|
|
11
|
+
/**
|
|
12
|
+
* Ensures all detected postprocessors exist in the YAML config with valid IDs.
|
|
13
|
+
*/
|
|
14
|
+
export async function ensurePostProcessorsExistInYaml(postprocessorsArray, config) {
|
|
15
|
+
const updatedPostProcessorsArray = [];
|
|
16
|
+
let yamlUpdated = false;
|
|
17
|
+
const existingPostProcessors = config?.postprocessors || [];
|
|
18
|
+
const existingMap = new Map(existingPostProcessors.map((p) => [p.name, p]));
|
|
19
|
+
for (const postprocessor of postprocessorsArray) {
|
|
20
|
+
const existing = existingMap.get(postprocessor.name);
|
|
21
|
+
if (existing && existing.postprocessorId) {
|
|
22
|
+
updatedPostProcessorsArray.push({
|
|
23
|
+
...postprocessor,
|
|
24
|
+
postprocessorId: existing.postprocessorId
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
const created = await createPostProcessorViaApi(postprocessor, config, existingPostProcessors, existing);
|
|
29
|
+
updatedPostProcessorsArray.push(created);
|
|
30
|
+
yamlUpdated = true;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (yamlUpdated) {
|
|
34
|
+
await updateYamlWithPostProcessors(config);
|
|
35
|
+
}
|
|
36
|
+
return updatedPostProcessorsArray;
|
|
37
|
+
}
|
|
38
|
+
async function createPostProcessorViaApi(postprocessor, config, existingPostProcessors, existingPostProcessor) {
|
|
39
|
+
try {
|
|
40
|
+
const apiKey = await loadApiKey();
|
|
41
|
+
if (!apiKey)
|
|
42
|
+
throw new Error("No API key found. Run 'lua auth configure' first.");
|
|
43
|
+
const agentId = config?.agent?.agentId;
|
|
44
|
+
if (!agentId)
|
|
45
|
+
throw new Error("No agent ID found in lua.skill.yaml. Run 'lua init' first.");
|
|
46
|
+
const payload = {
|
|
47
|
+
name: postprocessor.name,
|
|
48
|
+
description: postprocessor.description || `A Lua postprocessor for ${postprocessor.name}`,
|
|
49
|
+
context: postprocessor.context || ''
|
|
50
|
+
};
|
|
51
|
+
const api = new PostProcessorApi(BASE_URLS.API, apiKey, agentId);
|
|
52
|
+
const result = await api.createPostProcessor(payload);
|
|
53
|
+
if (result.success && result.data && result.data.id) {
|
|
54
|
+
const newId = result.data.id;
|
|
55
|
+
if (!existingPostProcessor) {
|
|
56
|
+
existingPostProcessors.push({
|
|
57
|
+
name: postprocessor.name || '',
|
|
58
|
+
version: postprocessor.version || SKILL_DEFAULTS.VERSION,
|
|
59
|
+
postprocessorId: newId
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
existingPostProcessor.postprocessorId = newId;
|
|
64
|
+
}
|
|
65
|
+
if (!config.postprocessors) {
|
|
66
|
+
config.postprocessors = existingPostProcessors;
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
...postprocessor,
|
|
70
|
+
postprocessorId: newId
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
throw new Error(result.error?.message || "Failed to create postprocessor");
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
console.warn(`Warning: Could not create postprocessor ${postprocessor.name}:`, error);
|
|
79
|
+
return postprocessor;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async function updateYamlWithPostProcessors(config) {
|
|
83
|
+
const yamlPath = path.join(process.cwd(), 'lua.skill.yaml');
|
|
84
|
+
const yamlContent = yaml.dump(config, { ...YAML_FORMAT, sortKeys: (a, b) => {
|
|
85
|
+
const order = ['agent', 'skills', 'webhooks', 'jobs', 'preprocessors', 'postprocessors', 'skill'];
|
|
86
|
+
const aIndex = order.indexOf(a);
|
|
87
|
+
const bIndex = order.indexOf(b);
|
|
88
|
+
if (aIndex !== -1 && bIndex !== -1)
|
|
89
|
+
return aIndex - bIndex;
|
|
90
|
+
if (aIndex !== -1)
|
|
91
|
+
return -1;
|
|
92
|
+
if (bIndex !== -1)
|
|
93
|
+
return 1;
|
|
94
|
+
return a.localeCompare(b);
|
|
95
|
+
} });
|
|
96
|
+
fs.writeFileSync(yamlPath, yamlContent);
|
|
97
|
+
}
|
|
98
|
+
export async function syncServerPostProcessorsWithYaml(config) {
|
|
99
|
+
const messages = [];
|
|
100
|
+
try {
|
|
101
|
+
const apiKey = await loadApiKey();
|
|
102
|
+
if (!apiKey) {
|
|
103
|
+
console.warn("⚠️ No API key found. Skipping server postprocessor sync.");
|
|
104
|
+
return messages;
|
|
105
|
+
}
|
|
106
|
+
const agentId = config?.agent?.agentId;
|
|
107
|
+
if (!agentId) {
|
|
108
|
+
console.warn("⚠️ No agent ID found. Skipping server postprocessor sync.");
|
|
109
|
+
return messages;
|
|
110
|
+
}
|
|
111
|
+
// Server sync will be implemented when backend is ready
|
|
112
|
+
return messages;
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
console.warn("⚠️ Error syncing postprocessors:", error);
|
|
116
|
+
return messages;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-Bundle Jobs - Extract and bundle Jobs.create() before tool bundling
|
|
3
|
+
*
|
|
4
|
+
* Flow:
|
|
5
|
+
* 1. Detect Jobs.create() in source
|
|
6
|
+
* 2. Extract execute function
|
|
7
|
+
* 3. Bundle execute with ALL dependencies
|
|
8
|
+
* 4. Replace execute with placeholder
|
|
9
|
+
* 5. Bundle tool/webhook/job
|
|
10
|
+
* 6. Replace placeholder with bundled job code
|
|
11
|
+
* 7. Compress
|
|
12
|
+
*/
|
|
13
|
+
import { Project } from 'ts-morph';
|
|
14
|
+
/**
|
|
15
|
+
* Pre-processes source file to extract and bundle Jobs.create() execute functions
|
|
16
|
+
* Returns modified source code with placeholders
|
|
17
|
+
*/
|
|
18
|
+
export declare function preBundleJobsInSource(sourceFilePath: string, project: Project, distDir: string): Promise<{
|
|
19
|
+
modifiedSource: string;
|
|
20
|
+
jobBundles: Map<string, string>;
|
|
21
|
+
}>;
|
|
22
|
+
/**
|
|
23
|
+
* Replaces placeholders in bundled code with actual job bundles
|
|
24
|
+
* The bundled job code is NOT compressed - it's JavaScript that will be part of the tool
|
|
25
|
+
*/
|
|
26
|
+
export declare function replaceJobPlaceholders(bundledCode: string, jobBundles: Map<string, string>): string;
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-Bundle Jobs - Extract and bundle Jobs.create() before tool bundling
|
|
3
|
+
*
|
|
4
|
+
* Flow:
|
|
5
|
+
* 1. Detect Jobs.create() in source
|
|
6
|
+
* 2. Extract execute function
|
|
7
|
+
* 3. Bundle execute with ALL dependencies
|
|
8
|
+
* 4. Replace execute with placeholder
|
|
9
|
+
* 5. Bundle tool/webhook/job
|
|
10
|
+
* 6. Replace placeholder with bundled job code
|
|
11
|
+
* 7. Compress
|
|
12
|
+
*/
|
|
13
|
+
import { Node } from 'ts-morph';
|
|
14
|
+
import { build } from 'esbuild';
|
|
15
|
+
import fs from 'fs';
|
|
16
|
+
import path from 'path';
|
|
17
|
+
import { sandboxGlobalsPlugin } from './bundling.js';
|
|
18
|
+
const JOB_PLACEHOLDER = '__BUNDLED_JOB_EXECUTE__';
|
|
19
|
+
/**
|
|
20
|
+
* Pre-processes source file to extract and bundle Jobs.create() execute functions
|
|
21
|
+
* Returns modified source code with placeholders
|
|
22
|
+
*/
|
|
23
|
+
export async function preBundleJobsInSource(sourceFilePath, project, distDir) {
|
|
24
|
+
const sourceFile = project.getSourceFile(sourceFilePath);
|
|
25
|
+
if (!sourceFile) {
|
|
26
|
+
return { modifiedSource: fs.readFileSync(sourceFilePath, 'utf8'), jobBundles: new Map() };
|
|
27
|
+
}
|
|
28
|
+
const jobBundles = new Map();
|
|
29
|
+
const bundlingPromises = [];
|
|
30
|
+
let jobIndex = 0;
|
|
31
|
+
let modifiedSource = sourceFile.getFullText();
|
|
32
|
+
// Find all Jobs.create() calls
|
|
33
|
+
sourceFile.forEachDescendant((node) => {
|
|
34
|
+
if (Node.isCallExpression(node)) {
|
|
35
|
+
const expression = node.getExpression();
|
|
36
|
+
// Check if this is Jobs.create()
|
|
37
|
+
if (Node.isPropertyAccessExpression(expression)) {
|
|
38
|
+
const object = expression.getExpression();
|
|
39
|
+
const property = expression.getName();
|
|
40
|
+
if (object.getText() === 'Jobs' && property === 'create') {
|
|
41
|
+
jobIndex++;
|
|
42
|
+
const placeholder = `${JOB_PLACEHOLDER}_${jobIndex}`;
|
|
43
|
+
console.log(`[PreBundleJobs] Found Jobs.create() #${jobIndex}`);
|
|
44
|
+
// Get the config object argument
|
|
45
|
+
const args = node.getArguments();
|
|
46
|
+
if (args.length > 0 && Node.isObjectLiteralExpression(args[0])) {
|
|
47
|
+
const configObj = args[0];
|
|
48
|
+
// Find the execute property
|
|
49
|
+
const executeProperty = configObj.getProperty('execute');
|
|
50
|
+
if (executeProperty && Node.isPropertyAssignment(executeProperty)) {
|
|
51
|
+
const executeInitializer = executeProperty.getInitializer();
|
|
52
|
+
if (executeInitializer) {
|
|
53
|
+
const executeCode = executeInitializer.getText();
|
|
54
|
+
console.log(`[PreBundleJobs] Extracting execute function for bundling...`);
|
|
55
|
+
// Bundle the execute function with dependencies (async)
|
|
56
|
+
const bundlePromise = bundleJobExecuteFunction(executeCode, `job_${jobIndex}`, distDir, sourceFilePath).then(bundledCode => {
|
|
57
|
+
if (bundledCode) {
|
|
58
|
+
jobBundles.set(placeholder, bundledCode);
|
|
59
|
+
console.log(`[PreBundleJobs] ✅ Bundled job #${jobIndex} with dependencies`);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
bundlingPromises.push(bundlePromise);
|
|
63
|
+
// Replace execute function with a function that references the bundled code
|
|
64
|
+
// Don't use string placeholder - use a reference
|
|
65
|
+
const originalText = executeProperty.getText();
|
|
66
|
+
const replacementText = `execute: ${placeholder}`;
|
|
67
|
+
modifiedSource = modifiedSource.replace(originalText, replacementText);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
// Wait for all bundling to complete
|
|
76
|
+
if (bundlingPromises.length > 0) {
|
|
77
|
+
console.log(`[PreBundleJobs] Waiting for ${bundlingPromises.length} job(s) to bundle...`);
|
|
78
|
+
await Promise.all(bundlingPromises);
|
|
79
|
+
console.log(`[PreBundleJobs] ✅ All job bundles complete`);
|
|
80
|
+
}
|
|
81
|
+
return { modifiedSource, jobBundles };
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Bundles a job execute function with all its dependencies
|
|
85
|
+
*/
|
|
86
|
+
async function bundleJobExecuteFunction(executeCode, jobName, distDir, sourceFilePath) {
|
|
87
|
+
const tempDir = path.join(distDir, '.temp');
|
|
88
|
+
if (!fs.existsSync(tempDir)) {
|
|
89
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
90
|
+
}
|
|
91
|
+
const tempOutput = path.join(tempDir, `${jobName}-bundled.js`);
|
|
92
|
+
try {
|
|
93
|
+
// Read the original source file to get its imports
|
|
94
|
+
const sourceContent = fs.readFileSync(sourceFilePath, 'utf8');
|
|
95
|
+
// Extract all import statements from the source
|
|
96
|
+
const importPattern = /^import\s+.+\s+from\s+['"][^'"]+['"];?$/gm;
|
|
97
|
+
const imports = sourceContent.match(importPattern) || [];
|
|
98
|
+
// Filter to only imports that might be used (exclude lua-cli internal classes and APIs)
|
|
99
|
+
const relevantImports = imports.filter(imp => !imp.includes('LuaTool') &&
|
|
100
|
+
!imp.includes('LuaWebhook') &&
|
|
101
|
+
!imp.includes('LuaJob') &&
|
|
102
|
+
!imp.includes('lua-cli') && // Exclude all lua-cli imports (they're available in sandbox)
|
|
103
|
+
!imp.includes('api-exports') && // Exclude api-exports
|
|
104
|
+
!imp.includes('../../../dist/api-exports') // Exclude direct api-exports imports
|
|
105
|
+
);
|
|
106
|
+
console.log(`[PreBundleJobs] Including ${relevantImports.length} import(s) in job bundle`);
|
|
107
|
+
// Create a TypeScript module with the execute function and all imports
|
|
108
|
+
const moduleCode = `
|
|
109
|
+
// Auto-generated job execute bundle
|
|
110
|
+
${relevantImports.join('\n')}
|
|
111
|
+
|
|
112
|
+
// The execute function with all dependencies available
|
|
113
|
+
const executeFunc = ${executeCode};
|
|
114
|
+
|
|
115
|
+
export default executeFunc;
|
|
116
|
+
`;
|
|
117
|
+
// Bundle with esbuild using stdin to preserve import resolution
|
|
118
|
+
await build({
|
|
119
|
+
stdin: {
|
|
120
|
+
contents: moduleCode,
|
|
121
|
+
resolveDir: path.dirname(sourceFilePath),
|
|
122
|
+
sourcefile: sourceFilePath,
|
|
123
|
+
loader: 'ts',
|
|
124
|
+
},
|
|
125
|
+
bundle: true,
|
|
126
|
+
platform: 'node',
|
|
127
|
+
target: 'node16',
|
|
128
|
+
format: 'cjs',
|
|
129
|
+
minify: true,
|
|
130
|
+
outfile: tempOutput,
|
|
131
|
+
external: [],
|
|
132
|
+
plugins: [sandboxGlobalsPlugin],
|
|
133
|
+
});
|
|
134
|
+
// Read bundled code
|
|
135
|
+
const bundledCode = fs.readFileSync(tempOutput, 'utf8');
|
|
136
|
+
console.log(`[PreBundleJobs] Bundle size: ${(bundledCode.length / 1024).toFixed(2)}KB`);
|
|
137
|
+
// DON'T compress - just wrap for VM execution
|
|
138
|
+
// Compression happens later when the entire tool is bundled
|
|
139
|
+
const wrappedCode = `(async (job) => {
|
|
140
|
+
${bundledCode}
|
|
141
|
+
const executeFunction = module.exports.default || module.exports;
|
|
142
|
+
return await executeFunction(job);
|
|
143
|
+
})`;
|
|
144
|
+
// Cleanup temp files
|
|
145
|
+
try {
|
|
146
|
+
fs.unlinkSync(tempOutput);
|
|
147
|
+
}
|
|
148
|
+
catch { }
|
|
149
|
+
// Return uncompressed bundled code
|
|
150
|
+
return wrappedCode;
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
console.warn(`[PreBundleJobs] Error bundling job ${jobName}:`, error);
|
|
154
|
+
try {
|
|
155
|
+
if (fs.existsSync(tempOutput))
|
|
156
|
+
fs.unlinkSync(tempOutput);
|
|
157
|
+
}
|
|
158
|
+
catch { }
|
|
159
|
+
return '';
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Replaces placeholders in bundled code with actual job bundles
|
|
164
|
+
* The bundled job code is NOT compressed - it's JavaScript that will be part of the tool
|
|
165
|
+
*/
|
|
166
|
+
export function replaceJobPlaceholders(bundledCode, jobBundles) {
|
|
167
|
+
let result = bundledCode;
|
|
168
|
+
jobBundles.forEach((bundledJobCode, placeholder) => {
|
|
169
|
+
// The placeholder is used as an identifier (not a string), just replace it directly
|
|
170
|
+
// It appears as: execute: __BUNDLED_JOB_EXECUTE___1
|
|
171
|
+
const placeholderPattern = new RegExp(placeholder, 'g');
|
|
172
|
+
result = result.replace(placeholderPattern, bundledJobCode);
|
|
173
|
+
console.log(`[PreBundleJobs] ✅ Replaced ${placeholder} with bundled code (${(bundledJobCode.length / 1024).toFixed(1)}KB)`);
|
|
174
|
+
});
|
|
175
|
+
return result;
|
|
176
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PreProcessor Management Utilities
|
|
3
|
+
*/
|
|
4
|
+
import { SkillConfig } from '../types/compile.types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Ensures all detected preprocessors exist in the YAML config with valid IDs.
|
|
7
|
+
*/
|
|
8
|
+
export declare function ensurePreProcessorsExistInYaml(preprocessorsArray: any[], config: SkillConfig): Promise<any[]>;
|
|
9
|
+
export declare function syncServerPreProcessorsWithYaml(config: SkillConfig): Promise<string[]>;
|