@codemieai/cdk 0.1.543 → 0.1.545
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +421 -126
- package/dist/index.d.ts +2 -0
- package/dist/index.js +356 -61
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import ora from 'ora';
|
|
2
2
|
import * as crypto from 'crypto';
|
|
3
|
-
import { CodeMieClient, DataSourceType } from 'codemie-sdk';
|
|
3
|
+
import { CodeMieClient, DataSourceType, SkillVisibility } from 'codemie-sdk';
|
|
4
4
|
import * as fs from 'fs';
|
|
5
5
|
import * as path from 'path';
|
|
6
6
|
import * as yaml6 from 'yaml';
|
|
@@ -689,11 +689,23 @@ var init_fileUtils = __esm({
|
|
|
689
689
|
}
|
|
690
690
|
});
|
|
691
691
|
|
|
692
|
+
// src/lib/skillUtils.ts
|
|
693
|
+
function isProjectOwnedSkill(skill, projectName) {
|
|
694
|
+
return skill.project.trim().toLowerCase() === projectName.trim().toLowerCase();
|
|
695
|
+
}
|
|
696
|
+
var init_skillUtils = __esm({
|
|
697
|
+
"src/lib/skillUtils.ts"() {
|
|
698
|
+
}
|
|
699
|
+
});
|
|
700
|
+
|
|
692
701
|
// src/lib/backupTransformers.ts
|
|
693
702
|
var backupTransformers_exports = {};
|
|
694
703
|
__export(backupTransformers_exports, {
|
|
704
|
+
collectReferencedSkillIds: () => collectReferencedSkillIds,
|
|
705
|
+
partitionSkillsForBackupYaml: () => partitionSkillsForBackupYaml,
|
|
695
706
|
prepareAssistantForYaml: () => prepareAssistantForYaml,
|
|
696
707
|
prepareDatasourceForYaml: () => prepareDatasourceForYaml,
|
|
708
|
+
prepareImportedSkillForYaml: () => prepareImportedSkillForYaml,
|
|
697
709
|
prepareSkillForYaml: () => prepareSkillForYaml,
|
|
698
710
|
prepareWorkflowForYaml: () => prepareWorkflowForYaml,
|
|
699
711
|
transformMcpServer: () => transformMcpServer,
|
|
@@ -850,6 +862,36 @@ function prepareWorkflowForYaml(workflow, state, assistants, backupDir) {
|
|
|
850
862
|
};
|
|
851
863
|
return resource;
|
|
852
864
|
}
|
|
865
|
+
function collectReferencedSkillIds(assistants) {
|
|
866
|
+
const referencedSkillIds = /* @__PURE__ */ new Set();
|
|
867
|
+
for (const assistant of assistants) {
|
|
868
|
+
const assistantData = assistant;
|
|
869
|
+
for (const skillId of assistantData.skill_ids || []) {
|
|
870
|
+
referencedSkillIds.add(skillId);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
return referencedSkillIds;
|
|
874
|
+
}
|
|
875
|
+
function partitionSkillsForBackupYaml(skills, projectName, referencedSkillIds) {
|
|
876
|
+
const ownedSkills = [];
|
|
877
|
+
const importedSkills = [];
|
|
878
|
+
for (const skill of skills) {
|
|
879
|
+
if (isProjectOwnedSkill(skill, projectName)) {
|
|
880
|
+
ownedSkills.push(skill);
|
|
881
|
+
continue;
|
|
882
|
+
}
|
|
883
|
+
if (referencedSkillIds.has(skill.id)) {
|
|
884
|
+
importedSkills.push(skill);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
return { ownedSkills, importedSkills };
|
|
888
|
+
}
|
|
889
|
+
function prepareImportedSkillForYaml(skill) {
|
|
890
|
+
return {
|
|
891
|
+
name: skill.name,
|
|
892
|
+
id: skill.id
|
|
893
|
+
};
|
|
894
|
+
}
|
|
853
895
|
function prepareSkillForYaml(skill, state) {
|
|
854
896
|
const resource = skillResponseToResource(skill);
|
|
855
897
|
if (!state.resources.skills) {
|
|
@@ -870,9 +912,17 @@ var init_backupTransformers = __esm({
|
|
|
870
912
|
init_converters();
|
|
871
913
|
init_fileUtils();
|
|
872
914
|
init_logger();
|
|
915
|
+
init_skillUtils();
|
|
873
916
|
init_typeGuards();
|
|
874
917
|
}
|
|
875
918
|
});
|
|
919
|
+
|
|
920
|
+
// src/lib/nameUtils.ts
|
|
921
|
+
function namesEqual(a, b) {
|
|
922
|
+
return a.toLowerCase() === b.toLowerCase();
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// src/lib/codemieConfigLoader.ts
|
|
876
926
|
var CodemieConfigLoader = class {
|
|
877
927
|
appConfig;
|
|
878
928
|
constructor(appConfig) {
|
|
@@ -967,6 +1017,14 @@ var CodemieConfigLoader = class {
|
|
|
967
1017
|
}
|
|
968
1018
|
}
|
|
969
1019
|
}
|
|
1020
|
+
for (const skill of config.imported?.skills || []) {
|
|
1021
|
+
if (!skill.name) {
|
|
1022
|
+
errors.push("Imported skill entry is missing required field: name");
|
|
1023
|
+
}
|
|
1024
|
+
if (!skill.id) {
|
|
1025
|
+
errors.push(`Imported skill "${skill.name || "<unknown>"}" is missing required field: id`);
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
970
1028
|
return {
|
|
971
1029
|
valid: errors.length === 0,
|
|
972
1030
|
errors
|
|
@@ -1181,6 +1239,7 @@ Import chain: ${[...visitedFiles].join(" \u2192 ")} \u2192 ${normalizedPath}`
|
|
|
1181
1239
|
* - imported.integrations (array): searches by 'alias' field
|
|
1182
1240
|
* - imported.assistants (array): searches by 'name' field
|
|
1183
1241
|
* - imported.datasources (array): searches by 'name' field
|
|
1242
|
+
* - imported.skills (array): searches by 'name' field
|
|
1184
1243
|
*/
|
|
1185
1244
|
resolveReference(config, ref, context) {
|
|
1186
1245
|
const parts = ref.split(".");
|
|
@@ -1204,9 +1263,16 @@ Import chain: ${[...visitedFiles].join(" \u2192 ")} \u2192 ${normalizedPath}`
|
|
|
1204
1263
|
if (Array.isArray(current) && hasMorePathSegments) {
|
|
1205
1264
|
const nextPathSegment = parts[i + 1];
|
|
1206
1265
|
const searchField = part === "integrations" ? "alias" : "name";
|
|
1207
|
-
const foundItem = current.find(
|
|
1208
|
-
(
|
|
1209
|
-
|
|
1266
|
+
const foundItem = current.find((item) => {
|
|
1267
|
+
if (typeof item !== "object" || item === null) {
|
|
1268
|
+
return false;
|
|
1269
|
+
}
|
|
1270
|
+
const fieldValue = item[searchField];
|
|
1271
|
+
if (typeof fieldValue !== "string") {
|
|
1272
|
+
return false;
|
|
1273
|
+
}
|
|
1274
|
+
return searchField === "alias" ? fieldValue === nextPathSegment : namesEqual(fieldValue, nextPathSegment);
|
|
1275
|
+
});
|
|
1210
1276
|
if (!foundItem) {
|
|
1211
1277
|
throw new Error(
|
|
1212
1278
|
`Reference path "${ref}" not found: no item with ${searchField}="${nextPathSegment}" in "${pathParts.join(".")}". Referenced in ${context}.`
|
|
@@ -1612,12 +1678,12 @@ var CleanupManager = class {
|
|
|
1612
1678
|
const configAssistantNames = new Set((config.resources.assistants || []).map(({ name }) => name));
|
|
1613
1679
|
const configDatasourceNames = new Set((config.resources.datasources || []).map(({ name }) => name));
|
|
1614
1680
|
const configWorkflowNames = new Set((config.resources.workflows || []).map(({ name }) => name));
|
|
1615
|
-
const configSkillNames = new Set((config.resources.skills || []).map(({ name }) => name));
|
|
1681
|
+
const configSkillNames = new Set((config.resources.skills || []).map(({ name }) => name.toLowerCase()));
|
|
1616
1682
|
return {
|
|
1617
1683
|
assistants: managedResources.assistants.filter((name) => !configAssistantNames.has(name)),
|
|
1618
1684
|
datasources: managedResources.datasources.filter((name) => !configDatasourceNames.has(name)),
|
|
1619
1685
|
workflows: managedResources.workflows.filter((name) => !configWorkflowNames.has(name)),
|
|
1620
|
-
skills: managedResources.skills.filter((name) => !configSkillNames.has(name))
|
|
1686
|
+
skills: managedResources.skills.filter((name) => !configSkillNames.has(name.toLowerCase()))
|
|
1621
1687
|
};
|
|
1622
1688
|
}
|
|
1623
1689
|
/**
|
|
@@ -1886,6 +1952,62 @@ function checkSkillExists(client, name, stateManager) {
|
|
|
1886
1952
|
);
|
|
1887
1953
|
}
|
|
1888
1954
|
|
|
1955
|
+
// src/lib/resourceResolver.ts
|
|
1956
|
+
init_logger();
|
|
1957
|
+
function findSkillStateByName(stateManager, skillName) {
|
|
1958
|
+
const exactMatch = stateManager.getSkillState(skillName);
|
|
1959
|
+
if (exactMatch) {
|
|
1960
|
+
return exactMatch;
|
|
1961
|
+
}
|
|
1962
|
+
for (const name of stateManager.getAllManagedResources().skills) {
|
|
1963
|
+
if (namesEqual(name, skillName)) {
|
|
1964
|
+
return stateManager.getSkillState(name);
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
return void 0;
|
|
1968
|
+
}
|
|
1969
|
+
function findConfiguredSkill(config, skillName) {
|
|
1970
|
+
return config.resources.skills?.find((skill) => namesEqual(skill.name, skillName));
|
|
1971
|
+
}
|
|
1972
|
+
function findImportedSkill(config, skillName) {
|
|
1973
|
+
return config.imported?.skills?.find((skill) => namesEqual(skill.name, skillName));
|
|
1974
|
+
}
|
|
1975
|
+
function lookupSkillId(skillName, stateManager, config) {
|
|
1976
|
+
const configuredSkill = findConfiguredSkill(config, skillName);
|
|
1977
|
+
const importedSkill = findImportedSkill(config, skillName);
|
|
1978
|
+
if (configuredSkill && importedSkill) {
|
|
1979
|
+
return void 0;
|
|
1980
|
+
}
|
|
1981
|
+
if (importedSkill) {
|
|
1982
|
+
return importedSkill.id;
|
|
1983
|
+
}
|
|
1984
|
+
if (!configuredSkill) {
|
|
1985
|
+
return void 0;
|
|
1986
|
+
}
|
|
1987
|
+
return findSkillStateByName(stateManager, configuredSkill.name)?.id;
|
|
1988
|
+
}
|
|
1989
|
+
function resolveSkillId(skillName, stateManager, config) {
|
|
1990
|
+
const configuredSkill = findConfiguredSkill(config, skillName);
|
|
1991
|
+
const importedSkill = findImportedSkill(config, skillName);
|
|
1992
|
+
if (configuredSkill && importedSkill) {
|
|
1993
|
+
throw new Error(
|
|
1994
|
+
`Skill "${skillName}" is defined in both resources.skills and imported.skills. Keep project-owned skills in resources.skills or external skills in imported.skills, not both.`
|
|
1995
|
+
);
|
|
1996
|
+
}
|
|
1997
|
+
const skillId = lookupSkillId(skillName, stateManager, config);
|
|
1998
|
+
if (skillId) {
|
|
1999
|
+
const source = importedSkill ? "imported" : "state";
|
|
2000
|
+
logger.info(` \u2713 Resolved${source === "imported" ? " (imported)" : ""} "${skillName}" \u2192 ${skillId}`);
|
|
2001
|
+
return skillId;
|
|
2002
|
+
}
|
|
2003
|
+
throw new Error(
|
|
2004
|
+
`Skill "${skillName}" cannot be resolved. For project-owned skills, define it in resources.skills and deploy it so state.json contains its ID. For external skills, add name and id to imported.skills.`
|
|
2005
|
+
);
|
|
2006
|
+
}
|
|
2007
|
+
function resolveSkillIds(skillNames, stateManager, config) {
|
|
2008
|
+
return skillNames.map((name) => resolveSkillId(name, stateManager, config));
|
|
2009
|
+
}
|
|
2010
|
+
|
|
1889
2011
|
// src/deploy.ts
|
|
1890
2012
|
function sortAssistantsByDependencies(assistants) {
|
|
1891
2013
|
const sorted = [];
|
|
@@ -2029,16 +2151,7 @@ async function deployAssistants(config, client, loader, stateManager) {
|
|
|
2029
2151
|
let resolvedSkillIds = [];
|
|
2030
2152
|
if (assistant.skills && assistant.skills.length > 0) {
|
|
2031
2153
|
logger.info(` Resolving ${assistant.skills.length} skill name(s)...`);
|
|
2032
|
-
|
|
2033
|
-
for (const skillName of assistant.skills) {
|
|
2034
|
-
const skillState = stateManager.getSkillState(skillName);
|
|
2035
|
-
if (!skillState) {
|
|
2036
|
-
throw new Error(`Skill "${skillName}" not found in state. Ensure the skill is deployed first.`);
|
|
2037
|
-
}
|
|
2038
|
-
resolvedIds.push(skillState.id);
|
|
2039
|
-
logger.info(` \u2713 Resolved "${skillName}" \u2192 ${skillState.id}`);
|
|
2040
|
-
}
|
|
2041
|
-
resolvedSkillIds = resolvedIds;
|
|
2154
|
+
resolvedSkillIds = resolveSkillIds(assistant.skills, stateManager, config);
|
|
2042
2155
|
}
|
|
2043
2156
|
const assistantWithResolved = {
|
|
2044
2157
|
...assistant,
|
|
@@ -2158,9 +2271,8 @@ async function deployAssistants(config, client, loader, stateManager) {
|
|
|
2158
2271
|
logger.error(` ${error.message}`);
|
|
2159
2272
|
logger.debug(` Stack:`, error.stack);
|
|
2160
2273
|
if ("statusCode" in error) {
|
|
2161
|
-
|
|
2162
|
-
logger.error(`
|
|
2163
|
-
logger.error(` Data: ${JSON.stringify(apiError.response, null, 2)}`);
|
|
2274
|
+
logger.error(` Status: ${String(error.statusCode)}`);
|
|
2275
|
+
logger.error(` Data: ${JSON.stringify("response" in error ? error.response : void 0, null, 2)}`);
|
|
2164
2276
|
} else if ("response" in error) {
|
|
2165
2277
|
const axiosError = error;
|
|
2166
2278
|
logger.error(` Status: ${axiosError.response?.status}`);
|
|
@@ -2607,6 +2719,13 @@ async function deployResources(options) {
|
|
|
2607
2719
|
}
|
|
2608
2720
|
if (orphaned.skills.length > 0) {
|
|
2609
2721
|
logger.info(` \u2022 ${orphaned.skills.length} skill(s)`);
|
|
2722
|
+
const importedSkillNames = new Set(config.imported?.skills?.map((skill) => skill.name.toLowerCase()) || []);
|
|
2723
|
+
const migratedImportedSkills = orphaned.skills.filter((name) => importedSkillNames.has(name.toLowerCase()));
|
|
2724
|
+
if (migratedImportedSkills.length > 0) {
|
|
2725
|
+
logger.info(
|
|
2726
|
+
` \u26A0\uFE0F ${migratedImportedSkills.length} skill(s) also exist in imported.skills. Run with --prune to remove stale IaC-managed skill state and delete old managed skill(s) from platform.`
|
|
2727
|
+
);
|
|
2728
|
+
}
|
|
2610
2729
|
}
|
|
2611
2730
|
if (process.env.SAMPLE_DEPLOY === "1") {
|
|
2612
2731
|
logger.info("\n\u{1F50E} SAMPLE_DEPLOY=1 -> Skipping orphan deletion (simulation / partial deploy mode)\n");
|
|
@@ -2804,6 +2923,32 @@ var DependencyValidator = class {
|
|
|
2804
2923
|
}
|
|
2805
2924
|
return errors;
|
|
2806
2925
|
}
|
|
2926
|
+
/**
|
|
2927
|
+
* Validate assistant skill references.
|
|
2928
|
+
* Check that all skill names point to resources.skills or imported.skills entries.
|
|
2929
|
+
*/
|
|
2930
|
+
static validateAssistantSkillReferences(assistants, managedSkills, importedSkills) {
|
|
2931
|
+
const errors = [];
|
|
2932
|
+
for (const managedSkill of managedSkills) {
|
|
2933
|
+
const importedSkill = importedSkills.find((skill) => namesEqual(skill.name, managedSkill.name));
|
|
2934
|
+
if (importedSkill) {
|
|
2935
|
+
errors.push(
|
|
2936
|
+
`Skill "${managedSkill.name}" is defined in both resources.skills and imported.skills. Keep project-owned skills in resources.skills or external skills in imported.skills, not both.`
|
|
2937
|
+
);
|
|
2938
|
+
}
|
|
2939
|
+
}
|
|
2940
|
+
const isSkillAvailable = (skillName) => managedSkills.some((skill) => namesEqual(skill.name, skillName)) || importedSkills.some((skill) => namesEqual(skill.name, skillName));
|
|
2941
|
+
for (const assistant of assistants) {
|
|
2942
|
+
for (const skillName of assistant.skills || []) {
|
|
2943
|
+
if (!isSkillAvailable(skillName)) {
|
|
2944
|
+
errors.push(
|
|
2945
|
+
`Assistant "${assistant.name}": Skill reference "${skillName}" not found in resources.skills or imported.skills`
|
|
2946
|
+
);
|
|
2947
|
+
}
|
|
2948
|
+
}
|
|
2949
|
+
}
|
|
2950
|
+
return errors;
|
|
2951
|
+
}
|
|
2807
2952
|
};
|
|
2808
2953
|
|
|
2809
2954
|
// src/validate.ts
|
|
@@ -2887,6 +3032,12 @@ async function validateConfig(options) {
|
|
|
2887
3032
|
if (config.resources.assistants && config.resources.assistants.length > 0) {
|
|
2888
3033
|
const dependencyErrors = DependencyValidator.validateAssistantDependencies(config.resources.assistants);
|
|
2889
3034
|
errors.push(...dependencyErrors);
|
|
3035
|
+
const skillReferenceErrors = DependencyValidator.validateAssistantSkillReferences(
|
|
3036
|
+
config.resources.assistants,
|
|
3037
|
+
config.resources.skills || [],
|
|
3038
|
+
config.imported?.skills || []
|
|
3039
|
+
);
|
|
3040
|
+
errors.push(...skillReferenceErrors);
|
|
2890
3041
|
}
|
|
2891
3042
|
if (config.resources.workflows && config.resources.workflows.length > 0) {
|
|
2892
3043
|
const workflowsWithContent = config.resources.workflows.map((workflow) => {
|
|
@@ -3021,7 +3172,7 @@ async function previewSkills(skills, loader, stateManager, client) {
|
|
|
3021
3172
|
}
|
|
3022
3173
|
return changes;
|
|
3023
3174
|
}
|
|
3024
|
-
async function previewAssistants(assistants, loader, stateManager, client) {
|
|
3175
|
+
async function previewAssistants(assistants, loader, stateManager, client, config) {
|
|
3025
3176
|
const changes = [];
|
|
3026
3177
|
for (const assistant of assistants) {
|
|
3027
3178
|
const promptContent = loader.loadPrompt(assistant.prompt);
|
|
@@ -3038,17 +3189,16 @@ async function previewAssistants(assistants, loader, stateManager, client) {
|
|
|
3038
3189
|
async () => {
|
|
3039
3190
|
const existingState = stateManager.getAssistantState(assistant.name);
|
|
3040
3191
|
const configChanged = existingState ? existingState.promptChecksum !== calculateChecksum(promptContent) || existingState.configChecksum !== configChecksum : false;
|
|
3041
|
-
if (
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
if (drifted) {
|
|
3192
|
+
if (assistant.skills !== void 0) {
|
|
3193
|
+
const { ids: desiredSkillIds, unresolved } = resolveDesiredSkillIds(assistant.skills, stateManager, config);
|
|
3194
|
+
if (unresolved.length > 0) {
|
|
3195
|
+
return {
|
|
3196
|
+
hasChanged: true,
|
|
3197
|
+
createDetails: `Unresolved skill reference(s): ${unresolved.join(", ")}`,
|
|
3198
|
+
updateDetails: `Unresolved skill reference(s): ${unresolved.join(", ")}`
|
|
3199
|
+
};
|
|
3200
|
+
}
|
|
3201
|
+
if (existingState?.id && await checkSkillAttachmentDrift(client, existingState.id, desiredSkillIds)) {
|
|
3052
3202
|
return {
|
|
3053
3203
|
hasChanged: true,
|
|
3054
3204
|
createDetails: `Model: ${assistant.model}`,
|
|
@@ -3056,6 +3206,13 @@ async function previewAssistants(assistants, loader, stateManager, client) {
|
|
|
3056
3206
|
};
|
|
3057
3207
|
}
|
|
3058
3208
|
}
|
|
3209
|
+
if (configChanged) {
|
|
3210
|
+
return {
|
|
3211
|
+
hasChanged: true,
|
|
3212
|
+
createDetails: `Model: ${assistant.model}`,
|
|
3213
|
+
updateDetails: "Prompt or configuration changed"
|
|
3214
|
+
};
|
|
3215
|
+
}
|
|
3059
3216
|
return {
|
|
3060
3217
|
hasChanged: false,
|
|
3061
3218
|
createDetails: `Model: ${assistant.model}`
|
|
@@ -3066,15 +3223,18 @@ async function previewAssistants(assistants, loader, stateManager, client) {
|
|
|
3066
3223
|
}
|
|
3067
3224
|
return changes;
|
|
3068
3225
|
}
|
|
3069
|
-
function resolveDesiredSkillIds(skillNames, stateManager) {
|
|
3226
|
+
function resolveDesiredSkillIds(skillNames, stateManager, config) {
|
|
3070
3227
|
const ids = [];
|
|
3228
|
+
const unresolved = [];
|
|
3071
3229
|
for (const name of skillNames) {
|
|
3072
|
-
const
|
|
3073
|
-
if (
|
|
3074
|
-
ids.push(
|
|
3230
|
+
const skillId = lookupSkillId(name, stateManager, config);
|
|
3231
|
+
if (skillId) {
|
|
3232
|
+
ids.push(skillId);
|
|
3233
|
+
} else {
|
|
3234
|
+
unresolved.push(name);
|
|
3075
3235
|
}
|
|
3076
3236
|
}
|
|
3077
|
-
return ids;
|
|
3237
|
+
return { ids, unresolved };
|
|
3078
3238
|
}
|
|
3079
3239
|
async function checkSkillAttachmentDrift(client, assistantId, desiredSkillIds) {
|
|
3080
3240
|
try {
|
|
@@ -3178,7 +3338,7 @@ async function previewChanges(appConfig, existingClient) {
|
|
|
3178
3338
|
changes.push(...skillChanges);
|
|
3179
3339
|
}
|
|
3180
3340
|
if (config.resources.assistants) {
|
|
3181
|
-
const assistantChanges = await previewAssistants(config.resources.assistants, loader, stateManager, client);
|
|
3341
|
+
const assistantChanges = await previewAssistants(config.resources.assistants, loader, stateManager, client, config);
|
|
3182
3342
|
changes.push(...assistantChanges);
|
|
3183
3343
|
}
|
|
3184
3344
|
if (config.resources.datasources) {
|
|
@@ -3205,6 +3365,10 @@ async function previewChanges(appConfig, existingClient) {
|
|
|
3205
3365
|
};
|
|
3206
3366
|
}
|
|
3207
3367
|
|
|
3368
|
+
// src/backup.ts
|
|
3369
|
+
init_backupTransformers();
|
|
3370
|
+
init_skillUtils();
|
|
3371
|
+
|
|
3208
3372
|
// src/lib/constants.ts
|
|
3209
3373
|
var PAGINATION = {
|
|
3210
3374
|
DEFAULT_PAGE_SIZE: 100};
|
|
@@ -3464,6 +3628,12 @@ function generateCodemieYaml(backup, projectName, backupDir, integrationSpecPath
|
|
|
3464
3628
|
const integrationIdToAlias = /* @__PURE__ */ new Map();
|
|
3465
3629
|
const integrationArray = [];
|
|
3466
3630
|
const skillIdToName = new Map(backup.resources.skills.map((skill) => [skill.id, skill.name]));
|
|
3631
|
+
const referencedSkillIds = collectReferencedSkillIds(backup.resources.assistants);
|
|
3632
|
+
const { ownedSkills, importedSkills } = partitionSkillsForBackupYaml(
|
|
3633
|
+
backup.resources.skills,
|
|
3634
|
+
projectName,
|
|
3635
|
+
referencedSkillIds
|
|
3636
|
+
);
|
|
3467
3637
|
for (const integration of backup.resources.integrations) {
|
|
3468
3638
|
const credentialType = integration.credential_type || "integration";
|
|
3469
3639
|
const alias = integration.alias || `${String(credentialType).toLowerCase()}_${integration.id.slice(0, 8)}`;
|
|
@@ -3509,6 +3679,7 @@ function generateCodemieYaml(backup, projectName, backupDir, integrationSpecPath
|
|
|
3509
3679
|
imported: {
|
|
3510
3680
|
assistants: [],
|
|
3511
3681
|
datasources: [],
|
|
3682
|
+
skills: importedSkills.map((skill) => prepareImportedSkillForYaml(skill)),
|
|
3512
3683
|
integrations: integrationArray
|
|
3513
3684
|
},
|
|
3514
3685
|
datasource_defaults: {
|
|
@@ -3530,8 +3701,8 @@ function generateCodemieYaml(backup, projectName, backupDir, integrationSpecPath
|
|
|
3530
3701
|
workflows: backup.resources.workflows.map(
|
|
3531
3702
|
(workflow) => prepareWorkflowForYaml(workflow, backup.state, backup.resources.assistants, backupDir)
|
|
3532
3703
|
),
|
|
3533
|
-
...
|
|
3534
|
-
skills:
|
|
3704
|
+
...ownedSkills.length > 0 && {
|
|
3705
|
+
skills: ownedSkills.map((skill) => prepareSkillForYaml(skill, backup.state))
|
|
3535
3706
|
}
|
|
3536
3707
|
}
|
|
3537
3708
|
};
|
|
@@ -3664,6 +3835,27 @@ async function* streamResources(fetchPage, resourceType) {
|
|
|
3664
3835
|
}
|
|
3665
3836
|
}
|
|
3666
3837
|
}
|
|
3838
|
+
function createFallbackImportedSkillDetail(skill) {
|
|
3839
|
+
return {
|
|
3840
|
+
id: skill.id,
|
|
3841
|
+
name: skill.name,
|
|
3842
|
+
description: "",
|
|
3843
|
+
project: skill.project || "",
|
|
3844
|
+
visibility: SkillVisibility.PUBLIC,
|
|
3845
|
+
created_by: null,
|
|
3846
|
+
categories: [],
|
|
3847
|
+
createdDate: "",
|
|
3848
|
+
updatedDate: null,
|
|
3849
|
+
is_attached: true,
|
|
3850
|
+
assistants_count: 0,
|
|
3851
|
+
user_abilities: [],
|
|
3852
|
+
unique_likes_count: 0,
|
|
3853
|
+
unique_dislikes_count: 0,
|
|
3854
|
+
content: "",
|
|
3855
|
+
toolkits: [],
|
|
3856
|
+
mcp_servers: []
|
|
3857
|
+
};
|
|
3858
|
+
}
|
|
3667
3859
|
async function saveAssistantToBackup(assistant, client, backupData, backupDir) {
|
|
3668
3860
|
logger.info(` \u2022 ${assistant.name} (${assistant.id})`);
|
|
3669
3861
|
const full = await withTimeout(
|
|
@@ -3856,16 +4048,24 @@ async function backupIntegrations(client, backupData, projectName) {
|
|
|
3856
4048
|
logger.info(`\u2713 Backed up ${projectIntegrations.length + userIntegrations.length} integration(s)
|
|
3857
4049
|
`);
|
|
3858
4050
|
}
|
|
3859
|
-
async function backupSkills(client, backupData, backupDir, transaction) {
|
|
4051
|
+
async function backupSkills(client, backupData, backupDir, transaction, projectName) {
|
|
3860
4052
|
logger.info("\u{1F9E0} Fetching skills...");
|
|
4053
|
+
const referencedSkillIds = collectReferencedSkillIds(backupData.resources.assistants);
|
|
3861
4054
|
const allSkills = [];
|
|
3862
4055
|
for await (const skill of streamResources((params) => client.skills.list(params), "skills")) {
|
|
3863
4056
|
allSkills.push(skill);
|
|
3864
4057
|
}
|
|
3865
|
-
|
|
3866
|
-
|
|
4058
|
+
const skillsToBackup = allSkills.filter(
|
|
4059
|
+
(skill) => referencedSkillIds.has(skill.id) || !skill.project || isProjectOwnedSkill({ project: skill.project }, projectName)
|
|
4060
|
+
);
|
|
4061
|
+
const skippedExternalSkills = allSkills.length - skillsToBackup.length;
|
|
4062
|
+
logger.info(` Found ${allSkills.length} skill(s), backing up ${skillsToBackup.length}`);
|
|
4063
|
+
if (skippedExternalSkills > 0) {
|
|
4064
|
+
logger.info(` \u21B7 Skipping ${skippedExternalSkills} unreferenced external skill(s)`);
|
|
4065
|
+
}
|
|
4066
|
+
transaction.setTotal("skills", skillsToBackup.length);
|
|
3867
4067
|
const limit = createConcurrentLimiter();
|
|
3868
|
-
for (const skill of
|
|
4068
|
+
for (const skill of skillsToBackup) {
|
|
3869
4069
|
if (transaction.isCompleted("skills", skill.id)) {
|
|
3870
4070
|
logger.info(` \u21B7 Skipping ${skill.name} (already backed up)`);
|
|
3871
4071
|
continue;
|
|
@@ -3874,16 +4074,37 @@ async function backupSkills(client, backupData, backupDir, transaction) {
|
|
|
3874
4074
|
await limit(
|
|
3875
4075
|
() => withRetry(async () => {
|
|
3876
4076
|
logger.info(` \u2022 ${skill.name} (${skill.id})`);
|
|
3877
|
-
const
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
4077
|
+
const listedAsProjectOwned = skill.project ? isProjectOwnedSkill({ project: skill.project }, projectName) : false;
|
|
4078
|
+
let full;
|
|
4079
|
+
try {
|
|
4080
|
+
full = await withTimeout(
|
|
4081
|
+
client.skills.get(skill.id),
|
|
4082
|
+
TIMEOUTS_MS.SKILL_FETCH,
|
|
4083
|
+
`Timeout fetching skill ${skill.id}`
|
|
4084
|
+
);
|
|
4085
|
+
} catch (error) {
|
|
4086
|
+
if (listedAsProjectOwned || !referencedSkillIds.has(skill.id)) {
|
|
4087
|
+
throw error;
|
|
4088
|
+
}
|
|
4089
|
+
logger.warn(
|
|
4090
|
+
` \u26A0\uFE0F Failed to fetch imported skill details for ${skill.name}. Using list response as fallback: ${error instanceof Error ? error.message : String(error)}`
|
|
4091
|
+
);
|
|
4092
|
+
full = createFallbackImportedSkillDetail(skill);
|
|
4093
|
+
}
|
|
4094
|
+
const fullIsProjectOwned = isProjectOwnedSkill(full, projectName);
|
|
4095
|
+
if (!fullIsProjectOwned && !referencedSkillIds.has(skill.id)) {
|
|
4096
|
+
logger.info(` \u21B7 Skipping unreferenced external skill ${skill.name} (owner: ${full.project})`);
|
|
4097
|
+
return;
|
|
4098
|
+
}
|
|
3882
4099
|
backupData.resources.skills.push(full);
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
4100
|
+
if (fullIsProjectOwned) {
|
|
4101
|
+
const fileName = `${sanitizeFileName(skill.name)}.skill.md`;
|
|
4102
|
+
const filePath = path.join(backupDir, "skills", fileName);
|
|
4103
|
+
ensureDirectoryExists(filePath);
|
|
4104
|
+
fs.writeFileSync(filePath, full.content || "", "utf8");
|
|
4105
|
+
} else {
|
|
4106
|
+
logger.info(` \u21B7 Skipping content file for imported skill ${skill.name} (owner: ${full.project})`);
|
|
4107
|
+
}
|
|
3887
4108
|
}, `Backup skill ${skill.name}`)
|
|
3888
4109
|
);
|
|
3889
4110
|
transaction.markCompleted("skills", skill.id);
|
|
@@ -3897,7 +4118,7 @@ async function backupSkills(client, backupData, backupDir, transaction) {
|
|
|
3897
4118
|
transaction.markFailed("skills", skill.id, errorDetails.message);
|
|
3898
4119
|
}
|
|
3899
4120
|
}
|
|
3900
|
-
logger.info(`\u2713 Backed up ${
|
|
4121
|
+
logger.info(`\u2713 Backed up ${backupData.resources.skills.length} skill(s)
|
|
3901
4122
|
`);
|
|
3902
4123
|
}
|
|
3903
4124
|
function getUniqueBackupDir(baseDir, timestamp) {
|
|
@@ -3982,11 +4203,11 @@ async function backupResources(options) {
|
|
|
3982
4203
|
}
|
|
3983
4204
|
}
|
|
3984
4205
|
};
|
|
3985
|
-
await backupSkills(client, backupData, tempBackupDir, transaction);
|
|
3986
4206
|
await backupIntegrations(client, backupData, config.project.name);
|
|
3987
4207
|
await backupDatasources(client, backupData, transaction, config.project.name);
|
|
3988
4208
|
await backupWorkflows(client, backupData, tempBackupDir, transaction);
|
|
3989
4209
|
await backupAssistants(client, backupData, tempBackupDir, transaction);
|
|
4210
|
+
await backupSkills(client, backupData, tempBackupDir, transaction, config.project.name);
|
|
3990
4211
|
const stats = transaction.getData();
|
|
3991
4212
|
const totalFailed = stats.resources.assistants.failed.length + stats.resources.datasources.failed.length + stats.resources.workflows.failed.length + stats.resources.skills.failed.length;
|
|
3992
4213
|
if (totalFailed > 0) {
|
|
@@ -4034,6 +4255,7 @@ async function backupResources(options) {
|
|
|
4034
4255
|
|
|
4035
4256
|
// src/import.ts
|
|
4036
4257
|
init_converters();
|
|
4258
|
+
init_skillUtils();
|
|
4037
4259
|
init_fileUtils();
|
|
4038
4260
|
init_logger();
|
|
4039
4261
|
init_checksumUtils();
|
|
@@ -4125,7 +4347,14 @@ Please use a more specific name.`);
|
|
|
4125
4347
|
function checkResourceExists2(config, resourceType, name) {
|
|
4126
4348
|
const pluralType = resourceType === "assistant" ? "assistants" : resourceType === "datasource" ? "datasources" : resourceType === "workflow" ? "workflows" : "skills";
|
|
4127
4349
|
const resources = config.resources[pluralType] || [];
|
|
4128
|
-
|
|
4350
|
+
if (resources.some((r) => namesEqual(r.name, name))) {
|
|
4351
|
+
return true;
|
|
4352
|
+
}
|
|
4353
|
+
if (resourceType === "skill") {
|
|
4354
|
+
const importedSkills = config.imported?.skills || [];
|
|
4355
|
+
return importedSkills.some((skill) => namesEqual(skill.name, name));
|
|
4356
|
+
}
|
|
4357
|
+
return false;
|
|
4129
4358
|
}
|
|
4130
4359
|
function writeResourceFiles(rootDir, resourceType, resource, apiResponse) {
|
|
4131
4360
|
const safeName = sanitizeFileName(resource.name);
|
|
@@ -4216,6 +4445,53 @@ function addImportToCodemieYaml(rootDir, codemieConfigPath, resourceType, relati
|
|
|
4216
4445
|
fs.writeFileSync(configPath, doc.toString(), "utf8");
|
|
4217
4446
|
logger.info(` \u2705 Added $import to ${codemieConfigPath} \u2192 resources.${pluralType}`);
|
|
4218
4447
|
}
|
|
4448
|
+
function addImportedSkillToCodemieYaml(rootDir, codemieConfigPath, skill) {
|
|
4449
|
+
const configPath = path.join(rootDir, codemieConfigPath);
|
|
4450
|
+
if (!fs.existsSync(configPath)) {
|
|
4451
|
+
throw new Error(`Configuration file not found: ${configPath}`);
|
|
4452
|
+
}
|
|
4453
|
+
const content = fs.readFileSync(configPath, "utf8");
|
|
4454
|
+
const doc = yaml6.parseDocument(content);
|
|
4455
|
+
let importedNode = doc.get("imported");
|
|
4456
|
+
if (!importedNode) {
|
|
4457
|
+
importedNode = doc.createNode({ assistants: [], datasources: [], skills: [], integrations: [] });
|
|
4458
|
+
doc.set("imported", importedNode);
|
|
4459
|
+
}
|
|
4460
|
+
let skillsArray = doc.getIn(["imported", "skills"]);
|
|
4461
|
+
if (!skillsArray) {
|
|
4462
|
+
skillsArray = doc.createNode([]);
|
|
4463
|
+
doc.setIn(["imported", "skills"], skillsArray);
|
|
4464
|
+
}
|
|
4465
|
+
const existingSkill = skillsArray.items.find((item) => {
|
|
4466
|
+
if (!yaml6.isMap(item)) {
|
|
4467
|
+
return false;
|
|
4468
|
+
}
|
|
4469
|
+
const existingName = item.get("name");
|
|
4470
|
+
return typeof existingName === "string" && namesEqual(existingName, skill.name);
|
|
4471
|
+
});
|
|
4472
|
+
if (existingSkill) {
|
|
4473
|
+
const existingId = existingSkill.get("id");
|
|
4474
|
+
if (existingId && existingId !== skill.id) {
|
|
4475
|
+
throw new Error(
|
|
4476
|
+
`Skill "${skill.name}" already exists in imported.skills with a different ID (${existingId}). Remove the existing entry or use the intended skill ID.`
|
|
4477
|
+
);
|
|
4478
|
+
}
|
|
4479
|
+
logger.info(` \u21B7 Skill "${skill.name}" already exists in imported.skills`);
|
|
4480
|
+
return;
|
|
4481
|
+
}
|
|
4482
|
+
skillsArray.add(doc.createNode({ name: skill.name, id: skill.id }));
|
|
4483
|
+
fs.writeFileSync(configPath, doc.toString(), "utf8");
|
|
4484
|
+
logger.info(` \u2705 Added skill to ${codemieConfigPath} \u2192 imported.skills`);
|
|
4485
|
+
}
|
|
4486
|
+
function ensureExternalSkillDoesNotConflictWithManagedSkill(config, skillName) {
|
|
4487
|
+
const managedSkill = config.resources.skills?.find((skill) => namesEqual(skill.name, skillName));
|
|
4488
|
+
if (!managedSkill) {
|
|
4489
|
+
return;
|
|
4490
|
+
}
|
|
4491
|
+
throw new Error(
|
|
4492
|
+
`External skill "${skillName}" is already defined in resources.skills. Keep project-owned skills in resources.skills or external skills in imported.skills, not both.`
|
|
4493
|
+
);
|
|
4494
|
+
}
|
|
4219
4495
|
function updateState(stateManager, resourceType, resource, apiResponse) {
|
|
4220
4496
|
switch (resourceType) {
|
|
4221
4497
|
case "assistant": {
|
|
@@ -4303,16 +4579,22 @@ async function importResource(options) {
|
|
|
4303
4579
|
logger.info(` Resolving ${assistantData.skill_ids.length} skill ID(s)...`);
|
|
4304
4580
|
const skillNames = [];
|
|
4305
4581
|
for (const skillId of assistantData.skill_ids) {
|
|
4582
|
+
let skill;
|
|
4306
4583
|
try {
|
|
4307
|
-
|
|
4584
|
+
skill = await withTimeout(
|
|
4308
4585
|
client.skills.get(skillId),
|
|
4309
4586
|
TIMEOUTS_MS.ASSISTANT_FETCH,
|
|
4310
4587
|
`Timeout fetching skill "${skillId}"`
|
|
4311
4588
|
);
|
|
4312
|
-
skillNames.push(skill.name);
|
|
4313
|
-
logger.info(` \u2713 Resolved skill ${skillId.slice(0, 8)}... \u2192 "${skill.name}"`);
|
|
4314
4589
|
} catch {
|
|
4315
4590
|
logger.warn(` \u26A0\uFE0F Could not resolve skill ID "${skillId}" \u2014 skipping`);
|
|
4591
|
+
continue;
|
|
4592
|
+
}
|
|
4593
|
+
skillNames.push(skill.name);
|
|
4594
|
+
logger.info(` \u2713 Resolved skill ${skillId.slice(0, 8)}... \u2192 "${skill.name}"`);
|
|
4595
|
+
if (!isProjectOwnedSkill(skill, config.project.name)) {
|
|
4596
|
+
ensureExternalSkillDoesNotConflictWithManagedSkill(config, skill.name);
|
|
4597
|
+
addImportedSkillToCodemieYaml(rootDir, appConfig.codemieConfig, { name: skill.name, id: skill.id });
|
|
4316
4598
|
}
|
|
4317
4599
|
}
|
|
4318
4600
|
if (skillNames.length > 0) {
|
|
@@ -4348,9 +4630,22 @@ async function importResource(options) {
|
|
|
4348
4630
|
logger.info(` Searching for skill "${slug}"...`);
|
|
4349
4631
|
const skill = await findSkill(client, slug);
|
|
4350
4632
|
logger.info(` \u2713 Found: ${skill.name} (${skill.id})`);
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4633
|
+
if (skill.name !== slug && checkResourceExists2(config, resourceType, skill.name)) {
|
|
4634
|
+
throw new Error(
|
|
4635
|
+
`A ${resourceType} named "${skill.name}" already exists in the configuration. Remove it first or use a different name.`
|
|
4636
|
+
);
|
|
4637
|
+
}
|
|
4638
|
+
if (isProjectOwnedSkill(skill, config.project.name)) {
|
|
4639
|
+
logger.info(` Project-owned skill \u2014 importing into resources.skills`);
|
|
4640
|
+
apiResponse = skill;
|
|
4641
|
+
resource = skillResponseToResource(skill);
|
|
4642
|
+
break;
|
|
4643
|
+
}
|
|
4644
|
+
addImportedSkillToCodemieYaml(rootDir, appConfig.codemieConfig, { name: skill.name, id: skill.id });
|
|
4645
|
+
logger.info(`
|
|
4646
|
+
\u2705 Successfully imported external skill "${skill.name}" into imported.skills
|
|
4647
|
+
`);
|
|
4648
|
+
return;
|
|
4354
4649
|
}
|
|
4355
4650
|
}
|
|
4356
4651
|
const relativeResourcePath = writeResourceFiles(rootDir, resourceType, resource, apiResponse);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codemieai/cdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.545",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Infrastructure as Code solution for managing Codemie AI assistants, datasources, and workflows through declarative YAML configuration",
|
|
6
6
|
"main": "./dist/index.js",
|