@codemieai/cdk 0.1.631 → 0.1.633

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
@@ -2,10 +2,10 @@
2
2
  import ora from 'ora';
3
3
  import * as crypto from 'crypto';
4
4
  import { DataSourceType, CodeMieClient, SkillVisibility } from 'codemie-sdk';
5
- import * as fs6 from 'fs';
6
- import * as path6 from 'path';
7
- import path6__default from 'path';
8
- import * as yaml5 from 'yaml';
5
+ import * as fs11 from 'fs';
6
+ import * as path16 from 'path';
7
+ import path16__default from 'path';
8
+ import * as yaml6 from 'yaml';
9
9
  import { program } from '@commander-js/extra-typings';
10
10
  import { z } from 'zod';
11
11
  import 'dotenv/config';
@@ -751,15 +751,15 @@ function sanitizeFileName(name, maxLength = 255) {
751
751
  }
752
752
  function validateBackupDirectory(backupDir, minSpaceGB = 1) {
753
753
  try {
754
- const parentDir = path6.dirname(backupDir);
755
- if (!fs6.existsSync(parentDir)) {
756
- fs6.mkdirSync(parentDir, { recursive: true });
754
+ const parentDir = path16.dirname(backupDir);
755
+ if (!fs11.existsSync(parentDir)) {
756
+ fs11.mkdirSync(parentDir, { recursive: true });
757
757
  }
758
- const testFile = path6.join(parentDir, ".write-test");
759
- fs6.writeFileSync(testFile, "test");
760
- fs6.unlinkSync(testFile);
758
+ const testFile = path16.join(parentDir, ".write-test");
759
+ fs11.writeFileSync(testFile, "test");
760
+ fs11.unlinkSync(testFile);
761
761
  try {
762
- const stats = fs6.statfsSync(parentDir);
762
+ const stats = fs11.statfsSync(parentDir);
763
763
  const availableGB = stats.bavail * stats.bsize / BYTES_IN_GB;
764
764
  if (availableGB < minSpaceGB) {
765
765
  throw new Error(`Insufficient disk space: ${availableGB.toFixed(2)}GB available, need ${minSpaceGB}GB`);
@@ -775,7 +775,7 @@ function validateBackupDirectory(backupDir, minSpaceGB = 1) {
775
775
  }
776
776
  function moveAtomically(tempPath, finalPath) {
777
777
  try {
778
- fs6.renameSync(tempPath, finalPath);
778
+ fs11.renameSync(tempPath, finalPath);
779
779
  } catch (error) {
780
780
  const err = error;
781
781
  if (err.code === "EEXIST") {
@@ -785,14 +785,14 @@ function moveAtomically(tempPath, finalPath) {
785
785
  }
786
786
  }
787
787
  function cleanupDirectory(dirPath) {
788
- if (fs6.existsSync(dirPath)) {
789
- fs6.rmSync(dirPath, { recursive: true, force: true });
788
+ if (fs11.existsSync(dirPath)) {
789
+ fs11.rmSync(dirPath, { recursive: true, force: true });
790
790
  }
791
791
  }
792
792
  function ensureDirectoryExists(filePath) {
793
- const dir = path6.dirname(filePath);
794
- if (!fs6.existsSync(dir)) {
795
- fs6.mkdirSync(dir, { recursive: true });
793
+ const dir = path16.dirname(filePath);
794
+ if (!fs11.existsSync(dir)) {
795
+ fs11.mkdirSync(dir, { recursive: true });
796
796
  }
797
797
  }
798
798
  var BYTES_IN_GB;
@@ -931,7 +931,7 @@ function prepareWorkflowForYaml(workflow, state, assistants, backupDir) {
931
931
  let finalYamlContent = yamlConfig || "";
932
932
  if (yamlConfig) {
933
933
  try {
934
- const workflowYaml = yaml5.parse(yamlConfig);
934
+ const workflowYaml = yaml6.parse(yamlConfig);
935
935
  const transformedAssistants = workflowYaml.assistants?.map((assistant) => {
936
936
  if (assistant.assistant_id && typeof assistant.assistant_id === "string") {
937
937
  const assistantId = assistant.assistant_id;
@@ -946,10 +946,10 @@ function prepareWorkflowForYaml(workflow, state, assistants, backupDir) {
946
946
  if (transformedAssistants) {
947
947
  const transformedYaml = { ...workflowYaml, assistants: transformedAssistants };
948
948
  const fileName = `${sanitizeFileName(workflow.name)}.yaml`;
949
- const filePath = path6.join(backupDir, "workflows", fileName);
949
+ const filePath = path16.join(backupDir, "workflows", fileName);
950
950
  ensureDirectoryExists(filePath);
951
- finalYamlContent = yaml5.stringify(transformedYaml);
952
- fs6.writeFileSync(filePath, finalYamlContent, "utf8");
951
+ finalYamlContent = yaml6.stringify(transformedYaml);
952
+ fs11.writeFileSync(filePath, finalYamlContent, "utf8");
953
953
  }
954
954
  } catch (error) {
955
955
  logger.warn(
@@ -1023,7 +1023,7 @@ var init_backupTransformers = __esm({
1023
1023
 
1024
1024
  // package.json
1025
1025
  var package_default = {
1026
- version: "0.1.631"};
1026
+ version: "0.1.633"};
1027
1027
  var appConfigSchema = z.object({
1028
1028
  rootDir: z.string(),
1029
1029
  codemieConfig: z.string(),
@@ -1041,23 +1041,49 @@ var defaultConfig = {
1041
1041
  };
1042
1042
 
1043
1043
  // src/appConfig/configLoader.ts
1044
- function loadAppConfig(userConfigPath) {
1045
- if (!userConfigPath) {
1046
- return defaultConfig;
1044
+ init_logger();
1045
+ var PROJECT_MARKER_FILE = "codemie.yaml";
1046
+ function loadAppConfig(userConfigPath, rootDir) {
1047
+ if (userConfigPath) {
1048
+ const userConfig = loadUserConfig(userConfigPath);
1049
+ const sanitizedUserConfig = removeEmptyFields(userConfig);
1050
+ const merged = { ...defaultConfig, ...sanitizedUserConfig };
1051
+ if (sanitizedUserConfig.rootDir === void 0 && rootDir) {
1052
+ merged.rootDir = path16.resolve(rootDir);
1053
+ }
1054
+ return appConfigSchema.parse(merged);
1055
+ }
1056
+ if (rootDir) {
1057
+ return { ...defaultConfig, rootDir: path16.resolve(rootDir) };
1058
+ }
1059
+ const detectedRoot = findProjectRoot(process.cwd());
1060
+ if (detectedRoot) {
1061
+ logger.info(`Using detected project root: ${detectedRoot}`);
1062
+ return { ...defaultConfig, rootDir: detectedRoot };
1063
+ }
1064
+ return defaultConfig;
1065
+ }
1066
+ function findProjectRoot(startDir) {
1067
+ let dir = path16.resolve(startDir);
1068
+ while (true) {
1069
+ if (fs11.existsSync(path16.join(dir, PROJECT_MARKER_FILE))) {
1070
+ return dir;
1071
+ }
1072
+ const parentDir = path16.dirname(dir);
1073
+ if (parentDir === dir || dir === path16.parse(dir).root) {
1074
+ return null;
1075
+ }
1076
+ dir = parentDir;
1047
1077
  }
1048
- const userConfig = loadUserConfig(userConfigPath);
1049
- const sanitizedUserConfig = removeEmptyFields(userConfig);
1050
- const merged = { ...defaultConfig, ...sanitizedUserConfig };
1051
- return appConfigSchema.parse(merged);
1052
1078
  }
1053
1079
  function loadUserConfig(configPath) {
1054
- const resolvedPath = path6.resolve(process.cwd(), configPath);
1055
- if (!fs6.existsSync(resolvedPath)) {
1080
+ const resolvedPath = path16.resolve(process.cwd(), configPath);
1081
+ if (!fs11.existsSync(resolvedPath)) {
1056
1082
  throw new Error(`Config file not found: ${resolvedPath}`);
1057
1083
  }
1058
1084
  let raw;
1059
1085
  try {
1060
- raw = fs6.readFileSync(resolvedPath, "utf8");
1086
+ raw = fs11.readFileSync(resolvedPath, "utf8");
1061
1087
  } catch (error) {
1062
1088
  throw new Error(`Failed to read config file: ${resolvedPath}`, { cause: error });
1063
1089
  }
@@ -1119,9 +1145,9 @@ var BackupTransaction = class {
1119
1145
  isDirty = false;
1120
1146
  saveTimer;
1121
1147
  constructor(backupDir, transactionId) {
1122
- this.transactionPath = path6.join(backupDir, "transaction.json");
1148
+ this.transactionPath = path16.join(backupDir, "transaction.json");
1123
1149
  try {
1124
- const content = fs6.readFileSync(this.transactionPath, "utf8");
1150
+ const content = fs11.readFileSync(this.transactionPath, "utf8");
1125
1151
  this.data = JSON.parse(content);
1126
1152
  logger.info(`\u{1F4C2} Resuming backup transaction ${this.data.id}`);
1127
1153
  } catch (error) {
@@ -1153,9 +1179,9 @@ var BackupTransaction = class {
1153
1179
  }
1154
1180
  writeTransactionExclusively(data) {
1155
1181
  try {
1156
- const fd = fs6.openSync(this.transactionPath, "wx");
1157
- fs6.writeSync(fd, JSON.stringify(data, null, 2));
1158
- fs6.closeSync(fd);
1182
+ const fd = fs11.openSync(this.transactionPath, "wx");
1183
+ fs11.writeSync(fd, JSON.stringify(data, null, 2));
1184
+ fs11.closeSync(fd);
1159
1185
  } catch (writeError) {
1160
1186
  const writeErr = writeError;
1161
1187
  if (writeErr.code === "EEXIST") {
@@ -1174,7 +1200,7 @@ var BackupTransaction = class {
1174
1200
  if (delayMs > 0) {
1175
1201
  busyWaitDelay(delayMs);
1176
1202
  }
1177
- const content = fs6.readFileSync(this.transactionPath, "utf8");
1203
+ const content = fs11.readFileSync(this.transactionPath, "utf8");
1178
1204
  this.data = JSON.parse(content);
1179
1205
  logger.info(`\u{1F4C2} Resuming backup transaction ${this.data.id} (created by concurrent process)`);
1180
1206
  break;
@@ -1197,7 +1223,7 @@ var BackupTransaction = class {
1197
1223
  */
1198
1224
  async save() {
1199
1225
  ensureDirectoryExists(this.transactionPath);
1200
- const savePromise = fs6.promises.writeFile(this.transactionPath, JSON.stringify(this.data, null, 2), "utf8");
1226
+ const savePromise = fs11.promises.writeFile(this.transactionPath, JSON.stringify(this.data, null, 2), "utf8");
1201
1227
  let timeoutId;
1202
1228
  const timeoutPromise = new Promise((_, reject) => {
1203
1229
  timeoutId = setTimeout(
@@ -1223,7 +1249,7 @@ var BackupTransaction = class {
1223
1249
  */
1224
1250
  saveSyncInternal() {
1225
1251
  ensureDirectoryExists(this.transactionPath);
1226
- fs6.writeFileSync(this.transactionPath, JSON.stringify(this.data, null, 2), "utf8");
1252
+ fs11.writeFileSync(this.transactionPath, JSON.stringify(this.data, null, 2), "utf8");
1227
1253
  this.isDirty = false;
1228
1254
  }
1229
1255
  /**
@@ -1334,8 +1360,8 @@ var BackupTransaction = class {
1334
1360
  * Clean up transaction file after successful completion
1335
1361
  */
1336
1362
  cleanup() {
1337
- if (fs6.existsSync(this.transactionPath)) {
1338
- fs6.unlinkSync(this.transactionPath);
1363
+ if (fs11.existsSync(this.transactionPath)) {
1364
+ fs11.unlinkSync(this.transactionPath);
1339
1365
  }
1340
1366
  }
1341
1367
  };
@@ -1425,10 +1451,10 @@ function generateCodemieYaml(backup, projectName, backupDir, integrationSpecPath
1425
1451
  }
1426
1452
  }
1427
1453
  };
1428
- return yaml5.stringify(config);
1454
+ return yaml6.stringify(config);
1429
1455
  }
1430
1456
  function saveIntegrationOpenApiSpecs(backupData, backupDir) {
1431
- const specsDir = path6.join(backupDir, "openapi_specs");
1457
+ const specsDir = path16.join(backupDir, "openapi_specs");
1432
1458
  let specsCount = 0;
1433
1459
  const integrationSpecPaths = /* @__PURE__ */ new Map();
1434
1460
  for (const integration of backupData.resources.integrations) {
@@ -1441,8 +1467,8 @@ function saveIntegrationOpenApiSpecs(backupData, backupDir) {
1441
1467
  if (specEntry && typeof specEntry.value === "string" && specEntry.value) {
1442
1468
  const alias = integration.alias || integration.id;
1443
1469
  const fileName = String(alias).toLowerCase().replaceAll(/[^a-z0-9]+/g, "-");
1444
- if (!fs6.existsSync(specsDir)) {
1445
- fs6.mkdirSync(specsDir, { recursive: true });
1470
+ if (!fs11.existsSync(specsDir)) {
1471
+ fs11.mkdirSync(specsDir, { recursive: true });
1446
1472
  }
1447
1473
  const specContent = specEntry.value;
1448
1474
  let fileExtension = ".yaml";
@@ -1455,8 +1481,8 @@ function saveIntegrationOpenApiSpecs(backupData, backupDir) {
1455
1481
  fileExtension = ".yaml";
1456
1482
  }
1457
1483
  const specFileName = `${fileName}${fileExtension}`;
1458
- const specPath = path6.join(specsDir, specFileName);
1459
- fs6.writeFileSync(specPath, contentToSave, "utf8");
1484
+ const specPath = path16.join(specsDir, specFileName);
1485
+ fs11.writeFileSync(specPath, contentToSave, "utf8");
1460
1486
  integrationSpecPaths.set(integration.id, `openapi_specs/${specFileName}`);
1461
1487
  specsCount++;
1462
1488
  }
@@ -1469,15 +1495,15 @@ function saveIntegrationOpenApiSpecs(backupData, backupDir) {
1469
1495
  function saveBackupFiles(backupData, backupDir, projectName) {
1470
1496
  logger.info("\u{1F4BE} Writing backup files...\n");
1471
1497
  const integrationSpecPaths = saveIntegrationOpenApiSpecs(backupData, backupDir);
1472
- const codemieYamlPath = path6.join(backupDir, "codemie.yaml");
1498
+ const codemieYamlPath = path16.join(backupDir, "codemie.yaml");
1473
1499
  const codemieYaml = generateCodemieYaml(backupData, projectName, backupDir, integrationSpecPaths);
1474
- fs6.writeFileSync(codemieYamlPath, codemieYaml, "utf8");
1500
+ fs11.writeFileSync(codemieYamlPath, codemieYaml, "utf8");
1475
1501
  logger.info(` \u2713 Saved config file: codemie.yaml`);
1476
- const backupJsonPath = path6.join(backupDir, "backup.json");
1477
- fs6.writeFileSync(backupJsonPath, JSON.stringify(backupData, null, 2), "utf8");
1502
+ const backupJsonPath = path16.join(backupDir, "backup.json");
1503
+ fs11.writeFileSync(backupJsonPath, JSON.stringify(backupData, null, 2), "utf8");
1478
1504
  logger.info(` \u2713 Saved full backup: backup.json`);
1479
- const statePath = path6.join(backupDir, "state.json");
1480
- fs6.writeFileSync(statePath, JSON.stringify(backupData.state, null, 2), "utf8");
1505
+ const statePath = path16.join(backupDir, "state.json");
1506
+ fs11.writeFileSync(statePath, JSON.stringify(backupData.state, null, 2), "utf8");
1481
1507
  logger.info(` \u2713 Saved state file: state.json`);
1482
1508
  }
1483
1509
  async function createClient(config) {
@@ -1507,6 +1533,141 @@ function namesEqual(a, b) {
1507
1533
  return a.toLowerCase() === b.toLowerCase();
1508
1534
  }
1509
1535
 
1536
+ // src/lib/configImports.ts
1537
+ function isPlainObject3(value) {
1538
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1539
+ }
1540
+ function resolveImports(obj, baseDir, visitedFiles = /* @__PURE__ */ new Set()) {
1541
+ if (!isPlainObject3(obj)) {
1542
+ return;
1543
+ }
1544
+ for (const key in obj) {
1545
+ const value = obj[key];
1546
+ if (isPlainObject3(value) && "$import" in value) {
1547
+ const importDirective = value;
1548
+ const importPath = path16.join(baseDir, importDirective.$import);
1549
+ const normalizedPath = path16.resolve(importPath);
1550
+ if (visitedFiles.has(normalizedPath)) {
1551
+ throw new Error(
1552
+ `Circular import detected: ${normalizedPath}
1553
+ Import chain: ${[...visitedFiles].join(" \u2192 ")} \u2192 ${normalizedPath}`
1554
+ );
1555
+ }
1556
+ visitedFiles.add(normalizedPath);
1557
+ const imported = loadYamlFile(importPath);
1558
+ obj[key] = imported;
1559
+ resolveImports(obj[key], path16.dirname(importPath), visitedFiles);
1560
+ visitedFiles.delete(normalizedPath);
1561
+ } else if (Array.isArray(value)) {
1562
+ obj[key] = resolveArrayImports(value, baseDir, visitedFiles);
1563
+ } else if (isPlainObject3(value)) {
1564
+ resolveImports(value, baseDir, visitedFiles);
1565
+ }
1566
+ }
1567
+ }
1568
+ function resolveArrayImports(arr, baseDir, visitedFiles = /* @__PURE__ */ new Set()) {
1569
+ const result = [];
1570
+ for (const item of arr) {
1571
+ if (isPlainObject3(item) && "$import" in item) {
1572
+ const importDirective = item;
1573
+ const importPath = path16.join(baseDir, importDirective.$import);
1574
+ const normalizedPath = path16.resolve(importPath);
1575
+ if (visitedFiles.has(normalizedPath)) {
1576
+ throw new Error(
1577
+ `Circular import detected: ${normalizedPath}
1578
+ Import chain: ${[...visitedFiles].join(" \u2192 ")} \u2192 ${normalizedPath}`
1579
+ );
1580
+ }
1581
+ visitedFiles.add(normalizedPath);
1582
+ const imported = loadYamlFile(importPath);
1583
+ if (Array.isArray(imported)) {
1584
+ const resolvedArray = resolveArrayImports(imported, path16.dirname(importPath), visitedFiles);
1585
+ for (const resolvedItem of resolvedArray) {
1586
+ result.push(resolvedItem);
1587
+ }
1588
+ } else if (isPlainObject3(imported)) {
1589
+ result.push(imported);
1590
+ resolveImports(imported, path16.dirname(importPath), visitedFiles);
1591
+ } else {
1592
+ throw new TypeError(
1593
+ `Import file ${importPath} must contain either an object or an array. Got: ${typeof imported}`
1594
+ );
1595
+ }
1596
+ visitedFiles.delete(normalizedPath);
1597
+ } else {
1598
+ result.push(item);
1599
+ if (isPlainObject3(item)) {
1600
+ resolveImports(item, baseDir, visitedFiles);
1601
+ }
1602
+ }
1603
+ }
1604
+ return result;
1605
+ }
1606
+ function loadYamlFile(filePath) {
1607
+ if (!fs11.existsSync(filePath)) {
1608
+ throw new Error(`Import file not found: ${filePath}`);
1609
+ }
1610
+ try {
1611
+ const content = fs11.readFileSync(filePath, "utf8");
1612
+ const parsed = yaml6.parse(content);
1613
+ if (parsed === null || parsed === void 0) {
1614
+ throw new TypeError(`Import file ${filePath} is empty`);
1615
+ }
1616
+ return parsed;
1617
+ } catch (error) {
1618
+ if (error instanceof Error) {
1619
+ throw new TypeError(`Failed to parse YAML file ${filePath}: ${error.message}`);
1620
+ }
1621
+ throw error;
1622
+ }
1623
+ }
1624
+ function findResourceDefinition(rootDir, codemieConfigPath, plural, name) {
1625
+ const configPath = path16.join(rootDir, codemieConfigPath);
1626
+ const config = loadYamlFile(configPath);
1627
+ if (!isPlainObject3(config) || !isPlainObject3(config.resources)) {
1628
+ return void 0;
1629
+ }
1630
+ const list = config.resources[plural];
1631
+ if (!Array.isArray(list)) {
1632
+ return void 0;
1633
+ }
1634
+ return findResourceInList(list, rootDir, null, name, /* @__PURE__ */ new Set());
1635
+ }
1636
+ function findResourceInList(items, baseDir, currentFile, name, visitedFiles) {
1637
+ for (const item of items) {
1638
+ if (!isPlainObject3(item)) {
1639
+ continue;
1640
+ }
1641
+ if (typeof item.$import === "string") {
1642
+ const importPath = path16.join(baseDir, item.$import);
1643
+ const normalizedPath = path16.resolve(importPath);
1644
+ if (visitedFiles.has(normalizedPath)) {
1645
+ throw new Error(
1646
+ `Circular import detected: ${normalizedPath}
1647
+ Import chain: ${[...visitedFiles].join(" \u2192 ")} \u2192 ${normalizedPath}`
1648
+ );
1649
+ }
1650
+ visitedFiles.add(normalizedPath);
1651
+ const loaded = loadYamlFile(importPath);
1652
+ let found;
1653
+ if (Array.isArray(loaded)) {
1654
+ found = findResourceInList(loaded, path16.dirname(importPath), normalizedPath, name, visitedFiles);
1655
+ } else if (isPlainObject3(loaded) && typeof loaded.name === "string" && namesEqual(loaded.name, name)) {
1656
+ found = { file: normalizedPath, resource: loaded, inList: false };
1657
+ }
1658
+ visitedFiles.delete(normalizedPath);
1659
+ if (found) {
1660
+ return found;
1661
+ }
1662
+ continue;
1663
+ }
1664
+ if (typeof item.name === "string" && namesEqual(item.name, name)) {
1665
+ return { file: currentFile, resource: item, inList: true };
1666
+ }
1667
+ }
1668
+ return void 0;
1669
+ }
1670
+
1510
1671
  // src/lib/codemieConfigLoader.ts
1511
1672
  var CodemieConfigLoader = class {
1512
1673
  appConfig;
@@ -1518,13 +1679,13 @@ var CodemieConfigLoader = class {
1518
1679
  */
1519
1680
  loadConfig() {
1520
1681
  const { rootDir, codemieConfig } = this.appConfig;
1521
- const configPath = path6.join(rootDir, codemieConfig);
1522
- if (!fs6.existsSync(configPath)) {
1682
+ const configPath = path16.join(rootDir, codemieConfig);
1683
+ if (!fs11.existsSync(configPath)) {
1523
1684
  throw new Error(`Configuration file not found: ${configPath}`);
1524
1685
  }
1525
- const content = fs6.readFileSync(configPath, "utf8");
1526
- const config = yaml5.parse(content);
1527
- this.resolveImports(config, rootDir);
1686
+ const content = fs11.readFileSync(configPath, "utf8");
1687
+ const config = yaml6.parse(content);
1688
+ resolveImports(config, rootDir);
1528
1689
  this.substituteEnvVars(config);
1529
1690
  this.applyDatasourceDefaults(config);
1530
1691
  this.resolveReferencesRecursive(config, config);
@@ -1536,34 +1697,34 @@ var CodemieConfigLoader = class {
1536
1697
  */
1537
1698
  loadPrompt(promptPath) {
1538
1699
  const { rootDir } = this.appConfig;
1539
- const fullPath = path6.join(rootDir, promptPath);
1540
- if (!fs6.existsSync(fullPath)) {
1700
+ const fullPath = path16.join(rootDir, promptPath);
1701
+ if (!fs11.existsSync(fullPath)) {
1541
1702
  throw new Error(`Prompt file not found: ${fullPath}`);
1542
1703
  }
1543
- return fs6.readFileSync(fullPath, "utf8");
1704
+ return fs11.readFileSync(fullPath, "utf8");
1544
1705
  }
1545
1706
  /**
1546
1707
  * Load skill content file (.skill.md)
1547
1708
  */
1548
1709
  loadSkillContent(contentPath) {
1549
1710
  const { rootDir } = this.appConfig;
1550
- const fullPath = path6.join(rootDir, contentPath);
1551
- if (!fs6.existsSync(fullPath)) {
1711
+ const fullPath = path16.join(rootDir, contentPath);
1712
+ if (!fs11.existsSync(fullPath)) {
1552
1713
  throw new Error(`Skill content file not found: ${fullPath}`);
1553
1714
  }
1554
- return fs6.readFileSync(fullPath, "utf8");
1715
+ return fs11.readFileSync(fullPath, "utf8");
1555
1716
  }
1556
1717
  /**
1557
1718
  * Load assistant configuration file
1558
1719
  */
1559
1720
  loadAssistantConfig(configPath) {
1560
1721
  const { rootDir } = this.appConfig;
1561
- const fullPath = path6.join(rootDir, configPath);
1562
- if (!fs6.existsSync(fullPath)) {
1722
+ const fullPath = path16.join(rootDir, configPath);
1723
+ if (!fs11.existsSync(fullPath)) {
1563
1724
  throw new Error(`Config file not found: ${fullPath}`);
1564
1725
  }
1565
- const content = fs6.readFileSync(fullPath, "utf8");
1566
- return yaml5.parse(content);
1726
+ const content = fs11.readFileSync(fullPath, "utf8");
1727
+ return yaml6.parse(content);
1567
1728
  }
1568
1729
  /**
1569
1730
  * Validate that all referenced files exist
@@ -1573,13 +1734,14 @@ var CodemieConfigLoader = class {
1573
1734
  const { rootDir } = this.appConfig;
1574
1735
  if (config.resources.assistants) {
1575
1736
  for (const assistant of config.resources.assistants) {
1576
- const promptPath = path6.join(rootDir, assistant.prompt);
1577
- if (!fs6.existsSync(promptPath)) {
1737
+ if (!assistant.prompt) {
1738
+ errors.push(`Missing 'prompt' for assistant: ${assistant.name}`);
1739
+ } else if (!fs11.existsSync(path16.join(rootDir, assistant.prompt))) {
1578
1740
  errors.push(`Prompt file not found for ${assistant.name}: ${assistant.prompt}`);
1579
1741
  }
1580
1742
  if (assistant.config) {
1581
- const configPath = path6.join(rootDir, assistant.config);
1582
- if (!fs6.existsSync(configPath)) {
1743
+ const configPath = path16.join(rootDir, assistant.config);
1744
+ if (!fs11.existsSync(configPath)) {
1583
1745
  errors.push(`Config file not found for ${assistant.name}: ${assistant.config}`);
1584
1746
  }
1585
1747
  }
@@ -1593,8 +1755,9 @@ var CodemieConfigLoader = class {
1593
1755
  }
1594
1756
  if (config.resources.skills) {
1595
1757
  for (const skill of config.resources.skills) {
1596
- const contentPath = path6.join(rootDir, skill.file);
1597
- if (!fs6.existsSync(contentPath)) {
1758
+ if (!skill.file) {
1759
+ errors.push(`Missing 'file' for skill: ${skill.name}`);
1760
+ } else if (!fs11.existsSync(path16.join(rootDir, skill.file))) {
1598
1761
  errors.push(`Content file not found for skill ${skill.name}: ${skill.file}`);
1599
1762
  }
1600
1763
  if (!skill.description) {
@@ -1615,109 +1778,6 @@ var CodemieConfigLoader = class {
1615
1778
  errors
1616
1779
  };
1617
1780
  }
1618
- /**
1619
- * Resolve $import directives recursively
1620
- * Automatically handles:
1621
- * 1. Single resource import (object)
1622
- * 2. Multiple resources import (array)
1623
- * 3. Mixed inline + imports
1624
- * Includes circular import detection to prevent infinite recursion
1625
- */
1626
- resolveImports(obj, baseDir, visitedFiles = /* @__PURE__ */ new Set()) {
1627
- if (!this.isPlainObject(obj)) {
1628
- return;
1629
- }
1630
- for (const key in obj) {
1631
- const value = obj[key];
1632
- if (this.isPlainObject(value) && "$import" in value) {
1633
- const importDirective = value;
1634
- const importPath = path6.join(baseDir, importDirective.$import);
1635
- const normalizedPath = path6.resolve(importPath);
1636
- if (visitedFiles.has(normalizedPath)) {
1637
- throw new Error(
1638
- `Circular import detected: ${normalizedPath}
1639
- Import chain: ${[...visitedFiles].join(" \u2192 ")} \u2192 ${normalizedPath}`
1640
- );
1641
- }
1642
- visitedFiles.add(normalizedPath);
1643
- const imported = this.loadYamlFile(importPath);
1644
- obj[key] = imported;
1645
- this.resolveImports(obj[key], path6.dirname(importPath), visitedFiles);
1646
- visitedFiles.delete(normalizedPath);
1647
- } else if (Array.isArray(value)) {
1648
- obj[key] = this.resolveArrayImports(value, baseDir, visitedFiles);
1649
- } else if (this.isPlainObject(value)) {
1650
- this.resolveImports(value, baseDir, visitedFiles);
1651
- }
1652
- }
1653
- }
1654
- /**
1655
- * Resolve imports in arrays
1656
- * Intelligently handles:
1657
- * - Inline objects (keep as-is)
1658
- * - $import with single object (add to array)
1659
- * - $import with array (flatten into parent array)
1660
- * Includes circular import detection
1661
- */
1662
- resolveArrayImports(arr, baseDir, visitedFiles = /* @__PURE__ */ new Set()) {
1663
- const result = [];
1664
- for (const item of arr) {
1665
- if (this.isPlainObject(item) && "$import" in item) {
1666
- const importDirective = item;
1667
- const importPath = path6.join(baseDir, importDirective.$import);
1668
- const normalizedPath = path6.resolve(importPath);
1669
- if (visitedFiles.has(normalizedPath)) {
1670
- throw new Error(
1671
- `Circular import detected: ${normalizedPath}
1672
- Import chain: ${[...visitedFiles].join(" \u2192 ")} \u2192 ${normalizedPath}`
1673
- );
1674
- }
1675
- visitedFiles.add(normalizedPath);
1676
- const imported = this.loadYamlFile(importPath);
1677
- if (Array.isArray(imported)) {
1678
- const resolvedArray = this.resolveArrayImports(imported, path6.dirname(importPath), visitedFiles);
1679
- for (const resolvedItem of resolvedArray) {
1680
- result.push(resolvedItem);
1681
- }
1682
- } else if (this.isPlainObject(imported)) {
1683
- result.push(imported);
1684
- this.resolveImports(imported, path6.dirname(importPath), visitedFiles);
1685
- } else {
1686
- throw new TypeError(
1687
- `Import file ${importPath} must contain either an object or an array. Got: ${typeof imported}`
1688
- );
1689
- }
1690
- visitedFiles.delete(normalizedPath);
1691
- } else {
1692
- result.push(item);
1693
- if (this.isPlainObject(item)) {
1694
- this.resolveImports(item, baseDir, visitedFiles);
1695
- }
1696
- }
1697
- }
1698
- return result;
1699
- }
1700
- /**
1701
- * Load YAML file with error handling
1702
- */
1703
- loadYamlFile(filePath) {
1704
- if (!fs6.existsSync(filePath)) {
1705
- throw new Error(`Import file not found: ${filePath}`);
1706
- }
1707
- try {
1708
- const content = fs6.readFileSync(filePath, "utf8");
1709
- const parsed = yaml5.parse(content);
1710
- if (parsed === null || parsed === void 0) {
1711
- throw new TypeError(`Import file ${filePath} is empty`);
1712
- }
1713
- return parsed;
1714
- } catch (error) {
1715
- if (error instanceof Error) {
1716
- throw new TypeError(`Failed to parse YAML file ${filePath}: ${error.message}`);
1717
- }
1718
- throw error;
1719
- }
1720
- }
1721
1781
  /**
1722
1782
  * Recursively resolve all $ref in an object/array structure
1723
1783
  * Handles both object references { $ref: "path" } and string references "$ref:path"
@@ -1726,28 +1786,28 @@ Import chain: ${[...visitedFiles].join(" \u2192 ")} \u2192 ${normalizedPath}`
1726
1786
  * @param rootConfig - Root config object (constant reference for resolving paths like "imported.integrations.xxx")
1727
1787
  * @param path - Current path in config tree (for error messages, e.g., "resources.assistants[0].toolkits")
1728
1788
  */
1729
- resolveReferencesRecursive(current, rootConfig, path16 = "") {
1789
+ resolveReferencesRecursive(current, rootConfig, path17 = "") {
1730
1790
  if (!current || typeof current !== "object") {
1731
1791
  return;
1732
1792
  }
1733
1793
  if (Array.isArray(current)) {
1734
- this.resolveArrayReferences(current, rootConfig, path16);
1794
+ this.resolveArrayReferences(current, rootConfig, path17);
1735
1795
  return;
1736
1796
  }
1737
1797
  if ("$ref" in current && typeof current.$ref === "string") {
1738
- this.resolveObjectReference(current, rootConfig, path16);
1798
+ this.resolveObjectReference(current, rootConfig, path17);
1739
1799
  return;
1740
1800
  }
1741
- this.resolveObjectProperties(current, rootConfig, path16);
1801
+ this.resolveObjectProperties(current, rootConfig, path17);
1742
1802
  }
1743
1803
  /**
1744
1804
  * Resolve $ref items in arrays and flatten if they point to arrays
1745
1805
  * Example: [{ $ref: "context_definitions.repos" }] where repos is [item1, item2]
1746
1806
  * becomes [item1, item2]
1747
1807
  */
1748
- resolveArrayReferences(arr, rootConfig, path16) {
1808
+ resolveArrayReferences(arr, rootConfig, path17) {
1749
1809
  const result = arr.flatMap((item, i) => {
1750
- const contextPath = `${path16}[${i}]`;
1810
+ const contextPath = `${path17}[${i}]`;
1751
1811
  if (!this.isRefObject(item) || item.$ref.startsWith("#")) {
1752
1812
  this.resolveReferencesRecursive(item, rootConfig, contextPath);
1753
1813
  return [item];
@@ -1765,9 +1825,9 @@ Import chain: ${[...visitedFiles].join(" \u2192 ")} \u2192 ${normalizedPath}`
1765
1825
  * Resolve object reference: { $ref: "path" }
1766
1826
  * Replaces object with resolved data (in-place mutation)
1767
1827
  */
1768
- resolveObjectReference(current, rootConfig, path16) {
1828
+ resolveObjectReference(current, rootConfig, path17) {
1769
1829
  const refPath = current.$ref;
1770
- const contextPath = path16 || "root";
1830
+ const contextPath = path17 || "root";
1771
1831
  if (refPath.startsWith("#")) {
1772
1832
  return;
1773
1833
  }
@@ -1777,24 +1837,24 @@ Import chain: ${[...visitedFiles].join(" \u2192 ")} \u2192 ${normalizedPath}`
1777
1837
  delete current[key];
1778
1838
  }
1779
1839
  Object.assign(current, filteredResolved);
1780
- this.resolveReferencesRecursive(current, rootConfig, path16);
1840
+ this.resolveReferencesRecursive(current, rootConfig, path17);
1781
1841
  }
1782
1842
  /**
1783
1843
  * Resolve object properties recursively
1784
1844
  * Handles both nested objects and "$ref:path" string references
1785
1845
  */
1786
- resolveObjectProperties(current, rootConfig, path16) {
1846
+ resolveObjectProperties(current, rootConfig, path17) {
1787
1847
  for (const [key, value] of Object.entries(current)) {
1788
1848
  if (typeof value === "string" && value.startsWith("$ref:")) {
1789
1849
  const refPath = value.slice(5);
1790
1850
  if (refPath.startsWith("#")) {
1791
1851
  continue;
1792
1852
  }
1793
- const contextPath = path16 ? `${path16}.${key}` : key;
1853
+ const contextPath = path17 ? `${path17}.${key}` : key;
1794
1854
  const resolved = this.resolveReference(rootConfig, refPath, contextPath);
1795
1855
  current[key] = resolved;
1796
1856
  } else {
1797
- this.resolveReferencesRecursive(value, rootConfig, path16 ? `${path16}.${key}` : key);
1857
+ this.resolveReferencesRecursive(value, rootConfig, path17 ? `${path17}.${key}` : key);
1798
1858
  }
1799
1859
  }
1800
1860
  }
@@ -2045,9 +2105,9 @@ async function saveAssistantToBackup(assistant, client, backupData, backupDir) {
2045
2105
  backupData.resources.assistants.push(full);
2046
2106
  if (full.system_prompt) {
2047
2107
  const fileName = `${full.slug || sanitizeFileName(full.name)}.prompt.md`;
2048
- const filePath = path6.join(backupDir, "system_prompts", fileName);
2108
+ const filePath = path16.join(backupDir, "system_prompts", fileName);
2049
2109
  ensureDirectoryExists(filePath);
2050
- fs6.writeFileSync(filePath, full.system_prompt, "utf8");
2110
+ fs11.writeFileSync(filePath, full.system_prompt, "utf8");
2051
2111
  }
2052
2112
  }
2053
2113
  async function backupAssistants(client, backupData, backupDir, transaction) {
@@ -2173,9 +2233,9 @@ async function backupWorkflows(client, backupData, backupDir, transaction) {
2173
2233
  backupData.resources.workflows.push(full);
2174
2234
  if (full.yaml_config) {
2175
2235
  const fileName = `${sanitizeFileName(full.name)}.yaml`;
2176
- const filePath = path6.join(backupDir, "workflows", fileName);
2236
+ const filePath = path16.join(backupDir, "workflows", fileName);
2177
2237
  ensureDirectoryExists(filePath);
2178
- fs6.writeFileSync(filePath, full.yaml_config, "utf8");
2238
+ fs11.writeFileSync(filePath, full.yaml_config, "utf8");
2179
2239
  }
2180
2240
  transaction.markCompleted("workflows", workflow.id);
2181
2241
  } catch (error) {
@@ -2278,9 +2338,9 @@ async function backupSkills(client, backupData, backupDir, transaction, projectN
2278
2338
  backupData.resources.skills.push(full);
2279
2339
  if (fullIsProjectOwned) {
2280
2340
  const fileName = `${sanitizeFileName(skill.name)}.skill.md`;
2281
- const filePath = path6.join(backupDir, "skills", fileName);
2341
+ const filePath = path16.join(backupDir, "skills", fileName);
2282
2342
  ensureDirectoryExists(filePath);
2283
- fs6.writeFileSync(filePath, full.content || "", "utf8");
2343
+ fs11.writeFileSync(filePath, full.content || "", "utf8");
2284
2344
  } else {
2285
2345
  logger.info(` \u21B7 Skipping content file for imported skill ${skill.name} (owner: ${full.project})`);
2286
2346
  }
@@ -2302,17 +2362,17 @@ async function backupSkills(client, backupData, backupDir, transaction, projectN
2302
2362
  }
2303
2363
  function getUniqueBackupDir(baseDir, timestamp) {
2304
2364
  let counter = 0;
2305
- let finalBackupDir = path6.join(baseDir, timestamp);
2306
- while (fs6.existsSync(finalBackupDir)) {
2365
+ let finalBackupDir = path16.join(baseDir, timestamp);
2366
+ while (fs11.existsSync(finalBackupDir)) {
2307
2367
  counter++;
2308
- finalBackupDir = path6.join(baseDir, `${timestamp}-${counter}`);
2368
+ finalBackupDir = path16.join(baseDir, `${timestamp}-${counter}`);
2309
2369
  }
2310
2370
  const dirSuffix = counter > 0 ? `${timestamp}-${counter}` : timestamp;
2311
- const tempBackupDir = path6.join(baseDir, `${BACKUP.TEMP_DIR_PREFIX}${dirSuffix}`);
2371
+ const tempBackupDir = path16.join(baseDir, `${BACKUP.TEMP_DIR_PREFIX}${dirSuffix}`);
2312
2372
  return { finalDir: finalBackupDir, tempDir: tempBackupDir };
2313
2373
  }
2314
2374
  function performCleanup(tempBackupDir, transaction) {
2315
- if (fs6.existsSync(tempBackupDir)) {
2375
+ if (fs11.existsSync(tempBackupDir)) {
2316
2376
  try {
2317
2377
  logger.info(`\u{1F9F9} Rolling back: cleaning up ${tempBackupDir}...`);
2318
2378
  cleanupDirectory(tempBackupDir);
@@ -2341,7 +2401,7 @@ async function backupResources(options) {
2341
2401
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "").replaceAll(":", "-");
2342
2402
  const { appConfig } = options;
2343
2403
  const { rootDir, backupsDirectory } = appConfig;
2344
- const backupBaseDir = path6.join(rootDir, backupsDirectory);
2404
+ const backupBaseDir = path16.join(rootDir, backupsDirectory);
2345
2405
  const { finalDir: finalBackupDir, tempDir: tempBackupDir } = getUniqueBackupDir(backupBaseDir, timestamp);
2346
2406
  let transaction = null;
2347
2407
  try {
@@ -2351,8 +2411,8 @@ async function backupResources(options) {
2351
2411
  logger.info("\u{1F50C} Connecting to Codemie API...");
2352
2412
  const client = await createClient(config);
2353
2413
  logger.info("\u2713 Connected to Codemie API\n");
2354
- if (!fs6.existsSync(tempBackupDir)) {
2355
- fs6.mkdirSync(tempBackupDir, { recursive: true });
2414
+ if (!fs11.existsSync(tempBackupDir)) {
2415
+ fs11.mkdirSync(tempBackupDir, { recursive: true });
2356
2416
  }
2357
2417
  logger.info(`\u{1F4C1} Temporary backup directory: ${tempBackupDir}
2358
2418
  `);
@@ -2442,6 +2502,25 @@ async function main(options) {
2442
2502
  }
2443
2503
  }
2444
2504
 
2505
+ // src/lib/apiErrorLogger.ts
2506
+ init_logger();
2507
+ function logApiErrorDetails(error) {
2508
+ if (error instanceof Error) {
2509
+ logger.error(` ${error.message}`);
2510
+ logger.debug(` Stack:`, error.stack);
2511
+ if ("statusCode" in error) {
2512
+ logger.error(` Status: ${String(error.statusCode)}`);
2513
+ logger.error(` Data: ${JSON.stringify("response" in error ? error.response : void 0, null, 2)}`);
2514
+ } else if ("response" in error) {
2515
+ const axiosError = error;
2516
+ logger.error(` Status: ${axiosError.response?.status}`);
2517
+ logger.error(` Data: ${JSON.stringify(axiosError.response?.data, null, 2)}`);
2518
+ }
2519
+ } else {
2520
+ logger.error(` ${String(error)}`);
2521
+ }
2522
+ }
2523
+
2445
2524
  // src/lib/paginationUtils.ts
2446
2525
  init_logger();
2447
2526
  async function findResourceByName(listFn, name, resourceType, options) {
@@ -2496,9 +2575,96 @@ async function findAssistantByName(client, name, options) {
2496
2575
  }
2497
2576
  return null;
2498
2577
  }
2499
- async function findSkillByName(client, name) {
2500
- const skill = await findResourceByName((params) => client.skills.list(params), name, "skill");
2501
- return skill?.id || null;
2578
+ async function findWorkflowByName(client, name, project) {
2579
+ const perPage = 100;
2580
+ const matches = [];
2581
+ let totalChecked = 0;
2582
+ let page = 0;
2583
+ while (true) {
2584
+ const workflows = await client.workflows.list({ page, per_page: perPage, projects: [project] }) ?? [];
2585
+ totalChecked += workflows.length;
2586
+ for (const workflow of workflows) {
2587
+ if (workflow.name === name && workflow.id) {
2588
+ matches.push(workflow.id);
2589
+ }
2590
+ }
2591
+ if (workflows.length < perPage) {
2592
+ break;
2593
+ }
2594
+ page++;
2595
+ if (page > 1e3) {
2596
+ throw new Error(
2597
+ `Workflow lookup for "${name}" in project "${project}" stopped after 1000 pages (${totalChecked} resources checked); refusing to create a possible duplicate`
2598
+ );
2599
+ }
2600
+ }
2601
+ if (matches.length > 1) {
2602
+ const ids = [...matches].sort((a, b) => a.localeCompare(b)).join(", ");
2603
+ throw new Error(`Multiple workflows named "${name}" in project "${project}": ${ids}`);
2604
+ }
2605
+ if (matches.length === 0) {
2606
+ logger.warn(
2607
+ ` \u26A0\uFE0F workflow "${name}" not found after checking ${totalChecked} resources across ${page + 1} page(s)`
2608
+ );
2609
+ return null;
2610
+ }
2611
+ return matches[0];
2612
+ }
2613
+ var SkillNameIndex = class {
2614
+ constructor(client) {
2615
+ this.client = client;
2616
+ }
2617
+ client;
2618
+ ids = /* @__PURE__ */ new Map();
2619
+ built = false;
2620
+ scanned = { skills: 0, pages: 0 };
2621
+ async find(name) {
2622
+ const known = this.ids.get(name);
2623
+ if (known) {
2624
+ return known;
2625
+ }
2626
+ if (!this.built) {
2627
+ await this.build();
2628
+ const id = this.ids.get(name);
2629
+ if (id) {
2630
+ return id;
2631
+ }
2632
+ }
2633
+ logger.warn(
2634
+ ` \u26A0\uFE0F skill "${name}" not found after checking ${this.scanned.skills} resources across ${this.scanned.pages} page(s)`
2635
+ );
2636
+ return null;
2637
+ }
2638
+ record(name, id) {
2639
+ this.ids.set(name, id);
2640
+ }
2641
+ async build() {
2642
+ const perPage = 100;
2643
+ let page = 0;
2644
+ let totalChecked = 0;
2645
+ while (true) {
2646
+ const skills = await this.client.skills.list({ page, per_page: perPage }) ?? [];
2647
+ totalChecked += skills.length;
2648
+ for (const skill of skills) {
2649
+ if (skill.name && skill.id && !this.ids.has(skill.name)) {
2650
+ this.ids.set(skill.name, skill.id);
2651
+ }
2652
+ }
2653
+ if (skills.length < perPage) {
2654
+ break;
2655
+ }
2656
+ page++;
2657
+ if (page > 1e3) {
2658
+ logger.warn(` \u26A0\uFE0F Stopped pagination after 1000 pages (${totalChecked} resources checked)`);
2659
+ break;
2660
+ }
2661
+ }
2662
+ this.built = true;
2663
+ this.scanned = { skills: totalChecked, pages: page + 1 };
2664
+ }
2665
+ };
2666
+ function findSkillByName(index, name) {
2667
+ return index.find(name);
2502
2668
  }
2503
2669
 
2504
2670
  // src/lib/assistantHelpers.ts
@@ -2827,16 +2993,16 @@ var StateManager = class {
2827
2993
  statePath;
2828
2994
  constructor(appConfig) {
2829
2995
  const { rootDir, codemieState } = appConfig;
2830
- this.statePath = path6.join(rootDir, codemieState);
2996
+ this.statePath = path16.join(rootDir, codemieState);
2831
2997
  }
2832
2998
  /**
2833
2999
  * Load state file
2834
3000
  */
2835
3001
  loadState() {
2836
- if (!fs6.existsSync(this.statePath)) {
3002
+ if (!fs11.existsSync(this.statePath)) {
2837
3003
  return this.createEmptyState();
2838
3004
  }
2839
- const content = fs6.readFileSync(this.statePath, "utf8");
3005
+ const content = fs11.readFileSync(this.statePath, "utf8");
2840
3006
  const state = JSON.parse(content);
2841
3007
  if (!state.resources) {
2842
3008
  state.resources = {
@@ -2864,12 +3030,12 @@ var StateManager = class {
2864
3030
  * Save state file
2865
3031
  */
2866
3032
  saveState(state) {
2867
- const dir = path6.dirname(this.statePath);
2868
- if (!fs6.existsSync(dir)) {
2869
- fs6.mkdirSync(dir, { recursive: true });
3033
+ const dir = path16.dirname(this.statePath);
3034
+ if (!fs11.existsSync(dir)) {
3035
+ fs11.mkdirSync(dir, { recursive: true });
2870
3036
  }
2871
3037
  state.lastSync = (/* @__PURE__ */ new Date()).toISOString();
2872
- fs6.writeFileSync(this.statePath, JSON.stringify(state, null, 2));
3038
+ fs11.writeFileSync(this.statePath, JSON.stringify(state, null, 2));
2873
3039
  }
2874
3040
  /**
2875
3041
  * Create empty state structure
@@ -3331,20 +3497,7 @@ async function deployAssistants(config, client, loader, stateManager) {
3331
3497
  logger.info("");
3332
3498
  } catch (error) {
3333
3499
  logger.error(` \u274C Failed to deploy ${assistant.name}:`);
3334
- if (error instanceof Error) {
3335
- logger.error(` ${error.message}`);
3336
- logger.debug(` Stack:`, error.stack);
3337
- if ("statusCode" in error) {
3338
- logger.error(` Status: ${String(error.statusCode)}`);
3339
- logger.error(` Data: ${JSON.stringify("response" in error ? error.response : void 0, null, 2)}`);
3340
- } else if ("response" in error) {
3341
- const axiosError = error;
3342
- logger.error(` Status: ${axiosError.response?.status}`);
3343
- logger.error(` Data: ${JSON.stringify(axiosError.response?.data, null, 2)}`);
3344
- }
3345
- } else {
3346
- logger.error(` ${String(error)}`);
3347
- }
3500
+ logApiErrorDetails(error);
3348
3501
  logger.info("");
3349
3502
  stats.failed++;
3350
3503
  }
@@ -3357,6 +3510,7 @@ async function deploySkills(config, client, loader, stateManager) {
3357
3510
  if (!config.resources.skills) {
3358
3511
  return stats;
3359
3512
  }
3513
+ const skillIndex = new SkillNameIndex(client);
3360
3514
  for (const skill of config.resources.skills) {
3361
3515
  try {
3362
3516
  logger.info(`Processing: ${skill.name}`);
@@ -3407,6 +3561,7 @@ async function deploySkills(config, client, loader, stateManager) {
3407
3561
  categories: skill.categories
3408
3562
  });
3409
3563
  logger.info(`\u2713 Created skill: ${skill.name} (${result.id})`);
3564
+ skillIndex.record(skill.name, result.id);
3410
3565
  stateManager.updateSkillState(skill.name, result.id, content, skill);
3411
3566
  stats.created++;
3412
3567
  }
@@ -3435,6 +3590,7 @@ async function deploySkills(config, client, loader, stateManager) {
3435
3590
  });
3436
3591
  skillId = result.id;
3437
3592
  logger.info(`\u2713 Created skill: ${skill.name} (${skillId})`);
3593
+ skillIndex.record(skill.name, skillId);
3438
3594
  stats.created++;
3439
3595
  } catch (createError) {
3440
3596
  const msg = createError instanceof Error ? createError.message : String(createError);
@@ -3442,7 +3598,7 @@ async function deploySkills(config, client, loader, stateManager) {
3442
3598
  throw createError;
3443
3599
  }
3444
3600
  logger.warn(` \u26A0\uFE0F Skill already exists on platform (no local state). Fetching ID to recover...`);
3445
- const existingId = await findSkillByName(client, skill.name);
3601
+ const existingId = await findSkillByName(skillIndex, skill.name);
3446
3602
  if (!existingId) {
3447
3603
  throw new Error(`Skill "${skill.name}" reported as existing but could not be found by name.`);
3448
3604
  }
@@ -3462,21 +3618,7 @@ async function deploySkills(config, client, loader, stateManager) {
3462
3618
  logger.info("");
3463
3619
  } catch (error) {
3464
3620
  logger.error(` \u274C Failed to deploy ${skill.name}:`);
3465
- if (error instanceof Error) {
3466
- logger.error(` ${error.message}`);
3467
- logger.debug(` Stack:`, error.stack);
3468
- if ("statusCode" in error) {
3469
- const apiError = error;
3470
- logger.error(` Status: ${apiError.statusCode}`);
3471
- logger.error(` Data: ${JSON.stringify(apiError.response, null, 2)}`);
3472
- } else if ("response" in error) {
3473
- const axiosError = error;
3474
- logger.error(` Status: ${axiosError.response?.status}`);
3475
- logger.error(` Data: ${JSON.stringify(axiosError.response?.data, null, 2)}`);
3476
- }
3477
- } else {
3478
- logger.error(` ${String(error)}`);
3479
- }
3621
+ logApiErrorDetails(error);
3480
3622
  logger.info("");
3481
3623
  stats.failed++;
3482
3624
  }
@@ -3576,17 +3718,7 @@ async function deployDatasources(config, client, stateManager) {
3576
3718
  logger.info("");
3577
3719
  } catch (error) {
3578
3720
  logger.error(` \u274C Failed to deploy ${datasource.name}:`);
3579
- if (error instanceof Error) {
3580
- logger.error(` ${error.message}`);
3581
- logger.debug(` Stack:`, error.stack);
3582
- if ("response" in error) {
3583
- const axiosError = error;
3584
- logger.error(` Status: ${axiosError.response?.status}`);
3585
- logger.error(` Data:`, JSON.stringify(axiosError.response?.data, null, 2));
3586
- }
3587
- } else {
3588
- logger.error(` ${String(error)}`);
3589
- }
3721
+ logApiErrorDetails(error);
3590
3722
  logger.info("");
3591
3723
  stats.failed++;
3592
3724
  }
@@ -3602,14 +3734,14 @@ async function deployWorkflows(config, client, stateManager, rootDir = process.c
3602
3734
  for (const workflow of config.resources.workflows) {
3603
3735
  try {
3604
3736
  logger.info(`Processing: ${workflow.name}`);
3605
- const yamlPath = path6.join(rootDir, workflow.definition);
3606
- if (!fs6.existsSync(yamlPath)) {
3737
+ const yamlPath = path16.join(rootDir, workflow.definition);
3738
+ if (!fs11.existsSync(yamlPath)) {
3607
3739
  throw new Error(`Workflow definition file not found: ${workflow.definition}`);
3608
3740
  }
3609
- const yamlConfigContent = fs6.readFileSync(yamlPath, "utf8");
3610
- const workflowYaml = yaml5.parse(yamlConfigContent);
3741
+ const yamlConfigContent = fs11.readFileSync(yamlPath, "utf8");
3742
+ const workflowYaml = yaml6.parse(yamlConfigContent);
3611
3743
  const referencesToResolve = [];
3612
- for (const assistant of workflowYaml.assistants) {
3744
+ for (const assistant of workflowYaml.assistants ?? []) {
3613
3745
  const isInline = !assistant.assistant_name && assistant.model && assistant.system_prompt;
3614
3746
  if (isInline) {
3615
3747
  logger.info(` Skipping inline assistant: ${assistant.id}`);
@@ -3648,48 +3780,21 @@ async function deployWorkflows(config, client, stateManager, rootDir = process.c
3648
3780
  });
3649
3781
  }
3650
3782
  }
3651
- const yamlConfig = yaml5.stringify(workflowYaml);
3783
+ const yamlConfig = yaml6.stringify(workflowYaml);
3652
3784
  const workflowYamlChecksum = calculateChecksum(yamlConfigContent);
3653
3785
  const configChecksum = calculateWorkflowConfigChecksum(workflow);
3654
3786
  const existingState = stateManager.getWorkflowState(workflow.name);
3655
- if (existingState) {
3656
- const existsOnPlatform = await checkWorkflowExists(client, workflow.name, stateManager);
3657
- if (existsOnPlatform) {
3658
- const hasChanged = existingState.workflowYamlChecksum !== workflowYamlChecksum || existingState.configChecksum !== configChecksum;
3659
- if (hasChanged) {
3660
- const apiParams = {
3661
- project: config.project.name,
3662
- name: workflow.name,
3663
- description: workflow.description,
3664
- yaml_config: yamlConfig,
3665
- mode: workflow.mode,
3666
- shared: workflow.shared,
3667
- icon_url: workflow.icon_url
3668
- };
3669
- if (process.env.DEBUG_API) {
3670
- logger.debug("\n=== DEBUG: API Params ===");
3671
- logger.debug(JSON.stringify(apiParams, null, 2));
3672
- logger.debug("=========================\n");
3673
- }
3674
- logger.info(` Updating workflow (ID: ${existingState.id})...`);
3675
- await client.workflows.update(existingState.id, apiParams);
3676
- logger.info(`\u2713 Updated workflow: ${workflow.name} (${existingState.id})`);
3677
- stateManager.updateWorkflowState(workflow.name, existingState.id, workflowYamlChecksum, configChecksum);
3678
- stats.updated++;
3679
- } else {
3680
- logger.info(` \u2713 No changes detected (ID: ${existingState.id})`);
3681
- stats.unchanged++;
3682
- }
3683
- } else {
3684
- logger.info(` \u26A0\uFE0F Workflow ID from state not found on platform, will create new`);
3685
- logger.info(` Creating new workflow...`);
3787
+ const existsOnPlatform = existingState ? await checkWorkflowExists(client, workflow.name, stateManager) : false;
3788
+ if (existingState && existsOnPlatform) {
3789
+ const hasChanged = existingState.workflowYamlChecksum !== workflowYamlChecksum || existingState.configChecksum !== configChecksum;
3790
+ if (hasChanged) {
3686
3791
  const apiParams = {
3687
3792
  project: config.project.name,
3688
3793
  name: workflow.name,
3689
3794
  description: workflow.description,
3690
3795
  yaml_config: yamlConfig,
3691
- mode: workflow.mode || "Sequential",
3692
- shared: workflow.shared ?? true,
3796
+ mode: workflow.mode,
3797
+ shared: workflow.shared,
3693
3798
  icon_url: workflow.icon_url
3694
3799
  };
3695
3800
  if (process.env.DEBUG_API) {
@@ -3697,17 +3802,19 @@ async function deployWorkflows(config, client, stateManager, rootDir = process.c
3697
3802
  logger.debug(JSON.stringify(apiParams, null, 2));
3698
3803
  logger.debug("=========================\n");
3699
3804
  }
3700
- const result = await client.workflows.create(apiParams);
3701
- const workflowId = result.data?.id || result.id;
3702
- if (!workflowId) {
3703
- throw new Error("Workflow created but ID not returned");
3704
- }
3705
- logger.info(`\u2713 Created workflow: ${workflow.name} (${workflowId})`);
3706
- stateManager.updateWorkflowState(workflow.name, workflowId, workflowYamlChecksum, configChecksum);
3707
- stats.created++;
3805
+ logger.info(` Updating workflow (ID: ${existingState.id})...`);
3806
+ await client.workflows.update(existingState.id, apiParams);
3807
+ logger.info(`\u2713 Updated workflow: ${workflow.name} (${existingState.id})`);
3808
+ stateManager.updateWorkflowState(workflow.name, existingState.id, workflowYamlChecksum, configChecksum);
3809
+ stats.updated++;
3810
+ } else {
3811
+ logger.info(` \u2713 No changes detected (ID: ${existingState.id})`);
3812
+ stats.unchanged++;
3708
3813
  }
3709
3814
  } else {
3710
- logger.info(` Creating new workflow...`);
3815
+ if (existingState) {
3816
+ logger.info(` \u26A0\uFE0F Workflow ID from state not found on platform, looking for a same-name workflow`);
3817
+ }
3711
3818
  const apiParams = {
3712
3819
  project: config.project.name,
3713
3820
  name: workflow.name,
@@ -3722,29 +3829,28 @@ async function deployWorkflows(config, client, stateManager, rootDir = process.c
3722
3829
  logger.debug(JSON.stringify(apiParams, null, 2));
3723
3830
  logger.debug("=========================\n");
3724
3831
  }
3725
- const result = await client.workflows.create(apiParams);
3726
- const workflowId = result.data?.id || result.id;
3727
- if (!workflowId) {
3728
- throw new Error("Workflow created but ID not returned");
3832
+ const foundId = await findWorkflowByName(client, workflow.name, config.project.name);
3833
+ if (foundId) {
3834
+ await client.workflows.update(foundId, apiParams);
3835
+ logger.info(`\u2713 Adopted existing workflow ${workflow.name} (${foundId})`);
3836
+ stateManager.updateWorkflowState(workflow.name, foundId, workflowYamlChecksum, configChecksum);
3837
+ stats.updated++;
3838
+ } else {
3839
+ logger.info(` Creating new workflow...`);
3840
+ const result = await client.workflows.create(apiParams);
3841
+ const workflowId = result.data?.id || result.id;
3842
+ if (!workflowId) {
3843
+ throw new Error("Workflow created but ID not returned");
3844
+ }
3845
+ logger.info(`\u2713 Created workflow: ${workflow.name} (${workflowId})`);
3846
+ stateManager.updateWorkflowState(workflow.name, workflowId, workflowYamlChecksum, configChecksum);
3847
+ stats.created++;
3729
3848
  }
3730
- logger.info(`\u2713 Created workflow: ${workflow.name} (${workflowId})`);
3731
- stateManager.updateWorkflowState(workflow.name, workflowId, workflowYamlChecksum, configChecksum);
3732
- stats.created++;
3733
3849
  }
3734
3850
  logger.info("");
3735
3851
  } catch (error) {
3736
3852
  logger.error(` \u274C Failed to deploy ${workflow.name}:`);
3737
- if (error instanceof Error) {
3738
- logger.error(` ${error.message}`);
3739
- logger.debug(` Stack:`, error.stack);
3740
- if ("response" in error) {
3741
- const axiosError = error;
3742
- logger.error(` Status: ${axiosError.response?.status}`);
3743
- logger.error(` Data:`, JSON.stringify(axiosError.response?.data, null, 2));
3744
- }
3745
- } else {
3746
- logger.error(` ${String(error)}`);
3747
- }
3853
+ logApiErrorDetails(error);
3748
3854
  logger.info("");
3749
3855
  stats.failed++;
3750
3856
  }
@@ -3754,7 +3860,7 @@ async function deployWorkflows(config, client, stateManager, rootDir = process.c
3754
3860
  async function deployResources(options) {
3755
3861
  logger.info("\u{1F680} Deploying resources to Codemie...\n");
3756
3862
  try {
3757
- const { appConfig, prune = false } = options;
3863
+ const { appConfig, prune = false, skipDatasources = false } = options;
3758
3864
  const loader = new CodemieConfigLoader(appConfig);
3759
3865
  const stateManager = new StateManager(appConfig);
3760
3866
  logger.info("\u{1F4C4} Loading configuration...");
@@ -3766,7 +3872,12 @@ async function deployResources(options) {
3766
3872
  logger.info("\u2713 Connected to Codemie API\n");
3767
3873
  logger.info("\u{1F9F9} Checking for orphaned resources...");
3768
3874
  const cleanupManager = new CleanupManager(client, stateManager);
3769
- const orphaned = cleanupManager.findOrphanedResources(config);
3875
+ const foundOrphaned = cleanupManager.findOrphanedResources(config);
3876
+ const orphaned = skipDatasources ? { ...foundOrphaned, datasources: [] } : foundOrphaned;
3877
+ const skippedOrphanedDatasources = foundOrphaned.datasources.length - orphaned.datasources.length;
3878
+ if (skippedOrphanedDatasources > 0) {
3879
+ logger.info(` \u23ED\uFE0F ${skippedOrphanedDatasources} orphaned datasource(s) skipped (--skip-datasources)`);
3880
+ }
3770
3881
  const totalOrphaned = orphaned.assistants.length + orphaned.datasources.length + orphaned.workflows.length + orphaned.skills.length;
3771
3882
  let deleted = 0;
3772
3883
  if (totalOrphaned > 0) {
@@ -3806,11 +3917,17 @@ async function deployResources(options) {
3806
3917
  let updated = 0;
3807
3918
  let unchanged = 0;
3808
3919
  let failed = 0;
3809
- const datasourceStats = await deployDatasources(config, client, stateManager);
3810
- created += datasourceStats.created;
3811
- updated += datasourceStats.updated;
3812
- unchanged += datasourceStats.unchanged;
3813
- failed += datasourceStats.failed;
3920
+ if (skipDatasources) {
3921
+ logger.info("\u{1F4CA} Processing datasources...\n");
3922
+ logger.info(` \u23ED\uFE0F Skipped (--skip-datasources): ${config.resources.datasources?.length ?? 0} datasource(s)
3923
+ `);
3924
+ } else {
3925
+ const datasourceStats = await deployDatasources(config, client, stateManager);
3926
+ created += datasourceStats.created;
3927
+ updated += datasourceStats.updated;
3928
+ unchanged += datasourceStats.unchanged;
3929
+ failed += datasourceStats.failed;
3930
+ }
3814
3931
  const skillStats = await deploySkills(config, client, loader, stateManager);
3815
3932
  created += skillStats.created;
3816
3933
  updated += skillStats.updated;
@@ -3993,14 +4110,34 @@ async function main3(options) {
3993
4110
  }
3994
4111
 
3995
4112
  // src/import.ts
4113
+ init_checksumUtils();
4114
+ init_codemieConfigChecksums();
3996
4115
  init_converters();
3997
- init_skillUtils();
3998
4116
  init_fileUtils();
3999
4117
  init_logger();
4000
- init_checksumUtils();
4001
- init_codemieConfigChecksums();
4118
+
4119
+ // src/lib/resourceTypes.ts
4002
4120
  var VALID_RESOURCE_TYPES = ["assistant", "datasource", "workflow", "skill"];
4121
+ var PLURAL_BY_RESOURCE_TYPE = {
4122
+ assistant: "assistants",
4123
+ datasource: "datasources",
4124
+ workflow: "workflows",
4125
+ skill: "skills"
4126
+ };
4127
+ function pluralTypeOf(resourceType) {
4128
+ return PLURAL_BY_RESOURCE_TYPE[resourceType];
4129
+ }
4130
+
4131
+ // src/import.ts
4132
+ init_skillUtils();
4003
4133
  async function findAssistant(client, slug) {
4134
+ if (UUID_RE.test(slug)) {
4135
+ return withTimeout(
4136
+ client.assistants.get(slug),
4137
+ TIMEOUTS_MS.ASSISTANT_FETCH,
4138
+ `Timeout fetching assistant "${slug}"`
4139
+ );
4140
+ }
4004
4141
  try {
4005
4142
  const assistant = await withTimeout(
4006
4143
  client.assistants.getBySlug(slug),
@@ -4084,7 +4221,7 @@ Please use a more specific name.`);
4084
4221
  return matches[0];
4085
4222
  }
4086
4223
  function checkResourceExists2(config, resourceType, name) {
4087
- const pluralType = resourceType === "assistant" ? "assistants" : resourceType === "datasource" ? "datasources" : resourceType === "workflow" ? "workflows" : "skills";
4224
+ const pluralType = pluralTypeOf(resourceType);
4088
4225
  const resources = config.resources[pluralType] || [];
4089
4226
  if (resources.some((r) => namesEqual(r.name, name))) {
4090
4227
  return true;
@@ -4095,68 +4232,252 @@ function checkResourceExists2(config, resourceType, name) {
4095
4232
  }
4096
4233
  return false;
4097
4234
  }
4235
+ function findManagedNameById(stateManager, resourceType, id) {
4236
+ const state = stateManager.loadState();
4237
+ const pluralType = pluralTypeOf(resourceType);
4238
+ const resources = state.resources[pluralType] || {};
4239
+ const matches = Object.keys(resources).filter((name) => resources[name]?.id === id);
4240
+ if (matches.length > 1) {
4241
+ throw new Error(
4242
+ `state.json is ambiguous: ${matches.length} ${pluralType} entries share platform id ${id} (${matches.map((name) => `"${name}"`).join(", ")}). Remove the duplicate entries so this ${resourceType} can be matched unambiguously.`
4243
+ );
4244
+ }
4245
+ return matches[0];
4246
+ }
4247
+ function nameClashError(resourceType, name) {
4248
+ return new Error(
4249
+ `A ${resourceType} named "${name}" already exists in the configuration. Re-run with --update to replace it with the platform version, or remove it first.`
4250
+ );
4251
+ }
4252
+ function assertResolvedNameIsFree(config, stateManager, resourceType, slug, resolvedName, id, update) {
4253
+ if (!update && !namesEqual(slug, resolvedName) && checkResourceExists2(config, resourceType, resolvedName)) {
4254
+ throw nameClashError(resourceType, resolvedName);
4255
+ }
4256
+ const managedName = findManagedNameById(stateManager, resourceType, id);
4257
+ if (!update && managedName !== void 0 && !namesEqual(managedName, resolvedName)) {
4258
+ throw new Error(
4259
+ `This ${resourceType} (id ${id}) is already managed by this project under the name "${managedName}", but is now named "${resolvedName}" on the platform.`
4260
+ );
4261
+ }
4262
+ return managedName;
4263
+ }
4098
4264
  function writeResourceFiles(rootDir, resourceType, resource, apiResponse) {
4099
4265
  const safeName = sanitizeFileName(resource.name);
4266
+ const target = resource;
4100
4267
  if (resourceType === "workflow") {
4101
4268
  const workflow = apiResponse;
4102
4269
  const workflowResource = resource;
4103
4270
  if (workflow.yaml_config) {
4104
4271
  const defFileName = `${safeName}.yaml`;
4105
- const defPath = path6.join(rootDir, "workflows", defFileName);
4272
+ const defPath = path16.join(rootDir, "workflows", defFileName);
4106
4273
  ensureDirectoryExists(defPath);
4107
- fs6.writeFileSync(defPath, workflow.yaml_config, "utf8");
4274
+ fs11.writeFileSync(defPath, workflow.yaml_config, "utf8");
4108
4275
  logger.info(` \u{1F4CB} Saved workflow definition: workflows/${defFileName}`);
4109
4276
  workflowResource.definition = `workflows/${defFileName}`;
4277
+ } else {
4278
+ delete target.definition;
4110
4279
  }
4111
- const configPath = path6.join(rootDir, "configs", "workflows", `${safeName}.yaml`);
4280
+ const configPath = path16.join(rootDir, "configs", "workflows", `${safeName}.yaml`);
4112
4281
  const relativeConfigPath = `configs/workflows/${safeName}.yaml`;
4113
4282
  ensureDirectoryExists(configPath);
4114
- const yamlContent2 = yaml5.stringify(workflowResource, { lineWidth: 120 });
4115
- fs6.writeFileSync(configPath, yamlContent2, "utf8");
4283
+ const yamlContent2 = yaml6.stringify(workflowResource, { lineWidth: 120 });
4284
+ fs11.writeFileSync(configPath, yamlContent2, "utf8");
4116
4285
  logger.info(` \u{1F4C4} Saved workflow config: ${relativeConfigPath}`);
4117
4286
  return relativeConfigPath;
4118
4287
  }
4119
- const pluralType = resourceType === "assistant" ? "assistants" : resourceType === "datasource" ? "datasources" : "skills";
4120
- const resourceDir = path6.join(rootDir, "configs", "imported-resources", pluralType);
4121
- const resourceFilePath = path6.join(resourceDir, `${safeName}.yaml`);
4288
+ const pluralType = pluralTypeOf(resourceType);
4289
+ const resourceDir = path16.join(rootDir, "configs", "imported-resources", pluralType);
4290
+ const resourceFilePath = path16.join(resourceDir, `${safeName}.yaml`);
4122
4291
  const relativeResourcePath = `configs/imported-resources/${pluralType}/${safeName}.yaml`;
4123
4292
  if (resourceType === "assistant") {
4124
4293
  const assistant = apiResponse;
4125
4294
  const assistantResource = resource;
4126
4295
  if (assistant.system_prompt) {
4127
4296
  const promptFileName = assistant.slug || safeName;
4128
- const promptPath = path6.join(rootDir, "system_prompts", `${promptFileName}.prompt.md`);
4297
+ const promptPath = path16.join(rootDir, "system_prompts", `${promptFileName}.prompt.md`);
4129
4298
  ensureDirectoryExists(promptPath);
4130
- fs6.writeFileSync(promptPath, assistant.system_prompt, "utf8");
4299
+ fs11.writeFileSync(promptPath, assistant.system_prompt, "utf8");
4131
4300
  logger.info(` \u{1F4DD} Saved prompt: system_prompts/${promptFileName}.prompt.md`);
4132
4301
  assistantResource.prompt = `system_prompts/${promptFileName}.prompt.md`;
4302
+ } else {
4303
+ delete target.prompt;
4133
4304
  }
4134
4305
  } else if (resourceType === "skill") {
4135
4306
  const skill = apiResponse;
4136
4307
  const skillResource = resource;
4137
4308
  if (skill.content) {
4138
4309
  const skillFileName = `${safeName}.skill.md`;
4139
- const skillPath = path6.join(rootDir, "skills", skillFileName);
4310
+ const skillPath = path16.join(rootDir, "skills", skillFileName);
4140
4311
  ensureDirectoryExists(skillPath);
4141
- fs6.writeFileSync(skillPath, skill.content, "utf8");
4312
+ fs11.writeFileSync(skillPath, skill.content, "utf8");
4142
4313
  logger.info(` \u{1F9E0} Saved skill content: skills/${skillFileName}`);
4143
4314
  skillResource.file = `skills/${skillFileName}`;
4315
+ } else {
4316
+ delete target.file;
4144
4317
  }
4145
4318
  }
4146
4319
  ensureDirectoryExists(resourceFilePath);
4147
- const yamlContent = yaml5.stringify(resource, { lineWidth: 120 });
4148
- fs6.writeFileSync(resourceFilePath, yamlContent, "utf8");
4320
+ const yamlContent = yaml6.stringify(resource, { lineWidth: 120 });
4321
+ fs11.writeFileSync(resourceFilePath, yamlContent, "utf8");
4149
4322
  logger.info(` \u{1F4C4} Saved resource config: ${relativeResourcePath}`);
4150
4323
  return relativeResourcePath;
4151
4324
  }
4325
+ function planCompanionWrite(rootDir, target, key, configuredValue, content, defaultPath, label) {
4326
+ const configuredPath = typeof configuredValue === "string" ? configuredValue : void 0;
4327
+ if (!content) {
4328
+ if (configuredPath === void 0) {
4329
+ delete target[key];
4330
+ } else {
4331
+ target[key] = configuredPath;
4332
+ }
4333
+ return void 0;
4334
+ }
4335
+ const relPath = configuredPath ?? defaultPath;
4336
+ target[key] = relPath;
4337
+ return { absPath: path16.join(rootDir, relPath), content, log: ` ${label}: ${relPath}` };
4338
+ }
4339
+ var IN_LIST_ITEM_INDENT = 2;
4340
+ function itemCarriesAnchorAliasOrMergeKey(node) {
4341
+ if (node.anchor !== void 0) {
4342
+ return true;
4343
+ }
4344
+ return node.items.some((pair) => {
4345
+ const key = pair.key;
4346
+ const keyValue = yaml6.isScalar(key) ? key.value : key;
4347
+ if (keyValue === "<<") {
4348
+ return true;
4349
+ }
4350
+ const value = pair.value;
4351
+ if (yaml6.isAlias(value)) {
4352
+ return true;
4353
+ }
4354
+ return yaml6.isNode(value) && value.anchor !== void 0;
4355
+ });
4356
+ }
4357
+ function spliceResourceIntoList(filePath, relativeResourcePath, resource, existingName) {
4358
+ const source = fs11.readFileSync(filePath, "utf8");
4359
+ const doc = yaml6.parseDocument(source);
4360
+ const refusal = (reason) => new Error(
4361
+ `Cannot safely update "${existingName}" inside ${relativeResourcePath}: ${reason}. Update ${relativeResourcePath} manually.`
4362
+ );
4363
+ if (!yaml6.isSeq(doc.contents) || doc.contents.flow) {
4364
+ throw refusal("the file is not a block-style YAML sequence");
4365
+ }
4366
+ const node = doc.contents.items.find((item) => {
4367
+ if (!yaml6.isMap(item)) {
4368
+ return false;
4369
+ }
4370
+ const itemName = item.get("name");
4371
+ return typeof itemName === "string" && namesEqual(itemName, existingName);
4372
+ });
4373
+ if (!node?.range) {
4374
+ throw refusal("its entry could not be located as a block mapping in the sequence");
4375
+ }
4376
+ if (node.flow) {
4377
+ throw refusal("its entry uses flow style ({ ... })");
4378
+ }
4379
+ if (itemCarriesAnchorAliasOrMergeKey(node)) {
4380
+ throw refusal("its entry uses a YAML anchor, alias or merge key");
4381
+ }
4382
+ const [start, valueEnd] = node.range;
4383
+ const indent = start - (source.lastIndexOf("\n", start - 1) + 1);
4384
+ if (indent !== IN_LIST_ITEM_INDENT) {
4385
+ throw refusal(`its entry is indented by ${indent} spaces instead of ${IN_LIST_ITEM_INDENT}`);
4386
+ }
4387
+ const rendered = yaml6.stringify(resource, { lineWidth: 120 }).trimEnd();
4388
+ const replacement = rendered.split("\n").map((line, index) => index === 0 || !line ? line : `${" ".repeat(IN_LIST_ITEM_INDENT)}${line}`).join("\n");
4389
+ const tail = source.slice(valueEnd);
4390
+ const separator = tail.startsWith("\n") ? "" : "\n";
4391
+ return source.slice(0, start) + replacement + separator + tail;
4392
+ }
4393
+ function updateResourceFiles(rootDir, resourceType, resource, apiResponse, existing) {
4394
+ const filePath = existing.file;
4395
+ if (!filePath) {
4396
+ throw new Error("updateResourceFiles requires an existing resource backed by a file");
4397
+ }
4398
+ const existingResource = existing.resource;
4399
+ const target = resource;
4400
+ const safeName = sanitizeFileName(resource.name);
4401
+ for (const [key, value] of Object.entries(existingResource)) {
4402
+ if (key === "skill_ids" || key in target) {
4403
+ continue;
4404
+ }
4405
+ target[key] = value;
4406
+ }
4407
+ let companion;
4408
+ if (resourceType === "assistant") {
4409
+ const assistant = apiResponse;
4410
+ companion = planCompanionWrite(
4411
+ rootDir,
4412
+ target,
4413
+ "prompt",
4414
+ existingResource.prompt,
4415
+ assistant.system_prompt,
4416
+ `system_prompts/${assistant.slug || safeName}.prompt.md`,
4417
+ "\u{1F4DD} Updated prompt"
4418
+ );
4419
+ } else if (resourceType === "skill") {
4420
+ const skill = apiResponse;
4421
+ companion = planCompanionWrite(
4422
+ rootDir,
4423
+ target,
4424
+ "file",
4425
+ existingResource.file,
4426
+ skill.content,
4427
+ `skills/${safeName}.skill.md`,
4428
+ "\u{1F9E0} Updated skill content"
4429
+ );
4430
+ } else if (resourceType === "workflow") {
4431
+ const workflow = apiResponse;
4432
+ companion = planCompanionWrite(
4433
+ rootDir,
4434
+ target,
4435
+ "definition",
4436
+ existingResource.definition,
4437
+ workflow.yaml_config,
4438
+ `workflows/${safeName}.yaml`,
4439
+ "\u{1F4CB} Updated workflow definition"
4440
+ );
4441
+ }
4442
+ const relativeResourcePath = path16.relative(rootDir, filePath);
4443
+ const newResourceFileContent = existing.inList ? spliceResourceIntoList(filePath, relativeResourcePath, resource, String(existingResource.name)) : yaml6.stringify(resource, { lineWidth: 120 });
4444
+ if (companion) {
4445
+ ensureDirectoryExists(companion.absPath);
4446
+ fs11.writeFileSync(companion.absPath, companion.content, "utf8");
4447
+ logger.info(companion.log);
4448
+ }
4449
+ fs11.writeFileSync(filePath, newResourceFileContent, "utf8");
4450
+ logger.info(` \u{1F4C4} Updated resource config: ${relativeResourcePath}`);
4451
+ return relativeResourcePath;
4452
+ }
4453
+ function deleteStateEntry(stateManager, resourceType, name) {
4454
+ switch (resourceType) {
4455
+ case "assistant": {
4456
+ stateManager.deleteAssistantState(name);
4457
+ break;
4458
+ }
4459
+ case "datasource": {
4460
+ stateManager.deleteDatasourceState(name);
4461
+ break;
4462
+ }
4463
+ case "workflow": {
4464
+ stateManager.deleteWorkflowState(name);
4465
+ break;
4466
+ }
4467
+ case "skill": {
4468
+ stateManager.deleteSkillState(name);
4469
+ break;
4470
+ }
4471
+ }
4472
+ }
4152
4473
  function addImportToCodemieYaml(rootDir, codemieConfigPath, resourceType, relativeResourcePath) {
4153
- const configPath = path6.join(rootDir, codemieConfigPath);
4154
- if (!fs6.existsSync(configPath)) {
4474
+ const configPath = path16.join(rootDir, codemieConfigPath);
4475
+ if (!fs11.existsSync(configPath)) {
4155
4476
  throw new Error(`Configuration file not found: ${configPath}`);
4156
4477
  }
4157
- const content = fs6.readFileSync(configPath, "utf8");
4158
- const doc = yaml5.parseDocument(content);
4159
- const pluralType = resourceType === "assistant" ? "assistants" : resourceType === "datasource" ? "datasources" : resourceType === "workflow" ? "workflows" : "skills";
4478
+ const content = fs11.readFileSync(configPath, "utf8");
4479
+ const doc = yaml6.parseDocument(content);
4480
+ const pluralType = pluralTypeOf(resourceType);
4160
4481
  let resourcesNode = doc.get("resources");
4161
4482
  if (!resourcesNode) {
4162
4483
  resourcesNode = doc.createNode({});
@@ -4169,7 +4490,7 @@ function addImportToCodemieYaml(rootDir, codemieConfigPath, resourceType, relati
4169
4490
  }
4170
4491
  const importDirective = { $import: relativeResourcePath };
4171
4492
  const existingImports = typeArray.items.filter((item) => {
4172
- if (yaml5.isMap(item)) {
4493
+ if (yaml6.isMap(item)) {
4173
4494
  const importValue = item.get("$import");
4174
4495
  return importValue === relativeResourcePath;
4175
4496
  }
@@ -4181,16 +4502,16 @@ function addImportToCodemieYaml(rootDir, codemieConfigPath, resourceType, relati
4181
4502
  }
4182
4503
  const newNode = doc.createNode(importDirective);
4183
4504
  typeArray.add(newNode);
4184
- fs6.writeFileSync(configPath, doc.toString(), "utf8");
4505
+ fs11.writeFileSync(configPath, doc.toString(), "utf8");
4185
4506
  logger.info(` \u2705 Added $import to ${codemieConfigPath} \u2192 resources.${pluralType}`);
4186
4507
  }
4187
4508
  function addImportedSkillToCodemieYaml(rootDir, codemieConfigPath, skill) {
4188
- const configPath = path6.join(rootDir, codemieConfigPath);
4189
- if (!fs6.existsSync(configPath)) {
4509
+ const configPath = path16.join(rootDir, codemieConfigPath);
4510
+ if (!fs11.existsSync(configPath)) {
4190
4511
  throw new Error(`Configuration file not found: ${configPath}`);
4191
4512
  }
4192
- const content = fs6.readFileSync(configPath, "utf8");
4193
- const doc = yaml5.parseDocument(content);
4513
+ const content = fs11.readFileSync(configPath, "utf8");
4514
+ const doc = yaml6.parseDocument(content);
4194
4515
  let importedNode = doc.get("imported");
4195
4516
  if (!importedNode) {
4196
4517
  importedNode = doc.createNode({ assistants: [], datasources: [], skills: [], integrations: [] });
@@ -4202,7 +4523,7 @@ function addImportedSkillToCodemieYaml(rootDir, codemieConfigPath, skill) {
4202
4523
  doc.setIn(["imported", "skills"], skillsArray);
4203
4524
  }
4204
4525
  const existingSkill = skillsArray.items.find((item) => {
4205
- if (!yaml5.isMap(item)) {
4526
+ if (!yaml6.isMap(item)) {
4206
4527
  return false;
4207
4528
  }
4208
4529
  const existingName = item.get("name");
@@ -4219,9 +4540,47 @@ function addImportedSkillToCodemieYaml(rootDir, codemieConfigPath, skill) {
4219
4540
  return;
4220
4541
  }
4221
4542
  skillsArray.add(doc.createNode({ name: skill.name, id: skill.id }));
4222
- fs6.writeFileSync(configPath, doc.toString(), "utf8");
4543
+ fs11.writeFileSync(configPath, doc.toString(), "utf8");
4223
4544
  logger.info(` \u2705 Added skill to ${codemieConfigPath} \u2192 imported.skills`);
4224
4545
  }
4546
+ function updateImportedSkillInCodemieYaml(rootDir, codemieConfigPath, skill) {
4547
+ const configPath = path16.join(rootDir, codemieConfigPath);
4548
+ if (!fs11.existsSync(configPath)) {
4549
+ throw new Error(`Configuration file not found: ${configPath}`);
4550
+ }
4551
+ const content = fs11.readFileSync(configPath, "utf8");
4552
+ const doc = yaml6.parseDocument(content);
4553
+ const skillsArray = doc.getIn(["imported", "skills"]);
4554
+ if (!skillsArray) {
4555
+ return false;
4556
+ }
4557
+ const items = skillsArray.items.filter((item) => yaml6.isMap(item));
4558
+ const entryById = items.find((item) => {
4559
+ const existingId = item.get("id");
4560
+ return typeof existingId === "string" && existingId === skill.id;
4561
+ });
4562
+ const entry = entryById ?? items.find((item) => {
4563
+ const existingName = item.get("name");
4564
+ return typeof existingName === "string" && namesEqual(existingName, skill.name);
4565
+ });
4566
+ if (!entry) {
4567
+ return false;
4568
+ }
4569
+ entry.set("name", skill.name);
4570
+ entry.set("id", skill.id);
4571
+ fs11.writeFileSync(configPath, doc.toString(), "utf8");
4572
+ return true;
4573
+ }
4574
+ function upsertImportedSkill(rootDir, codemieConfigPath, skill, update) {
4575
+ if (update) {
4576
+ if (updateImportedSkillInCodemieYaml(rootDir, codemieConfigPath, skill)) {
4577
+ logger.info(` \u267B\uFE0F Refreshed imported.skills entry for external skill "${skill.name}"`);
4578
+ return;
4579
+ }
4580
+ logger.info(` No existing skill found in the configuration \u2014 importing as new`);
4581
+ }
4582
+ addImportedSkillToCodemieYaml(rootDir, codemieConfigPath, skill);
4583
+ }
4225
4584
  function ensureExternalSkillDoesNotConflictWithManagedSkill(config, skillName) {
4226
4585
  const managedSkill = config.resources.skills?.find((skill) => namesEqual(skill.name, skillName));
4227
4586
  if (!managedSkill) {
@@ -4274,7 +4633,7 @@ function buildIntegrationAliasMap(config) {
4274
4633
  return map;
4275
4634
  }
4276
4635
  async function importResource(options) {
4277
- const { appConfig, resourceType, slug } = options;
4636
+ const { appConfig, resourceType, slug, update = false } = options;
4278
4637
  if (!VALID_RESOURCE_TYPES.includes(resourceType)) {
4279
4638
  throw new Error(`Invalid resource type: "${resourceType}". Must be one of: ${VALID_RESOURCE_TYPES.join(", ")}`);
4280
4639
  }
@@ -4282,10 +4641,8 @@ async function importResource(options) {
4282
4641
  const config = configLoader.loadConfig();
4283
4642
  const rootDir = appConfig.rootDir;
4284
4643
  const stateManager = new StateManager(appConfig);
4285
- if (checkResourceExists2(config, resourceType, slug)) {
4286
- throw new Error(
4287
- `A ${resourceType} named "${slug}" already exists in the configuration. Remove it first or use a different name.`
4288
- );
4644
+ if (!update && checkResourceExists2(config, resourceType, slug)) {
4645
+ throw nameClashError(resourceType, slug);
4289
4646
  }
4290
4647
  logger.info(`
4291
4648
  \u{1F50D} Importing ${resourceType}: "${slug}"
@@ -4295,11 +4652,21 @@ async function importResource(options) {
4295
4652
  const integrationAliasMap = buildIntegrationAliasMap(config);
4296
4653
  let resource;
4297
4654
  let apiResponse;
4655
+ let managedName;
4298
4656
  switch (resourceType) {
4299
4657
  case "assistant": {
4300
4658
  logger.info(` Searching for assistant "${slug}"...`);
4301
4659
  const assistant = await findAssistant(client, slug);
4302
4660
  logger.info(` \u2713 Found: ${assistant.name} (${assistant.id})`);
4661
+ managedName = assertResolvedNameIsFree(
4662
+ config,
4663
+ stateManager,
4664
+ resourceType,
4665
+ slug,
4666
+ assistant.name,
4667
+ assistant.id,
4668
+ update
4669
+ );
4303
4670
  apiResponse = assistant;
4304
4671
  resource = assistantResponseToResource(assistant);
4305
4672
  if (integrationAliasMap.size > 0) {
@@ -4333,7 +4700,7 @@ async function importResource(options) {
4333
4700
  logger.info(` \u2713 Resolved skill ${skillId.slice(0, 8)}... \u2192 "${skill.name}"`);
4334
4701
  if (!isProjectOwnedSkill(skill, config.project.name)) {
4335
4702
  ensureExternalSkillDoesNotConflictWithManagedSkill(config, skill.name);
4336
- addImportedSkillToCodemieYaml(rootDir, appConfig.codemieConfig, { name: skill.name, id: skill.id });
4703
+ upsertImportedSkill(rootDir, appConfig.codemieConfig, { name: skill.name, id: skill.id }, update);
4337
4704
  }
4338
4705
  }
4339
4706
  if (skillNames.length > 0) {
@@ -4346,6 +4713,15 @@ async function importResource(options) {
4346
4713
  logger.info(` Searching for datasource "${slug}"...`);
4347
4714
  const datasource = await findDatasource(client, slug);
4348
4715
  logger.info(` \u2713 Found: ${datasource.name} (${datasource.id})`);
4716
+ managedName = assertResolvedNameIsFree(
4717
+ config,
4718
+ stateManager,
4719
+ resourceType,
4720
+ slug,
4721
+ datasource.name,
4722
+ datasource.id,
4723
+ update
4724
+ );
4349
4725
  apiResponse = datasource;
4350
4726
  const settingId = datasource.setting_id || "";
4351
4727
  const integrationAlias = integrationAliasMap.get(settingId);
@@ -4356,11 +4732,15 @@ async function importResource(options) {
4356
4732
  logger.info(` Searching for workflow "${slug}"...`);
4357
4733
  const workflow = await findWorkflow(client, slug);
4358
4734
  logger.info(` \u2713 Found: ${workflow.name} (${workflow.id})`);
4359
- if (workflow.name !== slug && checkResourceExists2(config, resourceType, workflow.name)) {
4360
- throw new Error(
4361
- `A ${resourceType} named "${workflow.name}" already exists in the configuration. Remove it first or use a different name.`
4362
- );
4363
- }
4735
+ managedName = assertResolvedNameIsFree(
4736
+ config,
4737
+ stateManager,
4738
+ resourceType,
4739
+ slug,
4740
+ workflow.name,
4741
+ workflow.id,
4742
+ update
4743
+ );
4364
4744
  apiResponse = workflow;
4365
4745
  resource = workflowResponseToResource(workflow);
4366
4746
  break;
@@ -4369,24 +4749,44 @@ async function importResource(options) {
4369
4749
  logger.info(` Searching for skill "${slug}"...`);
4370
4750
  const skill = await findSkill(client, slug);
4371
4751
  logger.info(` \u2713 Found: ${skill.name} (${skill.id})`);
4372
- if (skill.name !== slug && checkResourceExists2(config, resourceType, skill.name)) {
4373
- throw new Error(
4374
- `A ${resourceType} named "${skill.name}" already exists in the configuration. Remove it first or use a different name.`
4375
- );
4376
- }
4752
+ managedName = assertResolvedNameIsFree(config, stateManager, resourceType, slug, skill.name, skill.id, update);
4377
4753
  if (isProjectOwnedSkill(skill, config.project.name)) {
4378
4754
  logger.info(` Project-owned skill \u2014 importing into resources.skills`);
4379
4755
  apiResponse = skill;
4380
4756
  resource = skillResponseToResource(skill);
4381
4757
  break;
4382
4758
  }
4383
- addImportedSkillToCodemieYaml(rootDir, appConfig.codemieConfig, { name: skill.name, id: skill.id });
4759
+ upsertImportedSkill(rootDir, appConfig.codemieConfig, { name: skill.name, id: skill.id }, update);
4384
4760
  logger.info(`
4385
4761
  \u2705 Successfully imported external skill "${skill.name}" into imported.skills
4386
4762
  `);
4387
4763
  return;
4388
4764
  }
4389
4765
  }
4766
+ if (update) {
4767
+ const pluralType = pluralTypeOf(resourceType);
4768
+ const lookupName = managedName ?? resource.name;
4769
+ const existing = findResourceDefinition(rootDir, appConfig.codemieConfig, pluralType, lookupName);
4770
+ if (existing) {
4771
+ if (existing.file === null) {
4772
+ throw new Error(
4773
+ `Cannot update "${resource.name}": it is not defined in a file referenced by codemie.yaml. Update it manually.`
4774
+ );
4775
+ }
4776
+ logger.info(` \u267B\uFE0F Updating existing ${resourceType} config in place...`);
4777
+ updateResourceFiles(rootDir, resourceType, resource, apiResponse, existing);
4778
+ if (managedName !== void 0 && !namesEqual(managedName, resource.name)) {
4779
+ logger.info(` \u{1F3F7}\uFE0F Renamed from "${managedName}" to "${resource.name}" \u2014 updating state.json`);
4780
+ deleteStateEntry(stateManager, resourceType, managedName);
4781
+ }
4782
+ updateState(stateManager, resourceType, resource, apiResponse);
4783
+ logger.info(`
4784
+ \u2705 Successfully imported ${resourceType} "${resource.name}"
4785
+ `);
4786
+ return;
4787
+ }
4788
+ logger.info(` No existing ${resourceType} found in the configuration \u2014 importing as new`);
4789
+ }
4390
4790
  const relativeResourcePath = writeResourceFiles(rootDir, resourceType, resource, apiResponse);
4391
4791
  addImportToCodemieYaml(rootDir, appConfig.codemieConfig, resourceType, relativeResourcePath);
4392
4792
  updateState(stateManager, resourceType, resource, apiResponse);
@@ -4407,7 +4807,7 @@ async function main4(options) {
4407
4807
  }
4408
4808
  init_logger();
4409
4809
  function fileExists(filePath) {
4410
- return fs6.existsSync(filePath);
4810
+ return fs11.existsSync(filePath);
4411
4811
  }
4412
4812
  function generateEnvTemplate() {
4413
4813
  return `# Codemie API Configuration
@@ -4486,14 +4886,14 @@ function compareConfigWithDefaults(userConfig) {
4486
4886
  }
4487
4887
  async function saveConfigFile(config, defaultFilename = "config.json") {
4488
4888
  const configFilename = await promptUser(`Enter config file path`, defaultFilename);
4489
- const configPath = path6.resolve(process.cwd(), configFilename);
4889
+ const configPath = path16.resolve(process.cwd(), configFilename);
4490
4890
  if (fileExists(configPath)) {
4491
4891
  const shouldOverwrite = await confirmOverwrite(configFilename);
4492
4892
  if (!shouldOverwrite) {
4493
4893
  throw new Error(`Cancelled: Configuration file not saved.`);
4494
4894
  }
4495
4895
  }
4496
- fs6.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
4896
+ fs11.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
4497
4897
  return configFilename;
4498
4898
  }
4499
4899
  function suggestConfigUsage(configFileName) {
@@ -4512,8 +4912,8 @@ async function initProject(options) {
4512
4912
  logger.info("\u{1F680} Initialize Codemie IaC Project\n\n");
4513
4913
  try {
4514
4914
  const { appConfig } = options;
4515
- const envPath = path6.resolve(appConfig.rootDir, ".env");
4516
- const configPath = path6.resolve(appConfig.rootDir, appConfig.codemieConfig);
4915
+ const envPath = path16.resolve(appConfig.rootDir, ".env");
4916
+ const configPath = path16.resolve(appConfig.rootDir, appConfig.codemieConfig);
4517
4917
  logger.info("This wizard will create the following files:");
4518
4918
  logger.info(` \u{1F4C4} .env - Environment variables with API credentials`);
4519
4919
  logger.info(` \u{1F4C4} ${appConfig.codemieConfig} - Codemie IaC configuration
@@ -4566,7 +4966,7 @@ async function initProject(options) {
4566
4966
  logger.info("\n\u{1F4C1} Creating configuration files...\n");
4567
4967
  if (shouldCreateEnv) {
4568
4968
  const envContent = generateEnvTemplate();
4569
- fs6.writeFileSync(envPath, envContent, "utf-8");
4969
+ fs11.writeFileSync(envPath, envContent, "utf-8");
4570
4970
  logger.info(`\u2713 Created .env`);
4571
4971
  logger.warn(` \u26A0\uFE0F Remember to fill in your actual credentials!`);
4572
4972
  logger.warn(` \u26A0\uFE0F DO NOT commit .env to Git!
@@ -4574,7 +4974,7 @@ async function initProject(options) {
4574
4974
  }
4575
4975
  if (shouldCreateConfig) {
4576
4976
  const configContent = generateMinimalCodemieYaml(projectName, projectDescription);
4577
- fs6.writeFileSync(configPath, configContent, "utf-8");
4977
+ fs11.writeFileSync(configPath, configContent, "utf-8");
4578
4978
  logger.info(`\u2713 Created ${appConfig.codemieConfig}`);
4579
4979
  logger.info(` See examples/ directory for detailed configuration examples
4580
4980
 
@@ -4608,12 +5008,13 @@ async function main5(options) {
4608
5008
  // src/cli/index.ts
4609
5009
  init_commandSpinner();
4610
5010
  init_logger();
5011
+ init_logger();
4611
5012
 
4612
5013
  // src/lib/previewUtils.ts
4613
5014
  init_checksumUtils();
4614
5015
  init_codemieConfigChecksums();
4615
5016
  init_converters();
4616
- async function previewResource(resource, resourceType, getState, checkExists, calculateChecksums) {
5017
+ async function previewResource(resource, resourceType, getState, checkExists, calculateChecksums, staleDetails) {
4617
5018
  const existingState = getState(resource.name);
4618
5019
  if (existingState) {
4619
5020
  const existsOnPlatform = await checkExists();
@@ -4634,7 +5035,7 @@ async function previewResource(resource, resourceType, getState, checkExists, ca
4634
5035
  type: "create",
4635
5036
  resourceType,
4636
5037
  name: resource.name,
4637
- details: `ID from state not found on platform, will create new`
5038
+ details: staleDetails ?? `ID from state not found on platform, will create new`
4638
5039
  };
4639
5040
  }
4640
5041
  } else {
@@ -4833,14 +5234,16 @@ async function previewDatasources(datasources, stateManager, client) {
4833
5234
  }
4834
5235
  return changes;
4835
5236
  }
4836
- async function previewWorkflows(workflows, stateManager, client, rootDir = process.cwd()) {
5237
+ async function previewWorkflows(workflows, stateManager, client, rootDir = process.cwd(), project) {
4837
5238
  const changes = [];
5239
+ const projectClause = project ? ` in project ${project}` : "";
5240
+ const staleDetails = `ID from state not found; will adopt a same-name workflow${projectClause} if one exists, otherwise create`;
4838
5241
  for (const workflow of workflows) {
4839
- const yamlPath = path6.join(rootDir, workflow.definition);
4840
- if (!fs6.existsSync(yamlPath)) {
5242
+ const yamlPath = path16.join(rootDir, workflow.definition);
5243
+ if (!fs11.existsSync(yamlPath)) {
4841
5244
  throw new Error(`Workflow definition file not found: ${workflow.definition}`);
4842
5245
  }
4843
- const yamlConfig = fs6.readFileSync(yamlPath, "utf8");
5246
+ const yamlConfig = fs11.readFileSync(yamlPath, "utf8");
4844
5247
  const change = await previewResource(
4845
5248
  workflow,
4846
5249
  "workflow",
@@ -4855,7 +5258,8 @@ async function previewWorkflows(workflows, stateManager, client, rootDir = proce
4855
5258
  hasChanged,
4856
5259
  ...hasChanged ? { updateDetails: "Workflow YAML or configuration changed" } : {}
4857
5260
  };
4858
- }
5261
+ },
5262
+ staleDetails
4859
5263
  );
4860
5264
  if (!change.details && change.type === "create") {
4861
5265
  change.details = `Mode: ${workflow.mode}`;
@@ -4866,14 +5270,16 @@ async function previewWorkflows(workflows, stateManager, client, rootDir = proce
4866
5270
  }
4867
5271
 
4868
5272
  // src/preview.ts
4869
- async function previewChanges(appConfig, existingClient) {
5273
+ async function previewChanges(appConfig, existingClient, options) {
4870
5274
  const loader = new CodemieConfigLoader(appConfig);
4871
5275
  const stateManager = new StateManager(appConfig);
4872
5276
  const config = loader.loadConfig();
4873
5277
  const client = existingClient || await createClient(config);
4874
5278
  const changes = [];
4875
5279
  const cleanupManager = new CleanupManager(client, stateManager);
4876
- const orphaned = cleanupManager.findOrphanedResources(config);
5280
+ const skipDatasources = options?.skipDatasources === true;
5281
+ const foundOrphaned = cleanupManager.findOrphanedResources(config);
5282
+ const orphaned = skipDatasources ? { ...foundOrphaned, datasources: [] } : foundOrphaned;
4877
5283
  changes.push(...previewOrphanedResources(orphaned, stateManager));
4878
5284
  if (config.resources.skills) {
4879
5285
  const skillChanges = await previewSkills(config.resources.skills, loader, stateManager, client);
@@ -4883,12 +5289,18 @@ async function previewChanges(appConfig, existingClient) {
4883
5289
  const assistantChanges = await previewAssistants(config.resources.assistants, loader, stateManager, client, config);
4884
5290
  changes.push(...assistantChanges);
4885
5291
  }
4886
- if (config.resources.datasources) {
5292
+ if (config.resources.datasources && !skipDatasources) {
4887
5293
  const datasourceChanges = await previewDatasources(config.resources.datasources, stateManager, client);
4888
5294
  changes.push(...datasourceChanges);
4889
5295
  }
4890
5296
  if (config.resources.workflows) {
4891
- const workflowChanges = await previewWorkflows(config.resources.workflows, stateManager, client, appConfig.rootDir);
5297
+ const workflowChanges = await previewWorkflows(
5298
+ config.resources.workflows,
5299
+ stateManager,
5300
+ client,
5301
+ appConfig.rootDir,
5302
+ config.project.name
5303
+ );
4892
5304
  changes.push(...workflowChanges);
4893
5305
  }
4894
5306
  const toDelete = changes.filter((c) => c.type === "delete");
@@ -4927,7 +5339,12 @@ async function main6(options) {
4927
5339
  }
4928
5340
  logger.info("");
4929
5341
  const client = await createClient(config);
4930
- const { changes, summary } = await previewChanges(appConfig, client);
5342
+ if (options.skipDatasources) {
5343
+ logger.info("\u23ED\uFE0F Datasources excluded from preview (--skip-datasources)\n");
5344
+ }
5345
+ const { changes, summary } = await previewChanges(appConfig, client, {
5346
+ skipDatasources: options.skipDatasources
5347
+ });
4931
5348
  logPreviewSummary(changes, summary);
4932
5349
  process.exit(0);
4933
5350
  } catch (error) {
@@ -5014,10 +5431,10 @@ var DependencyValidator = class {
5014
5431
  const errors = [];
5015
5432
  const visited = /* @__PURE__ */ new Set();
5016
5433
  const recursionStack = /* @__PURE__ */ new Set();
5017
- const dfs = (name, path16) => {
5434
+ const dfs = (name, path17) => {
5018
5435
  if (recursionStack.has(name)) {
5019
- const cycleStart = path16.indexOf(name);
5020
- const cycle = [...path16.slice(cycleStart), name];
5436
+ const cycleStart = path17.indexOf(name);
5437
+ const cycle = [...path17.slice(cycleStart), name];
5021
5438
  errors.push(`Cyclic dependency detected: ${cycle.join(" \u2192 ")}`);
5022
5439
  return true;
5023
5440
  }
@@ -5026,14 +5443,14 @@ var DependencyValidator = class {
5026
5443
  }
5027
5444
  visited.add(name);
5028
5445
  recursionStack.add(name);
5029
- path16.push(name);
5446
+ path17.push(name);
5030
5447
  const assistant = assistantMap.get(name);
5031
5448
  const subAssistantRefs = assistant?.sub_assistants || [];
5032
5449
  for (const subRef of subAssistantRefs) {
5033
5450
  if (!assistantMap.has(subRef)) {
5034
5451
  continue;
5035
5452
  }
5036
- if (dfs(subRef, [...path16])) {
5453
+ if (dfs(subRef, [...path17])) {
5037
5454
  return true;
5038
5455
  }
5039
5456
  }
@@ -5092,7 +5509,7 @@ var DependencyValidator = class {
5092
5509
  }
5093
5510
  for (const workflow of workflows) {
5094
5511
  try {
5095
- const workflowYaml = yaml5.parse(workflow.definition);
5512
+ const workflowYaml = yaml6.parse(workflow.definition);
5096
5513
  if (!workflowYaml.assistants) {
5097
5514
  continue;
5098
5515
  }
@@ -5232,12 +5649,12 @@ async function validateConfig(options) {
5232
5649
  }
5233
5650
  if (config.resources.workflows && config.resources.workflows.length > 0) {
5234
5651
  const workflowsWithContent = config.resources.workflows.map((workflow) => {
5235
- const workflowPath = path6.resolve(workflow.definition);
5236
- if (!fs6.existsSync(workflowPath)) {
5652
+ const workflowPath = path16.resolve(workflow.definition);
5653
+ if (!fs11.existsSync(workflowPath)) {
5237
5654
  errors.push(`Workflow definition file not found: ${workflow.definition}`);
5238
5655
  return { ...workflow, definition: "" };
5239
5656
  }
5240
- const definition = fs6.readFileSync(workflowPath, "utf8");
5657
+ const definition = fs11.readFileSync(workflowPath, "utf8");
5241
5658
  return { ...workflow, definition };
5242
5659
  });
5243
5660
  const workflowErrors = DependencyValidator.validateWorkflowAssistantReferences(
@@ -5301,49 +5718,62 @@ async function main7(options) {
5301
5718
 
5302
5719
  // src/cli/index.ts
5303
5720
  program.name("codemie").description("Infrastructure as Code for Codemie platform").version(package_default.version);
5304
- program.command("init").description("Initialize a new Codemie IaC project").action(() => {
5305
- const appConfig = loadAppConfig();
5721
+ program.command("init").description("Initialize a new Codemie IaC project").option("-d, --root-dir <path>", "Project root directory (containing codemie.yaml)").action((options) => {
5722
+ const rootDir = options.rootDir ? path16__default.resolve(options.rootDir) : void 0;
5723
+ const appConfig = loadAppConfig(void 0, rootDir ?? process.cwd());
5306
5724
  void main5({ appConfig });
5307
5725
  });
5308
- program.command("deploy").description("Deploy resources to Codemie platform").option("-p, --prune", "Delete orphaned resources").option("-c, --config <path>", "Configuration file").action(async (options) => {
5309
- const configPath = options.config ? path6__default.resolve(options.config) : void 0;
5310
- const appConfig = loadAppConfig(configPath);
5311
- await runCommandWithSpinner("Deploying Codemie resources", () => main2({ appConfig, prune: options.prune }));
5726
+ program.command("deploy").description("Deploy resources to Codemie platform").option("-p, --prune", "Delete orphaned resources").option("--skip-datasources", "Skip the datasource stage entirely (no create/update/delete, no indexing)").option("-d, --root-dir <path>", "Project root directory (containing codemie.yaml)").option("-c, --config <path>", "Configuration file").action(async (options) => {
5727
+ const configPath = options.config ? path16__default.resolve(options.config) : void 0;
5728
+ const rootDir = options.rootDir ? path16__default.resolve(options.rootDir) : void 0;
5729
+ const appConfig = loadAppConfig(configPath, rootDir);
5730
+ await runCommandWithSpinner(
5731
+ "Deploying Codemie resources",
5732
+ () => main2({ appConfig, prune: options.prune, skipDatasources: options.skipDatasources })
5733
+ );
5312
5734
  });
5313
- program.command("backup").description("Backup all resources from Codemie platform").option("-c, --config <path>", "Configuration file").action(async (options) => {
5314
- const configPath = options.config ? path6__default.resolve(options.config) : void 0;
5315
- const appConfig = loadAppConfig(configPath);
5735
+ program.command("backup").description("Backup all resources from Codemie platform").option("-d, --root-dir <path>", "Project root directory (containing codemie.yaml)").option("-c, --config <path>", "Configuration file").action(async (options) => {
5736
+ const configPath = options.config ? path16__default.resolve(options.config) : void 0;
5737
+ const rootDir = options.rootDir ? path16__default.resolve(options.rootDir) : void 0;
5738
+ const appConfig = loadAppConfig(configPath, rootDir);
5316
5739
  await runCommandWithSpinner("Backing up Codemie resources", () => main({ appConfig }));
5317
5740
  });
5318
- program.command("destroy").description("Destroy all IaC-managed resources").option("-f, --force", "Skip confirmation prompt").option("-c, --config <path>", "Configuration file").action(async (options) => {
5319
- const configPath = options.config ? path6__default.resolve(options.config) : void 0;
5320
- const appConfig = loadAppConfig(configPath);
5741
+ program.command("destroy").description("Destroy all IaC-managed resources").option("-f, --force", "Skip confirmation prompt").option("-d, --root-dir <path>", "Project root directory (containing codemie.yaml)").option("-c, --config <path>", "Configuration file").action(async (options) => {
5742
+ const configPath = options.config ? path16__default.resolve(options.config) : void 0;
5743
+ const rootDir = options.rootDir ? path16__default.resolve(options.rootDir) : void 0;
5744
+ const appConfig = loadAppConfig(configPath, rootDir);
5321
5745
  await runCommandWithSpinner("Destroying Codemie resources", () => main3({ appConfig, force: options.force }));
5322
5746
  });
5323
- program.command("preview").description("Preview changes without applying them").option("-c, --config <path>", "Configuration file").action(async (options) => {
5324
- const appConfigPath = options.config ? path6__default.resolve(options.config) : void 0;
5325
- const appConfig = loadAppConfig(appConfigPath);
5326
- await runCommandWithSpinner("Generating Codemie preview", () => main6({ appConfig }));
5747
+ program.command("preview").description("Preview changes without applying them").option("--skip-datasources", "Exclude datasources from preview (mirrors deploy --skip-datasources)").option("-d, --root-dir <path>", "Project root directory (containing codemie.yaml)").option("-c, --config <path>", "Configuration file").action(async (options) => {
5748
+ const appConfigPath = options.config ? path16__default.resolve(options.config) : void 0;
5749
+ const rootDir = options.rootDir ? path16__default.resolve(options.rootDir) : void 0;
5750
+ const appConfig = loadAppConfig(appConfigPath, rootDir);
5751
+ await runCommandWithSpinner(
5752
+ "Generating Codemie preview",
5753
+ () => main6({ appConfig, skipDatasources: options.skipDatasources })
5754
+ );
5327
5755
  });
5328
- program.command("validate").description("Validate configuration").option("-a, --check-api", "Check for API conflicts").option("-c, --config <path>", "Configuration file").action(async (options) => {
5329
- const configPath = options.config ? path6__default.resolve(options.config) : void 0;
5330
- const appConfig = loadAppConfig(configPath);
5756
+ program.command("validate").description("Validate configuration").option("-a, --check-api", "Check for API conflicts").option("-d, --root-dir <path>", "Project root directory (containing codemie.yaml)").option("-c, --config <path>", "Configuration file").action(async (options) => {
5757
+ const configPath = options.config ? path16__default.resolve(options.config) : void 0;
5758
+ const rootDir = options.rootDir ? path16__default.resolve(options.rootDir) : void 0;
5759
+ const appConfig = loadAppConfig(configPath, rootDir);
5331
5760
  await runCommandWithSpinner(
5332
5761
  "Validating Codemie configuration",
5333
5762
  () => main7({ appConfig, checkApi: options.checkApi })
5334
5763
  );
5335
5764
  });
5336
- 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) => {
5337
- const validTypes = ["assistant", "datasource", "workflow", "skill"];
5338
- if (!validTypes.includes(type)) {
5765
+ 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 (a platform UUID also works for assistant and workflow)").option("-d, --root-dir <path>", "Project root directory (containing codemie.yaml)").option("-c, --config <path>", "Configuration file").option("-u, --update", "Refresh an already-managed resource in place instead of failing on a conflict").action(async (type, slug, options) => {
5766
+ if (!VALID_RESOURCE_TYPES.includes(type)) {
5767
+ logger.error(`\u274C Invalid resource type: "${type}". Must be one of: ${VALID_RESOURCE_TYPES.join(", ")}`);
5339
5768
  process.exitCode = 1;
5340
5769
  return;
5341
5770
  }
5342
- const configPath = options.config ? path6__default.resolve(options.config) : void 0;
5343
- const appConfig = loadAppConfig(configPath);
5771
+ const configPath = options.config ? path16__default.resolve(options.config) : void 0;
5772
+ const rootDir = options.rootDir ? path16__default.resolve(options.rootDir) : void 0;
5773
+ const appConfig = loadAppConfig(configPath, rootDir);
5344
5774
  await runCommandWithSpinner(
5345
5775
  `Importing ${type}`,
5346
- () => main4({ appConfig, resourceType: type, slug })
5776
+ () => main4({ appConfig, resourceType: type, slug, update: options.update })
5347
5777
  );
5348
5778
  });
5349
5779
  program.parse();