@codemieai/cdk 0.1.632 → 0.1.634

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
@@ -2653,11 +2687,11 @@ async function deployWorkflows(config, client, stateManager, rootDir = process.c
2653
2687
  try {
2654
2688
  logger.info(`Processing: ${workflow.name}`);
2655
2689
  const yamlPath = path.join(rootDir, workflow.definition);
2656
- if (!fs.existsSync(yamlPath)) {
2690
+ if (!fs12.existsSync(yamlPath)) {
2657
2691
  throw new Error(`Workflow definition file not found: ${workflow.definition}`);
2658
2692
  }
2659
- const yamlConfigContent = fs.readFileSync(yamlPath, "utf8");
2660
- const workflowYaml = yaml6.parse(yamlConfigContent);
2693
+ const yamlConfigContent = fs12.readFileSync(yamlPath, "utf8");
2694
+ const workflowYaml = yaml7.parse(yamlConfigContent);
2661
2695
  const referencesToResolve = [];
2662
2696
  for (const assistant of workflowYaml.assistants ?? []) {
2663
2697
  const isInline = !assistant.assistant_name && assistant.model && assistant.system_prompt;
@@ -2698,7 +2732,7 @@ async function deployWorkflows(config, client, stateManager, rootDir = process.c
2698
2732
  });
2699
2733
  }
2700
2734
  }
2701
- const yamlConfig = yaml6.stringify(workflowYaml);
2735
+ const yamlConfig = yaml7.stringify(workflowYaml);
2702
2736
  const workflowYamlChecksum = calculateChecksum(yamlConfigContent);
2703
2737
  const configChecksum = calculateWorkflowConfigChecksum(workflow);
2704
2738
  const existingState = stateManager.getWorkflowState(workflow.name);
@@ -2922,10 +2956,10 @@ var DependencyValidator = class {
2922
2956
  const errors = [];
2923
2957
  const visited = /* @__PURE__ */ new Set();
2924
2958
  const recursionStack = /* @__PURE__ */ new Set();
2925
- const dfs = (name, path12) => {
2959
+ const dfs = (name, path13) => {
2926
2960
  if (recursionStack.has(name)) {
2927
- const cycleStart = path12.indexOf(name);
2928
- const cycle = [...path12.slice(cycleStart), name];
2961
+ const cycleStart = path13.indexOf(name);
2962
+ const cycle = [...path13.slice(cycleStart), name];
2929
2963
  errors.push(`Cyclic dependency detected: ${cycle.join(" \u2192 ")}`);
2930
2964
  return true;
2931
2965
  }
@@ -2934,14 +2968,14 @@ var DependencyValidator = class {
2934
2968
  }
2935
2969
  visited.add(name);
2936
2970
  recursionStack.add(name);
2937
- path12.push(name);
2971
+ path13.push(name);
2938
2972
  const assistant = assistantMap.get(name);
2939
2973
  const subAssistantRefs = assistant?.sub_assistants || [];
2940
2974
  for (const subRef of subAssistantRefs) {
2941
2975
  if (!assistantMap.has(subRef)) {
2942
2976
  continue;
2943
2977
  }
2944
- if (dfs(subRef, [...path12])) {
2978
+ if (dfs(subRef, [...path13])) {
2945
2979
  return true;
2946
2980
  }
2947
2981
  }
@@ -3000,7 +3034,7 @@ var DependencyValidator = class {
3000
3034
  }
3001
3035
  for (const workflow of workflows) {
3002
3036
  try {
3003
- const workflowYaml = yaml6.parse(workflow.definition);
3037
+ const workflowYaml = yaml7.parse(workflow.definition);
3004
3038
  if (!workflowYaml.assistants) {
3005
3039
  continue;
3006
3040
  }
@@ -3141,11 +3175,11 @@ async function validateConfig(options) {
3141
3175
  if (config.resources.workflows && config.resources.workflows.length > 0) {
3142
3176
  const workflowsWithContent = config.resources.workflows.map((workflow) => {
3143
3177
  const workflowPath = path.resolve(workflow.definition);
3144
- if (!fs.existsSync(workflowPath)) {
3178
+ if (!fs12.existsSync(workflowPath)) {
3145
3179
  errors.push(`Workflow definition file not found: ${workflow.definition}`);
3146
3180
  return { ...workflow, definition: "" };
3147
3181
  }
3148
- const definition = fs.readFileSync(workflowPath, "utf8");
3182
+ const definition = fs12.readFileSync(workflowPath, "utf8");
3149
3183
  return { ...workflow, definition };
3150
3184
  });
3151
3185
  const workflowErrors = DependencyValidator.validateWorkflowAssistantReferences(
@@ -3396,10 +3430,10 @@ async function previewWorkflows(workflows, stateManager, client, rootDir = proce
3396
3430
  const staleDetails = `ID from state not found; will adopt a same-name workflow${projectClause} if one exists, otherwise create`;
3397
3431
  for (const workflow of workflows) {
3398
3432
  const yamlPath = path.join(rootDir, workflow.definition);
3399
- if (!fs.existsSync(yamlPath)) {
3433
+ if (!fs12.existsSync(yamlPath)) {
3400
3434
  throw new Error(`Workflow definition file not found: ${workflow.definition}`);
3401
3435
  }
3402
- const yamlConfig = fs.readFileSync(yamlPath, "utf8");
3436
+ const yamlConfig = fs12.readFileSync(yamlPath, "utf8");
3403
3437
  const change = await previewResource(
3404
3438
  workflow,
3405
3439
  "workflow",
@@ -3512,7 +3546,7 @@ var BackupTransaction = class {
3512
3546
  constructor(backupDir, transactionId) {
3513
3547
  this.transactionPath = path.join(backupDir, "transaction.json");
3514
3548
  try {
3515
- const content = fs.readFileSync(this.transactionPath, "utf8");
3549
+ const content = fs12.readFileSync(this.transactionPath, "utf8");
3516
3550
  this.data = JSON.parse(content);
3517
3551
  logger.info(`\u{1F4C2} Resuming backup transaction ${this.data.id}`);
3518
3552
  } catch (error) {
@@ -3544,9 +3578,9 @@ var BackupTransaction = class {
3544
3578
  }
3545
3579
  writeTransactionExclusively(data) {
3546
3580
  try {
3547
- const fd = fs.openSync(this.transactionPath, "wx");
3548
- fs.writeSync(fd, JSON.stringify(data, null, 2));
3549
- fs.closeSync(fd);
3581
+ const fd = fs12.openSync(this.transactionPath, "wx");
3582
+ fs12.writeSync(fd, JSON.stringify(data, null, 2));
3583
+ fs12.closeSync(fd);
3550
3584
  } catch (writeError) {
3551
3585
  const writeErr = writeError;
3552
3586
  if (writeErr.code === "EEXIST") {
@@ -3565,7 +3599,7 @@ var BackupTransaction = class {
3565
3599
  if (delayMs > 0) {
3566
3600
  busyWaitDelay(delayMs);
3567
3601
  }
3568
- const content = fs.readFileSync(this.transactionPath, "utf8");
3602
+ const content = fs12.readFileSync(this.transactionPath, "utf8");
3569
3603
  this.data = JSON.parse(content);
3570
3604
  logger.info(`\u{1F4C2} Resuming backup transaction ${this.data.id} (created by concurrent process)`);
3571
3605
  break;
@@ -3588,7 +3622,7 @@ var BackupTransaction = class {
3588
3622
  */
3589
3623
  async save() {
3590
3624
  ensureDirectoryExists(this.transactionPath);
3591
- 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");
3592
3626
  let timeoutId;
3593
3627
  const timeoutPromise = new Promise((_, reject) => {
3594
3628
  timeoutId = setTimeout(
@@ -3614,7 +3648,7 @@ var BackupTransaction = class {
3614
3648
  */
3615
3649
  saveSyncInternal() {
3616
3650
  ensureDirectoryExists(this.transactionPath);
3617
- fs.writeFileSync(this.transactionPath, JSON.stringify(this.data, null, 2), "utf8");
3651
+ fs12.writeFileSync(this.transactionPath, JSON.stringify(this.data, null, 2), "utf8");
3618
3652
  this.isDirty = false;
3619
3653
  }
3620
3654
  /**
@@ -3725,8 +3759,8 @@ var BackupTransaction = class {
3725
3759
  * Clean up transaction file after successful completion
3726
3760
  */
3727
3761
  cleanup() {
3728
- if (fs.existsSync(this.transactionPath)) {
3729
- fs.unlinkSync(this.transactionPath);
3762
+ if (fs12.existsSync(this.transactionPath)) {
3763
+ fs12.unlinkSync(this.transactionPath);
3730
3764
  }
3731
3765
  }
3732
3766
  };
@@ -3816,7 +3850,7 @@ function generateCodemieYaml(backup, projectName, backupDir, integrationSpecPath
3816
3850
  }
3817
3851
  }
3818
3852
  };
3819
- return yaml6.stringify(config);
3853
+ return yaml7.stringify(config);
3820
3854
  }
3821
3855
  function saveIntegrationOpenApiSpecs(backupData, backupDir) {
3822
3856
  const specsDir = path.join(backupDir, "openapi_specs");
@@ -3832,8 +3866,8 @@ function saveIntegrationOpenApiSpecs(backupData, backupDir) {
3832
3866
  if (specEntry && typeof specEntry.value === "string" && specEntry.value) {
3833
3867
  const alias = integration.alias || integration.id;
3834
3868
  const fileName = String(alias).toLowerCase().replaceAll(/[^a-z0-9]+/g, "-");
3835
- if (!fs.existsSync(specsDir)) {
3836
- fs.mkdirSync(specsDir, { recursive: true });
3869
+ if (!fs12.existsSync(specsDir)) {
3870
+ fs12.mkdirSync(specsDir, { recursive: true });
3837
3871
  }
3838
3872
  const specContent = specEntry.value;
3839
3873
  let fileExtension = ".yaml";
@@ -3847,7 +3881,7 @@ function saveIntegrationOpenApiSpecs(backupData, backupDir) {
3847
3881
  }
3848
3882
  const specFileName = `${fileName}${fileExtension}`;
3849
3883
  const specPath = path.join(specsDir, specFileName);
3850
- fs.writeFileSync(specPath, contentToSave, "utf8");
3884
+ fs12.writeFileSync(specPath, contentToSave, "utf8");
3851
3885
  integrationSpecPaths.set(integration.id, `openapi_specs/${specFileName}`);
3852
3886
  specsCount++;
3853
3887
  }
@@ -3862,13 +3896,13 @@ function saveBackupFiles(backupData, backupDir, projectName) {
3862
3896
  const integrationSpecPaths = saveIntegrationOpenApiSpecs(backupData, backupDir);
3863
3897
  const codemieYamlPath = path.join(backupDir, "codemie.yaml");
3864
3898
  const codemieYaml = generateCodemieYaml(backupData, projectName, backupDir, integrationSpecPaths);
3865
- fs.writeFileSync(codemieYamlPath, codemieYaml, "utf8");
3899
+ fs12.writeFileSync(codemieYamlPath, codemieYaml, "utf8");
3866
3900
  logger.info(` \u2713 Saved config file: codemie.yaml`);
3867
3901
  const backupJsonPath = path.join(backupDir, "backup.json");
3868
- fs.writeFileSync(backupJsonPath, JSON.stringify(backupData, null, 2), "utf8");
3902
+ fs12.writeFileSync(backupJsonPath, JSON.stringify(backupData, null, 2), "utf8");
3869
3903
  logger.info(` \u2713 Saved full backup: backup.json`);
3870
3904
  const statePath = path.join(backupDir, "state.json");
3871
- fs.writeFileSync(statePath, JSON.stringify(backupData.state, null, 2), "utf8");
3905
+ fs12.writeFileSync(statePath, JSON.stringify(backupData.state, null, 2), "utf8");
3872
3906
  logger.info(` \u2713 Saved state file: state.json`);
3873
3907
  }
3874
3908
 
@@ -3978,7 +4012,7 @@ async function saveAssistantToBackup(assistant, client, backupData, backupDir) {
3978
4012
  const fileName = `${full.slug || sanitizeFileName(full.name)}.prompt.md`;
3979
4013
  const filePath = path.join(backupDir, "system_prompts", fileName);
3980
4014
  ensureDirectoryExists(filePath);
3981
- fs.writeFileSync(filePath, full.system_prompt, "utf8");
4015
+ fs12.writeFileSync(filePath, full.system_prompt, "utf8");
3982
4016
  }
3983
4017
  }
3984
4018
  async function backupAssistants(client, backupData, backupDir, transaction) {
@@ -4106,7 +4140,7 @@ async function backupWorkflows(client, backupData, backupDir, transaction) {
4106
4140
  const fileName = `${sanitizeFileName(full.name)}.yaml`;
4107
4141
  const filePath = path.join(backupDir, "workflows", fileName);
4108
4142
  ensureDirectoryExists(filePath);
4109
- fs.writeFileSync(filePath, full.yaml_config, "utf8");
4143
+ fs12.writeFileSync(filePath, full.yaml_config, "utf8");
4110
4144
  }
4111
4145
  transaction.markCompleted("workflows", workflow.id);
4112
4146
  } catch (error) {
@@ -4211,7 +4245,7 @@ async function backupSkills(client, backupData, backupDir, transaction, projectN
4211
4245
  const fileName = `${sanitizeFileName(skill.name)}.skill.md`;
4212
4246
  const filePath = path.join(backupDir, "skills", fileName);
4213
4247
  ensureDirectoryExists(filePath);
4214
- fs.writeFileSync(filePath, full.content || "", "utf8");
4248
+ fs12.writeFileSync(filePath, full.content || "", "utf8");
4215
4249
  } else {
4216
4250
  logger.info(` \u21B7 Skipping content file for imported skill ${skill.name} (owner: ${full.project})`);
4217
4251
  }
@@ -4234,7 +4268,7 @@ async function backupSkills(client, backupData, backupDir, transaction, projectN
4234
4268
  function getUniqueBackupDir(baseDir, timestamp) {
4235
4269
  let counter = 0;
4236
4270
  let finalBackupDir = path.join(baseDir, timestamp);
4237
- while (fs.existsSync(finalBackupDir)) {
4271
+ while (fs12.existsSync(finalBackupDir)) {
4238
4272
  counter++;
4239
4273
  finalBackupDir = path.join(baseDir, `${timestamp}-${counter}`);
4240
4274
  }
@@ -4243,7 +4277,7 @@ function getUniqueBackupDir(baseDir, timestamp) {
4243
4277
  return { finalDir: finalBackupDir, tempDir: tempBackupDir };
4244
4278
  }
4245
4279
  function performCleanup(tempBackupDir, transaction) {
4246
- if (fs.existsSync(tempBackupDir)) {
4280
+ if (fs12.existsSync(tempBackupDir)) {
4247
4281
  try {
4248
4282
  logger.info(`\u{1F9F9} Rolling back: cleaning up ${tempBackupDir}...`);
4249
4283
  cleanupDirectory(tempBackupDir);
@@ -4282,8 +4316,8 @@ async function backupResources(options) {
4282
4316
  logger.info("\u{1F50C} Connecting to Codemie API...");
4283
4317
  const client = await createClient(config);
4284
4318
  logger.info("\u2713 Connected to Codemie API\n");
4285
- if (!fs.existsSync(tempBackupDir)) {
4286
- fs.mkdirSync(tempBackupDir, { recursive: true });
4319
+ if (!fs12.existsSync(tempBackupDir)) {
4320
+ fs12.mkdirSync(tempBackupDir, { recursive: true });
4287
4321
  }
4288
4322
  logger.info(`\u{1F4C1} Temporary backup directory: ${tempBackupDir}
4289
4323
  `);
@@ -4364,14 +4398,34 @@ async function backupResources(options) {
4364
4398
  }
4365
4399
 
4366
4400
  // src/import.ts
4401
+ init_checksumUtils();
4402
+ init_codemieConfigChecksums();
4367
4403
  init_converters();
4368
- init_skillUtils();
4369
4404
  init_fileUtils();
4370
4405
  init_logger();
4371
- init_checksumUtils();
4372
- init_codemieConfigChecksums();
4406
+
4407
+ // src/lib/resourceTypes.ts
4373
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();
4374
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
+ }
4375
4429
  try {
4376
4430
  const assistant = await withTimeout(
4377
4431
  client.assistants.getBySlug(slug),
@@ -4455,7 +4509,7 @@ Please use a more specific name.`);
4455
4509
  return matches[0];
4456
4510
  }
4457
4511
  function checkResourceExists2(config, resourceType, name) {
4458
- const pluralType = resourceType === "assistant" ? "assistants" : resourceType === "datasource" ? "datasources" : resourceType === "workflow" ? "workflows" : "skills";
4512
+ const pluralType = pluralTypeOf(resourceType);
4459
4513
  const resources = config.resources[pluralType] || [];
4460
4514
  if (resources.some((r) => namesEqual(r.name, name))) {
4461
4515
  return true;
@@ -4466,8 +4520,38 @@ function checkResourceExists2(config, resourceType, name) {
4466
4520
  }
4467
4521
  return false;
4468
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
+ }
4469
4552
  function writeResourceFiles(rootDir, resourceType, resource, apiResponse) {
4470
4553
  const safeName = sanitizeFileName(resource.name);
4554
+ const target = resource;
4471
4555
  if (resourceType === "workflow") {
4472
4556
  const workflow = apiResponse;
4473
4557
  const workflowResource = resource;
@@ -4475,19 +4559,21 @@ function writeResourceFiles(rootDir, resourceType, resource, apiResponse) {
4475
4559
  const defFileName = `${safeName}.yaml`;
4476
4560
  const defPath = path.join(rootDir, "workflows", defFileName);
4477
4561
  ensureDirectoryExists(defPath);
4478
- fs.writeFileSync(defPath, workflow.yaml_config, "utf8");
4562
+ fs12.writeFileSync(defPath, workflow.yaml_config, "utf8");
4479
4563
  logger.info(` \u{1F4CB} Saved workflow definition: workflows/${defFileName}`);
4480
4564
  workflowResource.definition = `workflows/${defFileName}`;
4565
+ } else {
4566
+ delete target.definition;
4481
4567
  }
4482
4568
  const configPath = path.join(rootDir, "configs", "workflows", `${safeName}.yaml`);
4483
4569
  const relativeConfigPath = `configs/workflows/${safeName}.yaml`;
4484
4570
  ensureDirectoryExists(configPath);
4485
- const yamlContent2 = yaml6.stringify(workflowResource, { lineWidth: 120 });
4486
- fs.writeFileSync(configPath, yamlContent2, "utf8");
4571
+ const yamlContent2 = yaml7.stringify(workflowResource, { lineWidth: 120 });
4572
+ fs12.writeFileSync(configPath, yamlContent2, "utf8");
4487
4573
  logger.info(` \u{1F4C4} Saved workflow config: ${relativeConfigPath}`);
4488
4574
  return relativeConfigPath;
4489
4575
  }
4490
- const pluralType = resourceType === "assistant" ? "assistants" : resourceType === "datasource" ? "datasources" : "skills";
4576
+ const pluralType = pluralTypeOf(resourceType);
4491
4577
  const resourceDir = path.join(rootDir, "configs", "imported-resources", pluralType);
4492
4578
  const resourceFilePath = path.join(resourceDir, `${safeName}.yaml`);
4493
4579
  const relativeResourcePath = `configs/imported-resources/${pluralType}/${safeName}.yaml`;
@@ -4498,9 +4584,11 @@ function writeResourceFiles(rootDir, resourceType, resource, apiResponse) {
4498
4584
  const promptFileName = assistant.slug || safeName;
4499
4585
  const promptPath = path.join(rootDir, "system_prompts", `${promptFileName}.prompt.md`);
4500
4586
  ensureDirectoryExists(promptPath);
4501
- fs.writeFileSync(promptPath, assistant.system_prompt, "utf8");
4587
+ fs12.writeFileSync(promptPath, assistant.system_prompt, "utf8");
4502
4588
  logger.info(` \u{1F4DD} Saved prompt: system_prompts/${promptFileName}.prompt.md`);
4503
4589
  assistantResource.prompt = `system_prompts/${promptFileName}.prompt.md`;
4590
+ } else {
4591
+ delete target.prompt;
4504
4592
  }
4505
4593
  } else if (resourceType === "skill") {
4506
4594
  const skill = apiResponse;
@@ -4509,25 +4597,175 @@ function writeResourceFiles(rootDir, resourceType, resource, apiResponse) {
4509
4597
  const skillFileName = `${safeName}.skill.md`;
4510
4598
  const skillPath = path.join(rootDir, "skills", skillFileName);
4511
4599
  ensureDirectoryExists(skillPath);
4512
- fs.writeFileSync(skillPath, skill.content, "utf8");
4600
+ fs12.writeFileSync(skillPath, skill.content, "utf8");
4513
4601
  logger.info(` \u{1F9E0} Saved skill content: skills/${skillFileName}`);
4514
4602
  skillResource.file = `skills/${skillFileName}`;
4603
+ } else {
4604
+ delete target.file;
4515
4605
  }
4516
4606
  }
4517
4607
  ensureDirectoryExists(resourceFilePath);
4518
- const yamlContent = yaml6.stringify(resource, { lineWidth: 120 });
4519
- fs.writeFileSync(resourceFilePath, yamlContent, "utf8");
4608
+ const yamlContent = yaml7.stringify(resource, { lineWidth: 120 });
4609
+ fs12.writeFileSync(resourceFilePath, yamlContent, "utf8");
4520
4610
  logger.info(` \u{1F4C4} Saved resource config: ${relativeResourcePath}`);
4521
4611
  return relativeResourcePath;
4522
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
+ }
4523
4761
  function addImportToCodemieYaml(rootDir, codemieConfigPath, resourceType, relativeResourcePath) {
4524
4762
  const configPath = path.join(rootDir, codemieConfigPath);
4525
- if (!fs.existsSync(configPath)) {
4763
+ if (!fs12.existsSync(configPath)) {
4526
4764
  throw new Error(`Configuration file not found: ${configPath}`);
4527
4765
  }
4528
- const content = fs.readFileSync(configPath, "utf8");
4529
- const doc = yaml6.parseDocument(content);
4530
- 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);
4531
4769
  let resourcesNode = doc.get("resources");
4532
4770
  if (!resourcesNode) {
4533
4771
  resourcesNode = doc.createNode({});
@@ -4540,7 +4778,7 @@ function addImportToCodemieYaml(rootDir, codemieConfigPath, resourceType, relati
4540
4778
  }
4541
4779
  const importDirective = { $import: relativeResourcePath };
4542
4780
  const existingImports = typeArray.items.filter((item) => {
4543
- if (yaml6.isMap(item)) {
4781
+ if (yaml7.isMap(item)) {
4544
4782
  const importValue = item.get("$import");
4545
4783
  return importValue === relativeResourcePath;
4546
4784
  }
@@ -4552,16 +4790,16 @@ function addImportToCodemieYaml(rootDir, codemieConfigPath, resourceType, relati
4552
4790
  }
4553
4791
  const newNode = doc.createNode(importDirective);
4554
4792
  typeArray.add(newNode);
4555
- fs.writeFileSync(configPath, doc.toString(), "utf8");
4793
+ fs12.writeFileSync(configPath, doc.toString(), "utf8");
4556
4794
  logger.info(` \u2705 Added $import to ${codemieConfigPath} \u2192 resources.${pluralType}`);
4557
4795
  }
4558
4796
  function addImportedSkillToCodemieYaml(rootDir, codemieConfigPath, skill) {
4559
4797
  const configPath = path.join(rootDir, codemieConfigPath);
4560
- if (!fs.existsSync(configPath)) {
4798
+ if (!fs12.existsSync(configPath)) {
4561
4799
  throw new Error(`Configuration file not found: ${configPath}`);
4562
4800
  }
4563
- const content = fs.readFileSync(configPath, "utf8");
4564
- const doc = yaml6.parseDocument(content);
4801
+ const content = fs12.readFileSync(configPath, "utf8");
4802
+ const doc = yaml7.parseDocument(content);
4565
4803
  let importedNode = doc.get("imported");
4566
4804
  if (!importedNode) {
4567
4805
  importedNode = doc.createNode({ assistants: [], datasources: [], skills: [], integrations: [] });
@@ -4573,7 +4811,7 @@ function addImportedSkillToCodemieYaml(rootDir, codemieConfigPath, skill) {
4573
4811
  doc.setIn(["imported", "skills"], skillsArray);
4574
4812
  }
4575
4813
  const existingSkill = skillsArray.items.find((item) => {
4576
- if (!yaml6.isMap(item)) {
4814
+ if (!yaml7.isMap(item)) {
4577
4815
  return false;
4578
4816
  }
4579
4817
  const existingName = item.get("name");
@@ -4590,9 +4828,47 @@ function addImportedSkillToCodemieYaml(rootDir, codemieConfigPath, skill) {
4590
4828
  return;
4591
4829
  }
4592
4830
  skillsArray.add(doc.createNode({ name: skill.name, id: skill.id }));
4593
- fs.writeFileSync(configPath, doc.toString(), "utf8");
4831
+ fs12.writeFileSync(configPath, doc.toString(), "utf8");
4594
4832
  logger.info(` \u2705 Added skill to ${codemieConfigPath} \u2192 imported.skills`);
4595
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
+ }
4596
4872
  function ensureExternalSkillDoesNotConflictWithManagedSkill(config, skillName) {
4597
4873
  const managedSkill = config.resources.skills?.find((skill) => namesEqual(skill.name, skillName));
4598
4874
  if (!managedSkill) {
@@ -4645,7 +4921,7 @@ function buildIntegrationAliasMap(config) {
4645
4921
  return map;
4646
4922
  }
4647
4923
  async function importResource(options) {
4648
- const { appConfig, resourceType, slug } = options;
4924
+ const { appConfig, resourceType, slug, update = false } = options;
4649
4925
  if (!VALID_RESOURCE_TYPES.includes(resourceType)) {
4650
4926
  throw new Error(`Invalid resource type: "${resourceType}". Must be one of: ${VALID_RESOURCE_TYPES.join(", ")}`);
4651
4927
  }
@@ -4653,10 +4929,8 @@ async function importResource(options) {
4653
4929
  const config = configLoader.loadConfig();
4654
4930
  const rootDir = appConfig.rootDir;
4655
4931
  const stateManager = new StateManager(appConfig);
4656
- if (checkResourceExists2(config, resourceType, slug)) {
4657
- throw new Error(
4658
- `A ${resourceType} named "${slug}" already exists in the configuration. Remove it first or use a different name.`
4659
- );
4932
+ if (!update && checkResourceExists2(config, resourceType, slug)) {
4933
+ throw nameClashError(resourceType, slug);
4660
4934
  }
4661
4935
  logger.info(`
4662
4936
  \u{1F50D} Importing ${resourceType}: "${slug}"
@@ -4666,11 +4940,21 @@ async function importResource(options) {
4666
4940
  const integrationAliasMap = buildIntegrationAliasMap(config);
4667
4941
  let resource;
4668
4942
  let apiResponse;
4943
+ let managedName;
4669
4944
  switch (resourceType) {
4670
4945
  case "assistant": {
4671
4946
  logger.info(` Searching for assistant "${slug}"...`);
4672
4947
  const assistant = await findAssistant(client, slug);
4673
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
+ );
4674
4958
  apiResponse = assistant;
4675
4959
  resource = assistantResponseToResource(assistant);
4676
4960
  if (integrationAliasMap.size > 0) {
@@ -4704,7 +4988,7 @@ async function importResource(options) {
4704
4988
  logger.info(` \u2713 Resolved skill ${skillId.slice(0, 8)}... \u2192 "${skill.name}"`);
4705
4989
  if (!isProjectOwnedSkill(skill, config.project.name)) {
4706
4990
  ensureExternalSkillDoesNotConflictWithManagedSkill(config, skill.name);
4707
- addImportedSkillToCodemieYaml(rootDir, appConfig.codemieConfig, { name: skill.name, id: skill.id });
4991
+ upsertImportedSkill(rootDir, appConfig.codemieConfig, { name: skill.name, id: skill.id }, update);
4708
4992
  }
4709
4993
  }
4710
4994
  if (skillNames.length > 0) {
@@ -4717,6 +5001,15 @@ async function importResource(options) {
4717
5001
  logger.info(` Searching for datasource "${slug}"...`);
4718
5002
  const datasource = await findDatasource(client, slug);
4719
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
+ );
4720
5013
  apiResponse = datasource;
4721
5014
  const settingId = datasource.setting_id || "";
4722
5015
  const integrationAlias = integrationAliasMap.get(settingId);
@@ -4727,11 +5020,15 @@ async function importResource(options) {
4727
5020
  logger.info(` Searching for workflow "${slug}"...`);
4728
5021
  const workflow = await findWorkflow(client, slug);
4729
5022
  logger.info(` \u2713 Found: ${workflow.name} (${workflow.id})`);
4730
- if (workflow.name !== slug && checkResourceExists2(config, resourceType, workflow.name)) {
4731
- throw new Error(
4732
- `A ${resourceType} named "${workflow.name}" already exists in the configuration. Remove it first or use a different name.`
4733
- );
4734
- }
5023
+ managedName = assertResolvedNameIsFree(
5024
+ config,
5025
+ stateManager,
5026
+ resourceType,
5027
+ slug,
5028
+ workflow.name,
5029
+ workflow.id,
5030
+ update
5031
+ );
4735
5032
  apiResponse = workflow;
4736
5033
  resource = workflowResponseToResource(workflow);
4737
5034
  break;
@@ -4740,24 +5037,44 @@ async function importResource(options) {
4740
5037
  logger.info(` Searching for skill "${slug}"...`);
4741
5038
  const skill = await findSkill(client, slug);
4742
5039
  logger.info(` \u2713 Found: ${skill.name} (${skill.id})`);
4743
- if (skill.name !== slug && checkResourceExists2(config, resourceType, skill.name)) {
4744
- throw new Error(
4745
- `A ${resourceType} named "${skill.name}" already exists in the configuration. Remove it first or use a different name.`
4746
- );
4747
- }
5040
+ managedName = assertResolvedNameIsFree(config, stateManager, resourceType, slug, skill.name, skill.id, update);
4748
5041
  if (isProjectOwnedSkill(skill, config.project.name)) {
4749
5042
  logger.info(` Project-owned skill \u2014 importing into resources.skills`);
4750
5043
  apiResponse = skill;
4751
5044
  resource = skillResponseToResource(skill);
4752
5045
  break;
4753
5046
  }
4754
- addImportedSkillToCodemieYaml(rootDir, appConfig.codemieConfig, { name: skill.name, id: skill.id });
5047
+ upsertImportedSkill(rootDir, appConfig.codemieConfig, { name: skill.name, id: skill.id }, update);
4755
5048
  logger.info(`
4756
5049
  \u2705 Successfully imported external skill "${skill.name}" into imported.skills
4757
5050
  `);
4758
5051
  return;
4759
5052
  }
4760
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
+ }
4761
5078
  const relativeResourcePath = writeResourceFiles(rootDir, resourceType, resource, apiResponse);
4762
5079
  addImportToCodemieYaml(rootDir, appConfig.codemieConfig, resourceType, relativeResourcePath);
4763
5080
  updateState(stateManager, resourceType, resource, apiResponse);