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