@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/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import ora from 'ora';
2
2
  import * as crypto from 'crypto';
3
3
  import { CodeMieClient, DataSourceType, SkillVisibility } from 'codemie-sdk';
4
- import * as fs from 'fs';
4
+ import * as fs12 from 'fs';
5
5
  import * as path from 'path';
6
- import * as yaml6 from 'yaml';
6
+ import * as yaml7 from 'yaml';
7
7
  import 'dotenv/config';
8
8
  import pLimit from 'p-limit';
9
9
  import * as readline from 'readline';
@@ -183,7 +183,7 @@ var init_logger = __esm({
183
183
  });
184
184
 
185
185
  // src/lib/mcpSanitizer.ts
186
- function isPlainObject(value) {
186
+ function isPlainObject2(value) {
187
187
  return typeof value === "object" && value !== null && !Array.isArray(value);
188
188
  }
189
189
  function sanitizeObject(obj, isTopLevel) {
@@ -195,12 +195,12 @@ function sanitizeObject(obj, isTopLevel) {
195
195
  if (isTopLevel && key === "mcp_connect_url" && value === "") {
196
196
  continue;
197
197
  }
198
- result[key] = isPlainObject(value) ? sanitizeObject(value, false) : value;
198
+ result[key] = isPlainObject2(value) ? sanitizeObject(value, false) : value;
199
199
  }
200
200
  return result;
201
201
  }
202
202
  function sanitizeMcpServer(server) {
203
- if (!isPlainObject(server)) {
203
+ if (!isPlainObject2(server)) {
204
204
  return server;
205
205
  }
206
206
  return sanitizeObject(server, true);
@@ -243,11 +243,11 @@ function normalizeToolkits(toolkits) {
243
243
  settings: normalizeIntegrationSettings(settings)
244
244
  }));
245
245
  }
