@codemieai/cdk 0.1.439 → 0.1.445

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/cli/index.js CHANGED
@@ -1,98 +1,26 @@
1
1
  #!/usr/bin/env node
2
+ import * as fs6 from 'fs';
2
3
  import * as path6 from 'path';
3
4
  import path6__default from 'path';
5
+ import ora from 'ora';
6
+ import * as crypto2 from 'crypto';
7
+ import { DataSourceType, CodeMieClient } from 'codemie-sdk';
8
+ import * as yaml5 from 'yaml';
4
9
  import { program } from '@commander-js/extra-typings';
5
- import * as fs2 from 'fs';
6
10
  import { z } from 'zod';
7
11
  import 'dotenv/config';
8
- import * as crypto from 'crypto';
9
- import * as yaml3 from 'yaml';
10
- import { CodeMieClient, DataSourceType } from 'codemie-sdk';
11
12
  import pLimit from 'p-limit';
12
13
  import * as readline from 'readline';
13
14
 
14
- // package.json
15
- var package_default = {
16
- version: "0.1.439"};
17
- var appConfigSchema = z.object({
18
- rootDir: z.string(),
19
- codemieConfig: z.string(),
20
- codemieState: z.string(),
21
- backupsDirectory: z.string()
22
- });
23
- var externalConfigSchema = appConfigSchema.partial();
24
-
25
- // src/appConfig/defaultConfig.ts
26
- var defaultConfig = {
27
- rootDir: "./",
28
- codemieConfig: "codemie.yaml",
29
- codemieState: ".codemie/state.json",
30
- backupsDirectory: "backups"
31
- };
32
-
33
- // src/appConfig/configLoader.ts
34
- function loadAppConfig(userConfigPath) {
35
- if (!userConfigPath) {
36
- return defaultConfig;
37
- }
38
- const userConfig = loadUserConfig(userConfigPath);
39
- const sanitizedUserConfig = removeEmptyFields(userConfig);
40
- const merged = { ...defaultConfig, ...sanitizedUserConfig };
41
- return appConfigSchema.parse(merged);
42
- }
43
- function loadUserConfig(configPath) {
44
- const resolvedPath = path6.resolve(process.cwd(), configPath);
45
- if (!fs2.existsSync(resolvedPath)) {
46
- throw new Error(`Config file not found: ${resolvedPath}`);
47
- }
48
- let raw;
49
- try {
50
- raw = fs2.readFileSync(resolvedPath, "utf8");
51
- } catch (error) {
52
- throw new Error(`Failed to read config file: ${resolvedPath}`, { cause: error });
53
- }
54
- let json;
55
- try {
56
- json = JSON.parse(raw);
57
- } catch (error) {
58
- throw new Error(`Malformed JSON in config file: ${resolvedPath}`, { cause: error });
59
- }
60
- try {
61
- return externalConfigSchema.parse(json);
62
- } catch (error) {
63
- throw new Error(`Invalid config structure: ${error instanceof Error ? error.message : String(error)}`);
64
- }
65
- }
66
- function removeEmptyFields(userConfig) {
67
- return Object.fromEntries(
68
- Object.entries(userConfig).filter(([_, value]) => {
69
- if (value === null || value === void 0) {
70
- return false;
71
- }
72
- return value.trim() !== "";
73
- })
74
- );
75
- }
76
-
77
- // src/lib/constants.ts
78
- var PAGINATION = {
79
- DEFAULT_PAGE_SIZE: 100};
80
- var TIMEOUTS_MS = {
81
- ASSISTANT_FETCH: 3e4,
82
- DATASOURCE_FETCH: 3e4,
83
- WORKFLOW_FETCH: 3e4,
84
- INTEGRATION_FETCH: 3e4
85
- };
86
- var RATE_LIMITING = {
87
- MAX_CONCURRENT_REQUESTS: 5,
88
- RETRY_ATTEMPTS: 3,
89
- RETRY_DELAY_MS: 1e3
15
+ var __defProp = Object.defineProperty;
16
+ var __getOwnPropNames = Object.getOwnPropertyNames;
17
+ var __esm = (fn, res) => function __init() {
18
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
90
19
  };
91
- var BACKUP = {
92
- TEMP_DIR_PREFIX: ".temp-",
93
- TRANSACTION_SAVE_TIMEOUT_MS: 5e3
20
+ var __export = (target, all) => {
21
+ for (var name in all)
22
+ __defProp(target, name, { get: all[name], enumerable: true });
94
23
  };
95
- var BYTES_IN_GB = 1024 ** 3;
96
24
  function sanitizeFileName(name, maxLength = 255) {
97
25
  const nameWithHyphens = name.replaceAll(/[/\\]/g, "-");
98
26
  const sanitized = nameWithHyphens.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replaceAll(/^-+|-+$/g, "").slice(0, maxLength);
@@ -104,14 +32,14 @@ function sanitizeFileName(name, maxLength = 255) {
104
32
  function validateBackupDirectory(backupDir, minSpaceGB = 1) {
105
33
  try {
106
34
  const parentDir = path6.dirname(backupDir);
107
- if (!fs2.existsSync(parentDir)) {
108
- fs2.mkdirSync(parentDir, { recursive: true });
35
+ if (!fs6.existsSync(parentDir)) {
36
+ fs6.mkdirSync(parentDir, { recursive: true });
109
37
  }
110
38
  const testFile = path6.join(parentDir, ".write-test");
111
- fs2.writeFileSync(testFile, "test");
112
- fs2.unlinkSync(testFile);
39
+ fs6.writeFileSync(testFile, "test");
40
+ fs6.unlinkSync(testFile);
113
41
  try {
114
- const stats = fs2.statfsSync(parentDir);
42
+ const stats = fs6.statfsSync(parentDir);
115
43
  const availableGB = stats.bavail * stats.bsize / BYTES_IN_GB;
116
44
  if (availableGB < minSpaceGB) {
117
45
  throw new Error(`Insufficient disk space: ${availableGB.toFixed(2)}GB available, need ${minSpaceGB}GB`);
@@ -127,7 +55,7 @@ function validateBackupDirectory(backupDir, minSpaceGB = 1) {
127
55
  }
128
56
  function moveAtomically(tempPath, finalPath) {
129
57
  try {
130
- fs2.renameSync(tempPath, finalPath);
58
+ fs6.renameSync(tempPath, finalPath);
131
59
  } catch (error) {
132
60
  const err = error;
133
61
  if (err.code === "EEXIST") {
@@ -137,287 +65,247 @@ function moveAtomically(tempPath, finalPath) {
137
65
  }
138
66
  }
139
67
  function cleanupDirectory(dirPath) {
140
- if (fs2.existsSync(dirPath)) {
141
- fs2.rmSync(dirPath, { recursive: true, force: true });
68
+ if (fs6.existsSync(dirPath)) {
69
+ fs6.rmSync(dirPath, { recursive: true, force: true });
142
70
  }
143
71
  }
144
72
  function ensureDirectoryExists(filePath) {
145
73
  const dir = path6.dirname(filePath);
146
- if (!fs2.existsSync(dir)) {
147
- fs2.mkdirSync(dir, { recursive: true });
74
+ if (!fs6.existsSync(dir)) {
75
+ fs6.mkdirSync(dir, { recursive: true });
148
76
  }
149
77
  }
150
-
151
- // src/lib/logger.ts
152
- var Logger = class _Logger {
153
- static instance;
154
- level = 1 /* INFO */;
155
- constructor() {
78
+ var BYTES_IN_GB;
79
+ var init_fileUtils = __esm({
80
+ "src/lib/fileUtils.ts"() {
81
+ BYTES_IN_GB = 1024 ** 3;
156
82
  }
157
- static getInstance() {
158
- if (!_Logger.instance) {
159
- _Logger.instance = new _Logger();
160
- }
161
- return _Logger.instance;
83
+ });
84
+ function formatDuration(durationMs) {
85
+ if (durationMs < 1e3) {
86
+ return `${durationMs}ms`;
162
87
  }
163
- setLevel(level) {
164
- this.level = level;
88
+ const seconds = durationMs / 1e3;
89
+ if (seconds < 60) {
90
+ return `${seconds.toFixed(1)}s`;
165
91
  }
166
- debug(message, ...args) {
167
- if (this.level <= 0 /* DEBUG */) {
168
- console.debug(message, ...args);
169
- }
92
+ const minutes = Math.floor(seconds / 60);
93
+ const remainingSeconds = Math.round(seconds % 60);
94
+ return `${minutes}m ${remainingSeconds}s`;
95
+ }
96
+ function normalizeExitCode(code) {
97
+ if (typeof code === "number" && Number.isFinite(code)) {
98
+ return code;
170
99
  }
171
- info(message, ...args) {
172
- if (this.level <= 1 /* INFO */) {
173
- console.log(message, ...args);
100
+ if (typeof code === "string") {
101
+ const parsed = Number(code);
102
+ if (Number.isFinite(parsed)) {
103
+ return parsed;
174
104
  }
175
105
  }
176
- warn(message, ...args) {
177
- if (this.level <= 2 /* WARN */) {
178
- console.warn(message, ...args);
179
- }
106
+ return 0;
107
+ }
108
+ function startSpinner(text) {
109
+ spinnerManager.start(text);
110
+ }
111
+ function succeedSpinner(text) {
112
+ spinnerManager.succeed(text);
113
+ }
114
+ function failSpinner(text) {
115
+ spinnerManager.fail(text);
116
+ }
117
+ function pauseSpinner(operation) {
118
+ return spinnerManager.pause(operation);
119
+ }
120
+ function pauseSpinnerWhile(operation) {
121
+ return spinnerManager.pauseWhile(operation);
122
+ }
123
+ async function runCommandWithSpinner(commandLabel, action) {
124
+ const originalExit = process.exit;
125
+ const previousExitCode = process.exitCode;
126
+ const startTime = Date.now();
127
+ let interceptedExitCode;
128
+ startSpinner(`${commandLabel}...`);
129
+ process.exit = ((code) => {
130
+ interceptedExitCode = normalizeExitCode(code);
131
+ process.exitCode = interceptedExitCode;
132
+ return void 0;
133
+ });
134
+ try {
135
+ await action();
136
+ } catch (error) {
137
+ const duration2 = formatDuration(Date.now() - startTime);
138
+ failSpinner(`${commandLabel} failed in ${duration2}`);
139
+ if ((interceptedExitCode ?? process.exitCode ?? 0) === 0) {
140
+ process.exitCode = 1;
141
+ const errorMessage = error instanceof Error ? error.message : String(error);
142
+ console.error(errorMessage);
143
+ }
144
+ return;
145
+ } finally {
146
+ process.exit = originalExit;
180
147
  }
181
- error(message, ...args) {
182
- if (this.level <= 3 /* ERROR */) {
183
- console.error(message, ...args);
184
- }
148
+ const exitCode = interceptedExitCode ?? process.exitCode ?? previousExitCode ?? 0;
149
+ const duration = formatDuration(Date.now() - startTime);
150
+ if (exitCode === 0) {
151
+ succeedSpinner(`${commandLabel} completed in ${duration}`);
152
+ } else {
153
+ failSpinner(`${commandLabel} failed in ${duration}`);
185
154
  }
186
- };
187
- var logger = Logger.getInstance();
188
-
189
- // src/lib/backupTransaction.ts
190
- function busyWaitDelay(ms) {
191
155
  }
192
- var BackupTransaction = class {
193
- data;
194
- transactionPath;
195
- isDirty = false;
196
- saveTimer;
197
- constructor(backupDir, transactionId) {
198
- this.transactionPath = path6.join(backupDir, "transaction.json");
199
- try {
200
- const content = fs2.readFileSync(this.transactionPath, "utf8");
201
- this.data = JSON.parse(content);
202
- logger.info(`\u{1F4C2} Resuming backup transaction ${this.data.id}`);
203
- } catch (error) {
204
- const err = error;
205
- if (err.code === "ENOENT") {
206
- this.data = this.createNewTransaction(backupDir, transactionId);
207
- } else {
208
- throw error;
156
+ var defaultSpinnerFactory, CommandSpinnerManager, spinnerManager;
157
+ var init_commandSpinner = __esm({
158
+ "src/lib/commandSpinner.ts"() {
159
+ defaultSpinnerFactory = (text) => ora({
160
+ text,
161
+ discardStdin: false
162
+ });
163
+ CommandSpinnerManager = class {
164
+ spinner = null;
165
+ spinnerText = "";
166
+ factory = defaultSpinnerFactory;
167
+ setFactory(factory) {
168
+ this.factory = factory;
209
169
  }
210
- }
211
- }
212
- createNewTransaction(backupDir, transactionId) {
213
- const newData = {
214
- id: transactionId || this.generateTransactionId(),
215
- status: "in-progress",
216
- startTime: (/* @__PURE__ */ new Date()).toISOString(),
217
- backupDir,
218
- resources: {
219
- assistants: { total: 0, completed: [], failed: [] },
220
- datasources: { total: 0, completed: [], failed: [] },
221
- workflows: { total: 0, completed: [], failed: [] },
222
- integrations: { total: 0, completed: [], failed: [] }
170
+ resetFactory() {
171
+ this.factory = defaultSpinnerFactory;
172
+ this.reset();
173
+ }
174
+ reset() {
175
+ if (this.spinner?.isSpinning) {
176
+ this.spinner.stop();
177
+ }
178
+ this.spinner = null;
179
+ this.spinnerText = "";
180
+ }
181
+ isEnabled() {
182
+ return Boolean(process.stderr?.isTTY);
183
+ }
184
+ start(text) {
185
+ this.spinnerText = text;
186
+ if (!this.isEnabled()) {
187
+ return;
188
+ }
189
+ if (!this.spinner) {
190
+ this.spinner = this.factory(text);
191
+ this.spinner.start();
192
+ return;
193
+ }
194
+ this.spinner.text = text;
195
+ if (!this.spinner.isSpinning) {
196
+ this.spinner.start();
197
+ }
198
+ }
199
+ stop() {
200
+ if (this.spinner?.isSpinning) {
201
+ this.spinner.stop();
202
+ }
203
+ }
204
+ succeed(text) {
205
+ if (!this.spinner) {
206
+ return;
207
+ }
208
+ this.spinner.succeed(text ?? this.spinnerText);
209
+ this.spinner = null;
210
+ this.spinnerText = "";
211
+ }
212
+ fail(text) {
213
+ if (!this.spinner) {
214
+ return;
215
+ }
216
+ this.spinner.fail(text ?? this.spinnerText);
217
+ this.spinner = null;
218
+ this.spinnerText = "";
219
+ }
220
+ pause(operation) {
221
+ const shouldResume = Boolean(this.spinner?.isSpinning);
222
+ const currentText = this.spinner?.text ?? this.spinnerText;
223
+ if (shouldResume) {
224
+ this.spinner?.stop();
225
+ }
226
+ try {
227
+ return operation();
228
+ } finally {
229
+ if (shouldResume && this.spinner) {
230
+ this.spinner.text = currentText;
231
+ this.spinner.start();
232
+ }
233
+ }
234
+ }
235
+ async pauseWhile(operation) {
236
+ const shouldResume = Boolean(this.spinner?.isSpinning);
237
+ const currentText = this.spinner?.text ?? this.spinnerText;
238
+ if (shouldResume) {
239
+ this.spinner?.stop();
240
+ }
241
+ try {
242
+ return await operation();
243
+ } finally {
244
+ if (shouldResume && this.spinner) {
245
+ this.spinner.text = currentText;
246
+ this.spinner.start();
247
+ }
248
+ }
223
249
  }
224
250
  };
225
- ensureDirectoryExists(this.transactionPath);
226
- this.writeTransactionExclusively(newData);
227
- return newData;
251
+ spinnerManager = new CommandSpinnerManager();
228
252
  }
229
- writeTransactionExclusively(data) {
230
- try {
231
- const fd = fs2.openSync(this.transactionPath, "wx");
232
- fs2.writeSync(fd, JSON.stringify(data, null, 2));
233
- fs2.closeSync(fd);
234
- } catch (writeError) {
235
- const writeErr = writeError;
236
- if (writeErr.code === "EEXIST") {
237
- this.readWithRetry();
238
- } else {
239
- throw writeError;
253
+ });
254
+
255
+ // src/lib/logger.ts
256
+ var Logger, logger;
257
+ var init_logger = __esm({
258
+ "src/lib/logger.ts"() {
259
+ init_commandSpinner();
260
+ Logger = class _Logger {
261
+ static instance;
262
+ level = 1 /* INFO */;
263
+ constructor() {
240
264
  }
241
- }
242
- }
243
- readWithRetry() {
244
- let retries = 3;
245
- let lastReadError;
246
- while (retries > 0) {
247
- try {
248
- const delayMs = 100 * (4 - retries);
249
- if (delayMs > 0) {
250
- busyWaitDelay(delayMs);
265
+ static getInstance() {
266
+ if (!_Logger.instance) {
267
+ _Logger.instance = new _Logger();
251
268
  }
252
- const content = fs2.readFileSync(this.transactionPath, "utf8");
253
- this.data = JSON.parse(content);
254
- logger.info(`\u{1F4C2} Resuming backup transaction ${this.data.id} (created by concurrent process)`);
255
- break;
256
- } catch (readError) {
257
- lastReadError = readError instanceof Error ? readError : new Error(String(readError));
258
- retries--;
259
- if (retries === 0) {
260
- throw new Error(
261
- `Failed to read transaction file after retries: ${lastReadError.message}. File may be corrupted. Delete ${this.transactionPath} and retry.`
262
- );
269
+ return _Logger.instance;
270
+ }
271
+ setLevel(level) {
272
+ this.level = level;
273
+ }
274
+ debug(message, ...args) {
275
+ if (this.level <= 0 /* DEBUG */) {
276
+ pauseSpinner(() => {
277
+ console.debug(message, ...args);
278
+ });
263
279
  }
264
280
  }
265
- }
266
- }
267
- generateTransactionId() {
268
- return crypto.randomBytes(8).toString("hex");
269
- }
270
- /**
271
- * Save transaction state to disk (checkpoint) with timeout protection
272
- */
273
- async save() {
274
- ensureDirectoryExists(this.transactionPath);
275
- const savePromise = fs2.promises.writeFile(this.transactionPath, JSON.stringify(this.data, null, 2), "utf8");
276
- let timeoutId;
277
- const timeoutPromise = new Promise((_, reject) => {
278
- timeoutId = setTimeout(
279
- () => reject(new Error(`Transaction save timeout after ${BACKUP.TRANSACTION_SAVE_TIMEOUT_MS}ms`)),
280
- BACKUP.TRANSACTION_SAVE_TIMEOUT_MS
281
- );
282
- timeoutId.unref();
283
- });
284
- try {
285
- await Promise.race([savePromise, timeoutPromise]);
286
- this.isDirty = false;
287
- } catch (error) {
288
- const err = error instanceof Error ? error : new Error(String(error));
289
- throw new Error(`Failed to save transaction file: ${err.message}`);
290
- } finally {
291
- if (timeoutId !== void 0) {
292
- clearTimeout(timeoutId);
281
+ info(message, ...args) {
282
+ if (this.level <= 1 /* INFO */) {
283
+ pauseSpinner(() => {
284
+ console.log(message, ...args);
285
+ });
286
+ }
293
287
  }
294
- }
295
- }
296
- /**
297
- * Internal synchronous save for batching
298
- */
299
- saveSyncInternal() {
300
- ensureDirectoryExists(this.transactionPath);
301
- fs2.writeFileSync(this.transactionPath, JSON.stringify(this.data, null, 2), "utf8");
302
- this.isDirty = false;
303
- }
304
- /**
305
- * Schedule batched save (debounced to avoid excessive disk writes)
306
- */
307
- scheduleSave() {
308
- if (this.saveTimer) {
309
- return;
310
- }
311
- this.saveTimer = setTimeout(() => {
312
- if (this.isDirty) {
313
- this.saveSyncInternal();
288
+ warn(message, ...args) {
289
+ if (this.level <= 2 /* WARN */) {
290
+ pauseSpinner(() => {
291
+ console.warn(message, ...args);
292
+ });
293
+ }
314
294
  }
315
- this.saveTimer = void 0;
316
- }, 1e3);
317
- this.saveTimer.unref();
295
+ error(message, ...args) {
296
+ if (this.level <= 3 /* ERROR */) {
297
+ pauseSpinner(() => {
298
+ console.error(message, ...args);
299
+ });
300
+ }
301
+ }
302
+ };
303
+ logger = Logger.getInstance();
318
304
  }
319
- /**
320
- * Internal method to finalize transaction with a specific status
321
- */
322
- end(status) {
323
- this.data.status = status;
324
- this.data.endTime = (/* @__PURE__ */ new Date()).toISOString();
325
- this.flush();
326
- }
327
- /**
328
- * Force immediate save (flush pending changes)
329
- */
330
- flush() {
331
- if (this.saveTimer) {
332
- clearTimeout(this.saveTimer);
333
- this.saveTimer = void 0;
334
- }
335
- if (this.isDirty) {
336
- this.saveSyncInternal();
337
- }
338
- }
339
- /**
340
- * Set total count for a resource type
341
- */
342
- setTotal(resourceType, total) {
343
- this.data.resources[resourceType].total = total;
344
- this.isDirty = true;
345
- this.scheduleSave();
346
- }
347
- /**
348
- * Mark resource as completed
349
- */
350
- markCompleted(resourceType, resourceId) {
351
- if (!this.data.resources[resourceType].completed.includes(resourceId)) {
352
- this.data.resources[resourceType].completed.push(resourceId);
353
- this.isDirty = true;
354
- this.scheduleSave();
355
- }
356
- }
357
- /**
358
- * Mark resource as failed
359
- */
360
- markFailed(resourceType, resourceId, error) {
361
- const failedEntry = { id: resourceId, error };
362
- const failedList = this.data.resources[resourceType].failed;
363
- if (!failedList.some((f) => f.id === resourceId)) {
364
- failedList.push(failedEntry);
365
- this.isDirty = true;
366
- this.scheduleSave();
367
- }
368
- }
369
- /**
370
- * Check if resource was already backed up
371
- */
372
- isCompleted(resourceType, resourceId) {
373
- return this.data.resources[resourceType].completed.includes(resourceId);
374
- }
375
- /**
376
- * Mark transaction as completed
377
- */
378
- complete() {
379
- this.end("completed");
380
- }
381
- /**
382
- * Mark transaction as failed
383
- */
384
- fail() {
385
- this.end("failed");
386
- }
387
- /**
388
- * Get transaction data (returns deep copy to prevent external modifications)
389
- */
390
- getData() {
391
- return structuredClone(this.data);
392
- }
393
- /**
394
- * Get summary of backup progress
395
- */
396
- getSummary() {
397
- const { resources } = this.data;
398
- const lines = [];
399
- for (const [type, data] of Object.entries(resources)) {
400
- const completed = data.completed.length;
401
- const failed = data.failed.length;
402
- const total = data.total;
403
- const percent = total > 0 ? Math.round(completed / total * 100) : 0;
404
- lines.push(` ${type}: ${completed}/${total} (${percent}%) ${failed > 0 ? `[${failed} failed]` : ""}`);
405
- }
406
- return lines.join("\n");
407
- }
408
- /**
409
- * Clean up transaction file after successful completion
410
- */
411
- cleanup() {
412
- if (fs2.existsSync(this.transactionPath)) {
413
- fs2.unlinkSync(this.transactionPath);
414
- }
415
- }
416
- };
417
- var DEFAULT_LLM_MODEL = "gpt-4";
418
- function normalizeIntegrationSettings(settings) {
419
- if (!settings || typeof settings !== "object") {
420
- return settings;
305
+ });
306
+ function normalizeIntegrationSettings(settings) {
307
+ if (!settings || typeof settings !== "object") {
308
+ return settings;
421
309
  }
422
310
  if ("$ref" in settings && typeof settings.$ref === "string") {
423
311
  return { $ref: settings.$ref };
@@ -466,7 +354,7 @@ function calculateChecksum(content) {
466
354
  if (content.length === 0) {
467
355
  logger.warn("\u26A0\uFE0F Calculating checksum of empty string");
468
356
  }
469
- return crypto.createHash("sha256").update(content, "utf8").digest("hex");
357
+ return crypto2.createHash("sha256").update(content, "utf8").digest("hex");
470
358
  }
471
359
  function normalizeAssistantConfig(assistant, buildConfig = null) {
472
360
  return {
@@ -483,6 +371,7 @@ function normalizeAssistantConfig(assistant, buildConfig = null) {
483
371
  mcp_servers: normalizeMcpServers(assistant.mcp_servers),
484
372
  assistant_ids: assistant.assistant_ids || [],
485
373
  sub_assistants: assistant.sub_assistants || [],
374
+ skills: assistant.skills || [],
486
375
  conversation_starters: assistant.conversation_starters || [],
487
376
  buildConfig
488
377
  };
@@ -491,13 +380,18 @@ function calculateAssistantConfigChecksum(assistant, buildConfig = null) {
491
380
  const normalized = normalizeAssistantConfig(assistant, buildConfig);
492
381
  return calculateChecksum(JSON.stringify(normalized));
493
382
  }
383
+ var DEFAULT_LLM_MODEL;
384
+ var init_checksumUtils = __esm({
385
+ "src/lib/checksumUtils.ts"() {
386
+ init_logger();
387
+ DEFAULT_LLM_MODEL = "gpt-4";
388
+ }
389
+ });
494
390
 
495
391
  // src/lib/codemieConfigChecksums.ts
496
392
  function createExcludeSet(keys) {
497
393
  return new Set(keys);
498
394
  }
499
- var DATASOURCE_EXCLUDED_FIELDS = createExcludeSet(["force_reindex"]);
500
- var WORKFLOW_EXCLUDED_FIELDS = createExcludeSet(["definition"]);
501
395
  function buildChecksumObject(src, excluded) {
502
396
  const out = {};
503
397
  const keys = Object.keys(src).sort();
@@ -520,6 +414,19 @@ function calculateWorkflowConfigChecksum(workflow) {
520
414
  const filtered = buildChecksumObject(workflow, WORKFLOW_EXCLUDED_FIELDS);
521
415
  return calculateChecksum(JSON.stringify(filtered));
522
416
  }
417
+ function calculateSkillConfigChecksum(skill) {
418
+ const filtered = buildChecksumObject(skill, SKILL_EXCLUDED_FIELDS);
419
+ return calculateChecksum(JSON.stringify(filtered));
420
+ }
421
+ var DATASOURCE_EXCLUDED_FIELDS, WORKFLOW_EXCLUDED_FIELDS, SKILL_EXCLUDED_FIELDS;
422
+ var init_codemieConfigChecksums = __esm({
423
+ "src/lib/codemieConfigChecksums.ts"() {
424
+ init_checksumUtils();
425
+ DATASOURCE_EXCLUDED_FIELDS = createExcludeSet(["force_reindex"]);
426
+ WORKFLOW_EXCLUDED_FIELDS = createExcludeSet(["definition"]);
427
+ SKILL_EXCLUDED_FIELDS = createExcludeSet(["file"]);
428
+ }
429
+ });
523
430
 
524
431
  // src/lib/typeGuards.ts
525
432
  function hasName(obj) {
@@ -531,8 +438,34 @@ function hasSettingId(obj) {
531
438
  function isResolvedIntegration(obj) {
532
439
  return typeof obj === "object" && obj !== null && "id" in obj && typeof obj.id === "string";
533
440
  }
534
-
535
- // src/lib/converters.ts
441
+ var init_typeGuards = __esm({
442
+ "src/lib/typeGuards.ts"() {
443
+ }
444
+ });
445
+ function isChunkSummaryType(type) {
446
+ return CHUNK_SUMMARY_ALIASES.has(type);
447
+ }
448
+ function isFileSummaryType(type) {
449
+ return FILE_SUMMARY_ALIASES.has(type);
450
+ }
451
+ function normalizeCodeIndexType(datasource) {
452
+ if (datasource.code?.indexType) {
453
+ return datasource.code.indexType;
454
+ }
455
+ if (datasource.type === DataSourceType.CODE || datasource.type === DataSourceType.SUMMARY) {
456
+ return datasource.type;
457
+ }
458
+ if (datasource.type === DataSourceType.CHUNK_SUMMARY) {
459
+ return DataSourceType.CHUNK_SUMMARY;
460
+ }
461
+ if (isFileSummaryType(datasource.type)) {
462
+ return DataSourceType.SUMMARY;
463
+ }
464
+ if (isChunkSummaryType(datasource.type)) {
465
+ return DataSourceType.CHUNK_SUMMARY;
466
+ }
467
+ return void 0;
468
+ }
536
469
  function validateMcpCommandArgs(s, hasTopLevelCommand) {
537
470
  if (hasTopLevelCommand) {
538
471
  return true;
@@ -587,14 +520,15 @@ function convertMcpServers(servers) {
587
520
  return validServers;
588
521
  }
589
522
  function assistantResponseToResource(assistant) {
523
+ const safeName = assistant.name || assistant.id || "assistant";
590
524
  const slug = assistant.slug || "";
591
- const promptFileName = slug || assistant.name.toLowerCase().replaceAll(/\s+/g, "-");
525
+ const promptFileName = slug || String(safeName).toLowerCase().replaceAll(/\s+/g, "-");
592
526
  const mcpServers = convertMcpServers(assistant.mcp_servers);
593
527
  const nestedAssistants = assistant.nested_assistants;
594
528
  const subAssistants = nestedAssistants?.map((nested) => nested.name).filter((name) => Boolean(name));
595
529
  const categories = assistant.categories?.map((category) => category.id);
596
530
  return {
597
- name: assistant.name,
531
+ name: assistant.name || safeName,
598
532
  description: assistant.description || "",
599
533
  prompt: `system_prompts/${promptFileName}.prompt.md`,
600
534
  model: assistant.llm_model_type || DEFAULT_LLM_MODEL,
@@ -615,6 +549,7 @@ function assistantResponseToResource(assistant) {
615
549
  }
616
550
  function convertCodeDatasource(datasource, base) {
617
551
  const desc = datasource.description || "";
552
+ const indexType = normalizeCodeIndexType(datasource);
618
553
  if (datasource.code) {
619
554
  return {
620
555
  ...base,
@@ -622,7 +557,7 @@ function convertCodeDatasource(datasource, base) {
622
557
  description: desc,
623
558
  link: datasource.code.link,
624
559
  branch: datasource.code.branch,
625
- index_type: datasource.code.indexType,
560
+ ...indexType && { index_type: indexType },
626
561
  summarization_model: datasource.code.summarizationModel,
627
562
  files_filter: datasource.code.filesFilter
628
563
  };
@@ -631,12 +566,17 @@ function convertCodeDatasource(datasource, base) {
631
566
  ...base,
632
567
  type: "code",
633
568
  description: desc,
569
+ ...indexType && { index_type: indexType },
634
570
  link: void 0
635
571
  };
636
572
  }
637
573
  function datasourceResponseToResource(datasource, integrationAlias) {
574
+ const isBedrockDatasource = datasource.type === DataSourceType.BEDROCK;
575
+ const isChunkSummaryDatasource = isChunkSummaryType(datasource.type);
576
+ const isFileSummaryDatasource = isFileSummaryType(datasource.type);
577
+ const isCodeFamilyDatasource = datasource.type === DataSourceType.CODE || datasource.type === DataSourceType.SUMMARY || datasource.type === DataSourceType.CHUNK_SUMMARY || isChunkSummaryDatasource || isFileSummaryDatasource;
638
578
  const settingId = integrationAlias ? `$ref:imported.integrations.${integrationAlias}.id` : datasource.setting_id ?? "";
639
- const allowedWithoutSettingId = /* @__PURE__ */ new Set(["knowledge_base_file", "llm_routing_google"]);
579
+ const allowedWithoutSettingId = /* @__PURE__ */ new Set([DataSourceType.FILE, DataSourceType.GOOGLE, DataSourceType.BEDROCK]);
640
580
  if (!settingId && !allowedWithoutSettingId.has(datasource.type)) {
641
581
  logger.warn(`\u26A0\uFE0F Datasource "${datasource.name}" is missing setting_id (integration reference)`);
642
582
  }
@@ -647,7 +587,7 @@ function datasourceResponseToResource(datasource, integrationAlias) {
647
587
  setting_id: settingId || void 0,
648
588
  shared_with_project: datasource.shared_with_project
649
589
  };
650
- if (datasource.type === DataSourceType.CODE) {
590
+ if (isCodeFamilyDatasource) {
651
591
  return convertCodeDatasource(datasource, base);
652
592
  }
653
593
  if (datasource.type === DataSourceType.CONFLUENCE && datasource.confluence) {
@@ -681,6 +621,13 @@ function datasourceResponseToResource(datasource, integrationAlias) {
681
621
  description: datasource.description || ""
682
622
  };
683
623
  }
624
+ if (isBedrockDatasource) {
625
+ return {
626
+ ...base,
627
+ type: datasource.type,
628
+ description: datasource.description || ""
629
+ };
630
+ }
684
631
  logger.warn(` \u26A0\uFE0F Unknown datasource type '${datasource.type}' - saving with basic fields only`);
685
632
  return {
686
633
  ...base,
@@ -688,15 +635,26 @@ function datasourceResponseToResource(datasource, integrationAlias) {
688
635
  };
689
636
  }
690
637
  function workflowResponseToResource(workflow) {
638
+ const safeName = workflow.name || workflow.id || "workflow";
691
639
  return {
692
- name: workflow.name,
640
+ name: workflow.name || safeName,
693
641
  description: workflow.description || "",
694
- definition: `workflows/${workflow.name.toLowerCase().replaceAll(/\s+/g, "-")}.yaml`,
642
+ definition: `workflows/${String(safeName).toLowerCase().replaceAll(/\s+/g, "-")}.yaml`,
695
643
  ...workflow.mode && { mode: workflow.mode },
696
644
  ...workflow.shared !== void 0 && { shared: workflow.shared },
697
645
  ...workflow.icon_url && { icon_url: workflow.icon_url }
698
646
  };
699
647
  }
648
+ function skillResponseToResource(skill) {
649
+ const fileName = skill.name.toLowerCase().replaceAll(/\s+/g, "-");
650
+ return {
651
+ name: skill.name,
652
+ description: skill.description || "",
653
+ file: `skills/${fileName}.skill.md`,
654
+ ...skill.categories && skill.categories.length > 0 && { categories: skill.categories },
655
+ ...skill.visibility && skill.visibility !== "project" && { visibility: skill.visibility }
656
+ };
657
+ }
700
658
  function isValidCodeParams(params) {
701
659
  return typeof params === "object" && params !== null && "link" in params && typeof params.link === "string" && params.link.length > 0;
702
660
  }
@@ -761,6 +719,7 @@ function assistantResourceToCreateParams(assistant, projectName, promptContent)
761
719
  model,
762
720
  sub_assistants: _subAssistants,
763
721
  datasource_names: _datasourceNames,
722
+ skills: _skills,
764
723
  toolkits,
765
724
  mcp_servers: mcpServers,
766
725
  ...sdkFields
@@ -781,8 +740,28 @@ function assistantResourceToCreateParams(assistant, projectName, promptContent)
781
740
  prompt_variables: assistant.prompt_variables || []
782
741
  };
783
742
  }
743
+ var CHUNK_SUMMARY_ALIASES, FILE_SUMMARY_ALIASES;
744
+ var init_converters = __esm({
745
+ "src/lib/converters.ts"() {
746
+ init_checksumUtils();
747
+ init_logger();
748
+ init_typeGuards();
749
+ CHUNK_SUMMARY_ALIASES = /* @__PURE__ */ new Set(["chunk_summary", "chunk-summary"]);
750
+ FILE_SUMMARY_ALIASES = /* @__PURE__ */ new Set(["file_summary", "file-summary", "file-summay"]);
751
+ }
752
+ });
784
753
 
785
754
  // src/lib/backupTransformers.ts
755
+ var backupTransformers_exports = {};
756
+ __export(backupTransformers_exports, {
757
+ prepareAssistantForYaml: () => prepareAssistantForYaml,
758
+ prepareDatasourceForYaml: () => prepareDatasourceForYaml,
759
+ prepareSkillForYaml: () => prepareSkillForYaml,
760
+ prepareWorkflowForYaml: () => prepareWorkflowForYaml,
761
+ transformMcpServer: () => transformMcpServer,
762
+ transformTool: () => transformTool,
763
+ transformToolkits: () => transformToolkits
764
+ });
786
765
  function transformIntegrationSettings(settings, integrationIdToAlias, integrationSpecPaths, contextLabel = "integration") {
787
766
  const integrationId = settings.id;
788
767
  const alias = settings.alias || integrationId && integrationIdToAlias.get(integrationId);
@@ -849,94 +828,439 @@ function transformMcpServer(mcp, integrationIdToAlias) {
849
828
  );
850
829
  }
851
830
  }
852
- return result;
853
- }
854
- function transformToolkits(toolkits, integrationIdToAlias, integrationSpecPaths) {
855
- return toolkits?.map(({ toolkit, tools, label, settings_config, is_external, settings }) => ({
856
- toolkit,
857
- tools: tools.map((tool) => transformTool(tool, integrationIdToAlias, integrationSpecPaths)),
858
- label,
859
- settings_config,
860
- is_external,
861
- ...settings ? {
862
- settings: transformIntegrationSettings(settings, integrationIdToAlias, integrationSpecPaths, "toolkit")
863
- } : {}
864
- }));
865
- }
866
- function prepareAssistantForYaml(assistant, state, integrationIdToAlias, integrationSpecPaths) {
867
- const transformedToolkits = transformToolkits(assistant.toolkits, integrationIdToAlias, integrationSpecPaths);
868
- const transformedMcpServers = assistant.mcp_servers?.map((mcp) => transformMcpServer(mcp, integrationIdToAlias));
869
- const finalAssistant = {
870
- ...assistantResponseToResource(assistant),
871
- ...transformedToolkits && { toolkits: transformedToolkits },
872
- ...transformedMcpServers && { mcp_servers: transformedMcpServers }
873
- };
874
- state.resources.assistants[assistant.name] = {
875
- id: assistant.id,
876
- lastDeployed: (/* @__PURE__ */ new Date()).toISOString(),
877
- promptChecksum: calculateChecksum(assistant.system_prompt || ""),
878
- configChecksum: calculateAssistantConfigChecksum(finalAssistant)
879
- };
880
- return finalAssistant;
881
- }
882
- function prepareDatasourceForYaml(datasource, state, integrationIdToAlias) {
883
- const settingId = hasSettingId(datasource) ? String(datasource.setting_id || "") : "";
884
- const integrationAlias = integrationIdToAlias.get(settingId);
885
- const finalDatasource = datasourceResponseToResource(datasource, integrationAlias);
886
- state.resources.datasources[datasource.name] = {
887
- id: datasource.id,
888
- lastDeployed: (/* @__PURE__ */ new Date()).toISOString(),
889
- configChecksum: calculateDatasourceConfigChecksum(finalDatasource)
890
- };
891
- return finalDatasource;
892
- }
893
- function prepareWorkflowForYaml(workflow, state, assistants, backupDir) {
894
- const resource = workflowResponseToResource(workflow);
895
- const yamlConfig = workflow.yaml_config;
896
- let finalYamlContent = yamlConfig || "";
897
- if (yamlConfig) {
898
- try {
899
- const workflowYaml = yaml3.parse(yamlConfig);
900
- const transformedAssistants = workflowYaml.assistants?.map((assistant) => {
901
- if (assistant.assistant_id && typeof assistant.assistant_id === "string") {
902
- const assistantId = assistant.assistant_id;
903
- const matchedAssistant = assistants.find(({ id }) => id === assistantId);
904
- if (matchedAssistant && hasName(matchedAssistant)) {
905
- const { assistant_id: _assistantId, ...rest } = assistant;
906
- return { ...rest, assistant_name: matchedAssistant.name };
907
- }
908
- }
909
- return assistant;
910
- });
911
- if (transformedAssistants) {
912
- const transformedYaml = { ...workflowYaml, assistants: transformedAssistants };
913
- const fileName = `${sanitizeFileName(workflow.name)}.yaml`;
914
- const filePath = path6.join(backupDir, "workflows", fileName);
915
- ensureDirectoryExists(filePath);
916
- finalYamlContent = yaml3.stringify(transformedYaml);
917
- fs2.writeFileSync(filePath, finalYamlContent, "utf8");
918
- }
919
- } catch (error) {
920
- logger.warn(
921
- ` \u26A0\uFE0F Failed to transform workflow YAML for ${workflow.name}: ${error instanceof Error ? error.message : String(error)}`
922
- );
831
+ return result;
832
+ }
833
+ function transformToolkits(toolkits, integrationIdToAlias, integrationSpecPaths) {
834
+ return toolkits?.map(({ toolkit, tools, label, settings_config, is_external, settings }) => ({
835
+ toolkit,
836
+ tools: tools.map((tool) => transformTool(tool, integrationIdToAlias, integrationSpecPaths)),
837
+ label,
838
+ settings_config,
839
+ is_external,
840
+ ...settings ? {
841
+ settings: transformIntegrationSettings(settings, integrationIdToAlias, integrationSpecPaths, "toolkit")
842
+ } : {}
843
+ }));
844
+ }
845
+ function prepareAssistantForYaml(assistant, state, integrationIdToAlias, integrationSpecPaths, skillIdToName = /* @__PURE__ */ new Map()) {
846
+ const transformedToolkits = transformToolkits(assistant.toolkits, integrationIdToAlias, integrationSpecPaths);
847
+ const transformedMcpServers = assistant.mcp_servers?.map((mcp) => transformMcpServer(mcp, integrationIdToAlias));
848
+ const assistantData = assistant;
849
+ const resolvedSkillNames = assistantData.skill_ids && assistantData.skill_ids.length > 0 ? assistantData.skill_ids.map((id) => skillIdToName.get(id)).filter((name) => Boolean(name)) : void 0;
850
+ const finalAssistant = {
851
+ ...assistantResponseToResource(assistant),
852
+ ...transformedToolkits && { toolkits: transformedToolkits },
853
+ ...transformedMcpServers && { mcp_servers: transformedMcpServers },
854
+ ...resolvedSkillNames && resolvedSkillNames.length > 0 && { skills: resolvedSkillNames }
855
+ };
856
+ state.resources.assistants[assistant.name] = {
857
+ id: assistant.id,
858
+ lastDeployed: (/* @__PURE__ */ new Date()).toISOString(),
859
+ promptChecksum: calculateChecksum(assistant.system_prompt || ""),
860
+ configChecksum: calculateAssistantConfigChecksum(finalAssistant)
861
+ };
862
+ return finalAssistant;
863
+ }
864
+ function prepareDatasourceForYaml(datasource, state, integrationIdToAlias) {
865
+ const settingId = hasSettingId(datasource) ? String(datasource.setting_id || "") : "";
866
+ const integrationAlias = integrationIdToAlias.get(settingId);
867
+ const finalDatasource = datasourceResponseToResource(datasource, integrationAlias);
868
+ state.resources.datasources[datasource.name] = {
869
+ id: datasource.id,
870
+ lastDeployed: (/* @__PURE__ */ new Date()).toISOString(),
871
+ configChecksum: calculateDatasourceConfigChecksum(finalDatasource)
872
+ };
873
+ return finalDatasource;
874
+ }
875
+ function prepareWorkflowForYaml(workflow, state, assistants, backupDir) {
876
+ const resource = workflowResponseToResource(workflow);
877
+ const yamlConfig = workflow.yaml_config;
878
+ let finalYamlContent = yamlConfig || "";
879
+ if (yamlConfig) {
880
+ try {
881
+ const workflowYaml = yaml5.parse(yamlConfig);
882
+ const transformedAssistants = workflowYaml.assistants?.map((assistant) => {
883
+ if (assistant.assistant_id && typeof assistant.assistant_id === "string") {
884
+ const assistantId = assistant.assistant_id;
885
+ const matchedAssistant = assistants.find(({ id }) => id === assistantId);
886
+ if (matchedAssistant && hasName(matchedAssistant)) {
887
+ const { assistant_id: _assistantId, ...rest } = assistant;
888
+ return { ...rest, assistant_name: matchedAssistant.name };
889
+ }
890
+ }
891
+ return assistant;
892
+ });
893
+ if (transformedAssistants) {
894
+ const transformedYaml = { ...workflowYaml, assistants: transformedAssistants };
895
+ const fileName = `${sanitizeFileName(workflow.name)}.yaml`;
896
+ const filePath = path6.join(backupDir, "workflows", fileName);
897
+ ensureDirectoryExists(filePath);
898
+ finalYamlContent = yaml5.stringify(transformedYaml);
899
+ fs6.writeFileSync(filePath, finalYamlContent, "utf8");
900
+ }
901
+ } catch (error) {
902
+ logger.warn(
903
+ ` \u26A0\uFE0F Failed to transform workflow YAML for ${workflow.name}: ${error instanceof Error ? error.message : String(error)}`
904
+ );
905
+ }
906
+ }
907
+ state.resources.workflows[workflow.name] = {
908
+ id: workflow.id,
909
+ lastDeployed: (/* @__PURE__ */ new Date()).toISOString(),
910
+ workflowYamlChecksum: calculateChecksum(finalYamlContent),
911
+ configChecksum: calculateWorkflowConfigChecksum(resource)
912
+ };
913
+ return resource;
914
+ }
915
+ function prepareSkillForYaml(skill, state) {
916
+ const resource = skillResponseToResource(skill);
917
+ if (!state.resources.skills) {
918
+ state.resources.skills = {};
919
+ }
920
+ state.resources.skills[skill.name] = {
921
+ id: skill.id,
922
+ lastDeployed: (/* @__PURE__ */ new Date()).toISOString(),
923
+ contentChecksum: calculateChecksum(skill.content || ""),
924
+ configChecksum: calculateSkillConfigChecksum(resource)
925
+ };
926
+ return resource;
927
+ }
928
+ var init_backupTransformers = __esm({
929
+ "src/lib/backupTransformers.ts"() {
930
+ init_checksumUtils();
931
+ init_codemieConfigChecksums();
932
+ init_converters();
933
+ init_fileUtils();
934
+ init_logger();
935
+ init_typeGuards();
936
+ }
937
+ });
938
+
939
+ // package.json
940
+ var package_default = {
941
+ version: "0.1.445"};
942
+ var appConfigSchema = z.object({
943
+ rootDir: z.string(),
944
+ codemieConfig: z.string(),
945
+ codemieState: z.string(),
946
+ backupsDirectory: z.string()
947
+ });
948
+ var externalConfigSchema = appConfigSchema.partial();
949
+
950
+ // src/appConfig/defaultConfig.ts
951
+ var defaultConfig = {
952
+ rootDir: "./",
953
+ codemieConfig: "codemie.yaml",
954
+ codemieState: ".codemie/state.json",
955
+ backupsDirectory: "backups"
956
+ };
957
+
958
+ // src/appConfig/configLoader.ts
959
+ function loadAppConfig(userConfigPath) {
960
+ if (!userConfigPath) {
961
+ return defaultConfig;
962
+ }
963
+ const userConfig = loadUserConfig(userConfigPath);
964
+ const sanitizedUserConfig = removeEmptyFields(userConfig);
965
+ const merged = { ...defaultConfig, ...sanitizedUserConfig };
966
+ return appConfigSchema.parse(merged);
967
+ }
968
+ function loadUserConfig(configPath) {
969
+ const resolvedPath = path6.resolve(process.cwd(), configPath);
970
+ if (!fs6.existsSync(resolvedPath)) {
971
+ throw new Error(`Config file not found: ${resolvedPath}`);
972
+ }
973
+ let raw;
974
+ try {
975
+ raw = fs6.readFileSync(resolvedPath, "utf8");
976
+ } catch (error) {
977
+ throw new Error(`Failed to read config file: ${resolvedPath}`, { cause: error });
978
+ }
979
+ let json;
980
+ try {
981
+ json = JSON.parse(raw);
982
+ } catch (error) {
983
+ throw new Error(`Malformed JSON in config file: ${resolvedPath}`, { cause: error });
984
+ }
985
+ try {
986
+ return externalConfigSchema.parse(json);
987
+ } catch (error) {
988
+ throw new Error(`Invalid config structure: ${error instanceof Error ? error.message : String(error)}`);
989
+ }
990
+ }
991
+ function removeEmptyFields(userConfig) {
992
+ return Object.fromEntries(
993
+ Object.entries(userConfig).filter(([_, value]) => {
994
+ if (value === null || value === void 0) {
995
+ return false;
996
+ }
997
+ return value.trim() !== "";
998
+ })
999
+ );
1000
+ }
1001
+
1002
+ // src/lib/constants.ts
1003
+ var PAGINATION = {
1004
+ DEFAULT_PAGE_SIZE: 100};
1005
+ var TIMEOUTS_MS = {
1006
+ ASSISTANT_FETCH: 3e4,
1007
+ DATASOURCE_FETCH: 3e4,
1008
+ WORKFLOW_FETCH: 3e4,
1009
+ INTEGRATION_FETCH: 3e4,
1010
+ SKILL_FETCH: 3e4
1011
+ };
1012
+ var RATE_LIMITING = {
1013
+ MAX_CONCURRENT_REQUESTS: 5,
1014
+ RETRY_ATTEMPTS: 3,
1015
+ RETRY_DELAY_MS: 1e3
1016
+ };
1017
+ var BACKUP = {
1018
+ TEMP_DIR_PREFIX: ".temp-",
1019
+ TRANSACTION_SAVE_TIMEOUT_MS: 5e3
1020
+ };
1021
+
1022
+ // src/lib/backupTransaction.ts
1023
+ init_fileUtils();
1024
+ init_logger();
1025
+ function busyWaitDelay(ms) {
1026
+ }
1027
+ var BackupTransaction = class {
1028
+ data;
1029
+ transactionPath;
1030
+ isDirty = false;
1031
+ saveTimer;
1032
+ constructor(backupDir, transactionId) {
1033
+ this.transactionPath = path6.join(backupDir, "transaction.json");
1034
+ try {
1035
+ const content = fs6.readFileSync(this.transactionPath, "utf8");
1036
+ this.data = JSON.parse(content);
1037
+ logger.info(`\u{1F4C2} Resuming backup transaction ${this.data.id}`);
1038
+ } catch (error) {
1039
+ const err = error;
1040
+ if (err.code === "ENOENT") {
1041
+ this.data = this.createNewTransaction(backupDir, transactionId);
1042
+ } else {
1043
+ throw error;
1044
+ }
1045
+ }
1046
+ }
1047
+ createNewTransaction(backupDir, transactionId) {
1048
+ const newData = {
1049
+ id: transactionId || this.generateTransactionId(),
1050
+ status: "in-progress",
1051
+ startTime: (/* @__PURE__ */ new Date()).toISOString(),
1052
+ backupDir,
1053
+ resources: {
1054
+ assistants: { total: 0, completed: [], failed: [] },
1055
+ datasources: { total: 0, completed: [], failed: [] },
1056
+ workflows: { total: 0, completed: [], failed: [] },
1057
+ integrations: { total: 0, completed: [], failed: [] },
1058
+ skills: { total: 0, completed: [], failed: [] }
1059
+ }
1060
+ };
1061
+ ensureDirectoryExists(this.transactionPath);
1062
+ this.writeTransactionExclusively(newData);
1063
+ return newData;
1064
+ }
1065
+ writeTransactionExclusively(data) {
1066
+ try {
1067
+ const fd = fs6.openSync(this.transactionPath, "wx");
1068
+ fs6.writeSync(fd, JSON.stringify(data, null, 2));
1069
+ fs6.closeSync(fd);
1070
+ } catch (writeError) {
1071
+ const writeErr = writeError;
1072
+ if (writeErr.code === "EEXIST") {
1073
+ this.readWithRetry();
1074
+ } else {
1075
+ throw writeError;
1076
+ }
1077
+ }
1078
+ }
1079
+ readWithRetry() {
1080
+ let retries = 3;
1081
+ let lastReadError;
1082
+ while (retries > 0) {
1083
+ try {
1084
+ const delayMs = 100 * (4 - retries);
1085
+ if (delayMs > 0) {
1086
+ busyWaitDelay(delayMs);
1087
+ }
1088
+ const content = fs6.readFileSync(this.transactionPath, "utf8");
1089
+ this.data = JSON.parse(content);
1090
+ logger.info(`\u{1F4C2} Resuming backup transaction ${this.data.id} (created by concurrent process)`);
1091
+ break;
1092
+ } catch (readError) {
1093
+ lastReadError = readError instanceof Error ? readError : new Error(String(readError));
1094
+ retries--;
1095
+ if (retries === 0) {
1096
+ throw new Error(
1097
+ `Failed to read transaction file after retries: ${lastReadError.message}. File may be corrupted. Delete ${this.transactionPath} and retry.`
1098
+ );
1099
+ }
1100
+ }
1101
+ }
1102
+ }
1103
+ generateTransactionId() {
1104
+ return crypto2.randomBytes(8).toString("hex");
1105
+ }
1106
+ /**
1107
+ * Save transaction state to disk (checkpoint) with timeout protection
1108
+ */
1109
+ async save() {
1110
+ ensureDirectoryExists(this.transactionPath);
1111
+ const savePromise = fs6.promises.writeFile(this.transactionPath, JSON.stringify(this.data, null, 2), "utf8");
1112
+ let timeoutId;
1113
+ const timeoutPromise = new Promise((_, reject) => {
1114
+ timeoutId = setTimeout(
1115
+ () => reject(new Error(`Transaction save timeout after ${BACKUP.TRANSACTION_SAVE_TIMEOUT_MS}ms`)),
1116
+ BACKUP.TRANSACTION_SAVE_TIMEOUT_MS
1117
+ );
1118
+ timeoutId.unref();
1119
+ });
1120
+ try {
1121
+ await Promise.race([savePromise, timeoutPromise]);
1122
+ this.isDirty = false;
1123
+ } catch (error) {
1124
+ const err = error instanceof Error ? error : new Error(String(error));
1125
+ throw new Error(`Failed to save transaction file: ${err.message}`);
1126
+ } finally {
1127
+ if (timeoutId !== void 0) {
1128
+ clearTimeout(timeoutId);
1129
+ }
1130
+ }
1131
+ }
1132
+ /**
1133
+ * Internal synchronous save for batching
1134
+ */
1135
+ saveSyncInternal() {
1136
+ ensureDirectoryExists(this.transactionPath);
1137
+ fs6.writeFileSync(this.transactionPath, JSON.stringify(this.data, null, 2), "utf8");
1138
+ this.isDirty = false;
1139
+ }
1140
+ /**
1141
+ * Schedule batched save (debounced to avoid excessive disk writes)
1142
+ */
1143
+ scheduleSave() {
1144
+ if (this.saveTimer) {
1145
+ return;
1146
+ }
1147
+ this.saveTimer = setTimeout(() => {
1148
+ if (this.isDirty) {
1149
+ this.saveSyncInternal();
1150
+ }
1151
+ this.saveTimer = void 0;
1152
+ }, 1e3);
1153
+ this.saveTimer.unref();
1154
+ }
1155
+ /**
1156
+ * Internal method to finalize transaction with a specific status
1157
+ */
1158
+ end(status) {
1159
+ this.data.status = status;
1160
+ this.data.endTime = (/* @__PURE__ */ new Date()).toISOString();
1161
+ this.flush();
1162
+ }
1163
+ /**
1164
+ * Force immediate save (flush pending changes)
1165
+ */
1166
+ flush() {
1167
+ if (this.saveTimer) {
1168
+ clearTimeout(this.saveTimer);
1169
+ this.saveTimer = void 0;
1170
+ }
1171
+ if (this.isDirty) {
1172
+ this.saveSyncInternal();
1173
+ }
1174
+ }
1175
+ /**
1176
+ * Set total count for a resource type
1177
+ */
1178
+ setTotal(resourceType, total) {
1179
+ this.data.resources[resourceType].total = total;
1180
+ this.isDirty = true;
1181
+ this.scheduleSave();
1182
+ }
1183
+ /**
1184
+ * Mark resource as completed
1185
+ */
1186
+ markCompleted(resourceType, resourceId) {
1187
+ if (!this.data.resources[resourceType].completed.includes(resourceId)) {
1188
+ this.data.resources[resourceType].completed.push(resourceId);
1189
+ this.isDirty = true;
1190
+ this.scheduleSave();
1191
+ }
1192
+ }
1193
+ /**
1194
+ * Mark resource as failed
1195
+ */
1196
+ markFailed(resourceType, resourceId, error) {
1197
+ const failedEntry = { id: resourceId, error };
1198
+ const failedList = this.data.resources[resourceType].failed;
1199
+ if (!failedList.some((f) => f.id === resourceId)) {
1200
+ failedList.push(failedEntry);
1201
+ this.isDirty = true;
1202
+ this.scheduleSave();
1203
+ }
1204
+ }
1205
+ /**
1206
+ * Check if resource was already backed up
1207
+ */
1208
+ isCompleted(resourceType, resourceId) {
1209
+ return this.data.resources[resourceType].completed.includes(resourceId);
1210
+ }
1211
+ /**
1212
+ * Mark transaction as completed
1213
+ */
1214
+ complete() {
1215
+ this.end("completed");
1216
+ }
1217
+ /**
1218
+ * Mark transaction as failed
1219
+ */
1220
+ fail() {
1221
+ this.end("failed");
1222
+ }
1223
+ /**
1224
+ * Get transaction data (returns deep copy to prevent external modifications)
1225
+ */
1226
+ getData() {
1227
+ return structuredClone(this.data);
1228
+ }
1229
+ /**
1230
+ * Get summary of backup progress
1231
+ */
1232
+ getSummary() {
1233
+ const { resources } = this.data;
1234
+ const lines = [];
1235
+ for (const [type, data] of Object.entries(resources)) {
1236
+ const completed = data.completed.length;
1237
+ const failed = data.failed.length;
1238
+ const total = data.total;
1239
+ const percent = total > 0 ? Math.round(completed / total * 100) : 0;
1240
+ lines.push(` ${type}: ${completed}/${total} (${percent}%) ${failed > 0 ? `[${failed} failed]` : ""}`);
1241
+ }
1242
+ return lines.join("\n");
1243
+ }
1244
+ /**
1245
+ * Clean up transaction file after successful completion
1246
+ */
1247
+ cleanup() {
1248
+ if (fs6.existsSync(this.transactionPath)) {
1249
+ fs6.unlinkSync(this.transactionPath);
923
1250
  }
924
1251
  }
925
- state.resources.workflows[workflow.name] = {
926
- id: workflow.id,
927
- lastDeployed: (/* @__PURE__ */ new Date()).toISOString(),
928
- workflowYamlChecksum: calculateChecksum(finalYamlContent),
929
- configChecksum: calculateWorkflowConfigChecksum(resource)
930
- };
931
- return resource;
932
- }
1252
+ };
933
1253
 
934
1254
  // src/lib/backupYamlGenerator.ts
1255
+ init_backupTransformers();
1256
+ init_logger();
935
1257
  function generateCodemieYaml(backup, projectName, backupDir, integrationSpecPaths = /* @__PURE__ */ new Map()) {
936
1258
  const integrationIdToAlias = /* @__PURE__ */ new Map();
937
1259
  const integrationArray = [];
1260
+ const skillIdToName = new Map(backup.resources.skills.map((skill) => [skill.id, skill.name]));
938
1261
  for (const integration of backup.resources.integrations) {
939
- const alias = integration.alias || `${integration.credential_type.toLowerCase()}_${integration.id.slice(0, 8)}`;
1262
+ const credentialType = integration.credential_type || "integration";
1263
+ const alias = integration.alias || `${String(credentialType).toLowerCase()}_${integration.id.slice(0, 8)}`;
940
1264
  integrationIdToAlias.set(integration.id, alias);
941
1265
  const specPath = integrationSpecPaths.get(integration.id);
942
1266
  let credentialValues = integration.credential_values;
@@ -992,17 +1316,20 @@ function generateCodemieYaml(backup, projectName, backupDir, integrationSpecPath
992
1316
  },
993
1317
  resources: {
994
1318
  assistants: backup.resources.assistants.map(
995
- (assistant) => prepareAssistantForYaml(assistant, backup.state, integrationIdToAlias, integrationSpecPaths)
1319
+ (assistant) => prepareAssistantForYaml(assistant, backup.state, integrationIdToAlias, integrationSpecPaths, skillIdToName)
996
1320
  ),
997
1321
  datasources: backup.resources.datasources.map(
998
1322
  (datasource) => prepareDatasourceForYaml(datasource, backup.state, integrationIdToAlias)
999
1323
  ),
1000
1324
  workflows: backup.resources.workflows.map(
1001
1325
  (workflow) => prepareWorkflowForYaml(workflow, backup.state, backup.resources.assistants, backupDir)
1002
- )
1326
+ ),
1327
+ ...backup.resources.skills.length > 0 && {
1328
+ skills: backup.resources.skills.map((skill) => prepareSkillForYaml(skill, backup.state))
1329
+ }
1003
1330
  }
1004
1331
  };
1005
- return yaml3.stringify(config);
1332
+ return yaml5.stringify(config);
1006
1333
  }
1007
1334
  function saveIntegrationOpenApiSpecs(backupData, backupDir) {
1008
1335
  const specsDir = path6.join(backupDir, "openapi_specs");
@@ -1018,8 +1345,8 @@ function saveIntegrationOpenApiSpecs(backupData, backupDir) {
1018
1345
  if (specEntry && typeof specEntry.value === "string" && specEntry.value) {
1019
1346
  const alias = integration.alias || integration.id;
1020
1347
  const fileName = String(alias).toLowerCase().replaceAll(/[^a-z0-9]+/g, "-");
1021
- if (!fs2.existsSync(specsDir)) {
1022
- fs2.mkdirSync(specsDir, { recursive: true });
1348
+ if (!fs6.existsSync(specsDir)) {
1349
+ fs6.mkdirSync(specsDir, { recursive: true });
1023
1350
  }
1024
1351
  const specContent = specEntry.value;
1025
1352
  let fileExtension = ".yaml";
@@ -1033,7 +1360,7 @@ function saveIntegrationOpenApiSpecs(backupData, backupDir) {
1033
1360
  }
1034
1361
  const specFileName = `${fileName}${fileExtension}`;
1035
1362
  const specPath = path6.join(specsDir, specFileName);
1036
- fs2.writeFileSync(specPath, contentToSave, "utf8");
1363
+ fs6.writeFileSync(specPath, contentToSave, "utf8");
1037
1364
  integrationSpecPaths.set(integration.id, `openapi_specs/${specFileName}`);
1038
1365
  specsCount++;
1039
1366
  }
@@ -1048,13 +1375,13 @@ function saveBackupFiles(backupData, backupDir, projectName) {
1048
1375
  const integrationSpecPaths = saveIntegrationOpenApiSpecs(backupData, backupDir);
1049
1376
  const codemieYamlPath = path6.join(backupDir, "codemie.yaml");
1050
1377
  const codemieYaml = generateCodemieYaml(backupData, projectName, backupDir, integrationSpecPaths);
1051
- fs2.writeFileSync(codemieYamlPath, codemieYaml, "utf8");
1378
+ fs6.writeFileSync(codemieYamlPath, codemieYaml, "utf8");
1052
1379
  logger.info(` \u2713 Saved config file: codemie.yaml`);
1053
1380
  const backupJsonPath = path6.join(backupDir, "backup.json");
1054
- fs2.writeFileSync(backupJsonPath, JSON.stringify(backupData, null, 2), "utf8");
1381
+ fs6.writeFileSync(backupJsonPath, JSON.stringify(backupData, null, 2), "utf8");
1055
1382
  logger.info(` \u2713 Saved full backup: backup.json`);
1056
1383
  const statePath = path6.join(backupDir, "state.json");
1057
- fs2.writeFileSync(statePath, JSON.stringify(backupData.state, null, 2), "utf8");
1384
+ fs6.writeFileSync(statePath, JSON.stringify(backupData.state, null, 2), "utf8");
1058
1385
  logger.info(` \u2713 Saved state file: state.json`);
1059
1386
  }
1060
1387
  async function createClient(config) {
@@ -1068,7 +1395,14 @@ async function createClient(config) {
1068
1395
  password: config.environment.password,
1069
1396
  verify_ssl: true
1070
1397
  });
1071
- await client.initialize();
1398
+ try {
1399
+ await client.initialize();
1400
+ } catch (err) {
1401
+ const reason = err instanceof Error ? err.message : String(err);
1402
+ throw new Error(
1403
+ `Failed to initialize Codemie client (auth: ${config.environment.auth_server_url}, api: ${config.environment.codemie_api_url}): ${reason}`
1404
+ );
1405
+ }
1072
1406
  return client;
1073
1407
  }
1074
1408
  var CodemieConfigLoader = class {
@@ -1082,11 +1416,11 @@ var CodemieConfigLoader = class {
1082
1416
  loadConfig() {
1083
1417
  const { rootDir, codemieConfig } = this.appConfig;
1084
1418
  const configPath = path6.join(rootDir, codemieConfig);
1085
- if (!fs2.existsSync(configPath)) {
1419
+ if (!fs6.existsSync(configPath)) {
1086
1420
  throw new Error(`Configuration file not found: ${configPath}`);
1087
1421
  }
1088
- const content = fs2.readFileSync(configPath, "utf8");
1089
- const config = yaml3.parse(content);
1422
+ const content = fs6.readFileSync(configPath, "utf8");
1423
+ const config = yaml5.parse(content);
1090
1424
  this.resolveImports(config, rootDir);
1091
1425
  this.substituteEnvVars(config);
1092
1426
  this.applyDatasourceDefaults(config);
@@ -1100,10 +1434,21 @@ var CodemieConfigLoader = class {
1100
1434
  loadPrompt(promptPath) {
1101
1435
  const { rootDir } = this.appConfig;
1102
1436
  const fullPath = path6.join(rootDir, promptPath);
1103
- if (!fs2.existsSync(fullPath)) {
1437
+ if (!fs6.existsSync(fullPath)) {
1104
1438
  throw new Error(`Prompt file not found: ${fullPath}`);
1105
1439
  }
1106
- return fs2.readFileSync(fullPath, "utf8");
1440
+ return fs6.readFileSync(fullPath, "utf8");
1441
+ }
1442
+ /**
1443
+ * Load skill content file (.skill.md)
1444
+ */
1445
+ loadSkillContent(contentPath) {
1446
+ const { rootDir } = this.appConfig;
1447
+ const fullPath = path6.join(rootDir, contentPath);
1448
+ if (!fs6.existsSync(fullPath)) {
1449
+ throw new Error(`Skill content file not found: ${fullPath}`);
1450
+ }
1451
+ return fs6.readFileSync(fullPath, "utf8");
1107
1452
  }
1108
1453
  /**
1109
1454
  * Load assistant configuration file
@@ -1111,11 +1456,11 @@ var CodemieConfigLoader = class {
1111
1456
  loadAssistantConfig(configPath) {
1112
1457
  const { rootDir } = this.appConfig;
1113
1458
  const fullPath = path6.join(rootDir, configPath);
1114
- if (!fs2.existsSync(fullPath)) {
1459
+ if (!fs6.existsSync(fullPath)) {
1115
1460
  throw new Error(`Config file not found: ${fullPath}`);
1116
1461
  }
1117
- const content = fs2.readFileSync(fullPath, "utf8");
1118
- return yaml3.parse(content);
1462
+ const content = fs6.readFileSync(fullPath, "utf8");
1463
+ return yaml5.parse(content);
1119
1464
  }
1120
1465
  /**
1121
1466
  * Validate that all referenced files exist
@@ -1126,12 +1471,12 @@ var CodemieConfigLoader = class {
1126
1471
  if (config.resources.assistants) {
1127
1472
  for (const assistant of config.resources.assistants) {
1128
1473
  const promptPath = path6.join(rootDir, assistant.prompt);
1129
- if (!fs2.existsSync(promptPath)) {
1474
+ if (!fs6.existsSync(promptPath)) {
1130
1475
  errors.push(`Prompt file not found for ${assistant.name}: ${assistant.prompt}`);
1131
1476
  }
1132
1477
  if (assistant.config) {
1133
1478
  const configPath = path6.join(rootDir, assistant.config);
1134
- if (!fs2.existsSync(configPath)) {
1479
+ if (!fs6.existsSync(configPath)) {
1135
1480
  errors.push(`Config file not found for ${assistant.name}: ${assistant.config}`);
1136
1481
  }
1137
1482
  }
@@ -1143,6 +1488,17 @@ var CodemieConfigLoader = class {
1143
1488
  }
1144
1489
  }
1145
1490
  }
1491
+ if (config.resources.skills) {
1492
+ for (const skill of config.resources.skills) {
1493
+ const contentPath = path6.join(rootDir, skill.file);
1494
+ if (!fs6.existsSync(contentPath)) {
1495
+ errors.push(`Content file not found for skill ${skill.name}: ${skill.file}`);
1496
+ }
1497
+ if (!skill.description) {
1498
+ errors.push(`Missing 'description' for skill: ${skill.name}`);
1499
+ }
1500
+ }
1501
+ }
1146
1502
  return {
1147
1503
  valid: errors.length === 0,
1148
1504
  errors
@@ -1234,12 +1590,12 @@ Import chain: ${[...visitedFiles].join(" \u2192 ")} \u2192 ${normalizedPath}`
1234
1590
  * Load YAML file with error handling
1235
1591
  */
1236
1592
  loadYamlFile(filePath) {
1237
- if (!fs2.existsSync(filePath)) {
1593
+ if (!fs6.existsSync(filePath)) {
1238
1594
  throw new Error(`Import file not found: ${filePath}`);
1239
1595
  }
1240
1596
  try {
1241
- const content = fs2.readFileSync(filePath, "utf8");
1242
- const parsed = yaml3.parse(content);
1597
+ const content = fs6.readFileSync(filePath, "utf8");
1598
+ const parsed = yaml5.parse(content);
1243
1599
  if (parsed === null || parsed === void 0) {
1244
1600
  throw new TypeError(`Import file ${filePath} is empty`);
1245
1601
  }
@@ -1259,28 +1615,28 @@ Import chain: ${[...visitedFiles].join(" \u2192 ")} \u2192 ${normalizedPath}`
1259
1615
  * @param rootConfig - Root config object (constant reference for resolving paths like "imported.integrations.xxx")
1260
1616
  * @param path - Current path in config tree (for error messages, e.g., "resources.assistants[0].toolkits")
1261
1617
  */
1262
- resolveReferencesRecursive(current, rootConfig, path15 = "") {
1618
+ resolveReferencesRecursive(current, rootConfig, path16 = "") {
1263
1619
  if (!current || typeof current !== "object") {
1264
1620
  return;
1265
1621
  }
1266
1622
  if (Array.isArray(current)) {
1267
- this.resolveArrayReferences(current, rootConfig, path15);
1623
+ this.resolveArrayReferences(current, rootConfig, path16);
1268
1624
  return;
1269
1625
  }
1270
1626
  if ("$ref" in current && typeof current.$ref === "string") {
1271
- this.resolveObjectReference(current, rootConfig, path15);
1627
+ this.resolveObjectReference(current, rootConfig, path16);
1272
1628
  return;
1273
1629
  }
1274
- this.resolveObjectProperties(current, rootConfig, path15);
1630
+ this.resolveObjectProperties(current, rootConfig, path16);
1275
1631
  }
1276
1632
  /**
1277
1633
  * Resolve $ref items in arrays and flatten if they point to arrays
1278
1634
  * Example: [{ $ref: "context_definitions.repos" }] where repos is [item1, item2]
1279
1635
  * becomes [item1, item2]
1280
1636
  */
1281
- resolveArrayReferences(arr, rootConfig, path15) {
1637
+ resolveArrayReferences(arr, rootConfig, path16) {
1282
1638
  const result = arr.flatMap((item, i) => {
1283
- const contextPath = `${path15}[${i}]`;
1639
+ const contextPath = `${path16}[${i}]`;
1284
1640
  if (!this.isRefObject(item) || item.$ref.startsWith("#")) {
1285
1641
  this.resolveReferencesRecursive(item, rootConfig, contextPath);
1286
1642
  return [item];
@@ -1298,9 +1654,9 @@ Import chain: ${[...visitedFiles].join(" \u2192 ")} \u2192 ${normalizedPath}`
1298
1654
  * Resolve object reference: { $ref: "path" }
1299
1655
  * Replaces object with resolved data (in-place mutation)
1300
1656
  */
1301
- resolveObjectReference(current, rootConfig, path15) {
1657
+ resolveObjectReference(current, rootConfig, path16) {
1302
1658
  const refPath = current.$ref;
1303
- const contextPath = path15 || "root";
1659
+ const contextPath = path16 || "root";
1304
1660
  if (refPath.startsWith("#")) {
1305
1661
  return;
1306
1662
  }
@@ -1310,24 +1666,24 @@ Import chain: ${[...visitedFiles].join(" \u2192 ")} \u2192 ${normalizedPath}`
1310
1666
  delete current[key];
1311
1667
  }
1312
1668
  Object.assign(current, filteredResolved);
1313
- this.resolveReferencesRecursive(current, rootConfig, path15);
1669
+ this.resolveReferencesRecursive(current, rootConfig, path16);
1314
1670
  }
1315
1671
  /**
1316
1672
  * Resolve object properties recursively
1317
1673
  * Handles both nested objects and "$ref:path" string references
1318
1674
  */
1319
- resolveObjectProperties(current, rootConfig, path15) {
1675
+ resolveObjectProperties(current, rootConfig, path16) {
1320
1676
  for (const [key, value] of Object.entries(current)) {
1321
1677
  if (typeof value === "string" && value.startsWith("$ref:")) {
1322
1678
  const refPath = value.slice(5);
1323
1679
  if (refPath.startsWith("#")) {
1324
1680
  continue;
1325
1681
  }
1326
- const contextPath = path15 ? `${path15}.${key}` : key;
1682
+ const contextPath = path16 ? `${path16}.${key}` : key;
1327
1683
  const resolved = this.resolveReference(rootConfig, refPath, contextPath);
1328
1684
  current[key] = resolved;
1329
1685
  } else {
1330
- this.resolveReferencesRecursive(value, rootConfig, path15 ? `${path15}.${key}` : key);
1686
+ this.resolveReferencesRecursive(value, rootConfig, path16 ? `${path16}.${key}` : key);
1331
1687
  }
1332
1688
  }
1333
1689
  }
@@ -1465,6 +1821,11 @@ Import chain: ${[...visitedFiles].join(" \u2192 ")} \u2192 ${normalizedPath}`
1465
1821
  }
1466
1822
  }
1467
1823
  };
1824
+
1825
+ // src/backup.ts
1826
+ init_fileUtils();
1827
+ init_logger();
1828
+ init_logger();
1468
1829
  function createConcurrentLimiter(maxConcurrent = RATE_LIMITING.MAX_CONCURRENT_REQUESTS) {
1469
1830
  return pLimit(maxConcurrent);
1470
1831
  }
@@ -1484,8 +1845,7 @@ async function withRetry(fn, operation, maxAttempts = RATE_LIMITING.RETRY_ATTEMP
1484
1845
  const delayMs = RATE_LIMITING.RETRY_DELAY_MS * 2 ** (attempt - 1);
1485
1846
  logger.warn(` \u26A0\uFE0F Retry ${attempt}/${maxAttempts} for ${operation} after ${delayMs}ms...`);
1486
1847
  await new Promise((resolve6) => {
1487
- const timerId = setTimeout(resolve6, delayMs);
1488
- timerId.unref();
1848
+ setTimeout(resolve6, delayMs);
1489
1849
  });
1490
1850
  }
1491
1851
  }
@@ -1509,6 +1869,7 @@ async function withTimeout(promise, timeoutMs, operation) {
1509
1869
  }
1510
1870
 
1511
1871
  // src/backup.ts
1872
+ init_backupTransformers();
1512
1873
  async function* streamResources(fetchPage, resourceType) {
1513
1874
  let page = 0;
1514
1875
  let hasMore = true;
@@ -1546,7 +1907,7 @@ async function saveAssistantToBackup(assistant, client, backupData, backupDir) {
1546
1907
  const fileName = `${full.slug || sanitizeFileName(full.name)}.prompt.md`;
1547
1908
  const filePath = path6.join(backupDir, "system_prompts", fileName);
1548
1909
  ensureDirectoryExists(filePath);
1549
- fs2.writeFileSync(filePath, full.system_prompt, "utf8");
1910
+ fs6.writeFileSync(filePath, full.system_prompt, "utf8");
1550
1911
  }
1551
1912
  }
1552
1913
  async function backupAssistants(client, backupData, backupDir, transaction) {
@@ -1586,21 +1947,42 @@ async function backupAssistants(client, backupData, backupDir, transaction) {
1586
1947
  }
1587
1948
  async function processDatasourceBackup(datasource, client, backupData, transaction) {
1588
1949
  logger.info(` \u2022 ${datasource.name} (${datasource.id})`);
1589
- const full = await withTimeout(
1590
- client.datasources.get(datasource.id),
1591
- TIMEOUTS_MS.DATASOURCE_FETCH,
1592
- `Timeout fetching datasource ${datasource.id}`
1593
- );
1950
+ let full;
1951
+ try {
1952
+ full = await withTimeout(
1953
+ client.datasources.get(datasource.id),
1954
+ TIMEOUTS_MS.DATASOURCE_FETCH,
1955
+ `Timeout fetching datasource ${datasource.id}`
1956
+ );
1957
+ } catch (error) {
1958
+ logger.warn(
1959
+ ` \u26A0\uFE0F Failed to fetch full datasource details for ${datasource.name}. Using list response as fallback: ${error instanceof Error ? error.message : String(error)}`
1960
+ );
1961
+ full = datasource;
1962
+ }
1594
1963
  backupData.resources.datasources.push(full);
1595
1964
  transaction.markCompleted("datasources", datasource.id);
1596
1965
  }
1597
- async function backupDatasources(client, backupData, transaction) {
1966
+ function normalizeProjectName(projectName) {
1967
+ return String(projectName || "").trim().toLowerCase();
1968
+ }
1969
+ function filterResourcesByProject(resources, projectName) {
1970
+ const normalized = normalizeProjectName(projectName);
1971
+ if (!normalized) {
1972
+ return resources;
1973
+ }
1974
+ return resources.filter((r) => normalizeProjectName(r?.project_name) === normalized);
1975
+ }
1976
+ async function backupDatasources(client, backupData, transaction, projectName) {
1598
1977
  logger.info("\u{1F4CA} Fetching datasources...");
1599
- const datasources = [];
1978
+ const allDatasources = [];
1600
1979
  for await (const datasource of streamResources((params) => client.datasources.list(params), "datasources")) {
1601
- datasources.push(datasource);
1980
+ allDatasources.push(datasource);
1602
1981
  }
1603
- logger.info(` Found ${datasources.length} datasource(s)`);
1982
+ const datasources = filterResourcesByProject(allDatasources, projectName);
1983
+ logger.info(
1984
+ ` Found ${datasources.length} datasource(s) for project '${projectName}' (from ${allDatasources.length} total)`
1985
+ );
1604
1986
  transaction.setTotal("datasources", datasources.length);
1605
1987
  const limit = createConcurrentLimiter();
1606
1988
  for (const datasource of datasources) {
@@ -1653,7 +2035,7 @@ async function backupWorkflows(client, backupData, backupDir, transaction) {
1653
2035
  const fileName = `${sanitizeFileName(full.name)}.yaml`;
1654
2036
  const filePath = path6.join(backupDir, "workflows", fileName);
1655
2037
  ensureDirectoryExists(filePath);
1656
- fs2.writeFileSync(filePath, full.yaml_config, "utf8");
2038
+ fs6.writeFileSync(filePath, full.yaml_config, "utf8");
1657
2039
  }
1658
2040
  transaction.markCompleted("workflows", workflow.id);
1659
2041
  } catch (error) {
@@ -1669,9 +2051,9 @@ async function backupWorkflows(client, backupData, backupDir, transaction) {
1669
2051
  logger.info(`\u2713 Backed up ${transaction.getData().resources.workflows.completed.length} workflow(s)
1670
2052
  `);
1671
2053
  }
1672
- async function backupIntegrations(client, backupData) {
2054
+ async function backupIntegrations(client, backupData, projectName) {
1673
2055
  logger.info("\u{1F50C} Fetching integrations...");
1674
- const projectIntegrations = await withTimeout(
2056
+ const allProjectIntegrations = await withTimeout(
1675
2057
  client.integrations.list({
1676
2058
  per_page: PAGINATION.DEFAULT_PAGE_SIZE,
1677
2059
  page: 0,
@@ -1680,7 +2062,10 @@ async function backupIntegrations(client, backupData) {
1680
2062
  TIMEOUTS_MS.INTEGRATION_FETCH,
1681
2063
  "Timeout fetching project integrations"
1682
2064
  );
1683
- logger.info(` Found ${projectIntegrations.length} project integration(s)`);
2065
+ const projectIntegrations = filterResourcesByProject(allProjectIntegrations, projectName);
2066
+ logger.info(
2067
+ ` Found ${projectIntegrations.length} project integration(s) for project '${projectName}' (from ${allProjectIntegrations.length} total)`
2068
+ );
1684
2069
  for (const integration of projectIntegrations) {
1685
2070
  logger.info(` \u2022 ${integration.alias || integration.credential_type} (${integration.id}) [project]`);
1686
2071
  backupData.resources.integrations.push(integration);
@@ -1702,10 +2087,54 @@ async function backupIntegrations(client, backupData) {
1702
2087
  logger.info(`\u2713 Backed up ${projectIntegrations.length + userIntegrations.length} integration(s)
1703
2088
  `);
1704
2089
  }
2090
+ async function backupSkills(client, backupData, backupDir, transaction) {
2091
+ logger.info("\u{1F9E0} Fetching skills...");
2092
+ const allSkills = [];
2093
+ for await (const skill of streamResources((params) => client.skills.list(params), "skills")) {
2094
+ allSkills.push(skill);
2095
+ }
2096
+ logger.info(` Found ${allSkills.length} skill(s)`);
2097
+ transaction.setTotal("skills", allSkills.length);
2098
+ const limit = createConcurrentLimiter();
2099
+ for (const skill of allSkills) {
2100
+ if (transaction.isCompleted("skills", skill.id)) {
2101
+ logger.info(` \u21B7 Skipping ${skill.name} (already backed up)`);
2102
+ continue;
2103
+ }
2104
+ try {
2105
+ await limit(
2106
+ () => withRetry(async () => {
2107
+ logger.info(` \u2022 ${skill.name} (${skill.id})`);
2108
+ const full = await withTimeout(
2109
+ client.skills.get(skill.id),
2110
+ TIMEOUTS_MS.SKILL_FETCH,
2111
+ `Timeout fetching skill ${skill.id}`
2112
+ );
2113
+ backupData.resources.skills.push(full);
2114
+ const fileName = `${sanitizeFileName(skill.name)}.skill.md`;
2115
+ const filePath = path6.join(backupDir, "skills", fileName);
2116
+ ensureDirectoryExists(filePath);
2117
+ fs6.writeFileSync(filePath, full.content || "", "utf8");
2118
+ }, `Backup skill ${skill.name}`)
2119
+ );
2120
+ transaction.markCompleted("skills", skill.id);
2121
+ } catch (error) {
2122
+ const errorDetails = error instanceof Error ? { message: error.message, stack: error.stack, name: error.name } : { message: String(error) };
2123
+ logger.error(` \u274C Failed to backup ${skill.name}:`);
2124
+ logger.error(` ${errorDetails.message}`);
2125
+ if (errorDetails.stack) {
2126
+ logger.error(` Stack trace: ${errorDetails.stack.split("\n").slice(1, 3).join("\n ")}`);
2127
+ }
2128
+ transaction.markFailed("skills", skill.id, errorDetails.message);
2129
+ }
2130
+ }
2131
+ logger.info(`\u2713 Backed up ${transaction.getData().resources.skills.completed.length} skill(s)
2132
+ `);
2133
+ }
1705
2134
  function getUniqueBackupDir(baseDir, timestamp) {
1706
2135
  let counter = 0;
1707
2136
  let finalBackupDir = path6.join(baseDir, timestamp);
1708
- while (fs2.existsSync(finalBackupDir)) {
2137
+ while (fs6.existsSync(finalBackupDir)) {
1709
2138
  counter++;
1710
2139
  finalBackupDir = path6.join(baseDir, `${timestamp}-${counter}`);
1711
2140
  }
@@ -1714,7 +2143,7 @@ function getUniqueBackupDir(baseDir, timestamp) {
1714
2143
  return { finalDir: finalBackupDir, tempDir: tempBackupDir };
1715
2144
  }
1716
2145
  function performCleanup(tempBackupDir, transaction) {
1717
- if (fs2.existsSync(tempBackupDir)) {
2146
+ if (fs6.existsSync(tempBackupDir)) {
1718
2147
  try {
1719
2148
  logger.info(`\u{1F9F9} Rolling back: cleaning up ${tempBackupDir}...`);
1720
2149
  cleanupDirectory(tempBackupDir);
@@ -1753,8 +2182,8 @@ async function backupResources(options) {
1753
2182
  logger.info("\u{1F50C} Connecting to Codemie API...");
1754
2183
  const client = await createClient(config);
1755
2184
  logger.info("\u2713 Connected to Codemie API\n");
1756
- if (!fs2.existsSync(tempBackupDir)) {
1757
- fs2.mkdirSync(tempBackupDir, { recursive: true });
2185
+ if (!fs6.existsSync(tempBackupDir)) {
2186
+ fs6.mkdirSync(tempBackupDir, { recursive: true });
1758
2187
  }
1759
2188
  logger.info(`\u{1F4C1} Temporary backup directory: ${tempBackupDir}
1760
2189
  `);
@@ -1769,7 +2198,8 @@ async function backupResources(options) {
1769
2198
  assistants: [],
1770
2199
  datasources: [],
1771
2200
  workflows: [],
1772
- integrations: []
2201
+ integrations: [],
2202
+ skills: []
1773
2203
  },
1774
2204
  state: {
1775
2205
  version: "1.0",
@@ -1778,21 +2208,23 @@ async function backupResources(options) {
1778
2208
  resources: {
1779
2209
  assistants: {},
1780
2210
  datasources: {},
1781
- workflows: {}
2211
+ workflows: {},
2212
+ skills: {}
1782
2213
  }
1783
2214
  }
1784
2215
  };
1785
- await backupAssistants(client, backupData, tempBackupDir, transaction);
1786
- await backupDatasources(client, backupData, transaction);
2216
+ await backupSkills(client, backupData, tempBackupDir, transaction);
2217
+ await backupIntegrations(client, backupData, config.project.name);
2218
+ await backupDatasources(client, backupData, transaction, config.project.name);
1787
2219
  await backupWorkflows(client, backupData, tempBackupDir, transaction);
1788
- await backupIntegrations(client, backupData);
2220
+ await backupAssistants(client, backupData, tempBackupDir, transaction);
1789
2221
  const stats = transaction.getData();
1790
- const totalFailed = stats.resources.assistants.failed.length + stats.resources.datasources.failed.length + stats.resources.workflows.failed.length;
2222
+ const totalFailed = stats.resources.assistants.failed.length + stats.resources.datasources.failed.length + stats.resources.workflows.failed.length + stats.resources.skills.failed.length;
1791
2223
  if (totalFailed > 0) {
1792
2224
  logger.warn(`
1793
2225
  \u26A0\uFE0F Backup completed with ${totalFailed} failed resource(s)`);
1794
2226
  logger.warn("Review transaction.json for details\n");
1795
- const totalResources = stats.resources.assistants.total + stats.resources.datasources.total + stats.resources.workflows.total;
2227
+ const totalResources = stats.resources.assistants.total + stats.resources.datasources.total + stats.resources.workflows.total + stats.resources.skills.total;
1796
2228
  const failureRate = totalResources > 0 ? totalFailed / totalResources * 100 : 0;
1797
2229
  if (failureRate > 20) {
1798
2230
  throw new Error(
@@ -1814,6 +2246,7 @@ async function backupResources(options) {
1814
2246
  logger.info(` \u{1F4CA} Datasources: ${backupData.resources.datasources.length}`);
1815
2247
  logger.info(` \u{1F504} Workflows: ${backupData.resources.workflows.length}`);
1816
2248
  logger.info(` \u{1F50C} Integrations: ${backupData.resources.integrations.length}`);
2249
+ logger.info(` \u{1F9E0} Skills: ${backupData.resources.skills.length}`);
1817
2250
  logger.info(`
1818
2251
  \u{1F4C1} Location: ${finalBackupDir}
1819
2252
  `);
@@ -1833,12 +2266,15 @@ async function main(options) {
1833
2266
  try {
1834
2267
  await backupResources(options);
1835
2268
  process.exit(0);
1836
- } catch {
2269
+ } catch (error) {
2270
+ logger.error("\n\u274C Backup failed:");
2271
+ logger.error(error instanceof Error ? error.message : String(error));
1837
2272
  process.exit(1);
1838
2273
  }
1839
2274
  }
1840
2275
 
1841
2276
  // src/lib/paginationUtils.ts
2277
+ init_logger();
1842
2278
  async function findResourceByName(listFn, name, resourceType) {
1843
2279
  let page = 0;
1844
2280
  const perPage = 100;
@@ -1871,6 +2307,10 @@ async function findAssistantByName(client, name) {
1871
2307
  const assistant = await findResourceByName((params) => client.assistants.list(params), name, "assistant");
1872
2308
  return assistant?.id || null;
1873
2309
  }
2310
+ async function findSkillByName(client, name) {
2311
+ const skill = await findResourceByName((params) => client.skills.list(params), name, "skill");
2312
+ return skill?.id || null;
2313
+ }
1874
2314
 
1875
2315
  // src/lib/assistantHelpers.ts
1876
2316
  async function createAssistantAndGetId(client, params, slug) {
@@ -1889,12 +2329,18 @@ async function createAssistantAndGetId(client, params, slug) {
1889
2329
  return assistantId;
1890
2330
  }
1891
2331
 
2332
+ // src/deploy.ts
2333
+ init_checksumUtils();
2334
+
1892
2335
  // src/lib/cleanupManager.ts
2336
+ init_logger();
1893
2337
  var CleanupManager = class {
1894
2338
  constructor(client, stateManager) {
1895
2339
  this.client = client;
1896
2340
  this.stateManager = stateManager;
1897
2341
  }
2342
+ client;
2343
+ stateManager;
1898
2344
  /**
1899
2345
  * Check if an error indicates that a resource was not found on the platform
1900
2346
  * Handles both proper 404 responses
@@ -1935,10 +2381,12 @@ var CleanupManager = class {
1935
2381
  const configAssistantNames = new Set((config.resources.assistants || []).map(({ name }) => name));
1936
2382
  const configDatasourceNames = new Set((config.resources.datasources || []).map(({ name }) => name));
1937
2383
  const configWorkflowNames = new Set((config.resources.workflows || []).map(({ name }) => name));
2384
+ const configSkillNames = new Set((config.resources.skills || []).map(({ name }) => name));
1938
2385
  return {
1939
2386
  assistants: managedResources.assistants.filter((name) => !configAssistantNames.has(name)),
1940
2387
  datasources: managedResources.datasources.filter((name) => !configDatasourceNames.has(name)),
1941
- workflows: managedResources.workflows.filter((name) => !configWorkflowNames.has(name))
2388
+ workflows: managedResources.workflows.filter((name) => !configWorkflowNames.has(name)),
2389
+ skills: managedResources.skills.filter((name) => !configSkillNames.has(name))
1942
2390
  };
1943
2391
  }
1944
2392
  /**
@@ -1952,7 +2400,8 @@ var CleanupManager = class {
1952
2400
  deleted: {
1953
2401
  assistants: [],
1954
2402
  datasources: [],
1955
- workflows: []
2403
+ workflows: [],
2404
+ skills: []
1956
2405
  },
1957
2406
  errors: []
1958
2407
  };
@@ -2031,13 +2480,36 @@ var CleanupManager = class {
2031
2480
  );
2032
2481
  }
2033
2482
  }
2483
+ for (const name of orphaned.skills || []) {
2484
+ try {
2485
+ if (!this.stateManager.isManagedResource("skill", name)) {
2486
+ logger.info(` \u26A0\uFE0F Skipping ${name} - not in state (safety check)`);
2487
+ continue;
2488
+ }
2489
+ const id = this.stateManager.getIdByName("skill", name);
2490
+ if (!id) {
2491
+ logger.info(` \u26A0\uFE0F Skipping ${name} - no ID in state`);
2492
+ continue;
2493
+ }
2494
+ await this.deleteResourceSafely("skill", name, id, () => this.client.skills.delete(id));
2495
+ this.stateManager.deleteSkillState(name);
2496
+ result.deleted.skills.push(name);
2497
+ } catch (error) {
2498
+ result.errors.push({
2499
+ type: "skill",
2500
+ name,
2501
+ error: error instanceof Error ? error.message : String(error)
2502
+ });
2503
+ logger.error(` \u274C Failed to delete skill ${name}: ${error instanceof Error ? error.message : String(error)}`);
2504
+ }
2505
+ }
2034
2506
  return result;
2035
2507
  }
2036
2508
  /**
2037
2509
  * Get summary of orphaned resources
2038
2510
  */
2039
2511
  getOrphanedSummary(orphaned) {
2040
- const total = orphaned.assistants.length + orphaned.datasources.length + orphaned.workflows.length;
2512
+ const total = orphaned.assistants.length + orphaned.datasources.length + orphaned.workflows.length + (orphaned.skills?.length ?? 0);
2041
2513
  if (total === 0) {
2042
2514
  return "No orphaned resources found";
2043
2515
  }
@@ -2051,10 +2523,18 @@ var CleanupManager = class {
2051
2523
  if (orphaned.workflows.length > 0) {
2052
2524
  parts.push(`${orphaned.workflows.length} workflow(s)`);
2053
2525
  }
2526
+ if ((orphaned.skills?.length ?? 0) > 0) {
2527
+ parts.push(`${orphaned.skills.length} skill(s)`);
2528
+ }
2054
2529
  return `Found ${total} orphaned resource(s): ${parts.join(", ")}`;
2055
2530
  }
2056
2531
  };
2057
2532
 
2533
+ // src/deploy.ts
2534
+ init_codemieConfigChecksums();
2535
+ init_converters();
2536
+ init_logger();
2537
+
2058
2538
  // src/lib/resourceExistenceChecker.ts
2059
2539
  async function checkResourceExists(getState, getResource) {
2060
2540
  const existingState = getState();
@@ -2086,6 +2566,16 @@ function checkWorkflowExists(client, name, stateManager) {
2086
2566
  (id) => client.workflows.get(id)
2087
2567
  );
2088
2568
  }
2569
+ function checkSkillExists(client, name, stateManager) {
2570
+ return checkResourceExists(
2571
+ () => stateManager.getSkillState(name),
2572
+ (id) => client.skills.get(id)
2573
+ );
2574
+ }
2575
+
2576
+ // src/lib/stateManager.ts
2577
+ init_checksumUtils();
2578
+ init_codemieConfigChecksums();
2089
2579
  var StateManager = class {
2090
2580
  statePath;
2091
2581
  constructor(appConfig) {
@@ -2096,16 +2586,17 @@ var StateManager = class {
2096
2586
  * Load state file
2097
2587
  */
2098
2588
  loadState() {
2099
- if (!fs2.existsSync(this.statePath)) {
2589
+ if (!fs6.existsSync(this.statePath)) {
2100
2590
  return this.createEmptyState();
2101
2591
  }
2102
- const content = fs2.readFileSync(this.statePath, "utf8");
2592
+ const content = fs6.readFileSync(this.statePath, "utf8");
2103
2593
  const state = JSON.parse(content);
2104
2594
  if (!state.resources) {
2105
2595
  state.resources = {
2106
2596
  assistants: {},
2107
2597
  datasources: {},
2108
- workflows: {}
2598
+ workflows: {},
2599
+ skills: {}
2109
2600
  };
2110
2601
  }
2111
2602
  if (!state.resources.assistants) {
@@ -2117,6 +2608,9 @@ var StateManager = class {
2117
2608
  if (!state.resources.workflows) {
2118
2609
  state.resources.workflows = {};
2119
2610
  }
2611
+ if (!state.resources.skills) {
2612
+ state.resources.skills = {};
2613
+ }
2120
2614
  return state;
2121
2615
  }
2122
2616
  /**
@@ -2124,11 +2618,11 @@ var StateManager = class {
2124
2618
  */
2125
2619
  saveState(state) {
2126
2620
  const dir = path6.dirname(this.statePath);
2127
- if (!fs2.existsSync(dir)) {
2128
- fs2.mkdirSync(dir, { recursive: true });
2621
+ if (!fs6.existsSync(dir)) {
2622
+ fs6.mkdirSync(dir, { recursive: true });
2129
2623
  }
2130
2624
  state.lastSync = (/* @__PURE__ */ new Date()).toISOString();
2131
- fs2.writeFileSync(this.statePath, JSON.stringify(state, null, 2));
2625
+ fs6.writeFileSync(this.statePath, JSON.stringify(state, null, 2));
2132
2626
  }
2133
2627
  /**
2134
2628
  * Create empty state structure
@@ -2141,7 +2635,8 @@ var StateManager = class {
2141
2635
  resources: {
2142
2636
  assistants: {},
2143
2637
  datasources: {},
2144
- workflows: {}
2638
+ workflows: {},
2639
+ skills: {}
2145
2640
  }
2146
2641
  };
2147
2642
  }
@@ -2236,19 +2731,20 @@ var StateManager = class {
2236
2731
  }
2237
2732
  /**
2238
2733
  * Get all managed resources (for cleanup/destroy)
2239
- * Returns: { assistants: [name1, name2], datasources: [name1], workflows: [name1] }
2734
+ * Returns: { assistants: [name1, name2], datasources: [name1], workflows: [name1], skills: [name1] }
2240
2735
  */
2241
2736
  getAllManagedResources() {
2242
2737
  const state = this.loadState();
2243
2738
  return {
2244
2739
  assistants: Object.keys(state.resources.assistants),
2245
2740
  datasources: Object.keys(state.resources.datasources),
2246
- workflows: Object.keys(state.resources.workflows)
2741
+ workflows: Object.keys(state.resources.workflows),
2742
+ skills: Object.keys(state.resources.skills || {})
2247
2743
  };
2248
2744
  }
2249
2745
  /**
2250
2746
  * Check if a resource is managed by IaC (exists in state.json)
2251
- * @param type Resource type ('assistant', 'datasource', 'workflow')
2747
+ * @param type Resource type ('assistant', 'datasource', 'workflow', 'skill')
2252
2748
  * @param name Resource name
2253
2749
  */
2254
2750
  isManagedResource(type, name) {
@@ -2263,6 +2759,9 @@ var StateManager = class {
2263
2759
  case "workflow": {
2264
2760
  return name in state.resources.workflows;
2265
2761
  }
2762
+ case "skill": {
2763
+ return name in (state.resources.skills || {});
2764
+ }
2266
2765
  default: {
2267
2766
  return false;
2268
2767
  }
@@ -2283,11 +2782,47 @@ var StateManager = class {
2283
2782
  case "workflow": {
2284
2783
  return state.resources.workflows[name]?.id;
2285
2784
  }
2785
+ case "skill": {
2786
+ return (state.resources.skills || {})[name]?.id;
2787
+ }
2286
2788
  default: {
2287
2789
  return void 0;
2288
2790
  }
2289
2791
  }
2290
2792
  }
2793
+ /**
2794
+ * Update skill state (keyed by NAME)
2795
+ */
2796
+ updateSkillState(name, id, content, skillResource) {
2797
+ const state = this.loadState();
2798
+ if (!state.resources.skills) {
2799
+ state.resources.skills = {};
2800
+ }
2801
+ state.resources.skills[name] = {
2802
+ id,
2803
+ lastDeployed: (/* @__PURE__ */ new Date()).toISOString(),
2804
+ contentChecksum: calculateChecksum(content),
2805
+ configChecksum: calculateSkillConfigChecksum(skillResource)
2806
+ };
2807
+ this.saveState(state);
2808
+ }
2809
+ /**
2810
+ * Get skill state by NAME
2811
+ */
2812
+ getSkillState(name) {
2813
+ const state = this.loadState();
2814
+ return (state.resources.skills || {})[name];
2815
+ }
2816
+ /**
2817
+ * Delete skill state by NAME
2818
+ */
2819
+ deleteSkillState(name) {
2820
+ const state = this.loadState();
2821
+ if (state.resources.skills) {
2822
+ delete state.resources.skills[name];
2823
+ }
2824
+ this.saveState(state);
2825
+ }
2291
2826
  };
2292
2827
 
2293
2828
  // src/deploy.ts
@@ -2380,69 +2915,268 @@ async function deployAssistants(config, client, loader, stateManager) {
2380
2915
  });
2381
2916
  logger.info(` \u2713 Resolved "${dsName}" \u2192 ${datasource.name} (${contextType})`);
2382
2917
  }
2383
- resolvedContext = [...resolvedContext, ...datasourceContextEntries];
2918
+ resolvedContext = [...resolvedContext, ...datasourceContextEntries];
2919
+ }
2920
+ let resolvedSkillIds = [];
2921
+ if (assistant.skills && assistant.skills.length > 0) {
2922
+ logger.info(` Resolving ${assistant.skills.length} skill name(s)...`);
2923
+ const resolvedIds = [];
2924
+ for (const skillName of assistant.skills) {
2925
+ const skillState = stateManager.getSkillState(skillName);
2926
+ if (!skillState) {
2927
+ throw new Error(`Skill "${skillName}" not found in state. Ensure the skill is deployed first.`);
2928
+ }
2929
+ resolvedIds.push(skillState.id);
2930
+ logger.info(` \u2713 Resolved "${skillName}" \u2192 ${skillState.id}`);
2931
+ }
2932
+ resolvedSkillIds = resolvedIds;
2933
+ }
2934
+ const assistantWithResolved = {
2935
+ ...assistant,
2936
+ assistant_ids: resolvedAssistantIds,
2937
+ context: resolvedContext,
2938
+ skill_ids: resolvedSkillIds
2939
+ };
2940
+ const apiParams = assistantResourceToCreateParams(assistantWithResolved, config.project.name, promptContent);
2941
+ const existingState = stateManager.getAssistantState(assistant.name);
2942
+ if (existingState) {
2943
+ const existsOnPlatform = await checkAssistantExists(client, assistant.name, stateManager);
2944
+ if (existsOnPlatform) {
2945
+ const hasChanged = existingState.promptChecksum !== calculateChecksum(promptContent) || existingState.configChecksum !== configChecksum;
2946
+ if (hasChanged) {
2947
+ logger.info(` Updating assistant (ID: ${existingState.id})...`);
2948
+ if (process.env.DEBUG_API) {
2949
+ logger.debug("\n=== DEBUG: Update API Params ===");
2950
+ logger.debug(JSON.stringify(apiParams, null, 2));
2951
+ logger.debug("================================\n");
2952
+ }
2953
+ await client.assistants.update(existingState.id, apiParams);
2954
+ logger.info(`\u2713 Updated assistant: ${assistant.name} (${existingState.id})`);
2955
+ stateManager.updateAssistantState(assistant.name, existingState.id, promptContent, assistant, buildConfig);
2956
+ stats.updated++;
2957
+ } else {
2958
+ logger.info(` \u2713 No changes detected (ID: ${existingState.id})`);
2959
+ stats.unchanged++;
2960
+ }
2961
+ } else {
2962
+ logger.info(` \u26A0\uFE0F Assistant ID from state not found on platform, will create new`);
2963
+ logger.info(` Creating new assistant...`);
2964
+ if (process.env.DEBUG_API) {
2965
+ logger.debug("\n=== DEBUG: API Params ===");
2966
+ logger.debug(JSON.stringify(apiParams, null, 2));
2967
+ logger.debug("=========================\n");
2968
+ }
2969
+ let assistantId;
2970
+ try {
2971
+ assistantId = await createAssistantAndGetId(client, apiParams, assistant.slug);
2972
+ logger.info(`\u2713 Created assistant: ${assistant.name} (${assistantId})`);
2973
+ stats.created++;
2974
+ } catch (createError) {
2975
+ logger.warn(` \u26A0\uFE0F Assistant create failed. Checking if it already exists on platform...`);
2976
+ let existingId = null;
2977
+ if (assistant.slug) {
2978
+ try {
2979
+ const existingBySlug = await client.assistants.getBySlug(assistant.slug);
2980
+ existingId = existingBySlug?.id || null;
2981
+ } catch {
2982
+ }
2983
+ }
2984
+ if (!existingId) {
2985
+ existingId = await findAssistantByName(client, assistant.name);
2986
+ }
2987
+ if (!existingId) {
2988
+ throw createError;
2989
+ }
2990
+ logger.info(` Found existing assistant ID: ${existingId}. Updating...`);
2991
+ await client.assistants.update(existingId, apiParams);
2992
+ assistantId = existingId;
2993
+ logger.info(`\u2713 Recovered and updated assistant: ${assistant.name} (${assistantId})`);
2994
+ stats.updated++;
2995
+ }
2996
+ stateManager.updateAssistantState(assistant.name, assistantId, promptContent, assistant, buildConfig);
2997
+ }
2998
+ } else {
2999
+ logger.info(` Creating new assistant...`);
3000
+ if (process.env.DEBUG_API) {
3001
+ logger.debug("\n=== DEBUG: API Params ===");
3002
+ logger.debug(JSON.stringify(apiParams, null, 2));
3003
+ logger.debug("=========================\n");
3004
+ }
3005
+ let assistantId;
3006
+ try {
3007
+ assistantId = await createAssistantAndGetId(client, apiParams, assistant.slug);
3008
+ logger.info(`\u2713 Created assistant: ${assistant.name} (${assistantId})`);
3009
+ stats.created++;
3010
+ } catch (createError) {
3011
+ logger.warn(` \u26A0\uFE0F Assistant create failed. Checking if it already exists on platform...`);
3012
+ let existingId = null;
3013
+ if (assistant.slug) {
3014
+ try {
3015
+ const existingBySlug = await client.assistants.getBySlug(assistant.slug);
3016
+ existingId = existingBySlug?.id || null;
3017
+ } catch {
3018
+ }
3019
+ }
3020
+ if (!existingId) {
3021
+ existingId = await findAssistantByName(client, assistant.name);
3022
+ }
3023
+ if (!existingId) {
3024
+ throw createError;
3025
+ }
3026
+ logger.info(` Found existing assistant ID: ${existingId}. Updating...`);
3027
+ await client.assistants.update(existingId, apiParams);
3028
+ assistantId = existingId;
3029
+ logger.info(`\u2713 Recovered and updated assistant: ${assistant.name} (${assistantId})`);
3030
+ stats.updated++;
3031
+ }
3032
+ stateManager.updateAssistantState(assistant.name, assistantId, promptContent, assistant, buildConfig);
2384
3033
  }
2385
- const assistantWithResolved = {
2386
- ...assistant,
2387
- assistant_ids: resolvedAssistantIds,
2388
- context: resolvedContext
2389
- };
2390
- const apiParams = assistantResourceToCreateParams(assistantWithResolved, config.project.name, promptContent);
2391
- const existingState = stateManager.getAssistantState(assistant.name);
3034
+ logger.info("");
3035
+ } catch (error) {
3036
+ logger.error(` \u274C Failed to deploy ${assistant.name}:`);
3037
+ if (error instanceof Error) {
3038
+ logger.error(` ${error.message}`);
3039
+ logger.debug(` Stack:`, error.stack);
3040
+ if ("statusCode" in error) {
3041
+ const apiError = error;
3042
+ logger.error(` Status: ${apiError.statusCode}`);
3043
+ logger.error(` Data: ${JSON.stringify(apiError.response, null, 2)}`);
3044
+ } else if ("response" in error) {
3045
+ const axiosError = error;
3046
+ logger.error(` Status: ${axiosError.response?.status}`);
3047
+ logger.error(` Data: ${JSON.stringify(axiosError.response?.data, null, 2)}`);
3048
+ }
3049
+ } else {
3050
+ logger.error(` ${String(error)}`);
3051
+ }
3052
+ logger.info("");
3053
+ stats.failed++;
3054
+ }
3055
+ }
3056
+ return stats;
3057
+ }
3058
+ async function deploySkills(config, client, loader, stateManager) {
3059
+ const stats = { created: 0, updated: 0, unchanged: 0, failed: 0 };
3060
+ logger.info("\u{1F3AF} Processing skills...\n");
3061
+ if (!config.resources.skills) {
3062
+ return stats;
3063
+ }
3064
+ for (const skill of config.resources.skills) {
3065
+ try {
3066
+ logger.info(`Processing: ${skill.name}`);
3067
+ const content = loader.loadSkillContent(skill.file);
3068
+ const contentChecksum = calculateChecksum(content);
3069
+ const configChecksum = calculateSkillConfigChecksum(skill);
3070
+ const visibility = skill.visibility ?? "project";
3071
+ const existingState = stateManager.getSkillState(skill.name);
2392
3072
  if (existingState) {
2393
- const existsOnPlatform = await checkAssistantExists(client, assistant.name, stateManager);
3073
+ const existsOnPlatform = await checkSkillExists(client, skill.name, stateManager);
2394
3074
  if (existsOnPlatform) {
2395
- const hasChanged = existingState.promptChecksum !== calculateChecksum(promptContent) || existingState.configChecksum !== configChecksum;
3075
+ const hasChanged = existingState.contentChecksum !== contentChecksum || existingState.configChecksum !== configChecksum;
2396
3076
  if (hasChanged) {
2397
- logger.info(` Updating assistant (ID: ${existingState.id})...`);
3077
+ logger.info(` Updating skill (ID: ${existingState.id})...`);
2398
3078
  if (process.env.DEBUG_API) {
2399
3079
  logger.debug("\n=== DEBUG: Update API Params ===");
2400
- logger.debug(JSON.stringify(apiParams, null, 2));
3080
+ logger.debug(
3081
+ JSON.stringify(
3082
+ { name: skill.name, description: skill.description, categories: skill.categories },
3083
+ null,
3084
+ 2
3085
+ )
3086
+ );
2401
3087
  logger.debug("================================\n");
2402
3088
  }
2403
- await client.assistants.update(existingState.id, apiParams);
2404
- logger.info(`\u2713 Updated assistant: ${assistant.name} (${existingState.id})`);
2405
- stateManager.updateAssistantState(assistant.name, existingState.id, promptContent, assistant, buildConfig);
3089
+ await client.skills.update(existingState.id, {
3090
+ name: skill.name,
3091
+ description: skill.description,
3092
+ content,
3093
+ categories: skill.categories
3094
+ });
3095
+ logger.info(`\u2713 Updated skill: ${skill.name} (${existingState.id})`);
3096
+ stateManager.updateSkillState(skill.name, existingState.id, content, skill);
2406
3097
  stats.updated++;
2407
3098
  } else {
2408
3099
  logger.info(` \u2713 No changes detected (ID: ${existingState.id})`);
2409
3100
  stats.unchanged++;
2410
3101
  }
2411
3102
  } else {
2412
- logger.info(` \u26A0\uFE0F Assistant ID from state not found on platform, will create new`);
2413
- logger.info(` Creating new assistant...`);
2414
- if (process.env.DEBUG_API) {
2415
- logger.debug("\n=== DEBUG: API Params ===");
2416
- logger.debug(JSON.stringify(apiParams, null, 2));
2417
- logger.debug("=========================\n");
2418
- }
2419
- const assistantId = await createAssistantAndGetId(client, apiParams, assistant.slug);
2420
- logger.info(`\u2713 Created assistant: ${assistant.name} (${assistantId})`);
2421
- stateManager.updateAssistantState(assistant.name, assistantId, promptContent, assistant, buildConfig);
3103
+ logger.info(` \u26A0\uFE0F Skill ID from state not found on platform, will create new`);
3104
+ logger.info(` Creating new skill...`);
3105
+ const result = await client.skills.create({
3106
+ name: skill.name,
3107
+ description: skill.description,
3108
+ content,
3109
+ project: config.project.name,
3110
+ visibility,
3111
+ categories: skill.categories
3112
+ });
3113
+ logger.info(`\u2713 Created skill: ${skill.name} (${result.id})`);
3114
+ stateManager.updateSkillState(skill.name, result.id, content, skill);
2422
3115
  stats.created++;
2423
3116
  }
2424
3117
  } else {
2425
- logger.info(` Creating new assistant...`);
3118
+ logger.info(` Creating new skill...`);
2426
3119
  if (process.env.DEBUG_API) {
2427
3120
  logger.debug("\n=== DEBUG: API Params ===");
2428
- logger.debug(JSON.stringify(apiParams, null, 2));
3121
+ logger.debug(
3122
+ JSON.stringify(
3123
+ { name: skill.name, description: skill.description, visibility, categories: skill.categories },
3124
+ null,
3125
+ 2
3126
+ )
3127
+ );
2429
3128
  logger.debug("=========================\n");
2430
3129
  }
2431
- const assistantId = await createAssistantAndGetId(client, apiParams, assistant.slug);
2432
- logger.info(`\u2713 Created assistant: ${assistant.name} (${assistantId})`);
2433
- stateManager.updateAssistantState(assistant.name, assistantId, promptContent, assistant, buildConfig);
2434
- stats.created++;
3130
+ let skillId;
3131
+ try {
3132
+ const result = await client.skills.create({
3133
+ name: skill.name,
3134
+ description: skill.description,
3135
+ content,
3136
+ project: config.project.name,
3137
+ visibility,
3138
+ categories: skill.categories
3139
+ });
3140
+ skillId = result.id;
3141
+ logger.info(`\u2713 Created skill: ${skill.name} (${skillId})`);
3142
+ stats.created++;
3143
+ } catch (createError) {
3144
+ const msg = createError instanceof Error ? createError.message : String(createError);
3145
+ if (!msg.includes("already exists")) {
3146
+ throw createError;
3147
+ }
3148
+ logger.warn(` \u26A0\uFE0F Skill already exists on platform (no local state). Fetching ID to recover...`);
3149
+ const existingId = await findSkillByName(client, skill.name);
3150
+ if (!existingId) {
3151
+ throw new Error(`Skill "${skill.name}" reported as existing but could not be found by name.`);
3152
+ }
3153
+ logger.info(` Found existing skill ID: ${existingId}. Updating...`);
3154
+ await client.skills.update(existingId, {
3155
+ name: skill.name,
3156
+ description: skill.description,
3157
+ content,
3158
+ categories: skill.categories
3159
+ });
3160
+ skillId = existingId;
3161
+ logger.info(`\u2713 Recovered and updated skill: ${skill.name} (${skillId})`);
3162
+ stats.updated++;
3163
+ }
3164
+ stateManager.updateSkillState(skill.name, skillId, content, skill);
2435
3165
  }
2436
3166
  logger.info("");
2437
3167
  } catch (error) {
2438
- logger.error(` \u274C Failed to deploy ${assistant.name}:`);
3168
+ logger.error(` \u274C Failed to deploy ${skill.name}:`);
2439
3169
  if (error instanceof Error) {
2440
3170
  logger.error(` ${error.message}`);
2441
3171
  logger.debug(` Stack:`, error.stack);
2442
- if ("response" in error) {
3172
+ if ("statusCode" in error) {
3173
+ const apiError = error;
3174
+ logger.error(` Status: ${apiError.statusCode}`);
3175
+ logger.error(` Data: ${JSON.stringify(apiError.response, null, 2)}`);
3176
+ } else if ("response" in error) {
2443
3177
  const axiosError = error;
2444
3178
  logger.error(` Status: ${axiosError.response?.status}`);
2445
- logger.error(` Data:`, JSON.stringify(axiosError.response?.data, null, 2));
3179
+ logger.error(` Data: ${JSON.stringify(axiosError.response?.data, null, 2)}`);
2446
3180
  }
2447
3181
  } else {
2448
3182
  logger.error(` ${String(error)}`);
@@ -2464,6 +3198,24 @@ async function createDatasourceAndGetId(client, createParams, datasourceName) {
2464
3198
  logger.info(` Found ID: ${datasourceId}`);
2465
3199
  return datasourceId;
2466
3200
  }
3201
+ async function createOrRecoverDatasource(client, createParams, datasourceName) {
3202
+ try {
3203
+ return await createDatasourceAndGetId(client, createParams, datasourceName);
3204
+ } catch (createError) {
3205
+ if (createError instanceof Error && createError.message.toLowerCase().includes("already exists")) {
3206
+ logger.warn(` \u26A0\uFE0F Datasource "${datasourceName}" already exists on platform. Recovering existing ID...`);
3207
+ const existingId = await findDatasourceByName(client, datasourceName);
3208
+ if (!existingId) {
3209
+ throw new Error(
3210
+ `Datasource "${datasourceName}" already exists but could not be found by name. Manual intervention required.`
3211
+ );
3212
+ }
3213
+ logger.info(` Found existing ID: ${existingId}`);
3214
+ return existingId;
3215
+ }
3216
+ throw createError;
3217
+ }
3218
+ }
2467
3219
  async function deployDatasources(config, client, stateManager) {
2468
3220
  const stats = { created: 0, updated: 0, unchanged: 0, failed: 0 };
2469
3221
  logger.info("\u{1F4CA} Processing datasources...\n");
@@ -2490,12 +3242,24 @@ async function deployDatasources(config, client, stateManager) {
2490
3242
  ...createParams,
2491
3243
  ...datasource.force_reindex && { full_reindex: true }
2492
3244
  };
2493
- await client.datasources.update(updateParams);
2494
- logger.info(
2495
- `\u2713 ${datasource.force_reindex && !hasChanged ? "Reindexed" : "Updated"} datasource: ${datasource.name} (${existingState.id})`
2496
- );
2497
- stateManager.updateDatasourceState(datasource.name, existingState.id, datasource);
2498
- stats.updated++;
3245
+ try {
3246
+ await client.datasources.update(updateParams);
3247
+ logger.info(
3248
+ `\u2713 ${datasource.force_reindex && !hasChanged ? "Reindexed" : "Updated"} datasource: ${datasource.name} (${existingState.id})`
3249
+ );
3250
+ stateManager.updateDatasourceState(datasource.name, existingState.id, datasource);
3251
+ stats.updated++;
3252
+ } catch (updateError) {
3253
+ if (updateError instanceof Error && updateError.message.includes("unknown not found")) {
3254
+ logger.warn(
3255
+ ` \u26A0\uFE0F Datasource "${datasource.name}" update returned 404 \u2014 likely externally managed and read-only. Skipping update.`
3256
+ );
3257
+ stateManager.updateDatasourceState(datasource.name, existingState.id, datasource);
3258
+ stats.unchanged++;
3259
+ } else {
3260
+ throw updateError;
3261
+ }
3262
+ }
2499
3263
  } else {
2500
3264
  logger.info(` \u2713 No changes detected (ID: ${existingState.id})`);
2501
3265
  stats.unchanged++;
@@ -2503,13 +3267,13 @@ async function deployDatasources(config, client, stateManager) {
2503
3267
  } else {
2504
3268
  logger.info(` \u26A0\uFE0F Datasource ID from state not found on platform, will create new`);
2505
3269
  logger.info(` Creating new datasource...`);
2506
- const datasourceId = await createDatasourceAndGetId(client, createParams, datasource.name);
3270
+ const datasourceId = await createOrRecoverDatasource(client, createParams, datasource.name);
2507
3271
  stateManager.updateDatasourceState(datasource.name, datasourceId, datasource);
2508
3272
  stats.created++;
2509
3273
  }
2510
3274
  } else {
2511
3275
  logger.info(` Creating new datasource...`);
2512
- const datasourceId = await createDatasourceAndGetId(client, createParams, datasource.name);
3276
+ const datasourceId = await createOrRecoverDatasource(client, createParams, datasource.name);
2513
3277
  stateManager.updateDatasourceState(datasource.name, datasourceId, datasource);
2514
3278
  stats.created++;
2515
3279
  }
@@ -2543,11 +3307,11 @@ async function deployWorkflows(config, client, stateManager, rootDir = process.c
2543
3307
  try {
2544
3308
  logger.info(`Processing: ${workflow.name}`);
2545
3309
  const yamlPath = path6.join(rootDir, workflow.definition);
2546
- if (!fs2.existsSync(yamlPath)) {
3310
+ if (!fs6.existsSync(yamlPath)) {
2547
3311
  throw new Error(`Workflow definition file not found: ${workflow.definition}`);
2548
3312
  }
2549
- const yamlConfigContent = fs2.readFileSync(yamlPath, "utf8");
2550
- const workflowYaml = yaml3.parse(yamlConfigContent);
3313
+ const yamlConfigContent = fs6.readFileSync(yamlPath, "utf8");
3314
+ const workflowYaml = yaml5.parse(yamlConfigContent);
2551
3315
  const referencesToResolve = [];
2552
3316
  for (const assistant of workflowYaml.assistants) {
2553
3317
  const isInline = !assistant.assistant_name && assistant.model && assistant.system_prompt;
@@ -2588,7 +3352,7 @@ async function deployWorkflows(config, client, stateManager, rootDir = process.c
2588
3352
  });
2589
3353
  }
2590
3354
  }
2591
- const yamlConfig = yaml3.stringify(workflowYaml);
3355
+ const yamlConfig = yaml5.stringify(workflowYaml);
2592
3356
  const workflowYamlChecksum = calculateChecksum(yamlConfigContent);
2593
3357
  const configChecksum = calculateWorkflowConfigChecksum(workflow);
2594
3358
  const existingState = stateManager.getWorkflowState(workflow.name);
@@ -2707,7 +3471,7 @@ async function deployResources(options) {
2707
3471
  logger.info("\u{1F9F9} Checking for orphaned resources...");
2708
3472
  const cleanupManager = new CleanupManager(client, stateManager);
2709
3473
  const orphaned = cleanupManager.findOrphanedResources(config);
2710
- const totalOrphaned = orphaned.assistants.length + orphaned.datasources.length + orphaned.workflows.length;
3474
+ const totalOrphaned = orphaned.assistants.length + orphaned.datasources.length + orphaned.workflows.length + orphaned.skills.length;
2711
3475
  let deleted = 0;
2712
3476
  if (totalOrphaned > 0) {
2713
3477
  logger.info(`
@@ -2721,12 +3485,15 @@ async function deployResources(options) {
2721
3485
  if (orphaned.workflows.length > 0) {
2722
3486
  logger.info(` \u2022 ${orphaned.workflows.length} workflow(s)`);
2723
3487
  }
3488
+ if (orphaned.skills.length > 0) {
3489
+ logger.info(` \u2022 ${orphaned.skills.length} skill(s)`);
3490
+ }
2724
3491
  if (process.env.SAMPLE_DEPLOY === "1") {
2725
3492
  logger.info("\n\u{1F50E} SAMPLE_DEPLOY=1 -> Skipping orphan deletion (simulation / partial deploy mode)\n");
2726
3493
  } else if (prune) {
2727
3494
  logger.info("\n\u{1F5D1}\uFE0F Deleting orphaned resources (removed from config)...\n");
2728
3495
  const cleanupResult = await cleanupManager.deleteOrphanedResources(orphaned);
2729
- deleted = cleanupResult.deleted.assistants.length + cleanupResult.deleted.datasources.length + cleanupResult.deleted.workflows.length;
3496
+ deleted = cleanupResult.deleted.assistants.length + cleanupResult.deleted.datasources.length + cleanupResult.deleted.workflows.length + cleanupResult.deleted.skills.length;
2730
3497
  if (deleted > 0) {
2731
3498
  logger.info(`
2732
3499
  \u2713 Deleted ${deleted} orphaned resource(s)
@@ -2753,6 +3520,11 @@ async function deployResources(options) {
2753
3520
  updated += datasourceStats.updated;
2754
3521
  unchanged += datasourceStats.unchanged;
2755
3522
  failed += datasourceStats.failed;
3523
+ const skillStats = await deploySkills(config, client, loader, stateManager);
3524
+ created += skillStats.created;
3525
+ updated += skillStats.updated;
3526
+ unchanged += skillStats.unchanged;
3527
+ failed += skillStats.failed;
2756
3528
  const assistantStats = await deployAssistants(config, client, loader, stateManager);
2757
3529
  created += assistantStats.created;
2758
3530
  updated += assistantStats.updated;
@@ -2790,10 +3562,37 @@ async function main2(options) {
2790
3562
  try {
2791
3563
  await deployResources(options);
2792
3564
  process.exit(0);
2793
- } catch {
3565
+ } catch (error) {
3566
+ logger.error("\n\u274C Deployment failed:");
3567
+ logger.error(error instanceof Error ? error.message : String(error));
2794
3568
  process.exit(1);
2795
3569
  }
2796
3570
  }
3571
+ init_logger();
3572
+
3573
+ // src/lib/terminalPromptHelpers.ts
3574
+ init_commandSpinner();
3575
+ async function promptUser(question, defaultValue) {
3576
+ const rl = readline.createInterface({
3577
+ input: process.stdin,
3578
+ output: process.stdout
3579
+ });
3580
+ const displayQuestion = defaultValue ? `${question} (${defaultValue}): ` : `${question}: `;
3581
+ const answer = await pauseSpinnerWhile(
3582
+ () => new Promise((resolve6) => {
3583
+ rl.question(displayQuestion, resolve6);
3584
+ })
3585
+ );
3586
+ rl.close();
3587
+ const trimmedAnswer = answer.trim();
3588
+ return trimmedAnswer || defaultValue || "";
3589
+ }
3590
+ async function confirmOverwrite(filename) {
3591
+ const answer = await promptUser(`File '${filename}' already exists. Overwrite? (y/n)`);
3592
+ return answer.toLowerCase() === "y";
3593
+ }
3594
+
3595
+ // src/destroy.ts
2797
3596
  async function destroyResources(options) {
2798
3597
  logger.info("\u{1F5D1}\uFE0F Destroy all managed resources\n");
2799
3598
  logger.info("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n");
@@ -2805,7 +3604,7 @@ async function destroyResources(options) {
2805
3604
  logger.info(`Project: ${config.project.name}
2806
3605
  `);
2807
3606
  const managed = stateManager.getAllManagedResources();
2808
- const total = managed.assistants.length + managed.datasources.length + managed.workflows.length;
3607
+ const total = managed.assistants.length + managed.datasources.length + managed.workflows.length + managed.skills.length;
2809
3608
  if (total === 0) {
2810
3609
  logger.info("\u2713 No managed resources found in state\n");
2811
3610
  logger.info("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n");
@@ -2831,21 +3630,19 @@ async function destroyResources(options) {
2831
3630
  logger.info(` \u2022 ${name}`);
2832
3631
  }
2833
3632
  }
3633
+ if (managed.skills.length > 0) {
3634
+ logger.info(` \u{1F3AF} Skills: ${managed.skills.length}`);
3635
+ for (const name of managed.skills) {
3636
+ logger.info(` \u2022 ${name}`);
3637
+ }
3638
+ }
2834
3639
  logger.warn("\n\u26A0\uFE0F WARNING: This will DELETE all resources listed above!");
2835
3640
  logger.warn("\u26A0\uFE0F These resources were created through IaC (in state.json)\n");
2836
3641
  logger.info("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n");
2837
3642
  if (force) {
2838
3643
  logger.info("\u{1F680} --force flag detected, skipping confirmation\n");
2839
3644
  } else {
2840
- const readline2 = await import('readline');
2841
- const rl = readline2.createInterface({
2842
- input: process.stdin,
2843
- output: process.stdout
2844
- });
2845
- const answer = await new Promise((resolve6) => {
2846
- rl.question('Type "destroy" to confirm deletion: ', resolve6);
2847
- });
2848
- rl.close();
3645
+ const answer = await promptUser('Type "destroy" to confirm deletion');
2849
3646
  if (answer.trim().toLowerCase() !== "destroy") {
2850
3647
  logger.info("\n\u274C Destruction cancelled\n");
2851
3648
  return;
@@ -2855,7 +3652,7 @@ async function destroyResources(options) {
2855
3652
  const client = await createClient(config);
2856
3653
  const cleanupManager = new CleanupManager(client, stateManager);
2857
3654
  const result = await cleanupManager.deleteOrphanedResources(managed);
2858
- const totalDeleted = result.deleted.assistants.length + result.deleted.datasources.length + result.deleted.workflows.length;
3655
+ const totalDeleted = result.deleted.assistants.length + result.deleted.datasources.length + result.deleted.workflows.length + result.deleted.skills.length;
2859
3656
  logger.info("\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n");
2860
3657
  logger.info("\u{1F4CA} Destruction Summary:\n");
2861
3658
  logger.info(` \u2705 Deleted: ${totalDeleted}`);
@@ -2888,27 +3685,349 @@ async function main3(options) {
2888
3685
  process.exit(1);
2889
3686
  }
2890
3687
  }
2891
- async function promptUser(question, defaultValue) {
2892
- const rl = readline.createInterface({
2893
- input: process.stdin,
2894
- output: process.stdout
2895
- });
2896
- const displayQuestion = defaultValue ? `${question} (${defaultValue}): ` : `${question}: `;
2897
- const answer = await new Promise((resolve6) => {
2898
- rl.question(displayQuestion, resolve6);
3688
+
3689
+ // src/import.ts
3690
+ init_converters();
3691
+ init_fileUtils();
3692
+ init_logger();
3693
+ init_checksumUtils();
3694
+ init_codemieConfigChecksums();
3695
+ var VALID_RESOURCE_TYPES = ["assistant", "datasource", "workflow", "skill"];
3696
+ async function findAssistant(client, slug) {
3697
+ try {
3698
+ const assistant = await withTimeout(
3699
+ client.assistants.getBySlug(slug),
3700
+ TIMEOUTS_MS.ASSISTANT_FETCH,
3701
+ `Timeout fetching assistant by slug "${slug}"`
3702
+ );
3703
+ if (assistant) {
3704
+ return assistant;
3705
+ }
3706
+ } catch {
3707
+ }
3708
+ const match = await findResourceByName2((params) => client.assistants.list(params), slug, "assistant");
3709
+ return withTimeout(
3710
+ client.assistants.get(match.id),
3711
+ TIMEOUTS_MS.ASSISTANT_FETCH,
3712
+ `Timeout fetching assistant "${match.id}"`
3713
+ );
3714
+ }
3715
+ async function findDatasource(client, name) {
3716
+ const match = await findResourceByName2((params) => client.datasources.list(params), name, "datasource");
3717
+ return withTimeout(
3718
+ client.datasources.get(match.id),
3719
+ TIMEOUTS_MS.DATASOURCE_FETCH,
3720
+ `Timeout fetching datasource "${match.id}"`
3721
+ );
3722
+ }
3723
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
3724
+ async function findWorkflow(client, name) {
3725
+ if (UUID_RE.test(name)) {
3726
+ return withTimeout(client.workflows.get(name), TIMEOUTS_MS.WORKFLOW_FETCH, `Timeout fetching workflow "${name}"`);
3727
+ }
3728
+ let match = null;
3729
+ try {
3730
+ match = await findResourceByName2((params) => client.workflows.list(params), name, "workflow");
3731
+ } catch {
3732
+ }
3733
+ if (match) {
3734
+ return withTimeout(
3735
+ client.workflows.get(match.id),
3736
+ TIMEOUTS_MS.WORKFLOW_FETCH,
3737
+ `Timeout fetching workflow "${match.id}"`
3738
+ );
3739
+ }
3740
+ throw new Error(
3741
+ `workflow "${name}" not found on platform via name search. If you know its ID, pass the UUID directly instead of the name.`
3742
+ );
3743
+ }
3744
+ async function findSkill(client, name) {
3745
+ const match = await findResourceByName2((params) => client.skills.list(params), name, "skill");
3746
+ return withTimeout(client.skills.get(match.id), TIMEOUTS_MS.SKILL_FETCH, `Timeout fetching skill "${match.id}"`);
3747
+ }
3748
+ async function findResourceByName2(fetchPage, name, resourceType) {
3749
+ const allResources = [];
3750
+ let page = 0;
3751
+ let hasMore = true;
3752
+ while (hasMore) {
3753
+ const resources = await fetchPage({
3754
+ per_page: PAGINATION.DEFAULT_PAGE_SIZE,
3755
+ page
3756
+ });
3757
+ allResources.push(...resources);
3758
+ if (resources.length < PAGINATION.DEFAULT_PAGE_SIZE) {
3759
+ hasMore = false;
3760
+ } else {
3761
+ page++;
3762
+ }
3763
+ }
3764
+ const lowerName = name.toLowerCase();
3765
+ const matches = allResources.filter((r) => r.name.toLowerCase() === lowerName);
3766
+ if (matches.length === 0) {
3767
+ throw new Error(
3768
+ `${resourceType} "${name}" not found on platform. Found ${allResources.length} ${resourceType}(s) total.`
3769
+ );
3770
+ }
3771
+ if (matches.length > 1) {
3772
+ const names = matches.map((m) => ` - ${m.name} (${m.id})`).join("\n");
3773
+ throw new Error(`Multiple ${resourceType}s matching "${name}" found:
3774
+ ${names}
3775
+ Please use a more specific name.`);
3776
+ }
3777
+ return matches[0];
3778
+ }
3779
+ function checkResourceExists2(config, resourceType, name) {
3780
+ const pluralType = resourceType === "assistant" ? "assistants" : resourceType === "datasource" ? "datasources" : resourceType === "workflow" ? "workflows" : "skills";
3781
+ const resources = config.resources[pluralType] || [];
3782
+ return resources.some((r) => r.name.toLowerCase() === name.toLowerCase());
3783
+ }
3784
+ function writeResourceFiles(rootDir, resourceType, resource, apiResponse) {
3785
+ const safeName = sanitizeFileName(resource.name);
3786
+ if (resourceType === "workflow") {
3787
+ const workflow = apiResponse;
3788
+ const workflowResource = resource;
3789
+ if (workflow.yaml_config) {
3790
+ const defFileName = `${safeName}.yaml`;
3791
+ const defPath = path6.join(rootDir, "workflows", defFileName);
3792
+ ensureDirectoryExists(defPath);
3793
+ fs6.writeFileSync(defPath, workflow.yaml_config, "utf8");
3794
+ logger.info(` \u{1F4CB} Saved workflow definition: workflows/${defFileName}`);
3795
+ workflowResource.definition = `workflows/${defFileName}`;
3796
+ }
3797
+ const configPath = path6.join(rootDir, "configs", "workflows", `${safeName}.yaml`);
3798
+ const relativeConfigPath = `configs/workflows/${safeName}.yaml`;
3799
+ ensureDirectoryExists(configPath);
3800
+ const yamlContent2 = yaml5.stringify(workflowResource, { lineWidth: 120 });
3801
+ fs6.writeFileSync(configPath, yamlContent2, "utf8");
3802
+ logger.info(` \u{1F4C4} Saved workflow config: ${relativeConfigPath}`);
3803
+ return relativeConfigPath;
3804
+ }
3805
+ const pluralType = resourceType === "assistant" ? "assistants" : resourceType === "datasource" ? "datasources" : "skills";
3806
+ const resourceDir = path6.join(rootDir, "configs", "imported-resources", pluralType);
3807
+ const resourceFilePath = path6.join(resourceDir, `${safeName}.yaml`);
3808
+ const relativeResourcePath = `configs/imported-resources/${pluralType}/${safeName}.yaml`;
3809
+ if (resourceType === "assistant") {
3810
+ const assistant = apiResponse;
3811
+ const assistantResource = resource;
3812
+ if (assistant.system_prompt) {
3813
+ const promptFileName = assistant.slug || safeName;
3814
+ const promptPath = path6.join(rootDir, "system_prompts", `${promptFileName}.prompt.md`);
3815
+ ensureDirectoryExists(promptPath);
3816
+ fs6.writeFileSync(promptPath, assistant.system_prompt, "utf8");
3817
+ logger.info(` \u{1F4DD} Saved prompt: system_prompts/${promptFileName}.prompt.md`);
3818
+ assistantResource.prompt = `system_prompts/${promptFileName}.prompt.md`;
3819
+ }
3820
+ } else if (resourceType === "skill") {
3821
+ const skill = apiResponse;
3822
+ const skillResource = resource;
3823
+ if (skill.content) {
3824
+ const skillFileName = `${safeName}.skill.md`;
3825
+ const skillPath = path6.join(rootDir, "skills", skillFileName);
3826
+ ensureDirectoryExists(skillPath);
3827
+ fs6.writeFileSync(skillPath, skill.content, "utf8");
3828
+ logger.info(` \u{1F9E0} Saved skill content: skills/${skillFileName}`);
3829
+ skillResource.file = `skills/${skillFileName}`;
3830
+ }
3831
+ }
3832
+ ensureDirectoryExists(resourceFilePath);
3833
+ const yamlContent = yaml5.stringify(resource, { lineWidth: 120 });
3834
+ fs6.writeFileSync(resourceFilePath, yamlContent, "utf8");
3835
+ logger.info(` \u{1F4C4} Saved resource config: ${relativeResourcePath}`);
3836
+ return relativeResourcePath;
3837
+ }
3838
+ function addImportToCodemieYaml(rootDir, codemieConfigPath, resourceType, relativeResourcePath) {
3839
+ const configPath = path6.join(rootDir, codemieConfigPath);
3840
+ if (!fs6.existsSync(configPath)) {
3841
+ throw new Error(`Configuration file not found: ${configPath}`);
3842
+ }
3843
+ const content = fs6.readFileSync(configPath, "utf8");
3844
+ const doc = yaml5.parseDocument(content);
3845
+ const pluralType = resourceType === "assistant" ? "assistants" : resourceType === "datasource" ? "datasources" : resourceType === "workflow" ? "workflows" : "skills";
3846
+ let resourcesNode = doc.get("resources");
3847
+ if (!resourcesNode) {
3848
+ resourcesNode = doc.createNode({});
3849
+ doc.set("resources", resourcesNode);
3850
+ }
3851
+ let typeArray = doc.getIn(["resources", pluralType]);
3852
+ if (!typeArray) {
3853
+ typeArray = doc.createNode([]);
3854
+ doc.setIn(["resources", pluralType], typeArray);
3855
+ }
3856
+ const importDirective = { $import: relativeResourcePath };
3857
+ const existingImports = typeArray.items.filter((item) => {
3858
+ if (yaml5.isMap(item)) {
3859
+ const importValue = item.get("$import");
3860
+ return importValue === relativeResourcePath;
3861
+ }
3862
+ return false;
2899
3863
  });
2900
- rl.close();
2901
- const trimmedAnswer = answer.trim();
2902
- return trimmedAnswer || defaultValue || "";
3864
+ if (existingImports.length > 0) {
3865
+ logger.info(` \u21B7 Import directive already exists in ${codemieConfigPath}`);
3866
+ return;
3867
+ }
3868
+ const newNode = doc.createNode(importDirective);
3869
+ typeArray.add(newNode);
3870
+ fs6.writeFileSync(configPath, doc.toString(), "utf8");
3871
+ logger.info(` \u2705 Added $import to ${codemieConfigPath} \u2192 resources.${pluralType}`);
2903
3872
  }
2904
- async function confirmOverwrite(filename) {
2905
- const answer = await promptUser(`File '${filename}' already exists. Overwrite? (y/n)`);
2906
- return answer.toLowerCase() === "y";
3873
+ function updateState(stateManager, resourceType, resource, apiResponse) {
3874
+ switch (resourceType) {
3875
+ case "assistant": {
3876
+ const assistant = apiResponse;
3877
+ const assistantResource = resource;
3878
+ stateManager.updateAssistantState(resource.name, assistant.id, assistant.system_prompt || "", assistantResource);
3879
+ break;
3880
+ }
3881
+ case "datasource": {
3882
+ const datasource = apiResponse;
3883
+ const datasourceResource = resource;
3884
+ stateManager.updateDatasourceState(resource.name, datasource.id, datasourceResource);
3885
+ break;
3886
+ }
3887
+ case "workflow": {
3888
+ const workflow = apiResponse;
3889
+ const workflowResource = resource;
3890
+ stateManager.updateWorkflowState(
3891
+ resource.name,
3892
+ workflow.id,
3893
+ calculateChecksum(workflow.yaml_config || ""),
3894
+ calculateWorkflowConfigChecksum(workflowResource)
3895
+ );
3896
+ break;
3897
+ }
3898
+ case "skill": {
3899
+ const skill = apiResponse;
3900
+ const skillResource = resource;
3901
+ stateManager.updateSkillState(resource.name, skill.id, skill.content || "", skillResource);
3902
+ break;
3903
+ }
3904
+ }
2907
3905
  }
2908
-
2909
- // src/lib/initHelpers.ts
3906
+ function buildIntegrationAliasMap(config) {
3907
+ const map = /* @__PURE__ */ new Map();
3908
+ for (const integration of config.imported?.integrations || []) {
3909
+ if (integration.id && integration.alias) {
3910
+ map.set(integration.id, integration.alias);
3911
+ }
3912
+ }
3913
+ return map;
3914
+ }
3915
+ async function importResource(options) {
3916
+ const { appConfig, resourceType, slug } = options;
3917
+ if (!VALID_RESOURCE_TYPES.includes(resourceType)) {
3918
+ throw new Error(`Invalid resource type: "${resourceType}". Must be one of: ${VALID_RESOURCE_TYPES.join(", ")}`);
3919
+ }
3920
+ const configLoader = new CodemieConfigLoader(appConfig);
3921
+ const config = configLoader.loadConfig();
3922
+ const rootDir = appConfig.rootDir;
3923
+ const stateManager = new StateManager(appConfig);
3924
+ if (checkResourceExists2(config, resourceType, slug)) {
3925
+ throw new Error(
3926
+ `A ${resourceType} named "${slug}" already exists in the configuration. Remove it first or use a different name.`
3927
+ );
3928
+ }
3929
+ logger.info(`
3930
+ \u{1F50D} Importing ${resourceType}: "${slug}"
3931
+ `);
3932
+ logger.info(" Connecting to CodeMie platform...");
3933
+ const client = await createClient(config);
3934
+ const integrationAliasMap = buildIntegrationAliasMap(config);
3935
+ let resource;
3936
+ let apiResponse;
3937
+ switch (resourceType) {
3938
+ case "assistant": {
3939
+ logger.info(` Searching for assistant "${slug}"...`);
3940
+ const assistant = await findAssistant(client, slug);
3941
+ logger.info(` \u2713 Found: ${assistant.name} (${assistant.id})`);
3942
+ apiResponse = assistant;
3943
+ resource = assistantResponseToResource(assistant);
3944
+ if (integrationAliasMap.size > 0) {
3945
+ const { transformToolkits: transformToolkits2, transformMcpServer: transformMcpServer2 } = await Promise.resolve().then(() => (init_backupTransformers(), backupTransformers_exports));
3946
+ const transformedToolkits = transformToolkits2(assistant.toolkits, integrationAliasMap);
3947
+ const transformedMcpServers = assistant.mcp_servers?.map((mcp) => transformMcpServer2(mcp, integrationAliasMap));
3948
+ if (transformedToolkits) {
3949
+ resource.toolkits = transformedToolkits;
3950
+ }
3951
+ if (transformedMcpServers) {
3952
+ resource.mcp_servers = transformedMcpServers;
3953
+ }
3954
+ }
3955
+ const assistantData = assistant;
3956
+ if (assistantData.skill_ids && assistantData.skill_ids.length > 0) {
3957
+ logger.info(` Resolving ${assistantData.skill_ids.length} skill ID(s)...`);
3958
+ const skillNames = [];
3959
+ for (const skillId of assistantData.skill_ids) {
3960
+ try {
3961
+ const skill = await withTimeout(
3962
+ client.skills.get(skillId),
3963
+ TIMEOUTS_MS.ASSISTANT_FETCH,
3964
+ `Timeout fetching skill "${skillId}"`
3965
+ );
3966
+ skillNames.push(skill.name);
3967
+ logger.info(` \u2713 Resolved skill ${skillId.slice(0, 8)}... \u2192 "${skill.name}"`);
3968
+ } catch {
3969
+ logger.warn(` \u26A0\uFE0F Could not resolve skill ID "${skillId}" \u2014 skipping`);
3970
+ }
3971
+ }
3972
+ if (skillNames.length > 0) {
3973
+ resource.skills = skillNames;
3974
+ }
3975
+ }
3976
+ break;
3977
+ }
3978
+ case "datasource": {
3979
+ logger.info(` Searching for datasource "${slug}"...`);
3980
+ const datasource = await findDatasource(client, slug);
3981
+ logger.info(` \u2713 Found: ${datasource.name} (${datasource.id})`);
3982
+ apiResponse = datasource;
3983
+ const settingId = datasource.setting_id || "";
3984
+ const integrationAlias = integrationAliasMap.get(settingId);
3985
+ resource = datasourceResponseToResource(datasource, integrationAlias);
3986
+ break;
3987
+ }
3988
+ case "workflow": {
3989
+ logger.info(` Searching for workflow "${slug}"...`);
3990
+ const workflow = await findWorkflow(client, slug);
3991
+ logger.info(` \u2713 Found: ${workflow.name} (${workflow.id})`);
3992
+ if (workflow.name !== slug && checkResourceExists2(config, resourceType, workflow.name)) {
3993
+ throw new Error(
3994
+ `A ${resourceType} named "${workflow.name}" already exists in the configuration. Remove it first or use a different name.`
3995
+ );
3996
+ }
3997
+ apiResponse = workflow;
3998
+ resource = workflowResponseToResource(workflow);
3999
+ break;
4000
+ }
4001
+ case "skill": {
4002
+ logger.info(` Searching for skill "${slug}"...`);
4003
+ const skill = await findSkill(client, slug);
4004
+ logger.info(` \u2713 Found: ${skill.name} (${skill.id})`);
4005
+ apiResponse = skill;
4006
+ resource = skillResponseToResource(skill);
4007
+ break;
4008
+ }
4009
+ }
4010
+ const relativeResourcePath = writeResourceFiles(rootDir, resourceType, resource, apiResponse);
4011
+ addImportToCodemieYaml(rootDir, appConfig.codemieConfig, resourceType, relativeResourcePath);
4012
+ updateState(stateManager, resourceType, resource, apiResponse);
4013
+ logger.info(`
4014
+ \u2705 Successfully imported ${resourceType} "${resource.name}"
4015
+ `);
4016
+ }
4017
+ async function main4(options) {
4018
+ try {
4019
+ await importResource(options);
4020
+ } catch (error) {
4021
+ const errorMessage = error instanceof Error ? error.message : String(error);
4022
+ logger.error(`
4023
+ \u274C Import failed: ${errorMessage}
4024
+ `);
4025
+ process.exitCode = 1;
4026
+ }
4027
+ }
4028
+ init_logger();
2910
4029
  function fileExists(filePath) {
2911
- return fs2.existsSync(filePath);
4030
+ return fs6.existsSync(filePath);
2912
4031
  }
2913
4032
  function generateEnvTemplate() {
2914
4033
  return `# Codemie API Configuration
@@ -2994,7 +4113,7 @@ async function saveConfigFile(config, defaultFilename = "config.json") {
2994
4113
  throw new Error(`Cancelled: Configuration file not saved.`);
2995
4114
  }
2996
4115
  }
2997
- fs2.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
4116
+ fs6.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
2998
4117
  return configFilename;
2999
4118
  }
3000
4119
  function suggestConfigUsage(configFileName) {
@@ -3008,6 +4127,7 @@ function suggestConfigUsage(configFileName) {
3008
4127
  }
3009
4128
 
3010
4129
  // src/init.ts
4130
+ init_logger();
3011
4131
  async function initProject(options) {
3012
4132
  logger.info("\u{1F680} Initialize Codemie IaC Project\n\n");
3013
4133
  try {
@@ -3066,7 +4186,7 @@ async function initProject(options) {
3066
4186
  logger.info("\n\u{1F4C1} Creating configuration files...\n");
3067
4187
  if (shouldCreateEnv) {
3068
4188
  const envContent = generateEnvTemplate();
3069
- fs2.writeFileSync(envPath, envContent, "utf-8");
4189
+ fs6.writeFileSync(envPath, envContent, "utf-8");
3070
4190
  logger.info(`\u2713 Created .env`);
3071
4191
  logger.warn(` \u26A0\uFE0F Remember to fill in your actual credentials!`);
3072
4192
  logger.warn(` \u26A0\uFE0F DO NOT commit .env to Git!
@@ -3074,7 +4194,7 @@ async function initProject(options) {
3074
4194
  }
3075
4195
  if (shouldCreateConfig) {
3076
4196
  const configContent = generateMinimalCodemieYaml(projectName, projectDescription);
3077
- fs2.writeFileSync(configPath, configContent, "utf-8");
4197
+ fs6.writeFileSync(configPath, configContent, "utf-8");
3078
4198
  logger.info(`\u2713 Created ${appConfig.codemieConfig}`);
3079
4199
  logger.info(` See examples/ directory for detailed configuration examples
3080
4200
 
@@ -3101,15 +4221,24 @@ async function initProject(options) {
3101
4221
  process.exit(1);
3102
4222
  }
3103
4223
  }
3104
- async function main4(options) {
4224
+ async function main5(options) {
3105
4225
  await initProject(options);
3106
4226
  }
4227
+
4228
+ // src/cli/index.ts
4229
+ init_commandSpinner();
4230
+ init_logger();
4231
+
4232
+ // src/lib/previewUtils.ts
4233
+ init_checksumUtils();
4234
+ init_codemieConfigChecksums();
4235
+ init_converters();
3107
4236
  async function previewResource(resource, resourceType, getState, checkExists, calculateChecksums) {
3108
4237
  const existingState = getState(resource.name);
3109
4238
  if (existingState) {
3110
4239
  const existsOnPlatform = await checkExists();
3111
4240
  if (existsOnPlatform) {
3112
- const { hasChanged, updateDetails } = calculateChecksums();
4241
+ const { hasChanged, updateDetails } = await calculateChecksums();
3113
4242
  return hasChanged ? {
3114
4243
  type: "update",
3115
4244
  resourceType,
@@ -3129,7 +4258,7 @@ async function previewResource(resource, resourceType, getState, checkExists, ca
3129
4258
  };
3130
4259
  }
3131
4260
  } else {
3132
- const { createDetails } = calculateChecksums();
4261
+ const { createDetails } = await calculateChecksums();
3133
4262
  return {
3134
4263
  type: "create",
3135
4264
  resourceType,
@@ -3167,6 +4296,42 @@ function previewOrphanedResources(orphaned, stateManager) {
3167
4296
  details: workflowState ? `Removed from config (ID: ${workflowState.id})` : "Removed from config"
3168
4297
  });
3169
4298
  }
4299
+ for (const name of orphaned.skills || []) {
4300
+ const skillState = stateManager.getSkillState(name);
4301
+ changes.push({
4302
+ type: "delete",
4303
+ resourceType: "skill",
4304
+ name,
4305
+ details: skillState ? `Removed from config (ID: ${skillState.id})` : "Removed from config"
4306
+ });
4307
+ }
4308
+ return changes;
4309
+ }
4310
+ async function previewSkills(skills, loader, stateManager, client) {
4311
+ const changes = [];
4312
+ for (const skill of skills) {
4313
+ const content = loader.loadSkillContent(skill.file);
4314
+ const contentChecksum = calculateChecksum(content);
4315
+ const configChecksum = calculateSkillConfigChecksum(skill);
4316
+ const change = await previewResource(
4317
+ skill,
4318
+ "skill",
4319
+ (name) => stateManager.getSkillState(name),
4320
+ () => checkSkillExists(client, skill.name, stateManager),
4321
+ () => {
4322
+ const existingState = stateManager.getSkillState(skill.name);
4323
+ const hasChanged = existingState ? existingState.contentChecksum !== contentChecksum || existingState.configChecksum !== configChecksum : false;
4324
+ return {
4325
+ hasChanged,
4326
+ updateDetails: "Content or configuration changed"
4327
+ };
4328
+ }
4329
+ );
4330
+ if (!change.details && change.type === "create") {
4331
+ change.details = skill.visibility === "public" ? "Visibility: public" : "Visibility: project";
4332
+ }
4333
+ changes.push(change);
4334
+ }
3170
4335
  return changes;
3171
4336
  }
3172
4337
  async function previewAssistants(assistants, loader, stateManager, client) {
@@ -3205,10 +4370,19 @@ async function previewDatasources(datasources, stateManager, client) {
3205
4370
  "datasource",
3206
4371
  (name) => stateManager.getDatasourceState(name),
3207
4372
  () => checkDatasourceExists(client, datasource.name, stateManager),
3208
- () => {
4373
+ async () => {
3209
4374
  const configChecksum = calculateDatasourceConfigChecksum(datasource);
3210
4375
  const existingState = stateManager.getDatasourceState(datasource.name);
3211
- const hasChanged = existingState ? existingState.configChecksum !== configChecksum : false;
4376
+ let hasChanged = existingState ? existingState.configChecksum !== configChecksum : false;
4377
+ if (hasChanged && existingState?.id) {
4378
+ try {
4379
+ const datasourceOnPlatform = await client.datasources.get(existingState.id);
4380
+ const normalizedPlatformDatasource = datasourceResponseToResource(datasourceOnPlatform);
4381
+ const platformChecksum = calculateDatasourceConfigChecksum(normalizedPlatformDatasource);
4382
+ hasChanged = platformChecksum !== configChecksum;
4383
+ } catch {
4384
+ }
4385
+ }
3212
4386
  if (hasChanged || datasource.force_reindex) {
3213
4387
  return {
3214
4388
  hasChanged: true,
@@ -3229,10 +4403,10 @@ async function previewWorkflows(workflows, stateManager, client, rootDir = proce
3229
4403
  const changes = [];
3230
4404
  for (const workflow of workflows) {
3231
4405
  const yamlPath = path6.join(rootDir, workflow.definition);
3232
- if (!fs2.existsSync(yamlPath)) {
4406
+ if (!fs6.existsSync(yamlPath)) {
3233
4407
  throw new Error(`Workflow definition file not found: ${workflow.definition}`);
3234
4408
  }
3235
- const yamlConfig = fs2.readFileSync(yamlPath, "utf8");
4409
+ const yamlConfig = fs6.readFileSync(yamlPath, "utf8");
3236
4410
  const change = await previewResource(
3237
4411
  workflow,
3238
4412
  "workflow",
@@ -3267,6 +4441,10 @@ async function previewChanges(appConfig, existingClient) {
3267
4441
  const cleanupManager = new CleanupManager(client, stateManager);
3268
4442
  const orphaned = cleanupManager.findOrphanedResources(config);
3269
4443
  changes.push(...previewOrphanedResources(orphaned, stateManager));
4444
+ if (config.resources.skills) {
4445
+ const skillChanges = await previewSkills(config.resources.skills, loader, stateManager, client);
4446
+ changes.push(...skillChanges);
4447
+ }
3270
4448
  if (config.resources.assistants) {
3271
4449
  const assistantChanges = await previewAssistants(config.resources.assistants, loader, stateManager, client);
3272
4450
  changes.push(...assistantChanges);
@@ -3294,7 +4472,7 @@ async function previewChanges(appConfig, existingClient) {
3294
4472
  }
3295
4473
  };
3296
4474
  }
3297
- async function main5(options) {
4475
+ async function main6(options) {
3298
4476
  logger.info("\u{1F4CB} Generating deployment preview...\n");
3299
4477
  try {
3300
4478
  const { appConfig } = options;
@@ -3402,10 +4580,10 @@ var DependencyValidator = class {
3402
4580
  const errors = [];
3403
4581
  const visited = /* @__PURE__ */ new Set();
3404
4582
  const recursionStack = /* @__PURE__ */ new Set();
3405
- const dfs = (name, path15) => {
4583
+ const dfs = (name, path16) => {
3406
4584
  if (recursionStack.has(name)) {
3407
- const cycleStart = path15.indexOf(name);
3408
- const cycle = [...path15.slice(cycleStart), name];
4585
+ const cycleStart = path16.indexOf(name);
4586
+ const cycle = [...path16.slice(cycleStart), name];
3409
4587
  errors.push(`Cyclic dependency detected: ${cycle.join(" \u2192 ")}`);
3410
4588
  return true;
3411
4589
  }
@@ -3414,14 +4592,14 @@ var DependencyValidator = class {
3414
4592
  }
3415
4593
  visited.add(name);
3416
4594
  recursionStack.add(name);
3417
- path15.push(name);
4595
+ path16.push(name);
3418
4596
  const assistant = assistantMap.get(name);
3419
4597
  const subAssistantRefs = assistant?.sub_assistants || [];
3420
4598
  for (const subRef of subAssistantRefs) {
3421
4599
  if (!assistantMap.has(subRef)) {
3422
4600
  continue;
3423
4601
  }
3424
- if (dfs(subRef, [...path15])) {
4602
+ if (dfs(subRef, [...path16])) {
3425
4603
  return true;
3426
4604
  }
3427
4605
  }
@@ -3480,7 +4658,7 @@ var DependencyValidator = class {
3480
4658
  }
3481
4659
  for (const workflow of workflows) {
3482
4660
  try {
3483
- const workflowYaml = yaml3.parse(workflow.definition);
4661
+ const workflowYaml = yaml5.parse(workflow.definition);
3484
4662
  if (!workflowYaml.assistants) {
3485
4663
  continue;
3486
4664
  }
@@ -3504,6 +4682,9 @@ var DependencyValidator = class {
3504
4682
  }
3505
4683
  };
3506
4684
 
4685
+ // src/validate.ts
4686
+ init_logger();
4687
+
3507
4688
  // src/lib/validationUtils.ts
3508
4689
  function isAssistantWithSlug(obj) {
3509
4690
  return typeof obj === "object" && obj !== null && "slug" in obj && "name" in obj && typeof obj.slug === "string" && typeof obj.name === "string";
@@ -3586,11 +4767,11 @@ async function validateConfig(options) {
3586
4767
  if (config.resources.workflows && config.resources.workflows.length > 0) {
3587
4768
  const workflowsWithContent = config.resources.workflows.map((workflow) => {
3588
4769
  const workflowPath = path6.resolve(workflow.definition);
3589
- if (!fs2.existsSync(workflowPath)) {
4770
+ if (!fs6.existsSync(workflowPath)) {
3590
4771
  errors.push(`Workflow definition file not found: ${workflow.definition}`);
3591
4772
  return { ...workflow, definition: "" };
3592
4773
  }
3593
- const definition = fs2.readFileSync(workflowPath, "utf8");
4774
+ const definition = fs6.readFileSync(workflowPath, "utf8");
3594
4775
  return { ...workflow, definition };
3595
4776
  });
3596
4777
  const workflowErrors = DependencyValidator.validateWorkflowAssistantReferences(
@@ -3609,7 +4790,7 @@ async function validateConfig(options) {
3609
4790
  }
3610
4791
  return { success: errors.length === 0, errors };
3611
4792
  }
3612
- async function main6(options) {
4793
+ async function main7(options) {
3613
4794
  const { checkApi = false, appConfig } = options;
3614
4795
  logger.info("\u{1F50D} Validating configuration...");
3615
4796
  if (checkApi) {
@@ -3656,31 +4837,47 @@ async function main6(options) {
3656
4837
  program.name("codemie").description("Infrastructure as Code for Codemie platform").version(package_default.version);
3657
4838
  program.command("init").description("Initialize a new Codemie IaC project").action(() => {
3658
4839
  const appConfig = loadAppConfig();
3659
- void main4({ appConfig });
4840
+ void main5({ appConfig });
3660
4841
  });
3661
- program.command("deploy").description("Deploy resources to Codemie platform").option("-p, --prune", "Delete orphaned resources").option("-c, --config <path>", "Configuration file").action((options) => {
4842
+ program.command("deploy").description("Deploy resources to Codemie platform").option("-p, --prune", "Delete orphaned resources").option("-c, --config <path>", "Configuration file").action(async (options) => {
3662
4843
  const configPath = options.config ? path6__default.resolve(options.config) : void 0;
3663
4844
  const appConfig = loadAppConfig(configPath);
3664
- void main2({ appConfig, prune: options.prune });
4845
+ await runCommandWithSpinner("Deploying Codemie resources", () => main2({ appConfig, prune: options.prune }));
3665
4846
  });
3666
- program.command("backup").description("Backup all resources from Codemie platform").option("-c, --config <path>", "Configuration file").action((options) => {
4847
+ program.command("backup").description("Backup all resources from Codemie platform").option("-c, --config <path>", "Configuration file").action(async (options) => {
3667
4848
  const configPath = options.config ? path6__default.resolve(options.config) : void 0;
3668
4849
  const appConfig = loadAppConfig(configPath);
3669
- void main({ appConfig });
4850
+ await runCommandWithSpinner("Backing up Codemie resources", () => main({ appConfig }));
3670
4851
  });
3671
- program.command("destroy").description("Destroy all IaC-managed resources").option("-f, --force", "Skip confirmation prompt").option("-c, --config <path>", "Configuration file").action((options) => {
4852
+ program.command("destroy").description("Destroy all IaC-managed resources").option("-f, --force", "Skip confirmation prompt").option("-c, --config <path>", "Configuration file").action(async (options) => {
3672
4853
  const configPath = options.config ? path6__default.resolve(options.config) : void 0;
3673
4854
  const appConfig = loadAppConfig(configPath);
3674
- void main3({ appConfig, force: options.force });
4855
+ await runCommandWithSpinner("Destroying Codemie resources", () => main3({ appConfig, force: options.force }));
3675
4856
  });
3676
- program.command("preview").description("Preview changes without applying them").option("-c, --config <path>", "Configuration file").action((options) => {
4857
+ program.command("preview").description("Preview changes without applying them").option("-c, --config <path>", "Configuration file").action(async (options) => {
3677
4858
  const appConfigPath = options.config ? path6__default.resolve(options.config) : void 0;
3678
4859
  const appConfig = loadAppConfig(appConfigPath);
3679
- void main5({ appConfig });
4860
+ await runCommandWithSpinner("Generating Codemie preview", () => main6({ appConfig }));
4861
+ });
4862
+ program.command("validate").description("Validate configuration").option("-a, --check-api", "Check for API conflicts").option("-c, --config <path>", "Configuration file").action(async (options) => {
4863
+ const configPath = options.config ? path6__default.resolve(options.config) : void 0;
4864
+ const appConfig = loadAppConfig(configPath);
4865
+ await runCommandWithSpinner(
4866
+ "Validating Codemie configuration",
4867
+ () => main7({ appConfig, checkApi: options.checkApi })
4868
+ );
3680
4869
  });
3681
- program.command("validate").description("Validate configuration").option("-a, --check-api", "Check for API conflicts").option("-c, --config <path>", "Configuration file").action((options) => {
4870
+ program.command("import").description("Import a single resource from the CodeMie platform into the local IaC configuration").argument("<type>", "Resource type: assistant, datasource, workflow, or skill").argument("<slug>", "Resource slug or name to import").option("-c, --config <path>", "Configuration file").action(async (type, slug, options) => {
4871
+ const validTypes = ["assistant", "datasource", "workflow", "skill"];
4872
+ if (!validTypes.includes(type)) {
4873
+ process.exitCode = 1;
4874
+ return;
4875
+ }
3682
4876
  const configPath = options.config ? path6__default.resolve(options.config) : void 0;
3683
4877
  const appConfig = loadAppConfig(configPath);
3684
- void main6({ appConfig, checkApi: options.checkApi });
4878
+ await runCommandWithSpinner(
4879
+ `Importing ${type}`,
4880
+ () => main4({ appConfig, resourceType: type, slug })
4881
+ );
3685
4882
  });
3686
4883
  program.parse();