@codemieai/cdk 0.1.632 → 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/README.md +18 -0
- package/dist/cli/index.js +657 -306
- package/dist/index.d.ts +5 -22
- package/dist/index.js +551 -234
- package/package.json +1 -1
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
|
|
4
|
+
import * as fs12 from 'fs';
|
|
5
5
|
import * as path from 'path';
|
|
6
|
-
import * as
|
|
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
|
|
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] =
|
|
198
|
+
result[key] = isPlainObject2(value) ? sanitizeObject(value, false) : value;
|
|
199
199
|
}
|
|
200
200
|
return result;
|
|
201
201
|
}
|
|
202
202
|
function sanitizeMcpServer(server) {
|
|
203
|
-
if (!
|
|
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
|
|
246
|
+
function isPlainObject3(value) {
|
|
247
247
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
248
248
|
}
|
|
249
249
|
function sortKeysDeep(value) {
|
|
250
|
-
if (!
|
|
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 (!
|
|
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 (!
|
|
694
|
-
|
|
693
|
+
if (!fs12.existsSync(parentDir)) {
|
|
694
|
+
fs12.mkdirSync(parentDir, { recursive: true });
|
|
695
695
|
}
|
|
696
696
|
const testFile = path.join(parentDir, ".write-test");
|
|
697
|
-
|
|
698
|
-
|
|
697
|
+
fs12.writeFileSync(testFile, "test");
|
|
698
|
+
fs12.unlinkSync(testFile);
|
|
699
699
|
try {
|
|
700
|
-
const stats =
|
|
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
|
-
|
|
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 (
|
|
727
|
-
|
|
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 (!
|
|
733
|
-
|
|
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 =
|
|
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 =
|
|
890
|
-
|
|
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 (!
|
|
1114
|
+
if (!fs12.existsSync(configPath)) {
|
|
980
1115
|
throw new Error(`Configuration file not found: ${configPath}`);
|
|
981
1116
|
}
|
|
982
|
-
const content =
|
|
983
|
-
const config =
|
|
984
|
-
|
|
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 (!
|
|
1132
|
+
if (!fs12.existsSync(fullPath)) {
|
|
998
1133
|
throw new Error(`Prompt file not found: ${fullPath}`);
|
|
999
1134
|
}
|
|
1000
|
-
return
|
|
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 (!
|
|
1143
|
+
if (!fs12.existsSync(fullPath)) {
|
|
1009
1144
|
throw new Error(`Skill content file not found: ${fullPath}`);
|
|
1010
1145
|
}
|
|
1011
|
-
return
|
|
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 (!
|
|
1154
|
+
if (!fs12.existsSync(fullPath)) {
|
|
1020
1155
|
throw new Error(`Config file not found: ${fullPath}`);
|
|
1021
1156
|
}
|
|
1022
|
-
const content =
|
|
1023
|
-
return
|
|
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
|
-
|
|
1034
|
-
|
|
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 (!
|
|
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
|
-
|
|
1054
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
1229
|
+
this.resolveObjectReference(current, rootConfig, path13);
|
|
1196
1230
|
return;
|
|
1197
1231
|
}
|
|
1198
|
-
this.resolveObjectProperties(current, rootConfig,
|
|
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,
|
|
1239
|
+
resolveArrayReferences(arr, rootConfig, path13) {
|
|
1206
1240
|
const result = arr.flatMap((item, i) => {
|
|
1207
|
-
const contextPath = `${
|
|
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,
|
|
1259
|
+
resolveObjectReference(current, rootConfig, path13) {
|
|
1226
1260
|
const refPath = current.$ref;
|
|
1227
|
-
const contextPath =
|
|
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,
|
|
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,
|
|
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 =
|
|
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,
|
|
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 (!
|
|
1448
|
+
if (!fs12.existsSync(this.statePath)) {
|
|
1415
1449
|
return this.createEmptyState();
|
|
1416
1450
|
}
|
|
1417
|
-
const content =
|
|
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 (!
|
|
1447
|
-
|
|
1480
|
+
if (!fs12.existsSync(dir)) {
|
|
1481
|
+
fs12.mkdirSync(dir, { recursive: true });
|
|
1448
1482
|
}
|
|
1449
1483
|
state.lastSync = (/* @__PURE__ */ new Date()).toISOString();
|
|
1450
|
-
|
|
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 (!
|
|
2690
|
+
if (!fs12.existsSync(yamlPath)) {
|
|
2657
2691
|
throw new Error(`Workflow definition file not found: ${workflow.definition}`);
|
|
2658
2692
|
}
|
|
2659
|
-
const yamlConfigContent =
|
|
2660
|
-
const workflowYaml =
|
|
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 =
|
|
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,
|
|
2959
|
+
const dfs = (name, path13) => {
|
|
2926
2960
|
if (recursionStack.has(name)) {
|
|
2927
|
-
const cycleStart =
|
|
2928
|
-
const cycle = [...
|
|
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
|
-
|
|
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, [...
|
|
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 =
|
|
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 (!
|
|
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 =
|
|
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 (!
|
|
3433
|
+
if (!fs12.existsSync(yamlPath)) {
|
|
3400
3434
|
throw new Error(`Workflow definition file not found: ${workflow.definition}`);
|
|
3401
3435
|
}
|
|
3402
|
-
const yamlConfig =
|
|
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 =
|
|
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 =
|
|
3548
|
-
|
|
3549
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 (
|
|
3729
|
-
|
|
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
|
|
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 (!
|
|
3836
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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 (
|
|
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 (!
|
|
4286
|
-
|
|
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
|
-
|
|
4372
|
-
|
|
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
|
|
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
|
-
|
|
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 =
|
|
4486
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
4519
|
-
|
|
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 (!
|
|
4763
|
+
if (!fs12.existsSync(configPath)) {
|
|
4526
4764
|
throw new Error(`Configuration file not found: ${configPath}`);
|
|
4527
4765
|
}
|
|
4528
|
-
const content =
|
|
4529
|
-
const doc =
|
|
4530
|
-
const pluralType = resourceType
|
|
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 (
|
|
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
|
-
|
|
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 (!
|
|
4798
|
+
if (!fs12.existsSync(configPath)) {
|
|
4561
4799
|
throw new Error(`Configuration file not found: ${configPath}`);
|
|
4562
4800
|
}
|
|
4563
|
-
const content =
|
|
4564
|
-
const doc =
|
|
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 (!
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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);
|