246
- function isPlainObject2(value) {
246
+ function isPlainObject3(value) {
247
247
  return typeof value === "object" && value !== null && !Array.isArray(value);
248
248
  }
249
249
  function sortKeysDeep(value) {
250
- if (!isPlainObject2(value)) {
250
+ if (!isPlainObject3(value)) {
251
251
  return value;
252
252
  }
253
253
  const sorted = {};
@@ -262,7 +262,7 @@ function normalizeMcpServers(mcpServers) {
262
262
  }
263
263
  return mcpServers.map((mcp) => {
264
264
  const sanitized = sanitizeMcpServer(mcp);
265
- if (!isPlainObject2(sanitized)) {
265
+ if (!isPlainObject3(sanitized)) {
266
266
  return sanitized;
267
267
  }
268
268
  return sortKeysDeep({
@@ -690,14 +690,14 @@ function sanitizeFileName(name, maxLength = 255) {
690
690
  function validateBackupDirectory(backupDir, minSpaceGB = 1) {
691
691
  try {
692
692
  const parentDir = path.dirname(backupDir);
693
- if (!fs.existsSync(parentDir)) {
694
- fs.mkdirSync(parentDir, { recursive: true });
693
+ if (!fs12.existsSync(parentDir)) {
694
+ fs12.mkdirSync(parentDir, { recursive: true });
695
695
  }
696
696
  const testFile = path.join(parentDir, ".write-test");
697
- fs.writeFileSync(testFile, "test");
698
- fs.unlinkSync(testFile);
697
+ fs12.writeFileSync(testFile, "test");
698
+ fs12.unlinkSync(testFile);
699
699
  try {
700
- const stats = fs.statfsSync(parentDir);
700
+ const stats = fs12.statfsSync(parentDir);
701
701
  const availableGB = stats.bavail * stats.bsize / BYTES_IN_GB;
702
702
  if (availableGB < minSpaceGB) {
703
703
  throw new Error(`Insufficient disk space: ${availableGB.toFixed(2)}GB available, need ${minSpaceGB}GB`);
@@ -713,7 +713,7 @@ function validateBackupDirectory(backupDir, minSpaceGB = 1) {
713
713
  }
714
714
  function moveAtomically(tempPath, finalPath) {
715
715
  try {
716
- fs.renameSync(tempPath, finalPath);
716
+ fs12.renameSync(tempPath, finalPath);
717
717
  } catch (error) {
718
718
  const err = error;
719
719
  if (err.code === "EEXIST") {
@@ -723,14 +723,14 @@ function moveAtomically(tempPath, finalPath) {
723
723
  }
724
724
  }
725
725
  function cleanupDirectory(dirPath) {
726
- if (fs.existsSync(dirPath)) {
727
- fs.rmSync(dirPath, { recursive: true, force: true });
726
+ if (fs12.existsSync(dirPath)) {
727
+ fs12.rmSync(dirPath, { recursive: true, force: true });
728
728
  }
729
729
  }
730
730
  function ensureDirectoryExists(filePath) {
731
731
  const dir = path.dirname(filePath);
732
- if (!fs.existsSync(dir)) {
733
- fs.mkdirSync(dir, { recursive: true });
732
+ if (!fs12.existsSync(dir)) {
733
+ fs12.mkdirSync(dir, { recursive: true });
734
734
  }
735
735
  }
736
736
  var BYTES_IN_GB;
@@ -869,7 +869,7 @@ function prepareWorkflowForYaml(workflow, state, assistants, backupDir) {
869
869
  let finalYamlContent = yamlConfig || "";
870
870
  if (yamlConfig) {
871
871
  try {
872
- const workflowYaml = yaml6.parse(yamlConfig);
872
+ const workflowYaml = yaml7.parse(yamlConfig);
873
873
  const transformedAssistants = workflowYaml.assistants?.map((assistant) => {
874
874
  if (assistant.assistant_id && typeof assistant.assistant_id === "string") {
875
875
  const assistantId = assistant.assistant_id;
@@ -886,8 +886,8 @@ function prepareWorkflowForYaml(workflow, state, assistants, backupDir) {
886
886
  const fileName = `${sanitizeFileName(workflow.name)}.yaml`;
887
887
  const filePath = path.join(backupDir, "workflows", fileName);
888
888
  ensureDirectoryExists(filePath);
889
- finalYamlContent = yaml6.stringify(transformedYaml);
890
- fs.writeFileSync(filePath, finalYamlContent, "utf8");
889
+ finalYamlContent = yaml7.stringify(transformedYaml);
890
+ fs12.writeFileSync(filePath, finalYamlContent, "utf8");
891
891
  }
892
892
  } catch (error) {
893
893
  logger.warn(
@@ -964,6 +964,141 @@ function namesEqual(a, b) {
964
964
  return a.toLowerCase() === b.toLowerCase();
965
965
  }
966
966
 
967
+ // src/lib/configImports.ts
968
+ function isPlainObject(value) {
969
+ return typeof value === "object" && value !== null && !Array.isArray(value);
970
+ }
971
+ function resolveImports(obj, baseDir, visitedFiles = /* @__PURE__ */ new Set()) {
972
+ if (!isPlainObject(obj)) {
973
+ return;
974
+ }
975
+ for (const key in obj) {
976
+ const value = obj[key];
977
+ if (isPlainObject(value) && "$import" in value) {
978
+ const importDirective = value;
979
+ const importPath = path.join(baseDir, importDirective.$import);
980
+ const normalizedPath = path.resolve(importPath);
981
+ if (visitedFiles.has(normalizedPath)) {
982
+ throw new Error(
983
+ `Circular import detected: ${normalizedPath}
984
+ Import chain: ${[...visitedFiles].join(" \u2192 ")} \u2192 ${normalizedPath}`
985
+ );
986
+ }
987
+ visitedFiles.add(normalizedPath);
988
+ const imported = loadYamlFile(importPath);
989
+ obj[key] = imported;
990
+ resolveImports(obj[key], path.dirname(importPath), visitedFiles);
991
+ visitedFiles.delete(normalizedPath);
992
+ } else if (Array.isArray(value)) {
993
+ obj[key] = resolveArrayImports(value, baseDir, visitedFiles);
994
+ } else if (isPlainObject(value)) {
995
+ resolveImports(value, baseDir, visitedFiles);
996
+ }
997
+ }
998
+ }
999
+ function resolveArrayImports(arr, baseDir, visitedFiles = /* @__PURE__ */ new Set()) {
1000
+ const result = [];
1001
+ for (const item of arr) {
1002
+ if (isPlainObject(item) && "$import" in item) {
1003
+ const importDirective = item;
1004
+ const importPath = path.join(baseDir, importDirective.$import);
1005
+ const normalizedPath = path.resolve(importPath);
1006
+ if (visitedFiles.has(normalizedPath)) {
1007
+ throw new Error(
1008
+ `Circular import detected: ${normalizedPath}
1009
+ Import chain: ${[...visitedFiles].join(" \u2192 ")} \u2192 ${normalizedPath}`
1010
+ );
1011
+ }
1012
+ visitedFiles.add(normalizedPath);
1013
+ const imported = loadYamlFile(importPath);
1014
+ if (Array.isArray(imported)) {
1015
+ const resolvedArray = resolveArrayImports(imported, path.dirname(importPath), visitedFiles);
1016
+ for (const resolvedItem of resolvedArray) {
1017
+ result.push(resolvedItem);
1018
+ }
1019
+ } else if (isPlainObject(imported)) {
1020
+ result.push(imported);
1021
+ resolveImports(imported, path.dirname(importPath), visitedFiles);
1022
+ } else {
1023
+ throw new TypeError(
1024
+ `Import file ${importPath} must contain either an object or an array. Got: ${typeof imported}`
1025
+ );
1026
+ }
1027
+ visitedFiles.delete(normalizedPath);
1028
+ } else {
1029
+ result.push(item);
1030
+ if (isPlainObject(item)) {
1031
+ resolveImports(item, baseDir, visitedFiles);
1032
+ }
1033
+ }
1034
+ }
1035
+ return result;
1036
+ }
1037
+ function loadYamlFile(filePath) {
1038
+ if (!fs12.existsSync(filePath)) {
1039
+ throw new Error(`Import file not found: ${filePath}`);
1040
+ }
1041
+ try {
1042
+ const content = fs12.readFileSync(filePath, "utf8");
1043
+ const parsed = yaml7.parse(content);
1044
+ if (parsed === null || parsed === void 0) {
1045
+ throw new TypeError(`Import file ${filePath} is empty`);
1046
+ }
1047
+ return parsed;
1048
+ } catch (error) {
1049
+ if (error instanceof Error) {
1050
+ throw new TypeError(`Failed to parse YAML file ${filePath}: ${error.message}`);
1051
+ }
1052
+ throw error;
1053
+ }
1054
+ }
1055
+ function findResourceDefinition(rootDir, codemieConfigPath, plural, name) {
1056
+ const configPath = path.join(rootDir, codemieConfigPath);
1057
+ const config = loadYamlFile(configPath);
1058
+ if (!isPlainObject(config) || !isPlainObject(config.resources)) {
1059
+ return void 0;
1060
+ }
1061
+ const list = config.resources[plural];
1062
+ if (!Array.isArray(list)) {
1063
+ return void 0;
1064
+ }
1065
+ return findResourceInList(list, rootDir, null, name, /* @__PURE__ */ new Set());
1066
+ }
1067
+ function findResourceInList(items, baseDir, currentFile, name, visitedFiles) {
1068
+ for (const item of items) {
1069
+ if (!isPlainObject(item)) {
1070
+ continue;
1071
+ }
1072
+ if (typeof item.$import === "string") {
1073
+ const importPath = path.join(baseDir, item.$import);
1074
+ const normalizedPath = path.resolve(importPath);
1075
+ if (visitedFiles.has(normalizedPath)) {
1076
+ throw new Error(
1077
+ `Circular import detected: ${normalizedPath}
1078
+ Import chain: ${[...visitedFiles].join(" \u2192 ")} \u2192 ${normalizedPath}`
1079
+ );
1080
+ }
1081
+ visitedFiles.add(normalizedPath);
1082
+ const loaded = loadYamlFile(importPath);
1083
+ let found;
1084
+ if (Array.isArray(loaded)) {
1085
+ found = findResourceInList(loaded, path.dirname(importPath), normalizedPath, name, visitedFiles);
1086
+ } else if (isPlainObject(loaded) && typeof loaded.name === "string" && namesEqual(loaded.name, name)) {
1087
+ found = { file: normalizedPath, resource: loaded, inList: false };
1088
+ }
1089
+ visitedFiles.delete(normalizedPath);
1090
+ if (found) {
1091
+ return found;
1092
+ }
1093
+ continue;
1094
+ }
1095
+ if (typeof item.name === "string" && namesEqual(item.name, name)) {
1096
+ return { file: currentFile, resource: item, inList: true };
1097
+ }
1098
+ }
1099
+ return void 0;
1100
+ }
1101
+
967
1102
  // src/lib/codemieConfigLoader.ts
968
1103
  var CodemieConfigLoader = class {
969
1104
  appConfig;
@@ -976,12 +1111,12 @@ var CodemieConfigLoader = class {
976
1111
  loadConfig() {
977
1112
  const { rootDir, codemieConfig } = this.appConfig;
978
1113
  const configPath = path.join(rootDir, codemieConfig);
979
- if (!fs.existsSync(configPath)) {
1114
+ if (!fs12.existsSync(configPath)) {
980
1115
  throw new Error(`Configuration file not found: ${configPath}`);
981
1116
  }
982
- const content = fs.readFileSync(configPath, "utf8");
983
- const config = yaml6.parse(content);
984
- this.resolveImports(config, rootDir);
1117
+ const content = fs12.readFileSync(configPath, "utf8");
1118
+ const config = yaml7.parse(content);
1119
+ resolveImports(config, rootDir);
985
1120
  this.substituteEnvVars(config);
986
1121
  this.applyDatasourceDefaults(config);
987
1122
  this.resolveReferencesRecursive(config, config);
@@ -994,10 +1129,10 @@ var CodemieConfigLoader = class {
994
1129
  loadPrompt(promptPath) {
995
1130
  const { rootDir } = this.appConfig;
996
1131
  const fullPath = path.join(rootDir, promptPath);
997
- if (!fs.existsSync(fullPath)) {
1132
+ if (!fs12.existsSync(fullPath)) {
998
1133
  throw new Error(`Prompt file not found: ${fullPath}`);
999
1134
  }
1000
- return fs.readFileSync(fullPath, "utf8");
1135
+ return fs12.readFileSync(fullPath, "utf8");
1001
1136
  }
1002
1137
  /**
1003
1138
  * Load skill content file (.skill.md)
@@ -1005,10 +1140,10 @@ var CodemieConfigLoader = class {
1005
1140
  loadSkillContent(contentPath) {
1006
1141
  const { rootDir } = this.appConfig;
1007
1142
  const fullPath = path.join(rootDir, contentPath);
1008
- if (!fs.existsSync(fullPath)) {
1143
+ if (!fs12.existsSync(fullPath)) {
1009
1144
  throw new Error(`Skill content file not found: ${fullPath}`);
1010
1145
  }
1011
- return fs.readFileSync(fullPath, "utf8");
1146
+ return fs12.readFileSync(fullPath, "utf8");
1012
1147
  }
1013
1148
  /**
1014
1149
  * Load assistant configuration file
@@ -1016,11 +1151,11 @@ var CodemieConfigLoader = class {
1016
1151
  loadAssistantConfig(configPath) {
1017
1152
  const { rootDir } = this.appConfig;
1018
1153
  const fullPath = path.join(rootDir, configPath);
1019
- if (!fs.existsSync(fullPath)) {
1154
+ if (!fs12.existsSync(fullPath)) {
1020
1155
  throw new Error(`Config file not found: ${fullPath}`);
1021
1156
  }
1022
- const content = fs.readFileSync(fullPath, "utf8");
1023
- return yaml6.parse(content);
1157
+ const content = fs12.readFileSync(fullPath, "utf8");
1158
+ return yaml7.parse(content);
1024
1159
  }
1025
1160
  /**
1026
1161
  * Validate that all referenced files exist
@@ -1030,13 +1165,14 @@ var CodemieConfigLoader = class {
1030
1165
  const { rootDir } = this.appConfig;
1031
1166
  if (config.resources.assistants) {
1032
1167
  for (const assistant of config.resources.assistants) {
1033
- const promptPath = path.join(rootDir, assistant.prompt);
1034
- if (!fs.existsSync(promptPath)) {
1168
+ if (!assistant.prompt) {
1169
+ errors.push(`Missing 'prompt' for assistant: ${assistant.name}`);
1170
+ } else if (!fs12.existsSync(path.join(rootDir, assistant.prompt))) {
1035
1171
  errors.push(`Prompt file not found for ${assistant.name}: ${assistant.prompt}`);
1036
1172
  }
1037
1173
  if (assistant.config) {
1038
1174
  const configPath = path.join(rootDir, assistant.config);
1039
- if (!fs.existsSync(configPath)) {
1175
+ if (!fs12.existsSync(configPath)) {
1040
1176
  errors.push(`Config file not found for ${assistant.name}: ${assistant.config}`);
1041
1177
  }
1042
1178
  }
@@ -1050,8 +1186,9 @@ var CodemieConfigLoader = class {
1050
1186
  }
1051
1187
  if (config.resources.skills) {
1052
1188
  for (const skill of config.resources.skills) {
1053
- const contentPath = path.join(rootDir, skill.file);
1054
- if (!fs.existsSync(contentPath)) {
1189
+ if (!skill.file) {
1190
+ errors.push(`Missing 'file' for skill: ${skill.name}`);
1191
+ } else if (!fs12.existsSync(path.join(rootDir, skill.file))) {
1055
1192
  errors.push(`Content file not found for skill ${skill.name}: ${skill.file}`);
1056
1193
  }
1057
1194
  if (!skill.description) {
@@ -1072,109 +1209,6 @@ var CodemieConfigLoader = class {
1072
1209
  errors
1073
1210
  };
1074
1211
  }
1075
- /**
1076
- * Resolve $import directives recursively
1077
- * Automatically handles:
1078
- * 1. Single resource import (object)
1079
- * 2. Multiple resources import (array)
1080
- * 3. Mixed inline + imports
1081
- * Includes circular import detection to prevent infinite recursion
1082
- */
1083
- resolveImports(obj, baseDir, visitedFiles = /* @__PURE__ */ new Set()) {
1084
- if (!this.isPlainObject(obj)) {
1085
- return;
1086
- }
1087
- for (const key in obj) {
1088
- const value = obj[key];
1089
- if (this.isPlainObject(value) && "$import" in value) {
1090
- const importDirective = value;
1091
- const importPath = path.join(baseDir, importDirective.$import);
1092
- const normalizedPath = path.resolve(importPath);
1093
- if (visitedFiles.has(normalizedPath)) {
1094
- throw new Error(
1095
- `Circular import detected: ${normalizedPath}
1096
- Import chain: ${[...visitedFiles].join(" \u2192 ")} \u2192 ${normalizedPath}`
1097
- );
1098
- }
1099
- visitedFiles.add(normalizedPath);
1100
- const imported = this.loadYamlFile(importPath);
1101
- obj[key] = imported;
1102
- this.resolveImports(obj[key], path.dirname(importPath), visitedFiles);
1103
- visitedFiles.delete(normalizedPath);
1104
- } else if (Array.isArray(value)) {
1105
- obj[key] = this.resolveArrayImports(value, baseDir, visitedFiles);
1106
- } else if (this.isPlainObject(value)) {
1107
- this.resolveImports(value, baseDir, visitedFiles);
1108
- }
1109
- }
1110
- }
1111
- /**
1112
- * Resolve imports in arrays
1113
- * Intelligently handles:
1114
- * - Inline objects (keep as-is)
1115
- * - $import with single object (add to array)
1116
- * - $import with array (flatten into parent array)
1117
- * Includes circular import detection
1118
- */
1119
- resolveArrayImports(arr, baseDir, visitedFiles = /* @__PURE__ */ new Set()) {
1120
- const result = [];
1121
- for (const item of arr) {
1122
- if (this.isPlainObject(item) && "$import" in item) {
1123
- const importDirective = item;
1124
- const importPath = path.join(baseDir, importDirective.$import);
1125
- const normalizedPath = path.resolve(importPath);
1126
- if (visitedFiles.has(normalizedPath)) {
1127
- throw new Error(
1128
- `Circular import detected: ${normalizedPath}
1129
- Import chain: ${[...visitedFiles].join(" \u2192 ")} \u2192 ${normalizedPath}`
1130
- );
1131
- }
1132
- visitedFiles.add(normalizedPath);
1133
- const imported = this.loadYamlFile(importPath);
1134
- if (Array.isArray(imported)) {
1135
- const resolvedArray = this.resolveArrayImports(imported, path.dirname(importPath), visitedFiles);
1136
- for (const resolvedItem of resolvedArray) {
1137
- result.push(resolvedItem);
1138
- }
1139
- } else if (this.isPlainObject(imported)) {
1140
- result.push(imported);
1141
- this.resolveImports(imported, path.dirname(importPath), visitedFiles);
1142
- } else {
1143
- throw new TypeError(
1144
- `Import file ${importPath} must contain either an object or an array. Got: ${typeof imported}`
1145
- );
1146
- }
1147
- visitedFiles.delete(normalizedPath);
1148
- } else {
1149
- result.push(item);
1150
- if (this.isPlainObject(item)) {
1151
- this.resolveImports(item, baseDir, visitedFiles);
1152
- }
1153
- }
1154
- }
1155
- return result;
1156
- }
1157
- /**
1158
- * Load YAML file with error handling
1159
- */
1160
- loadYamlFile(filePath) {
1161
- if (!fs.existsSync(filePath)) {
1162
- throw new Error(`Import file not found: ${filePath}`);
1163
- }
1164
- try {
1165
- const content = fs.readFileSync(filePath, "utf8");
1166
- const parsed = yaml6.parse(content);
1167
- if (parsed === null || parsed === void 0) {
1168
- throw new TypeError(`Import file ${filePath} is empty`);
1169
- }
1170
- return parsed;
1171
- } catch (error) {
1172
- if (error instanceof Error) {
1173
- throw new TypeError(`Failed to parse YAML file ${filePath}: ${error.message}`);
1174
- }
1175
- throw error;
1176
- }
1177
- }
1178
1212
  /**
1179
1213
  * Recursively resolve all $ref in an object/array structure
1180
1214
  * Handles both object references { $ref: "path" } and string references "$ref:path"
@@ -1183,28 +1217,28 @@ Import chain: ${[...visitedFiles].join(" \u2192 ")} \u2192 ${normalizedPath}`
1183
1217
  * @param rootConfig - Root config object (constant reference for resolving paths like "imported.integrations.xxx")
1184
1218
  * @param path - Current path in config tree (for error messages, e.g., "resources.assistants[0].toolkits")
1185
1219
  */
1186
- resolveReferencesRecursive(current, rootConfig, path12 = "") {
1220
+ resolveReferencesRecursive(current, rootConfig, path13 = "") {
1187
1221
  if (!current || typeof current !== "object") {
1188
1222
  return;
1189
1223
  }
1190
1224
  if (Array.isArray(current)) {
1191
- this.resolveArrayReferences(current, rootConfig, path12);
1225
+ this.resolveArrayReferences(current, rootConfig, path13);
1192
1226
  return;
1193
1227
  }
1194
1228
  if ("$ref" in current && typeof current.$ref === "string") {
1195
- this.resolveObjectReference(current, rootConfig, path12);
1229
+ this.resolveObjectReference(current, rootConfig, path13);
1196
1230
  return;
1197
1231
  }
1198
- this.resolveObjectProperties(current, rootConfig, path12);
1232
+ this.resolveObjectProperties(current, rootConfig, path13);
1199
1233
  }
1200
1234
  /**
1201
1235
  * Resolve $ref items in arrays and flatten if they point to arrays
1202
1236
  * Example: [{ $ref: "context_definitions.repos" }] where repos is [item1, item2]
1203
1237
  * becomes [item1, item2]
1204
1238
  */
1205
- resolveArrayReferences(arr, rootConfig, path12) {
1239
+ resolveArrayReferences(arr, rootConfig, path13) {
1206
1240
  const result = arr.flatMap((item, i) => {
1207
- const contextPath = `${path12}[${i}]`;
1241
+ const contextPath = `${path13}[${i}]`;
1208
1242
  if (!this.isRefObject(item) || item.$ref.startsWith("#")) {
1209
1243
  this.resolveReferencesRecursive(item, rootConfig, contextPath);
1210
1244
  return [item];
@@ -1222,9 +1256,9 @@ Import chain: ${[...visitedFiles].join(" \u2192 ")} \u2192 ${normalizedPath}`
1222
1256
  * Resolve object reference: { $ref: "path" }
1223
1257
  * Replaces object with resolved data (in-place mutation)
1224
1258
  */
1225
- resolveObjectReference(current, rootConfig, path12) {
1259
+ resolveObjectReference(current, rootConfig, path13) {
1226
1260
  const refPath = current.$ref;
1227
- const contextPath = path12 || "root";
1261
+ const contextPath = path13 || "root";
1228
1262
  if (refPath.startsWith("#")) {
1229
1263
  return;
1230
1264
  }
@@ -1234,24 +1268,24 @@ Import chain: ${[...visitedFiles].join(" \u2192 ")} \u2192 ${normalizedPath}`
1234
1268
  delete current[key];
1235
1269
  }
1236
1270
  Object.assign(current, filteredResolved);
1237
- this.resolveReferencesRecursive(current, rootConfig, path12);
1271
+ this.resolveReferencesRecursive(current, rootConfig, path13);
1238
1272
  }
1239
1273
  /**
1240
1274
  * Resolve object properties recursively
1241
1275
  * Handles both nested objects and "$ref:path" string references
1242
1276
  */
1243
- resolveObjectProperties(current, rootConfig, path12) {
1277
+ resolveObjectProperties(current, rootConfig, path13) {
1244
1278
  for (const [key, value] of Object.entries(current)) {
1245
1279
  if (typeof value === "string" && value.startsWith("$ref:")) {
1246
1280
  const refPath = value.slice(5);
1247
1281
  if (refPath.startsWith("#")) {
1248
1282
  continue;
1249
1283
  }
1250
- const contextPath = path12 ? `${path12}.${key}` : key;
1284
+ const contextPath = path13 ? `${path13}.${key}` : key;
1251
1285
  const resolved = this.resolveReference(rootConfig, refPath, contextPath);
1252
1286
  current[key] = resolved;
1253
1287
  } else {
1254
- this.resolveReferencesRecursive(value, rootConfig, path12 ? `${path12}.${key}` : key);
1288
+ this.resolveReferencesRecursive(value, rootConfig, path13 ? `${path13}.${key}` : key);
1255
1289
  }
1256
1290
  }
1257
1291
  }
@@ -1411,10 +1445,10 @@ var StateManager = class {
1411
1445
  * Load state file
1412
1446
  */
1413
1447
  loadState() {
1414
- if (!fs.existsSync(this.statePath)) {
1448
+ if (!fs12.existsSync(this.statePath)) {
1415
1449
  return this.createEmptyState();
1416
1450
  }
1417
- const content = fs.readFileSync(this.statePath, "utf8");
1451
+ const content = fs12.readFileSync(this.statePath, "utf8");
1418
1452
  const state = JSON.parse(content);
1419
1453
  if (!state.resources) {
1420
1454
  state.resources = {
@@ -1443,11 +1477,11 @@ var StateManager = class {
1443
1477
  */
1444
1478
  saveState(state) {
1445
1479
  const dir = path.dirname(this.statePath);
1446
- if (!fs.existsSync(dir)) {
1447
- fs.mkdirSync(dir, { recursive: true });
1480
+ if (!fs12.existsSync(dir)) {
1481
+ fs12.mkdirSync(dir, { recursive: true });
1448
1482
  }
1449
1483
  state.lastSync = (/* @__PURE__ */ new Date()).toISOString();
1450
- fs.writeFileSync(this.statePath, JSON.stringify(state, null, 2));
1484
+ fs12.writeFileSync(this.statePath, JSON.stringify(state, null, 2));
1451
1485
  }
1452
1486
  /**
1453
1487
  * Create empty state structure
@@ -1872,6 +1906,25 @@ var CleanupManager = class {
1872
1906
  // src/index.ts
1873
1907
  init_logger();
1874
1908
 
1909
+ // src/lib/apiErrorLogger.ts
1910
+ init_logger();
1911
+ function logApiErrorDetails(error) {
1912
+ if (error instanceof Error) {
1913
+ logger.error(` ${error.message}`);
1914
+ logger.debug(` Stack:`, error.stack);
1915
+ if ("statusCode" in error) {
1916
+ logger.error(` Status: ${String(error.statusCode)}`);
1917
+ logger.error(` Data: ${JSON.stringify("response" in error ? error.response : void 0, null, 2)}`);
1918
+ } else if ("response" in error) {
1919
+ const axiosError = error;
1920
+ logger.error(` Status: ${axiosError.response?.status}`);
1921
+ logger.error(` Data: ${JSON.stringify(axiosError.response?.data, null, 2)}`);
1922
+ }
1923
+ } else {
1924
+ logger.error(` ${String(error)}`);
1925
+ }
1926
+ }
1927
+
1875
1928
  // src/lib/paginationUtils.ts
1876
1929
  init_logger();
1877
1930
  async function findResourceByName(listFn, name, resourceType, options) {
@@ -1926,9 +1979,96 @@ async function findAssistantByName(client, name, options) {
1926
1979
  }
1927
1980
  return null;
1928
1981
  }
1929
- async function findSkillByName(client, name) {
1930
- const skill = await findResourceByName((params) => client.skills.list(params), name, "skill");
1931
- return skill?.id || null;
1982
+ async function findWorkflowByName(client, name, project) {
1983
+ const perPage = 100;
1984
+ const matches = [];
1985
+ let totalChecked = 0;
1986
+ let page = 0;
1987
+ while (true) {
1988
+ const workflows = await client.workflows.list({ page, per_page: perPage, projects: [project] }) ?? [];
1989
+ totalChecked += workflows.length;
1990
+ for (const workflow of workflows) {
1991
+ if (workflow.name === name && workflow.id) {
1992
+ matches.push(workflow.id);
1993
+ }
1994
+ }
1995
+ if (workflows.length < perPage) {
1996
+ break;
1997
+ }
1998
+ page++;
1999
+ if (page > 1e3) {
2000
+ throw new Error(
2001
+ `Workflow lookup for "${name}" in project "${project}" stopped after 1000 pages (${totalChecked} resources checked); refusing to create a possible duplicate`
2002
+ );
2003
+ }
2004
+ }
2005
+ if (matches.length > 1) {
2006
+ const ids = [...matches].sort((a, b) => a.localeCompare(b)).join(", ");
2007
+ throw new Error(`Multiple workflows named "${name}" in project "${project}": ${ids}`);
2008
+ }
2009
+ if (matches.length === 0) {
2010
+ logger.warn(
2011
+ ` \u26A0\uFE0F workflow "${name}" not found after checking ${totalChecked} resources across ${page + 1} page(s)`
2012
+ );
2013
+ return null;
2014
+ }
2015
+ return matches[0];
2016
+ }
2017
+ var SkillNameIndex = class {
2018
+ constructor(client) {
2019
+ this.client = client;
2020
+ }
2021
+ client;
2022
+ ids = /* @__PURE__ */ new Map();
2023
+ built = false;
2024
+ scanned = { skills: 0, pages: 0 };
2025
+ async find(name) {
2026
+ const known = this.ids.get(name);
2027
+ if (known) {
2028
+ return known;
2029
+ }
2030
+ if (!this.built) {
2031
+ await this.build();
2032
+ const id = this.ids.get(name);
2033
+ if (id) {
2034
+ return id;
2035
+ }
2036
+ }
2037
+ logger.warn(
2038
+ ` \u26A0\uFE0F skill "${name}" not found after checking ${this.scanned.skills} resources across ${this.scanned.pages} page(s)`
2039
+ );
2040
+ return null;
2041
+ }
2042
+ record(name, id) {
2043
+ this.ids.set(name, id);
2044
+ }
2045
+ async build() {
2046
+ const perPage = 100;
2047
+ let page = 0;
2048
+ let totalChecked = 0;
2049
+ while (true) {
2050
+ const skills = await this.client.skills.list({ page, per_page: perPage }) ?? [];
2051
+ totalChecked += skills.length;
2052
+ for (const skill of skills) {
2053
+ if (skill.name && skill.id && !this.ids.has(skill.name)) {
2054
+ this.ids.set(skill.name, skill.id);
2055
+ }
2056
+ }
2057
+ if (skills.length < perPage) {
2058
+ break;
2059
+ }
2060
+ page++;
2061
+ if (page > 1e3) {
2062
+ logger.warn(` \u26A0\uFE0F Stopped pagination after 1000 pages (${totalChecked} resources checked)`);
2063
+ break;
2064
+ }
2065
+ }
2066
+ this.built = true;
2067
+ this.scanned = { skills: totalChecked, pages: page + 1 };
2068
+ }
2069
+ };
2070
+ function findSkillByName(index, name) {
2071
+ return index.find(name);
1932
2072
  }
1933
2073
 
1934
2074
  // src/lib/assistantHelpers.ts
@@ -2309,20 +2449,7 @@ async function deployAssistants(config, client, loader, stateManager) {
2309
2449
  logger.info("");
2310
2450
  } catch (error) {
2311
2451
  logger.error(` \u274C Failed to deploy ${assistant.name}:`);
2312
- if (error instanceof Error) {
2313
- logger.error(` ${error.message}`);
2314
- logger.debug(` Stack:`, error.stack);
2315
- if ("statusCode" in error) {
2316
- logger.error(` Status: ${String(error.statusCode)}`);
2317
- logger.error(` Data: ${JSON.stringify("response" in error ? error.response : void 0, null, 2)}`);
2318
- } else if ("response" in error) {
2319
- const axiosError = error;
2320
- logger.error(` Status: ${axiosError.response?.status}`);
2321
- logger.error(` Data: ${JSON.stringify(axiosError.response?.data, null, 2)}`);
2322
- }
2323
- } else {
2324
- logger.error(` ${String(error)}`);
2325
- }
2452
+ logApiErrorDetails(error);
2326
2453
  logger.info("");
2327
2454
  stats.failed++;
2328
2455
  }
@@ -2335,6 +2462,7 @@ async function deploySkills(config, client, loader, stateManager) {
2335
2462
  if (!config.resources.skills) {
2336
2463
  return stats;
2337
2464
  }
2465
+ const skillIndex = new SkillNameIndex(client);
2338
2466
  for (const skill of config.resources.skills) {
2339
2467
  try {
2340
2468
  logger.info(`Processing: ${skill.name}`);
@@ -2385,6 +2513,7 @@ async function deploySkills(config, client, loader, stateManager) {
2385
2513
  categories: skill.categories
2386
2514
  });
2387
2515
  logger.info(`\u2713 Created skill: ${skill.name} (${result.id})`);
2516
+ skillIndex.record(skill.name, result.id);
2388
2517
  stateManager.updateSkillState(skill.name, result.id, content, skill);
2389
2518
  stats.created++;
2390
2519
  }
@@ -2413,6 +2542,7 @@ async function deploySkills(config, client, loader, stateManager) {
2413
2542
  });
2414
2543
  skillId = result.id;
2415
2544
  logger.info(`\u2713 Created skill: ${skill.name} (${skillId})`);
2545
+ skillIndex.record(skill.name, skillId);
2416
2546
  stats.created++;
2417
2547
  } catch (createError) {
2418
2548
  const msg = createError instanceof Error ? createError.message : String(createError);
@@ -2420,7 +2550,7 @@ async function deploySkills(config, client, loader, stateManager) {
2420
2550
  throw createError;
2421
2551
  }
2422
2552
  logger.warn(` \u26A0\uFE0F Skill already exists on platform (no local state). Fetching ID to recover...`);
2423
- const existingId = await findSkillByName(client, skill.name);
2553
+ const existingId = await findSkillByName(skillIndex, skill.name);
2424
2554
  if (!existingId) {
2425
2555
  throw new Error(`Skill "${skill.name}" reported as existing but could not be found by name.`);
2426
2556
  }
@@ -2440,21 +2570,7 @@ async function deploySkills(config, client, loader, stateManager) {
2440
2570
  logger.info("");
2441
2571
  } catch (error) {
2442
2572
  logger.error(` \u274C Failed to deploy ${skill.name}:`);
2443
- if (error instanceof Error) {
2444
- logger.error(` ${error.message}`);
2445
- logger.debug(` Stack:`, error.stack);
2446
- if ("statusCode" in error) {
2447
- const apiError = error;
2448
- logger.error(` Status: ${apiError.statusCode}`);
2449
- logger.error(` Data: ${JSON.stringify(apiError.response, null, 2)}`);
2450
- } else if ("response" in error) {
2451
- const axiosError = error;
2452
- logger.error(` Status: ${axiosError.response?.status}`);
2453
- logger.error(` Data: ${JSON.stringify(axiosError.response?.data, null, 2)}`);
2454
- }
2455
- } else {
2456
- logger.error(` ${String(error)}`);
2457
- }
2573
+ logApiErrorDetails(error);
2458
2574
  logger.info("");
2459
2575
  stats.failed++;
2460
2576
  }
@@ -2554,17 +2670,7 @@ async function deployDatasources(config, client, stateManager) {
2554
2670
  logger.info("");
2555
2671
  } catch (error) {
2556
2672
  logger.error(` \u274C Failed to deploy ${datasource.name}:`);
2557
- if (error instanceof Error) {
2558
- logger.error(` ${error.message}`);
2559
- logger.debug(` Stack:`, error.stack);
2560
- if ("response" in error) {
2561
- const axiosError = error;
2562
- logger.error(` Status: ${axiosError.response?.status}`);
2563
- logger.error(` Data:`, JSON.stringify(axiosError.response?.data, null, 2));
2564
- }
2565
- } else {
2566
- logger.error(` ${String(error)}`);
2567
- }
2673
+ logApiErrorDetails(error);
2568
2674
  logger.info("");
2569
2675
  stats.failed++;
2570
2676
  }
@@ -2581,13 +2687,13 @@ async function deployWorkflows(config, client, stateManager, rootDir = process.c
2581
2687
  try {
2582
2688
  logger.info(`Processing: ${workflow.name}`);
2583
2689
  const yamlPath = path.join(rootDir, workflow.definition);
2584
- if (!fs.existsSync(yamlPath)) {
2690
+ if (!fs12.existsSync(yamlPath)) {
2585
2691
  throw new Error(`Workflow definition file not found: ${workflow.definition}`);
2586
2692
  }
2587
- const yamlConfigContent = fs.readFileSync(yamlPath, "utf8");
2588
- const workflowYaml = yaml6.parse(yamlConfigContent);
2693
+ const yamlConfigContent = fs12.readFileSync(yamlPath, "utf8");
2694
+ const workflowYaml = yaml7.parse(yamlConfigContent);
2589
2695
  const referencesToResolve = [];
2590
- for (const assistant of workflowYaml.assistants) {
2696
+ for (const assistant of workflowYaml.assistants ?? []) {
2591
2697
  const isInline = !assistant.assistant_name && assistant.model && assistant.system_prompt;
2592
2698
  if (isInline) {
2593
2699
  logger.info(` Skipping inline assistant: ${assistant.id}`);
@@ -2626,48 +2732,21 @@ async function deployWorkflows(config, client, stateManager, rootDir = process.c
2626
2732
  });
2627
2733
  }
2628
2734
  }
2629
- const yamlConfig = yaml6.stringify(workflowYaml);
2735
+ const yamlConfig = yaml7.stringify(workflowYaml);
2630
2736
  const workflowYamlChecksum = calculateChecksum(yamlConfigContent);
2631
2737
  const configChecksum = calculateWorkflowConfigChecksum(workflow);
2632
2738
  const existingState = stateManager.getWorkflowState(workflow.name);
2633
- if (existingState) {
2634
- const existsOnPlatform = await checkWorkflowExists(client, workflow.name, stateManager);
2635
- if (existsOnPlatform) {
2636
- const hasChanged = existingState.workflowYamlChecksum !== workflowYamlChecksum || existingState.configChecksum !== configChecksum;
2637
- if (hasChanged) {
2638
- const apiParams = {
2639
- project: config.project.name,
2640
- name: workflow.name,
2641
- description: workflow.description,
2642
- yaml_config: yamlConfig,
2643
- mode: workflow.mode,
2644
- shared: workflow.shared,
2645
- icon_url: workflow.icon_url
2646
- };
2647
- if (process.env.DEBUG_API) {
2648
- logger.debug("\n=== DEBUG: API Params ===");
2649
- logger.debug(JSON.stringify(apiParams, null, 2));
2650
- logger.debug("=========================\n");
2651
- }
2652
- logger.info(` Updating workflow (ID: ${existingState.id})...`);
2653
- await client.workflows.update(existingState.id, apiParams);
2654
- logger.info(`\u2713 Updated workflow: ${workflow.name} (${existingState.id})`);
2655
- stateManager.updateWorkflowState(workflow.name, existingState.id, workflowYamlChecksum, configChecksum);
2656
- stats.updated++;
2657
- } else {
2658
- logger.info(` \u2713 No changes detected (ID: ${existingState.id})`);
2659
- stats.unchanged++;
2660
- }
2661
- } else {
2662
- logger.info(` \u26A0\uFE0F Workflow ID from state not found on platform, will create new`);
2663
- logger.info(` Creating new workflow...`);
2739
+ const existsOnPlatform = existingState ? await checkWorkflowExists(client, workflow.name, stateManager) : false;
2740
+ if (existingState && existsOnPlatform) {
2741
+ const hasChanged = existingState.workflowYamlChecksum !== workflowYamlChecksum || existingState.configChecksum !== configChecksum;
2742
+ if (hasChanged) {
2664
2743
  const apiParams = {
2665
2744
  project: config.project.name,
2666
2745
  name: workflow.name,
2667
2746
  description: workflow.description,
2668
2747
  yaml_config: yamlConfig,
2669
- mode: workflow.mode || "Sequential",
2670
- shared: workflow.shared ?? true,
2748
+ mode: workflow.mode,
2749
+ shared: workflow.shared,
2671
2750
  icon_url: workflow.icon_url
2672
2751
  };
2673
2752
  if (process.env.DEBUG_API) {
@@ -2675,17 +2754,19 @@ async function deployWorkflows(config, client, stateManager, rootDir = process.c
2675
2754
  logger.debug(JSON.stringify(apiParams, null, 2));
2676
2755
  logger.debug("=========================\n");
2677
2756
  }
2678
- const result = await client.workflows.create(apiParams);
2679
- const workflowId = result.data?.id || result.id;
2680
- if (!workflowId) {
2681
- throw new Error("Workflow created but ID not returned");
2682
- }
2683
- logger.info(`\u2713 Created workflow: ${workflow.name} (${workflowId})`);
2684
- stateManager.updateWorkflowState(workflow.name, workflowId, workflowYamlChecksum, configChecksum);
2685
- stats.created++;
2757
+ logger.info(` Updating workflow (ID: ${existingState.id})...`);
2758
+ await client.workflows.update(existingState.id, apiParams);
2759
+ logger.info(`\u2713 Updated workflow: ${workflow.name} (${existingState.id})`);
2760
+ stateManager.updateWorkflowState(workflow.name, existingState.id, workflowYamlChecksum, configChecksum);
2761
+ stats.updated++;
2762
+ } else {
2763
+ logger.info(` \u2713 No changes detected (ID: ${existingState.id})`);
2764
+ stats.unchanged++;
2686
2765
  }
2687
2766
  } else {
2688
- logger.info(` Creating new workflow...`);
2767
+ if (existingState) {
2768
+ logger.info(` \u26A0\uFE0F Workflow ID from state not found on platform, looking for a same-name workflow`);
2769
+ }
2689
2770
  const apiParams = {
2690
2771
  project: config.project.name,
2691
2772
  name: workflow.name,
@@ -2700,29 +2781,28 @@ async function deployWorkflows(config, client, stateManager, rootDir = process.c
2700
2781
  logger.debug(JSON.stringify(apiParams, null, 2));
2701
2782
  logger.debug("=========================\n");
2702
2783
  }
2703
- const result = await client.workflows.create(apiParams);
2704
- const workflowId = result.data?.id || result.id;
2705
- if (!workflowId) {
2706
- throw new Error("Workflow created but ID not returned");
2784
+ const foundId = await findWorkflowByName(client, workflow.name, config.project.name);
2785
+ if (foundId) {
2786
+ await client.workflows.update(foundId, apiParams);
2787
+ logger.info(`\u2713 Adopted existing workflow ${workflow.name} (${foundId})`);
2788
+ stateManager.updateWorkflowState(workflow.name, foundId, workflowYamlChecksum, configChecksum);
2789
+ stats.updated++;
2790
+ } else {
2791
+ logger.info(` Creating new workflow...`);
2792
+ const result = await client.workflows.create(apiParams);
2793
+ const workflowId = result.data?.id || result.id;
2794
+ if (!workflowId) {
2795
+ throw new Error("Workflow created but ID not returned");
2796
+ }
2797
+ logger.info(`\u2713 Created workflow: ${workflow.name} (${workflowId})`);
2798
+ stateManager.updateWorkflowState(workflow.name, workflowId, workflowYamlChecksum, configChecksum);
2799
+ stats.created++;
2707
2800
  }
2708
- logger.info(`\u2713 Created workflow: ${workflow.name} (${workflowId})`);
2709
- stateManager.updateWorkflowState(workflow.name, workflowId, workflowYamlChecksum, configChecksum);
2710
- stats.created++;
2711
2801
  }
2712
2802
  logger.info("");
2713
2803
  } catch (error) {
2714
2804
  logger.error(` \u274C Failed to deploy ${workflow.name}:`);
2715
- if (error instanceof Error) {
2716
- logger.error(` ${error.message}`);
2717
- logger.debug(` Stack:`, error.stack);
2718
- if ("response" in error) {
2719
- const axiosError = error;
2720
- logger.error(` Status: ${axiosError.response?.status}`);
2721
- logger.error(` Data:`, JSON.stringify(axiosError.response?.data, null, 2));
2722
- }
2723
- } else {
2724
- logger.error(` ${String(error)}`);
2725
- }
2805
+ logApiErrorDetails(error);
2726
2806
  logger.info("");
2727
2807
  stats.failed++;
2728
2808
  }
@@ -2732,7 +2812,7 @@ async function deployWorkflows(config, client, stateManager, rootDir = process.c
2732
2812
  async function deployResources(options) {
2733
2813
  logger.info("\u{1F680} Deploying resources to Codemie...\n");
2734
2814
  try {
2735
- const { appConfig, prune = false } = options;
2815
+ const { appConfig, prune = false, skipDatasources = false } = options;
2736
2816
  const loader = new CodemieConfigLoader(appConfig);
2737
2817
  const stateManager = new StateManager(appConfig);
2738
2818
  logger.info("\u{1F4C4} Loading configuration...");
@@ -2744,7 +2824,12 @@ async function deployResources(options) {
2744
2824
  logger.info("\u2713 Connected to Codemie API\n");
2745
2825
  logger.info("\u{1F9F9} Checking for orphaned resources...");
2746
2826
  const cleanupManager = new CleanupManager(client, stateManager);
2747
- const orphaned = cleanupManager.findOrphanedResources(config);
2827
+ const foundOrphaned = cleanupManager.findOrphanedResources(config);
2828
+ const orphaned = skipDatasources ? { ...foundOrphaned, datasources: [] } : foundOrphaned;
2829
+ const skippedOrphanedDatasources = foundOrphaned.datasources.length - orphaned.datasources.length;
2830
+ if (skippedOrphanedDatasources > 0) {
2831
+ logger.info(` \u23ED\uFE0F ${skippedOrphanedDatasources} orphaned datasource(s) skipped (--skip-datasources)`);
2832
+ }
2748
2833
  const totalOrphaned = orphaned.assistants.length + orphaned.datasources.length + orphaned.workflows.length + orphaned.skills.length;
2749
2834
  let deleted = 0;
2750
2835
  if (totalOrphaned > 0) {
@@ -2784,11 +2869,17 @@ async function deployResources(options) {
2784
2869
  let updated = 0;
2785
2870
  let unchanged = 0;
2786
2871
  let failed = 0;
2787
- const datasourceStats = await deployDatasources(config, client, stateManager);
2788
- created += datasourceStats.created;
2789
- updated += datasourceStats.updated;
2790
- unchanged += datasourceStats.unchanged;
2791
- failed += datasourceStats.failed;
2872
+ if (skipDatasources) {
2873
+ logger.info("\u{1F4CA} Processing datasources...\n");
2874
+ logger.info(` \u23ED\uFE0F Skipped (--skip-datasources): ${config.resources.datasources?.length ?? 0} datasource(s)
2875
+ `);
2876
+ } else {
2877
+ const datasourceStats = await deployDatasources(config, client, stateManager);
2878
+ created += datasourceStats.created;
2879
+ updated += datasourceStats.updated;
2880
+ unchanged += datasourceStats.unchanged;
2881
+ failed += datasourceStats.failed;
2882
+ }
2792
2883
  const skillStats = await deploySkills(config, client, loader, stateManager);
2793
2884
  created += skillStats.created;
2794
2885
  updated += skillStats.updated;
@@ -2865,10 +2956,10 @@ var DependencyValidator = class {
2865
2956
  const errors = [];
2866
2957
  const visited = /* @__PURE__ */ new Set();
2867
2958
  const recursionStack = /* @__PURE__ */ new Set();
2868
- const dfs = (name, path12) => {
2959
+ const dfs = (name, path13) => {
2869
2960
  if (recursionStack.has(name)) {
2870
- const cycleStart = path12.indexOf(name);
2871
- const cycle = [...path12.slice(cycleStart), name];
2961
+ const cycleStart = path13.indexOf(name);
2962
+ const cycle = [...path13.slice(cycleStart), name];
2872
2963
  errors.push(`Cyclic dependency detected: ${cycle.join(" \u2192 ")}`);
2873
2964
  return true;
2874
2965
  }
@@ -2877,14 +2968,14 @@ var DependencyValidator = class {
2877
2968
  }
2878
2969
  visited.add(name);
2879
2970
  recursionStack.add(name);
2880
- path12.push(name);
2971
+ path13.push(name);
2881
2972
  const assistant = assistantMap.get(name);
2882
2973
  const subAssistantRefs = assistant?.sub_assistants || [];
2883
2974
  for (const subRef of subAssistantRefs) {
2884
2975
  if (!assistantMap.has(subRef)) {
2885
2976
  continue;
2886
2977
  }
2887
- if (dfs(subRef, [...path12])) {
2978
+ if (dfs(subRef, [...path13])) {
2888
2979
  return true;
2889
2980
  }
2890
2981
  }
@@ -2943,7 +3034,7 @@ var DependencyValidator = class {
2943
3034
  }
2944
3035
  for (const workflow of workflows) {
2945
3036
  try {
2946
- const workflowYaml = yaml6.parse(workflow.definition);
3037
+ const workflowYaml = yaml7.parse(workflow.definition);
2947
3038
  if (!workflowYaml.assistants) {
2948
3039
  continue;
2949
3040
  }
@@ -3084,11 +3175,11 @@ async function validateConfig(options) {
3084
3175
  if (config.resources.workflows && config.resources.workflows.length > 0) {
3085
3176
  const workflowsWithContent = config.resources.workflows.map((workflow) => {
3086
3177
  const workflowPath = path.resolve(workflow.definition);
3087
- if (!fs.existsSync(workflowPath)) {
3178
+ if (!fs12.existsSync(workflowPath)) {
3088
3179
  errors.push(`Workflow definition file not found: ${workflow.definition}`);
3089
3180
  return { ...workflow, definition: "" };
3090
3181
  }
3091
- const definition = fs.readFileSync(workflowPath, "utf8");
3182
+ const definition = fs12.readFileSync(workflowPath, "utf8");
3092
3183
  return { ...workflow, definition };
3093
3184
  });
3094
3185
  const workflowErrors = DependencyValidator.validateWorkflowAssistantReferences(
@@ -3113,7 +3204,7 @@ init_logger();
3113
3204
  init_checksumUtils();
3114
3205
  init_codemieConfigChecksums();
3115
3206
  init_converters();
3116
- async function previewResource(resource, resourceType, getState, checkExists, calculateChecksums) {
3207
+ async function previewResource(resource, resourceType, getState, checkExists, calculateChecksums, staleDetails) {
3117
3208
  const existingState = getState(resource.name);
3118
3209
  if (existingState) {
3119
3210
  const existsOnPlatform = await checkExists();
@@ -3134,7 +3225,7 @@ async function previewResource(resource, resourceType, getState, checkExists, ca
3134
3225
  type: "create",
3135
3226
  resourceType,
3136
3227
  name: resource.name,
3137
- details: `ID from state not found on platform, will create new`
3228
+ details: staleDetails ?? `ID from state not found on platform, will create new`
3138
3229
  };
3139
3230
  }
3140
3231
  } else {
@@ -3333,14 +3424,16 @@ async function previewDatasources(datasources, stateManager, client) {
3333
3424
  }
3334
3425
  return changes;
3335
3426
  }
3336
- async function previewWorkflows(workflows, stateManager, client, rootDir = process.cwd()) {
3427
+ async function previewWorkflows(workflows, stateManager, client, rootDir = process.cwd(), project) {
3337
3428
  const changes = [];
3429
+ const projectClause = project ? ` in project ${project}` : "";
3430
+ const staleDetails = `ID from state not found; will adopt a same-name workflow${projectClause} if one exists, otherwise create`;
3338
3431
  for (const workflow of workflows) {
3339
3432
  const yamlPath = path.join(rootDir, workflow.definition);
3340
- if (!fs.existsSync(yamlPath)) {
3433
+ if (!fs12.existsSync(yamlPath)) {
3341
3434
  throw new Error(`Workflow definition file not found: ${workflow.definition}`);
3342
3435
  }
3343
- const yamlConfig = fs.readFileSync(yamlPath, "utf8");
3436
+ const yamlConfig = fs12.readFileSync(yamlPath, "utf8");
3344
3437
  const change = await previewResource(
3345
3438
  workflow,
3346
3439
  "workflow",
@@ -3355,7 +3448,8 @@ async function previewWorkflows(workflows, stateManager, client, rootDir = proce
3355
3448
  hasChanged,
3356
3449
  ...hasChanged ? { updateDetails: "Workflow YAML or configuration changed" } : {}
3357
3450
  };
3358
- }
3451
+ },
3452
+ staleDetails
3359
3453
  );
3360
3454
  if (!change.details && change.type === "create") {
3361
3455
  change.details = `Mode: ${workflow.mode}`;
@@ -3366,14 +3460,16 @@ async function previewWorkflows(workflows, stateManager, client, rootDir = proce
3366
3460
  }
3367
3461
 
3368
3462
  // src/preview.ts
3369
- async function previewChanges(appConfig, existingClient) {
3463
+ async function previewChanges(appConfig, existingClient, options) {
3370
3464
  const loader = new CodemieConfigLoader(appConfig);
3371
3465
  const stateManager = new StateManager(appConfig);
3372
3466
  const config = loader.loadConfig();
3373
3467
  const client = existingClient || await createClient(config);
3374
3468
  const changes = [];
3375
3469
  const cleanupManager = new CleanupManager(client, stateManager);
3376
- const orphaned = cleanupManager.findOrphanedResources(config);
3470
+ const skipDatasources = options?.skipDatasources === true;
3471
+ const foundOrphaned = cleanupManager.findOrphanedResources(config);
3472
+ const orphaned = skipDatasources ? { ...foundOrphaned, datasources: [] } : foundOrphaned;
3377
3473
  changes.push(...previewOrphanedResources(orphaned, stateManager));
3378
3474
  if (config.resources.skills) {
3379
3475
  const skillChanges = await previewSkills(config.resources.skills, loader, stateManager, client);
@@ -3383,12 +3479,18 @@ async function previewChanges(appConfig, existingClient) {
3383
3479
  const assistantChanges = await previewAssistants(config.resources.assistants, loader, stateManager, client, config);
3384
3480
  changes.push(...assistantChanges);
3385
3481
  }
3386
- if (config.resources.datasources) {
3482
+ if (config.resources.datasources && !skipDatasources) {
3387
3483
  const datasourceChanges = await previewDatasources(config.resources.datasources, stateManager, client);
3388
3484
  changes.push(...datasourceChanges);
3389
3485
  }
3390
3486
  if (config.resources.workflows) {
3391
- const workflowChanges = await previewWorkflows(config.resources.workflows, stateManager, client, appConfig.rootDir);
3487
+ const workflowChanges = await previewWorkflows(
3488
+ config.resources.workflows,
3489
+ stateManager,
3490
+ client,
3491
+ appConfig.rootDir,
3492
+ config.project.name
3493
+ );
3392
3494
  changes.push(...workflowChanges);
3393
3495
  }
3394
3496
  const toDelete = changes.filter((c) => c.type === "delete");
@@ -3444,7 +3546,7 @@ var BackupTransaction = class {
3444
3546
  constructor(backupDir, transactionId) {
3445
3547
  this.transactionPath = path.join(backupDir, "transaction.json");
3446
3548
  try {
3447
- const content = fs.readFileSync(this.transactionPath, "utf8");
3549
+ const content = fs12.readFileSync(this.transactionPath, "utf8");
3448
3550
  this.data = JSON.parse(content);
3449
3551
  logger.info(`\u{1F4C2} Resuming backup transaction ${this.data.id}`);
3450
3552
  } catch (error) {
@@ -3476,9 +3578,9 @@ var BackupTransaction = class {
3476
3578
  }
3477
3579
  writeTransactionExclusively(data) {
3478
3580
  try {
3479
- const fd = fs.openSync(this.transactionPath, "wx");
3480
- fs.writeSync(fd, JSON.stringify(data, null, 2));
3481
- fs.closeSync(fd);
3581
+ const fd = fs12.openSync(this.transactionPath, "wx");
3582
+ fs12.writeSync(fd, JSON.stringify(data, null, 2));
3583
+ fs12.closeSync(fd);
3482
3584
  } catch (writeError) {
3483
3585
  const writeErr = writeError;
3484
3586
  if (writeErr.code === "EEXIST") {
@@ -3497,7 +3599,7 @@ var BackupTransaction = class {
3497
3599
  if (delayMs > 0) {
3498
3600
  busyWaitDelay(delayMs);
3499
3601
  }
3500
- const content = fs.readFileSync(this.transactionPath, "utf8");
3602
+ const content = fs12.readFileSync(this.transactionPath, "utf8");
3501
3603
  this.data = JSON.parse(content);
3502
3604
  logger.info(`\u{1F4C2} Resuming backup transaction ${this.data.id} (created by concurrent process)`);
3503
3605
  break;
@@ -3520,7 +3622,7 @@ var BackupTransaction = class {
3520
3622
  */
3521
3623
  async save() {
3522
3624
  ensureDirectoryExists(this.transactionPath);
3523
- const savePromise = fs.promises.writeFile(this.transactionPath, JSON.stringify(this.data, null, 2), "utf8");
3625
+ const savePromise = fs12.promises.writeFile(this.transactionPath, JSON.stringify(this.data, null, 2), "utf8");
3524
3626
  let timeoutId;
3525
3627
  const timeoutPromise = new Promise((_, reject) => {
3526
3628
  timeoutId = setTimeout(
@@ -3546,7 +3648,7 @@ var BackupTransaction = class {
3546
3648
  */
3547
3649
  saveSyncInternal() {
3548
3650
  ensureDirectoryExists(this.transactionPath);
3549
- fs.writeFileSync(this.transactionPath, JSON.stringify(this.data, null, 2), "utf8");
3651
+ fs12.writeFileSync(this.transactionPath, JSON.stringify(this.data, null, 2), "utf8");
3550
3652
  this.isDirty = false;
3551
3653
  }
3552
3654
  /**
@@ -3657,8 +3759,8 @@ var BackupTransaction = class {
3657
3759
  * Clean up transaction file after successful completion
3658
3760
  */
3659
3761
  cleanup() {
3660
- if (fs.existsSync(this.transactionPath)) {
3661
- fs.unlinkSync(this.transactionPath);
3762
+ if (fs12.existsSync(this.transactionPath)) {
3763
+ fs12.unlinkSync(this.transactionPath);
3662
3764
  }
3663
3765
  }
3664
3766
  };
@@ -3748,7 +3850,7 @@ function generateCodemieYaml(backup, projectName, backupDir, integrationSpecPath
3748
3850
  }
3749
3851
  }
3750
3852
  };
3751
- return yaml6.stringify(config);
3853
+ return yaml7.stringify(config);
3752
3854
  }
3753
3855
  function saveIntegrationOpenApiSpecs(backupData, backupDir) {
3754
3856
  const specsDir = path.join(backupDir, "openapi_specs");
@@ -3764,8 +3866,8 @@ function saveIntegrationOpenApiSpecs(backupData, backupDir) {
3764
3866
  if (specEntry && typeof specEntry.value === "string" && specEntry.value) {
3765
3867
  const alias = integration.alias || integration.id;
3766
3868
  const fileName = String(alias).toLowerCase().replaceAll(/[^a-z0-9]+/g, "-");
3767
- if (!fs.existsSync(specsDir)) {
3768
- fs.mkdirSync(specsDir, { recursive: true });
3869
+ if (!fs12.existsSync(specsDir)) {
3870
+ fs12.mkdirSync(specsDir, { recursive: true });
3769
3871
  }
3770
3872
  const specContent = specEntry.value;
3771
3873
  let fileExtension = ".yaml";
@@ -3779,7 +3881,7 @@ function saveIntegrationOpenApiSpecs(backupData, backupDir) {
3779
3881
  }
3780
3882
  const specFileName = `${fileName}${fileExtension}`;
3781
3883
  const specPath = path.join(specsDir, specFileName);
3782
- fs.writeFileSync(specPath, contentToSave, "utf8");
3884
+ fs12.writeFileSync(specPath, contentToSave, "utf8");
3783
3885
  integrationSpecPaths.set(integration.id, `openapi_specs/${specFileName}`);
3784
3886
  specsCount++;
3785
3887
  }
@@ -3794,13 +3896,13 @@ function saveBackupFiles(backupData, backupDir, projectName) {
3794
3896
  const integrationSpecPaths = saveIntegrationOpenApiSpecs(backupData, backupDir);
3795
3897
  const codemieYamlPath = path.join(backupDir, "codemie.yaml");
3796
3898
  const codemieYaml = generateCodemieYaml(backupData, projectName, backupDir, integrationSpecPaths);
3797
- fs.writeFileSync(codemieYamlPath, codemieYaml, "utf8");
3899
+ fs12.writeFileSync(codemieYamlPath, codemieYaml, "utf8");
3798
3900
  logger.info(` \u2713 Saved config file: codemie.yaml`);
3799
3901
  const backupJsonPath = path.join(backupDir, "backup.json");
3800
- fs.writeFileSync(backupJsonPath, JSON.stringify(backupData, null, 2), "utf8");
3902
+ fs12.writeFileSync(backupJsonPath, JSON.stringify(backupData, null, 2), "utf8");
3801
3903
  logger.info(` \u2713 Saved full backup: backup.json`);
3802
3904
  const statePath = path.join(backupDir, "state.json");
3803
- fs.writeFileSync(statePath, JSON.stringify(backupData.state, null, 2), "utf8");
3905
+ fs12.writeFileSync(statePath, JSON.stringify(backupData.state, null, 2), "utf8");
3804
3906
  logger.info(` \u2713 Saved state file: state.json`);
3805
3907
  }
3806
3908
 
@@ -3910,7 +4012,7 @@ async function saveAssistantToBackup(assistant, client, backupData, backupDir) {
3910
4012
  const fileName = `${full.slug || sanitizeFileName(full.name)}.prompt.md`;
3911
4013
  const filePath = path.join(backupDir, "system_prompts", fileName);
3912
4014
  ensureDirectoryExists(filePath);
3913
- fs.writeFileSync(filePath, full.system_prompt, "utf8");
4015
+ fs12.writeFileSync(filePath, full.system_prompt, "utf8");
3914
4016
  }
3915
4017
  }
3916
4018
  async function backupAssistants(client, backupData, backupDir, transaction) {
@@ -4038,7 +4140,7 @@ async function backupWorkflows(client, backupData, backupDir, transaction) {
4038
4140
  const fileName = `${sanitizeFileName(full.name)}.yaml`;
4039
4141
  const filePath = path.join(backupDir, "workflows", fileName);
4040
4142
  ensureDirectoryExists(filePath);
4041
- fs.writeFileSync(filePath, full.yaml_config, "utf8");
4143
+ fs12.writeFileSync(filePath, full.yaml_config, "utf8");
4042
4144
  }
4043
4145
  transaction.markCompleted("workflows", workflow.id);
4044
4146
  } catch (error) {
@@ -4143,7 +4245,7 @@ async function backupSkills(client, backupData, backupDir, transaction, projectN
4143
4245
  const fileName = `${sanitizeFileName(skill.name)}.skill.md`;
4144
4246
  const filePath = path.join(backupDir, "skills", fileName);
4145
4247
  ensureDirectoryExists(filePath);
4146
- fs.writeFileSync(filePath, full.content || "", "utf8");
4248
+ fs12.writeFileSync(filePath, full.content || "", "utf8");
4147
4249
  } else {
4148
4250
  logger.info(` \u21B7 Skipping content file for imported skill ${skill.name} (owner: ${full.project})`);
4149
4251
  }
@@ -4166,7 +4268,7 @@ async function backupSkills(client, backupData, backupDir, transaction, projectN
4166
4268
  function getUniqueBackupDir(baseDir, timestamp) {
4167
4269
  let counter = 0;
4168
4270
  let finalBackupDir = path.join(baseDir, timestamp);
4169
- while (fs.existsSync(finalBackupDir)) {
4271
+ while (fs12.existsSync(finalBackupDir)) {
4170
4272
  counter++;
4171
4273
  finalBackupDir = path.join(baseDir, `${timestamp}-${counter}`);
4172
4274
  }
@@ -4175,7 +4277,7 @@ function getUniqueBackupDir(baseDir, timestamp) {
4175
4277
  return { finalDir: finalBackupDir, tempDir: tempBackupDir };
4176
4278
  }
4177
4279
  function performCleanup(tempBackupDir, transaction) {
4178
- if (fs.existsSync(tempBackupDir)) {
4280
+ if (fs12.existsSync(tempBackupDir)) {
4179
4281
  try {
4180
4282
  logger.info(`\u{1F9F9} Rolling back: cleaning up ${tempBackupDir}...`);
4181
4283
  cleanupDirectory(tempBackupDir);
@@ -4214,8 +4316,8 @@ async function backupResources(options) {
4214
4316
  logger.info("\u{1F50C} Connecting to Codemie API...");
4215
4317
  const client = await createClient(config);
4216
4318
  logger.info("\u2713 Connected to Codemie API\n");
4217
- if (!fs.existsSync(tempBackupDir)) {
4218
- fs.mkdirSync(tempBackupDir, { recursive: true });
4319
+ if (!fs12.existsSync(tempBackupDir)) {
4320
+ fs12.mkdirSync(tempBackupDir, { recursive: true });
4219
4321
  }
4220
4322
  logger.info(`\u{1F4C1} Temporary backup directory: ${tempBackupDir}
4221
4323
  `);
@@ -4296,14 +4398,34 @@ async function backupResources(options) {
4296
4398
  }
4297
4399
 
4298
4400
  // src/import.ts
4401
+ init_checksumUtils();
4402
+ init_codemieConfigChecksums();
4299
4403
  init_converters();
4300
- init_skillUtils();
4301
4404
  init_fileUtils();
4302
4405
  init_logger();
4303
- init_checksumUtils();
4304
- init_codemieConfigChecksums();
4406
+
4407
+ // src/lib/resourceTypes.ts
4305
4408
  var VALID_RESOURCE_TYPES = ["assistant", "datasource", "workflow", "skill"];
4409
+ var PLURAL_BY_RESOURCE_TYPE = {
4410
+ assistant: "assistants",
4411
+ datasource: "datasources",
4412
+ workflow: "workflows",
4413
+ skill: "skills"
4414
+ };
4415
+ function pluralTypeOf(resourceType) {
4416
+ return PLURAL_BY_RESOURCE_TYPE[resourceType];
4417
+ }
4418
+
4419
+ // src/import.ts
4420
+ init_skillUtils();
4306
4421
  async function findAssistant(client, slug) {
4422
+ if (UUID_RE.test(slug)) {
4423
+ return withTimeout(
4424
+ client.assistants.get(slug),
4425
+ TIMEOUTS_MS.ASSISTANT_FETCH,
4426
+ `Timeout fetching assistant "${slug}"`
4427
+ );
4428
+ }
4307
4429
  try {
4308
4430
  const assistant = await withTimeout(
4309
4431
  client.assistants.getBySlug(slug),
@@ -4387,7 +4509,7 @@ Please use a more specific name.`);
4387
4509
  return matches[0];
4388
4510
  }
4389
4511
  function checkResourceExists2(config, resourceType, name) {
4390
- const pluralType = resourceType === "assistant" ? "assistants" : resourceType === "datasource" ? "datasources" : resourceType === "workflow" ? "workflows" : "skills";
4512
+ const pluralType = pluralTypeOf(resourceType);
4391
4513
  const resources = config.resources[pluralType] || [];
4392
4514
  if (resources.some((r) => namesEqual(r.name, name))) {
4393
4515
  return true;
@@ -4398,8 +4520,38 @@ function checkResourceExists2(config, resourceType, name) {
4398
4520
  }
4399
4521
  return false;
4400
4522
  }
4523
+ function findManagedNameById(stateManager, resourceType, id) {
4524
+ const state = stateManager.loadState();
4525
+ const pluralType = pluralTypeOf(resourceType);
4526
+ const resources = state.resources[pluralType] || {};
4527
+ const matches = Object.keys(resources).filter((name) => resources[name]?.id === id);
4528
+ if (matches.length > 1) {
4529
+ throw new Error(
4530
+ `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.`
4531
+ );
4532
+ }
4533
+ return matches[0];
4534
+ }
4535
+ function nameClashError(resourceType, name) {
4536
+ return new Error(
4537
+ `A ${resourceType} named "${name}" already exists in the configuration. Re-run with --update to replace it with the platform version, or remove it first.`
4538
+ );
4539
+ }
4540
+ function assertResolvedNameIsFree(config, stateManager, resourceType, slug, resolvedName, id, update) {
4541
+ if (!update && !namesEqual(slug, resolvedName) && checkResourceExists2(config, resourceType, resolvedName)) {
4542
+ throw nameClashError(resourceType, resolvedName);
4543
+ }
4544
+ const managedName = findManagedNameById(stateManager, resourceType, id);
4545
+ if (!update && managedName !== void 0 && !namesEqual(managedName, resolvedName)) {
4546
+ throw new Error(
4547
+ `This ${resourceType} (id ${id}) is already managed by this project under the name "${managedName}", but is now named "${resolvedName}" on the platform.`
4548
+ );
4549
+ }
4550
+ return managedName;
4551
+ }
4401
4552
  function writeResourceFiles(rootDir, resourceType, resource, apiResponse) {
4402
4553
  const safeName = sanitizeFileName(resource.name);
4554
+ const target = resource;
4403
4555
  if (resourceType === "workflow") {
4404
4556
  const workflow = apiResponse;
4405
4557
  const workflowResource = resource;
@@ -4407,19 +4559,21 @@ function writeResourceFiles(rootDir, resourceType, resource, apiResponse) {
4407
4559
  const defFileName = `${safeName}.yaml`;
4408
4560
  const defPath = path.join(rootDir, "workflows", defFileName);
4409
4561
  ensureDirectoryExists(defPath);
4410
- fs.writeFileSync(defPath, workflow.yaml_config, "utf8");
4562
+ fs12.writeFileSync(defPath, workflow.yaml_config, "utf8");
4411
4563
  logger.info(` \u{1F4CB} Saved workflow definition: workflows/${defFileName}`);
4412
4564
  workflowResource.definition = `workflows/${defFileName}`;
4565
+ } else {
4566
+ delete target.definition;
4413
4567
  }
4414
4568
  const configPath = path.join(rootDir, "configs", "workflows", `${safeName}.yaml`);
4415
4569
  const relativeConfigPath = `configs/workflows/${safeName}.yaml`;
4416
4570
  ensureDirectoryExists(configPath);
4417
- const yamlContent2 = yaml6.stringify(workflowResource, { lineWidth: 120 });
4418
- fs.writeFileSync(configPath, yamlContent2, "utf8");
4571
+ const yamlContent2 = yaml7.stringify(workflowResource, { lineWidth: 120 });
4572
+ fs12.writeFileSync(configPath, yamlContent2, "utf8");
4419
4573
  logger.info(` \u{1F4C4} Saved workflow config: ${relativeConfigPath}`);
4420
4574
  return relativeConfigPath;
4421
4575
  }
4422
- const pluralType = resourceType === "assistant" ? "assistants" : resourceType === "datasource" ? "datasources" : "skills";
4576
+ const pluralType = pluralTypeOf(resourceType);
4423
4577
  const resourceDir = path.join(rootDir, "configs", "imported-resources", pluralType);
4424
4578
  const resourceFilePath = path.join(resourceDir, `${safeName}.yaml`);
4425
4579
  const relativeResourcePath = `configs/imported-resources/${pluralType}/${safeName}.yaml`;
@@ -4430,9 +4584,11 @@ function writeResourceFiles(rootDir, resourceType, resource, apiResponse) {
4430
4584
  const promptFileName = assistant.slug || safeName;
4431
4585
  const promptPath = path.join(rootDir, "system_prompts", `${promptFileName}.prompt.md`);
4432
4586
  ensureDirectoryExists(promptPath);
4433
- fs.writeFileSync(promptPath, assistant.system_prompt, "utf8");
4587
+ fs12.writeFileSync(promptPath, assistant.system_prompt, "utf8");
4434
4588
  logger.info(` \u{1F4DD} Saved prompt: system_prompts/${promptFileName}.prompt.md`);
4435
4589
  assistantResource.prompt = `system_prompts/${promptFileName}.prompt.md`;
4590
+ } else {
4591
+ delete target.prompt;
4436
4592
  }
4437
4593
  } else if (resourceType === "skill") {
4438
4594
  const skill = apiResponse;
@@ -4441,25 +4597,175 @@ function writeResourceFiles(rootDir, resourceType, resource, apiResponse) {
4441
4597
  const skillFileName = `${safeName}.skill.md`;
4442
4598
  const skillPath = path.join(rootDir, "skills", skillFileName);
4443
4599
  ensureDirectoryExists(skillPath);
4444
- fs.writeFileSync(skillPath, skill.content, "utf8");
4600
+ fs12.writeFileSync(skillPath, skill.content, "utf8");
4445
4601
  logger.info(` \u{1F9E0} Saved skill content: skills/${skillFileName}`);
4446
4602
  skillResource.file = `skills/${skillFileName}`;
4603
+ } else {
4604
+ delete target.file;
4447
4605
  }
4448
4606
  }
4449
4607
  ensureDirectoryExists(resourceFilePath);
4450
- const yamlContent = yaml6.stringify(resource, { lineWidth: 120 });
4451
- fs.writeFileSync(resourceFilePath, yamlContent, "utf8");
4608
+ const yamlContent = yaml7.stringify(resource, { lineWidth: 120 });
4609
+ fs12.writeFileSync(resourceFilePath, yamlContent, "utf8");
4452
4610
  logger.info(` \u{1F4C4} Saved resource config: ${relativeResourcePath}`);
4453
4611
  return relativeResourcePath;
4454
4612
  }
4613
+ function planCompanionWrite(rootDir, target, key, configuredValue, content, defaultPath, label) {
4614
+ const configuredPath = typeof configuredValue === "string" ? configuredValue : void 0;
4615
+ if (!content) {
4616
+ if (configuredPath === void 0) {
4617
+ delete target[key];
4618
+ } else {
4619
+ target[key] = configuredPath;
4620
+ }
4621
+ return void 0;
4622
+ }
4623
+ const relPath = configuredPath ?? defaultPath;
4624
+ target[key] = relPath;
4625
+ return { absPath: path.join(rootDir, relPath), content, log: ` ${label}: ${relPath}` };
4626
+ }
4627
+ var IN_LIST_ITEM_INDENT = 2;
4628
+ function itemCarriesAnchorAliasOrMergeKey(node) {
4629
+ if (node.anchor !== void 0) {
4630
+ return true;
4631
+ }
4632
+ return node.items.some((pair) => {
4633
+ const key = pair.key;
4634
+ const keyValue = yaml7.isScalar(key) ? key.value : key;
4635
+ if (keyValue === "<<") {
4636
+ return true;
4637
+ }
4638
+ const value = pair.value;
4639
+ if (yaml7.isAlias(value)) {
4640
+ return true;
4641
+ }
4642
+ return yaml7.isNode(value) && value.anchor !== void 0;
4643
+ });
4644
+ }
4645
+ function spliceResourceIntoList(filePath, relativeResourcePath, resource, existingName) {
4646
+ const source = fs12.readFileSync(filePath, "utf8");
4647
+ const doc = yaml7.parseDocument(source);
4648
+ const refusal = (reason) => new Error(
4649
+ `Cannot safely update "${existingName}" inside ${relativeResourcePath}: ${reason}. Update ${relativeResourcePath} manually.`
4650
+ );
4651
+ if (!yaml7.isSeq(doc.contents) || doc.contents.flow) {
4652
+ throw refusal("the file is not a block-style YAML sequence");
4653
+ }
4654
+ const node = doc.contents.items.find((item) => {
4655
+ if (!yaml7.isMap(item)) {
4656
+ return false;
4657
+ }
4658
+ const itemName = item.get("name");
4659
+ return typeof itemName === "string" && namesEqual(itemName, existingName);
4660
+ });
4661
+ if (!node?.range) {
4662
+ throw refusal("its entry could not be located as a block mapping in the sequence");
4663
+ }
4664
+ if (node.flow) {
4665
+ throw refusal("its entry uses flow style ({ ... })");
4666
+ }
4667
+ if (itemCarriesAnchorAliasOrMergeKey(node)) {
4668
+ throw refusal("its entry uses a YAML anchor, alias or merge key");
4669
+ }
4670
+ const [start, valueEnd] = node.range;
4671
+ const indent = start - (source.lastIndexOf("\n", start - 1) + 1);
4672
+ if (indent !== IN_LIST_ITEM_INDENT) {
4673
+ throw refusal(`its entry is indented by ${indent} spaces instead of ${IN_LIST_ITEM_INDENT}`);
4674
+ }
4675
+ const rendered = yaml7.stringify(resource, { lineWidth: 120 }).trimEnd();
4676
+ const replacement = rendered.split("\n").map((line, index) => index === 0 || !line ? line : `${" ".repeat(IN_LIST_ITEM_INDENT)}${line}`).join("\n");
4677
+ const tail = source.slice(valueEnd);
4678
+ const separator = tail.startsWith("\n") ? "" : "\n";
4679
+ return source.slice(0, start) + replacement + separator + tail;
4680
+ }
4681
+ function updateResourceFiles(rootDir, resourceType, resource, apiResponse, existing) {
4682
+ const filePath = existing.file;
4683
+ if (!filePath) {
4684
+ throw new Error("updateResourceFiles requires an existing resource backed by a file");
4685
+ }
4686
+ const existingResource = existing.resource;
4687
+ const target = resource;
4688
+ const safeName = sanitizeFileName(resource.name);
4689
+ for (const [key, value] of Object.entries(existingResource)) {
4690
+ if (key === "skill_ids" || key in target) {
4691
+ continue;
4692
+ }
4693
+ target[key] = value;
4694
+ }
4695
+ let companion;
4696
+ if (resourceType === "assistant") {
4697
+ const assistant = apiResponse;
4698
+ companion = planCompanionWrite(
4699
+ rootDir,
4700
+ target,
4701
+ "prompt",
4702
+ existingResource.prompt,
4703
+ assistant.system_prompt,
4704
+ `system_prompts/${assistant.slug || safeName}.prompt.md`,
4705
+ "\u{1F4DD} Updated prompt"
4706
+ );
4707
+ } else if (resourceType === "skill") {
4708
+ const skill = apiResponse;
4709
+ companion = planCompanionWrite(
4710
+ rootDir,
4711
+ target,
4712
+ "file",
4713
+ existingResource.file,
4714
+ skill.content,
4715
+ `skills/${safeName}.skill.md`,
4716
+ "\u{1F9E0} Updated skill content"
4717
+ );
4718
+ } else if (resourceType === "workflow") {
4719
+ const workflow = apiResponse;
4720
+ companion = planCompanionWrite(
4721
+ rootDir,
4722
+ target,
4723
+ "definition",
4724
+ existingResource.definition,
4725
+ workflow.yaml_config,
4726
+ `workflows/${safeName}.yaml`,
4727
+ "\u{1F4CB} Updated workflow definition"
4728
+ );
4729
+ }
4730
+ const relativeResourcePath = path.relative(rootDir, filePath);
4731
+ const newResourceFileContent = existing.inList ? spliceResourceIntoList(filePath, relativeResourcePath, resource, String(existingResource.name)) : yaml7.stringify(resource, { lineWidth: 120 });
4732
+ if (companion) {
4733
+ ensureDirectoryExists(companion.absPath);
4734
+ fs12.writeFileSync(companion.absPath, companion.content, "utf8");
4735
+ logger.info(companion.log);
4736
+ }
4737
+ fs12.writeFileSync(filePath, newResourceFileContent, "utf8");
4738
+ logger.info(` \u{1F4C4} Updated resource config: ${relativeResourcePath}`);
4739
+ return relativeResourcePath;
4740
+ }
4741
+ function deleteStateEntry(stateManager, resourceType, name) {
4742
+ switch (resourceType) {
4743
+ case "assistant": {
4744
+ stateManager.deleteAssistantState(name);
4745
+ break;
4746
+ }
4747
+ case "datasource": {
4748
+ stateManager.deleteDatasourceState(name);
4749
+ break;
4750
+ }
4751
+ case "workflow": {
4752
+ stateManager.deleteWorkflowState(name);
4753
+ break;
4754
+ }
4755
+ case "skill": {
4756
+ stateManager.deleteSkillState(name);
4757
+ break;
4758
+ }
4759
+ }
4760
+ }
4455
4761
  function addImportToCodemieYaml(rootDir, codemieConfigPath, resourceType, relativeResourcePath) {
4456
4762
  const configPath = path.join(rootDir, codemieConfigPath);
4457
- if (!fs.existsSync(configPath)) {
4763
+ if (!fs12.existsSync(configPath)) {
4458
4764
  throw new Error(`Configuration file not found: ${configPath}`);
4459
4765
  }
4460
- const content = fs.readFileSync(configPath, "utf8");
4461
- const doc = yaml6.parseDocument(content);
4462
- const pluralType = resourceType === "assistant" ? "assistants" : resourceType === "datasource" ? "datasources" : resourceType === "workflow" ? "workflows" : "skills";
4766
+ const content = fs12.readFileSync(configPath, "utf8");
4767
+ const doc = yaml7.parseDocument(content);
4768
+ const pluralType = pluralTypeOf(resourceType);
4463
4769
  let resourcesNode = doc.get("resources");
4464
4770
  if (!resourcesNode) {
4465
4771
  resourcesNode = doc.createNode({});
@@ -4472,7 +4778,7 @@ function addImportToCodemieYaml(rootDir, codemieConfigPath, resourceType, relati
4472
4778
  }
4473
4779
  const importDirective = { $import: relativeResourcePath };
4474
4780
  const existingImports = typeArray.items.filter((item) => {
4475
- if (yaml6.isMap(item)) {
4781
+ if (yaml7.isMap(item)) {
4476
4782
  const importValue = item.get("$import");
4477
4783
  return importValue === relativeResourcePath;
4478
4784
  }
@@ -4484,16 +4790,16 @@ function addImportToCodemieYaml(rootDir, codemieConfigPath, resourceType, relati
4484
4790
  }
4485
4791
  const newNode = doc.createNode(importDirective);
4486
4792
  typeArray.add(newNode);
4487
- fs.writeFileSync(configPath, doc.toString(), "utf8");
4793
+ fs12.writeFileSync(configPath, doc.toString(), "utf8");
4488
4794
  logger.info(` \u2705 Added $import to ${codemieConfigPath} \u2192 resources.${pluralType}`);
4489
4795
  }
4490
4796
  function addImportedSkillToCodemieYaml(rootDir, codemieConfigPath, skill) {
4491
4797
  const configPath = path.join(rootDir, codemieConfigPath);
4492
- if (!fs.existsSync(configPath)) {
4798
+ if (!fs12.existsSync(configPath)) {
4493
4799
  throw new Error(`Configuration file not found: ${configPath}`);
4494
4800
  }
4495
- const content = fs.readFileSync(configPath, "utf8");
4496
- const doc = yaml6.parseDocument(content);
4801
+ const content = fs12.readFileSync(configPath, "utf8");
4802
+ const doc = yaml7.parseDocument(content);
4497
4803
  let importedNode = doc.get("imported");
4498
4804
  if (!importedNode) {
4499
4805
  importedNode = doc.createNode({ assistants: [], datasources: [], skills: [], integrations: [] });
@@ -4505,7 +4811,7 @@ function addImportedSkillToCodemieYaml(rootDir, codemieConfigPath, skill) {
4505
4811
  doc.setIn(["imported", "skills"], skillsArray);
4506
4812
  }
4507
4813
  const existingSkill = skillsArray.items.find((item) => {
4508
- if (!yaml6.isMap(item)) {
4814
+ if (!yaml7.isMap(item)) {
4509
4815
  return false;
4510
4816
  }
4511
4817
  const existingName = item.get("name");
@@ -4522,9 +4828,47 @@ function addImportedSkillToCodemieYaml(rootDir, codemieConfigPath, skill) {
4522
4828
  return;
4523
4829
  }
4524
4830
  skillsArray.add(doc.createNode({ name: skill.name, id: skill.id }));
4525
- fs.writeFileSync(configPath, doc.toString(), "utf8");
4831
+ fs12.writeFileSync(configPath, doc.toString(), "utf8");
4526
4832
  logger.info(` \u2705 Added skill to ${codemieConfigPath} \u2192 imported.skills`);
4527
4833
  }
4834
+ function updateImportedSkillInCodemieYaml(rootDir, codemieConfigPath, skill) {
4835
+ const configPath = path.join(rootDir, codemieConfigPath);
4836
+ if (!fs12.existsSync(configPath)) {
4837
+ throw new Error(`Configuration file not found: ${configPath}`);
4838
+ }
4839
+ const content = fs12.readFileSync(configPath, "utf8");
4840
+ const doc = yaml7.parseDocument(content);
4841
+ const skillsArray = doc.getIn(["imported", "skills"]);
4842
+ if (!skillsArray) {
4843
+ return false;
4844
+ }
4845
+ const items = skillsArray.items.filter((item) => yaml7.isMap(item));
4846
+ const entryById = items.find((item) => {
4847
+ const existingId = item.get("id");
4848
+ return typeof existingId === "string" && existingId === skill.id;
4849
+ });
4850
+ const entry = entryById ?? items.find((item) => {
4851
+ const existingName = item.get("name");
4852
+ return typeof existingName === "string" && namesEqual(existingName, skill.name);
4853
+ });
4854
+ if (!entry) {
4855
+ return false;
4856
+ }
4857
+ entry.set("name", skill.name);
4858
+ entry.set("id", skill.id);
4859
+ fs12.writeFileSync(configPath, doc.toString(), "utf8");
4860
+ return true;
4861
+ }
4862
+ function upsertImportedSkill(rootDir, codemieConfigPath, skill, update) {
4863
+ if (update) {
4864
+ if (updateImportedSkillInCodemieYaml(rootDir, codemieConfigPath, skill)) {
4865
+ logger.info(` \u267B\uFE0F Refreshed imported.skills entry for external skill "${skill.name}"`);
4866
+ return;
4867
+ }
4868
+ logger.info(` No existing skill found in the configuration \u2014 importing as new`);
4869
+ }
4870
+ addImportedSkillToCodemieYaml(rootDir, codemieConfigPath, skill);
4871
+ }
4528
4872
  function ensureExternalSkillDoesNotConflictWithManagedSkill(config, skillName) {
4529
4873
  const managedSkill = config.resources.skills?.find((skill) => namesEqual(skill.name, skillName));
4530
4874
  if (!managedSkill) {
@@ -4577,7 +4921,7 @@ function buildIntegrationAliasMap(config) {
4577
4921
  return map;
4578
4922
  }
4579
4923
  async function importResource(options) {
4580
- const { appConfig, resourceType, slug } = options;
4924
+ const { appConfig, resourceType, slug, update = false } = options;
4581
4925
  if (!VALID_RESOURCE_TYPES.includes(resourceType)) {
4582
4926
  throw new Error(`Invalid resource type: "${resourceType}". Must be one of: ${VALID_RESOURCE_TYPES.join(", ")}`);
4583
4927
  }
@@ -4585,10 +4929,8 @@ async function importResource(options) {
4585
4929
  const config = configLoader.loadConfig();
4586
4930
  const rootDir = appConfig.rootDir;
4587
4931
  const stateManager = new StateManager(appConfig);
4588
- if (checkResourceExists2(config, resourceType, slug)) {
4589
- throw new Error(
4590
- `A ${resourceType} named "${slug}" already exists in the configuration. Remove it first or use a different name.`
4591
- );
4932
+ if (!update && checkResourceExists2(config, resourceType, slug)) {
4933
+ throw nameClashError(resourceType, slug);
4592
4934
  }
4593
4935
  logger.info(`
4594
4936
  \u{1F50D} Importing ${resourceType}: "${slug}"
@@ -4598,11 +4940,21 @@ async function importResource(options) {
4598
4940
  const integrationAliasMap = buildIntegrationAliasMap(config);
4599
4941
  let resource;
4600
4942
  let apiResponse;
4943
+ let managedName;
4601
4944
  switch (resourceType) {
4602
4945
  case "assistant": {
4603
4946
  logger.info(` Searching for assistant "${slug}"...`);
4604
4947
  const assistant = await findAssistant(client, slug);
4605
4948
  logger.info(` \u2713 Found: ${assistant.name} (${assistant.id})`);
4949
+ managedName = assertResolvedNameIsFree(
4950
+ config,
4951
+ stateManager,
4952
+ resourceType,
4953
+ slug,
4954
+ assistant.name,
4955
+ assistant.id,
4956
+ update
4957
+ );
4606
4958
  apiResponse = assistant;
4607
4959
  resource = assistantResponseToResource(assistant);
4608
4960
  if (integrationAliasMap.size > 0) {
@@ -4636,7 +4988,7 @@ async function importResource(options) {
4636
4988
  logger.info(` \u2713 Resolved skill ${skillId.slice(0, 8)}... \u2192 "${skill.name}"`);
4637
4989
  if (!isProjectOwnedSkill(skill, config.project.name)) {
4638
4990
  ensureExternalSkillDoesNotConflictWithManagedSkill(config, skill.name);
4639
- addImportedSkillToCodemieYaml(rootDir, appConfig.codemieConfig, { name: skill.name, id: skill.id });
4991
+ upsertImportedSkill(rootDir, appConfig.codemieConfig, { name: skill.name, id: skill.id }, update);
4640
4992
  }
4641
4993
  }
4642
4994
  if (skillNames.length > 0) {
@@ -4649,6 +5001,15 @@ async function importResource(options) {
4649
5001
  logger.info(` Searching for datasource "${slug}"...`);
4650
5002
  const datasource = await findDatasource(client, slug);
4651
5003
  logger.info(` \u2713 Found: ${datasource.name} (${datasource.id})`);
5004
+ managedName = assertResolvedNameIsFree(
5005
+ config,
5006
+ stateManager,
5007
+ resourceType,
5008
+ slug,
5009
+ datasource.name,
5010
+ datasource.id,
5011
+ update
5012
+ );
4652
5013
  apiResponse = datasource;
4653
5014
  const settingId = datasource.setting_id || "";
4654
5015
  const integrationAlias = integrationAliasMap.get(settingId);
@@ -4659,11 +5020,15 @@ async function importResource(options) {
4659
5020
  logger.info(` Searching for workflow "${slug}"...`);
4660
5021
  const workflow = await findWorkflow(client, slug);
4661
5022
  logger.info(` \u2713 Found: ${workflow.name} (${workflow.id})`);
4662
- if (workflow.name !== slug && checkResourceExists2(config, resourceType, workflow.name)) {
4663
- throw new Error(
4664
- `A ${resourceType} named "${workflow.name}" already exists in the configuration. Remove it first or use a different name.`
4665
- );
4666
- }
5023
+ managedName = assertResolvedNameIsFree(
5024
+ config,
5025
+ stateManager,
5026
+ resourceType,
5027
+ slug,
5028
+ workflow.name,
5029
+ workflow.id,
5030
+ update
5031
+ );
4667
5032
  apiResponse = workflow;
4668
5033
  resource = workflowResponseToResource(workflow);
4669
5034
  break;
@@ -4672,24 +5037,44 @@ async function importResource(options) {
4672
5037
  logger.info(` Searching for skill "${slug}"...`);
4673
5038
  const skill = await findSkill(client, slug);
4674
5039
  logger.info(` \u2713 Found: ${skill.name} (${skill.id})`);
4675
- if (skill.name !== slug && checkResourceExists2(config, resourceType, skill.name)) {
4676
- throw new Error(
4677
- `A ${resourceType} named "${skill.name}" already exists in the configuration. Remove it first or use a different name.`
4678
- );
4679
- }
5040
+ managedName = assertResolvedNameIsFree(config, stateManager, resourceType, slug, skill.name, skill.id, update);
4680
5041
  if (isProjectOwnedSkill(skill, config.project.name)) {
4681
5042
  logger.info(` Project-owned skill \u2014 importing into resources.skills`);
4682
5043
  apiResponse = skill;
4683
5044
  resource = skillResponseToResource(skill);
4684
5045
  break;
4685
5046
  }
4686
- addImportedSkillToCodemieYaml(rootDir, appConfig.codemieConfig, { name: skill.name, id: skill.id });
5047
+ upsertImportedSkill(rootDir, appConfig.codemieConfig, { name: skill.name, id: skill.id }, update);
4687
5048
  logger.info(`
4688
5049
  \u2705 Successfully imported external skill "${skill.name}" into imported.skills
4689
5050
  `);
4690
5051
  return;
4691
5052
  }
4692
5053
  }
5054
+ if (update) {
5055
+ const pluralType = pluralTypeOf(resourceType);
5056
+ const lookupName = managedName ?? resource.name;
5057
+ const existing = findResourceDefinition(rootDir, appConfig.codemieConfig, pluralType, lookupName);
5058
+ if (existing) {
5059
+ if (existing.file === null) {
5060
+ throw new Error(
5061
+ `Cannot update "${resource.name}": it is not defined in a file referenced by codemie.yaml. Update it manually.`
5062
+ );
5063
+ }
5064
+ logger.info(` \u267B\uFE0F Updating existing ${resourceType} config in place...`);
5065
+ updateResourceFiles(rootDir, resourceType, resource, apiResponse, existing);
5066
+ if (managedName !== void 0 && !namesEqual(managedName, resource.name)) {
5067
+ logger.info(` \u{1F3F7}\uFE0F Renamed from "${managedName}" to "${resource.name}" \u2014 updating state.json`);
5068
+ deleteStateEntry(stateManager, resourceType, managedName);
5069
+ }
5070
+ updateState(stateManager, resourceType, resource, apiResponse);
5071
+ logger.info(`
5072
+ \u2705 Successfully imported ${resourceType} "${resource.name}"
5073
+ `);
5074
+ return;
5075
+ }
5076
+ logger.info(` No existing ${resourceType} found in the configuration \u2014 importing as new`);
5077
+ }
4693
5078
  const relativeResourcePath = writeResourceFiles(rootDir, resourceType, resource, apiResponse);
4694
5079
  addImportToCodemieYaml(rootDir, appConfig.codemieConfig, resourceType, relativeResourcePath);
4695
5080
  updateState(stateManager, resourceType, resource, apiResponse);