@codemieai/cdk 0.1.440 → 0.1.445
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +1818 -621
- package/dist/index.d.ts +68 -9
- package/dist/index.js +1959 -851
- package/package.json +4 -3
package/dist/cli/index.js
CHANGED
|
@@ -1,98 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import * as fs6 from 'fs';
|
|
2
3
|
import * as path6 from 'path';
|
|
3
4
|
import path6__default from 'path';
|
|
5
|
+
import ora from 'ora';
|
|
6
|
+
import * as crypto2 from 'crypto';
|
|
7
|
+
import { DataSourceType, CodeMieClient } from 'codemie-sdk';
|
|
8
|
+
import * as yaml5 from 'yaml';
|
|
4
9
|
import { program } from '@commander-js/extra-typings';
|
|
5
|
-
import * as fs2 from 'fs';
|
|
6
10
|
import { z } from 'zod';
|
|
7
11
|
import 'dotenv/config';
|
|
8
|
-
import * as crypto from 'crypto';
|
|
9
|
-
import * as yaml3 from 'yaml';
|
|
10
|
-
import { CodeMieClient, DataSourceType } from 'codemie-sdk';
|
|
11
12
|
import pLimit from 'p-limit';
|
|
12
13
|
import * as readline from 'readline';
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
var
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
rootDir: z.string(),
|
|
19
|
-
codemieConfig: z.string(),
|
|
20
|
-
codemieState: z.string(),
|
|
21
|
-
backupsDirectory: z.string()
|
|
22
|
-
});
|
|
23
|
-
var externalConfigSchema = appConfigSchema.partial();
|
|
24
|
-
|
|
25
|
-
// src/appConfig/defaultConfig.ts
|
|
26
|
-
var defaultConfig = {
|
|
27
|
-
rootDir: "./",
|
|
28
|
-
codemieConfig: "codemie.yaml",
|
|
29
|
-
codemieState: ".codemie/state.json",
|
|
30
|
-
backupsDirectory: "backups"
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
// src/appConfig/configLoader.ts
|
|
34
|
-
function loadAppConfig(userConfigPath) {
|
|
35
|
-
if (!userConfigPath) {
|
|
36
|
-
return defaultConfig;
|
|
37
|
-
}
|
|
38
|
-
const userConfig = loadUserConfig(userConfigPath);
|
|
39
|
-
const sanitizedUserConfig = removeEmptyFields(userConfig);
|
|
40
|
-
const merged = { ...defaultConfig, ...sanitizedUserConfig };
|
|
41
|
-
return appConfigSchema.parse(merged);
|
|
42
|
-
}
|
|
43
|
-
function loadUserConfig(configPath) {
|
|
44
|
-
const resolvedPath = path6.resolve(process.cwd(), configPath);
|
|
45
|
-
if (!fs2.existsSync(resolvedPath)) {
|
|
46
|
-
throw new Error(`Config file not found: ${resolvedPath}`);
|
|
47
|
-
}
|
|
48
|
-
let raw;
|
|
49
|
-
try {
|
|
50
|
-
raw = fs2.readFileSync(resolvedPath, "utf8");
|
|
51
|
-
} catch (error) {
|
|
52
|
-
throw new Error(`Failed to read config file: ${resolvedPath}`, { cause: error });
|
|
53
|
-
}
|
|
54
|
-
let json;
|
|
55
|
-
try {
|
|
56
|
-
json = JSON.parse(raw);
|
|
57
|
-
} catch (error) {
|
|
58
|
-
throw new Error(`Malformed JSON in config file: ${resolvedPath}`, { cause: error });
|
|
59
|
-
}
|
|
60
|
-
try {
|
|
61
|
-
return externalConfigSchema.parse(json);
|
|
62
|
-
} catch (error) {
|
|
63
|
-
throw new Error(`Invalid config structure: ${error instanceof Error ? error.message : String(error)}`);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
function removeEmptyFields(userConfig) {
|
|
67
|
-
return Object.fromEntries(
|
|
68
|
-
Object.entries(userConfig).filter(([_, value]) => {
|
|
69
|
-
if (value === null || value === void 0) {
|
|
70
|
-
return false;
|
|
71
|
-
}
|
|
72
|
-
return value.trim() !== "";
|
|
73
|
-
})
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// src/lib/constants.ts
|
|
78
|
-
var PAGINATION = {
|
|
79
|
-
DEFAULT_PAGE_SIZE: 100};
|
|
80
|
-
var TIMEOUTS_MS = {
|
|
81
|
-
ASSISTANT_FETCH: 3e4,
|
|
82
|
-
DATASOURCE_FETCH: 3e4,
|
|
83
|
-
WORKFLOW_FETCH: 3e4,
|
|
84
|
-
INTEGRATION_FETCH: 3e4
|
|
85
|
-
};
|
|
86
|
-
var RATE_LIMITING = {
|
|
87
|
-
MAX_CONCURRENT_REQUESTS: 5,
|
|
88
|
-
RETRY_ATTEMPTS: 3,
|
|
89
|
-
RETRY_DELAY_MS: 1e3
|
|
15
|
+
var __defProp = Object.defineProperty;
|
|
16
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
17
|
+
var __esm = (fn, res) => function __init() {
|
|
18
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
90
19
|
};
|
|
91
|
-
var
|
|
92
|
-
|
|
93
|
-
|
|
20
|
+
var __export = (target, all) => {
|
|
21
|
+
for (var name in all)
|
|
22
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
94
23
|
};
|
|
95
|
-
var BYTES_IN_GB = 1024 ** 3;
|
|
96
24
|
function sanitizeFileName(name, maxLength = 255) {
|
|
97
25
|
const nameWithHyphens = name.replaceAll(/[/\\]/g, "-");
|
|
98
26
|
const sanitized = nameWithHyphens.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replaceAll(/^-+|-+$/g, "").slice(0, maxLength);
|
|
@@ -104,14 +32,14 @@ function sanitizeFileName(name, maxLength = 255) {
|
|
|
104
32
|
function validateBackupDirectory(backupDir, minSpaceGB = 1) {
|
|
105
33
|
try {
|
|
106
34
|
const parentDir = path6.dirname(backupDir);
|
|
107
|
-
if (!
|
|
108
|
-
|
|
35
|
+
if (!fs6.existsSync(parentDir)) {
|
|
36
|
+
fs6.mkdirSync(parentDir, { recursive: true });
|
|
109
37
|
}
|
|
110
38
|
const testFile = path6.join(parentDir, ".write-test");
|
|
111
|
-
|
|
112
|
-
|
|
39
|
+
fs6.writeFileSync(testFile, "test");
|
|
40
|
+
fs6.unlinkSync(testFile);
|
|
113
41
|
try {
|
|
114
|
-
const stats =
|
|
42
|
+
const stats = fs6.statfsSync(parentDir);
|
|
115
43
|
const availableGB = stats.bavail * stats.bsize / BYTES_IN_GB;
|
|
116
44
|
if (availableGB < minSpaceGB) {
|
|
117
45
|
throw new Error(`Insufficient disk space: ${availableGB.toFixed(2)}GB available, need ${minSpaceGB}GB`);
|
|
@@ -127,7 +55,7 @@ function validateBackupDirectory(backupDir, minSpaceGB = 1) {
|
|
|
127
55
|
}
|
|
128
56
|
function moveAtomically(tempPath, finalPath) {
|
|
129
57
|
try {
|
|
130
|
-
|
|
58
|
+
fs6.renameSync(tempPath, finalPath);
|
|
131
59
|
} catch (error) {
|
|
132
60
|
const err = error;
|
|
133
61
|
if (err.code === "EEXIST") {
|
|
@@ -137,287 +65,247 @@ function moveAtomically(tempPath, finalPath) {
|
|
|
137
65
|
}
|
|
138
66
|
}
|
|
139
67
|
function cleanupDirectory(dirPath) {
|
|
140
|
-
if (
|
|
141
|
-
|
|
68
|
+
if (fs6.existsSync(dirPath)) {
|
|
69
|
+
fs6.rmSync(dirPath, { recursive: true, force: true });
|
|
142
70
|
}
|
|
143
71
|
}
|
|
144
72
|
function ensureDirectoryExists(filePath) {
|
|
145
73
|
const dir = path6.dirname(filePath);
|
|
146
|
-
if (!
|
|
147
|
-
|
|
74
|
+
if (!fs6.existsSync(dir)) {
|
|
75
|
+
fs6.mkdirSync(dir, { recursive: true });
|
|
148
76
|
}
|
|
149
77
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
level = 1 /* INFO */;
|
|
155
|
-
constructor() {
|
|
78
|
+
var BYTES_IN_GB;
|
|
79
|
+
var init_fileUtils = __esm({
|
|
80
|
+
"src/lib/fileUtils.ts"() {
|
|
81
|
+
BYTES_IN_GB = 1024 ** 3;
|
|
156
82
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
161
|
-
return _Logger.instance;
|
|
83
|
+
});
|
|
84
|
+
function formatDuration(durationMs) {
|
|
85
|
+
if (durationMs < 1e3) {
|
|
86
|
+
return `${durationMs}ms`;
|
|
162
87
|
}
|
|
163
|
-
|
|
164
|
-
|
|
88
|
+
const seconds = durationMs / 1e3;
|
|
89
|
+
if (seconds < 60) {
|
|
90
|
+
return `${seconds.toFixed(1)}s`;
|
|
165
91
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
92
|
+
const minutes = Math.floor(seconds / 60);
|
|
93
|
+
const remainingSeconds = Math.round(seconds % 60);
|
|
94
|
+
return `${minutes}m ${remainingSeconds}s`;
|
|
95
|
+
}
|
|
96
|
+
function normalizeExitCode(code) {
|
|
97
|
+
if (typeof code === "number" && Number.isFinite(code)) {
|
|
98
|
+
return code;
|
|
170
99
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
100
|
+
if (typeof code === "string") {
|
|
101
|
+
const parsed = Number(code);
|
|
102
|
+
if (Number.isFinite(parsed)) {
|
|
103
|
+
return parsed;
|
|
174
104
|
}
|
|
175
105
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
106
|
+
return 0;
|
|
107
|
+
}
|
|
108
|
+
function startSpinner(text) {
|
|
109
|
+
spinnerManager.start(text);
|
|
110
|
+
}
|
|
111
|
+
function succeedSpinner(text) {
|
|
112
|
+
spinnerManager.succeed(text);
|
|
113
|
+
}
|
|
114
|
+
function failSpinner(text) {
|
|
115
|
+
spinnerManager.fail(text);
|
|
116
|
+
}
|
|
117
|
+
function pauseSpinner(operation) {
|
|
118
|
+
return spinnerManager.pause(operation);
|
|
119
|
+
}
|
|
120
|
+
function pauseSpinnerWhile(operation) {
|
|
121
|
+
return spinnerManager.pauseWhile(operation);
|
|
122
|
+
}
|
|
123
|
+
async function runCommandWithSpinner(commandLabel, action) {
|
|
124
|
+
const originalExit = process.exit;
|
|
125
|
+
const previousExitCode = process.exitCode;
|
|
126
|
+
const startTime = Date.now();
|
|
127
|
+
let interceptedExitCode;
|
|
128
|
+
startSpinner(`${commandLabel}...`);
|
|
129
|
+
process.exit = ((code) => {
|
|
130
|
+
interceptedExitCode = normalizeExitCode(code);
|
|
131
|
+
process.exitCode = interceptedExitCode;
|
|
132
|
+
return void 0;
|
|
133
|
+
});
|
|
134
|
+
try {
|
|
135
|
+
await action();
|
|
136
|
+
} catch (error) {
|
|
137
|
+
const duration2 = formatDuration(Date.now() - startTime);
|
|
138
|
+
failSpinner(`${commandLabel} failed in ${duration2}`);
|
|
139
|
+
if ((interceptedExitCode ?? process.exitCode ?? 0) === 0) {
|
|
140
|
+
process.exitCode = 1;
|
|
141
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
142
|
+
console.error(errorMessage);
|
|
143
|
+
}
|
|
144
|
+
return;
|
|
145
|
+
} finally {
|
|
146
|
+
process.exit = originalExit;
|
|
180
147
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
}
|
|
148
|
+
const exitCode = interceptedExitCode ?? process.exitCode ?? previousExitCode ?? 0;
|
|
149
|
+
const duration = formatDuration(Date.now() - startTime);
|
|
150
|
+
if (exitCode === 0) {
|
|
151
|
+
succeedSpinner(`${commandLabel} completed in ${duration}`);
|
|
152
|
+
} else {
|
|
153
|
+
failSpinner(`${commandLabel} failed in ${duration}`);
|
|
185
154
|
}
|
|
186
|
-
};
|
|
187
|
-
var logger = Logger.getInstance();
|
|
188
|
-
|
|
189
|
-
// src/lib/backupTransaction.ts
|
|
190
|
-
function busyWaitDelay(ms) {
|
|
191
155
|
}
|
|
192
|
-
var
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
if (err.code === "ENOENT") {
|
|
206
|
-
this.data = this.createNewTransaction(backupDir, transactionId);
|
|
207
|
-
} else {
|
|
208
|
-
throw error;
|
|
156
|
+
var defaultSpinnerFactory, CommandSpinnerManager, spinnerManager;
|
|
157
|
+
var init_commandSpinner = __esm({
|
|
158
|
+
"src/lib/commandSpinner.ts"() {
|
|
159
|
+
defaultSpinnerFactory = (text) => ora({
|
|
160
|
+
text,
|
|
161
|
+
discardStdin: false
|
|
162
|
+
});
|
|
163
|
+
CommandSpinnerManager = class {
|
|
164
|
+
spinner = null;
|
|
165
|
+
spinnerText = "";
|
|
166
|
+
factory = defaultSpinnerFactory;
|
|
167
|
+
setFactory(factory) {
|
|
168
|
+
this.factory = factory;
|
|
209
169
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
170
|
+
resetFactory() {
|
|
171
|
+
this.factory = defaultSpinnerFactory;
|
|
172
|
+
this.reset();
|
|
173
|
+
}
|
|
174
|
+
reset() {
|
|
175
|
+
if (this.spinner?.isSpinning) {
|
|
176
|
+
this.spinner.stop();
|
|
177
|
+
}
|
|
178
|
+
this.spinner = null;
|
|
179
|
+
this.spinnerText = "";
|
|
180
|
+
}
|
|
181
|
+
isEnabled() {
|
|
182
|
+
return Boolean(process.stderr?.isTTY);
|
|
183
|
+
}
|
|
184
|
+
start(text) {
|
|
185
|
+
this.spinnerText = text;
|
|
186
|
+
if (!this.isEnabled()) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (!this.spinner) {
|
|
190
|
+
this.spinner = this.factory(text);
|
|
191
|
+
this.spinner.start();
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
this.spinner.text = text;
|
|
195
|
+
if (!this.spinner.isSpinning) {
|
|
196
|
+
this.spinner.start();
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
stop() {
|
|
200
|
+
if (this.spinner?.isSpinning) {
|
|
201
|
+
this.spinner.stop();
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
succeed(text) {
|
|
205
|
+
if (!this.spinner) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
this.spinner.succeed(text ?? this.spinnerText);
|
|
209
|
+
this.spinner = null;
|
|
210
|
+
this.spinnerText = "";
|
|
211
|
+
}
|
|
212
|
+
fail(text) {
|
|
213
|
+
if (!this.spinner) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
this.spinner.fail(text ?? this.spinnerText);
|
|
217
|
+
this.spinner = null;
|
|
218
|
+
this.spinnerText = "";
|
|
219
|
+
}
|
|
220
|
+
pause(operation) {
|
|
221
|
+
const shouldResume = Boolean(this.spinner?.isSpinning);
|
|
222
|
+
const currentText = this.spinner?.text ?? this.spinnerText;
|
|
223
|
+
if (shouldResume) {
|
|
224
|
+
this.spinner?.stop();
|
|
225
|
+
}
|
|
226
|
+
try {
|
|
227
|
+
return operation();
|
|
228
|
+
} finally {
|
|
229
|
+
if (shouldResume && this.spinner) {
|
|
230
|
+
this.spinner.text = currentText;
|
|
231
|
+
this.spinner.start();
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
async pauseWhile(operation) {
|
|
236
|
+
const shouldResume = Boolean(this.spinner?.isSpinning);
|
|
237
|
+
const currentText = this.spinner?.text ?? this.spinnerText;
|
|
238
|
+
if (shouldResume) {
|
|
239
|
+
this.spinner?.stop();
|
|
240
|
+
}
|
|
241
|
+
try {
|
|
242
|
+
return await operation();
|
|
243
|
+
} finally {
|
|
244
|
+
if (shouldResume && this.spinner) {
|
|
245
|
+
this.spinner.text = currentText;
|
|
246
|
+
this.spinner.start();
|
|
247
|
+
}
|
|
248
|
+
}
|
|
223
249
|
}
|
|
224
250
|
};
|
|
225
|
-
|
|
226
|
-
this.writeTransactionExclusively(newData);
|
|
227
|
-
return newData;
|
|
251
|
+
spinnerManager = new CommandSpinnerManager();
|
|
228
252
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// src/lib/logger.ts
|
|
256
|
+
var Logger, logger;
|
|
257
|
+
var init_logger = __esm({
|
|
258
|
+
"src/lib/logger.ts"() {
|
|
259
|
+
init_commandSpinner();
|
|
260
|
+
Logger = class _Logger {
|
|
261
|
+
static instance;
|
|
262
|
+
level = 1 /* INFO */;
|
|
263
|
+
constructor() {
|
|
240
264
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
let retries = 3;
|
|
245
|
-
let lastReadError;
|
|
246
|
-
while (retries > 0) {
|
|
247
|
-
try {
|
|
248
|
-
const delayMs = 100 * (4 - retries);
|
|
249
|
-
if (delayMs > 0) {
|
|
250
|
-
busyWaitDelay(delayMs);
|
|
265
|
+
static getInstance() {
|
|
266
|
+
if (!_Logger.instance) {
|
|
267
|
+
_Logger.instance = new _Logger();
|
|
251
268
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
);
|
|
269
|
+
return _Logger.instance;
|
|
270
|
+
}
|
|
271
|
+
setLevel(level) {
|
|
272
|
+
this.level = level;
|
|
273
|
+
}
|
|
274
|
+
debug(message, ...args) {
|
|
275
|
+
if (this.level <= 0 /* DEBUG */) {
|
|
276
|
+
pauseSpinner(() => {
|
|
277
|
+
console.debug(message, ...args);
|
|
278
|
+
});
|
|
263
279
|
}
|
|
264
280
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
* Save transaction state to disk (checkpoint) with timeout protection
|
|
272
|
-
*/
|
|
273
|
-
async save() {
|
|
274
|
-
ensureDirectoryExists(this.transactionPath);
|
|
275
|
-
const savePromise = fs2.promises.writeFile(this.transactionPath, JSON.stringify(this.data, null, 2), "utf8");
|
|
276
|
-
let timeoutId;
|
|
277
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
278
|
-
timeoutId = setTimeout(
|
|
279
|
-
() => reject(new Error(`Transaction save timeout after ${BACKUP.TRANSACTION_SAVE_TIMEOUT_MS}ms`)),
|
|
280
|
-
BACKUP.TRANSACTION_SAVE_TIMEOUT_MS
|
|
281
|
-
);
|
|
282
|
-
timeoutId.unref();
|
|
283
|
-
});
|
|
284
|
-
try {
|
|
285
|
-
await Promise.race([savePromise, timeoutPromise]);
|
|
286
|
-
this.isDirty = false;
|
|
287
|
-
} catch (error) {
|
|
288
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
289
|
-
throw new Error(`Failed to save transaction file: ${err.message}`);
|
|
290
|
-
} finally {
|
|
291
|
-
if (timeoutId !== void 0) {
|
|
292
|
-
clearTimeout(timeoutId);
|
|
281
|
+
info(message, ...args) {
|
|
282
|
+
if (this.level <= 1 /* INFO */) {
|
|
283
|
+
pauseSpinner(() => {
|
|
284
|
+
console.log(message, ...args);
|
|
285
|
+
});
|
|
286
|
+
}
|
|
293
287
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
ensureDirectoryExists(this.transactionPath);
|
|
301
|
-
fs2.writeFileSync(this.transactionPath, JSON.stringify(this.data, null, 2), "utf8");
|
|
302
|
-
this.isDirty = false;
|
|
303
|
-
}
|
|
304
|
-
/**
|
|
305
|
-
* Schedule batched save (debounced to avoid excessive disk writes)
|
|
306
|
-
*/
|
|
307
|
-
scheduleSave() {
|
|
308
|
-
if (this.saveTimer) {
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
311
|
-
this.saveTimer = setTimeout(() => {
|
|
312
|
-
if (this.isDirty) {
|
|
313
|
-
this.saveSyncInternal();
|
|
288
|
+
warn(message, ...args) {
|
|
289
|
+
if (this.level <= 2 /* WARN */) {
|
|
290
|
+
pauseSpinner(() => {
|
|
291
|
+
console.warn(message, ...args);
|
|
292
|
+
});
|
|
293
|
+
}
|
|
314
294
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
295
|
+
error(message, ...args) {
|
|
296
|
+
if (this.level <= 3 /* ERROR */) {
|
|
297
|
+
pauseSpinner(() => {
|
|
298
|
+
console.error(message, ...args);
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
logger = Logger.getInstance();
|
|
318
304
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
this.data.status = status;
|
|
324
|
-
this.data.endTime = (/* @__PURE__ */ new Date()).toISOString();
|
|
325
|
-
this.flush();
|
|
326
|
-
}
|
|
327
|
-
/**
|
|
328
|
-
* Force immediate save (flush pending changes)
|
|
329
|
-
*/
|
|
330
|
-
flush() {
|
|
331
|
-
if (this.saveTimer) {
|
|
332
|
-
clearTimeout(this.saveTimer);
|
|
333
|
-
this.saveTimer = void 0;
|
|
334
|
-
}
|
|
335
|
-
if (this.isDirty) {
|
|
336
|
-
this.saveSyncInternal();
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
/**
|
|
340
|
-
* Set total count for a resource type
|
|
341
|
-
*/
|
|
342
|
-
setTotal(resourceType, total) {
|
|
343
|
-
this.data.resources[resourceType].total = total;
|
|
344
|
-
this.isDirty = true;
|
|
345
|
-
this.scheduleSave();
|
|
346
|
-
}
|
|
347
|
-
/**
|
|
348
|
-
* Mark resource as completed
|
|
349
|
-
*/
|
|
350
|
-
markCompleted(resourceType, resourceId) {
|
|
351
|
-
if (!this.data.resources[resourceType].completed.includes(resourceId)) {
|
|
352
|
-
this.data.resources[resourceType].completed.push(resourceId);
|
|
353
|
-
this.isDirty = true;
|
|
354
|
-
this.scheduleSave();
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
/**
|
|
358
|
-
* Mark resource as failed
|
|
359
|
-
*/
|
|
360
|
-
markFailed(resourceType, resourceId, error) {
|
|
361
|
-
const failedEntry = { id: resourceId, error };
|
|
362
|
-
const failedList = this.data.resources[resourceType].failed;
|
|
363
|
-
if (!failedList.some((f) => f.id === resourceId)) {
|
|
364
|
-
failedList.push(failedEntry);
|
|
365
|
-
this.isDirty = true;
|
|
366
|
-
this.scheduleSave();
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
/**
|
|
370
|
-
* Check if resource was already backed up
|
|
371
|
-
*/
|
|
372
|
-
isCompleted(resourceType, resourceId) {
|
|
373
|
-
return this.data.resources[resourceType].completed.includes(resourceId);
|
|
374
|
-
}
|
|
375
|
-
/**
|
|
376
|
-
* Mark transaction as completed
|
|
377
|
-
*/
|
|
378
|
-
complete() {
|
|
379
|
-
this.end("completed");
|
|
380
|
-
}
|
|
381
|
-
/**
|
|
382
|
-
* Mark transaction as failed
|
|
383
|
-
*/
|
|
384
|
-
fail() {
|
|
385
|
-
this.end("failed");
|
|
386
|
-
}
|
|
387
|
-
/**
|
|
388
|
-
* Get transaction data (returns deep copy to prevent external modifications)
|
|
389
|
-
*/
|
|
390
|
-
getData() {
|
|
391
|
-
return structuredClone(this.data);
|
|
392
|
-
}
|
|
393
|
-
/**
|
|
394
|
-
* Get summary of backup progress
|
|
395
|
-
*/
|
|
396
|
-
getSummary() {
|
|
397
|
-
const { resources } = this.data;
|
|
398
|
-
const lines = [];
|
|
399
|
-
for (const [type, data] of Object.entries(resources)) {
|
|
400
|
-
const completed = data.completed.length;
|
|
401
|
-
const failed = data.failed.length;
|
|
402
|
-
const total = data.total;
|
|
403
|
-
const percent = total > 0 ? Math.round(completed / total * 100) : 0;
|
|
404
|
-
lines.push(` ${type}: ${completed}/${total} (${percent}%) ${failed > 0 ? `[${failed} failed]` : ""}`);
|
|
405
|
-
}
|
|
406
|
-
return lines.join("\n");
|
|
407
|
-
}
|
|
408
|
-
/**
|
|
409
|
-
* Clean up transaction file after successful completion
|
|
410
|
-
*/
|
|
411
|
-
cleanup() {
|
|
412
|
-
if (fs2.existsSync(this.transactionPath)) {
|
|
413
|
-
fs2.unlinkSync(this.transactionPath);
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
};
|
|
417
|
-
var DEFAULT_LLM_MODEL = "gpt-4";
|
|
418
|
-
function normalizeIntegrationSettings(settings) {
|
|
419
|
-
if (!settings || typeof settings !== "object") {
|
|
420
|
-
return settings;
|
|
305
|
+
});
|
|
306
|
+
function normalizeIntegrationSettings(settings) {
|
|
307
|
+
if (!settings || typeof settings !== "object") {
|
|
308
|
+
return settings;
|
|
421
309
|
}
|
|
422
310
|
if ("$ref" in settings && typeof settings.$ref === "string") {
|
|
423
311
|
return { $ref: settings.$ref };
|
|
@@ -466,7 +354,7 @@ function calculateChecksum(content) {
|
|
|
466
354
|
if (content.length === 0) {
|
|
467
355
|
logger.warn("\u26A0\uFE0F Calculating checksum of empty string");
|
|
468
356
|
}
|
|
469
|
-
return
|
|
357
|
+
return crypto2.createHash("sha256").update(content, "utf8").digest("hex");
|
|
470
358
|
}
|
|
471
359
|
function normalizeAssistantConfig(assistant, buildConfig = null) {
|
|
472
360
|
return {
|
|
@@ -483,6 +371,7 @@ function normalizeAssistantConfig(assistant, buildConfig = null) {
|
|
|
483
371
|
mcp_servers: normalizeMcpServers(assistant.mcp_servers),
|
|
484
372
|
assistant_ids: assistant.assistant_ids || [],
|
|
485
373
|
sub_assistants: assistant.sub_assistants || [],
|
|
374
|
+
skills: assistant.skills || [],
|
|
486
375
|
conversation_starters: assistant.conversation_starters || [],
|
|
487
376
|
buildConfig
|
|
488
377
|
};
|
|
@@ -491,13 +380,18 @@ function calculateAssistantConfigChecksum(assistant, buildConfig = null) {
|
|
|
491
380
|
const normalized = normalizeAssistantConfig(assistant, buildConfig);
|
|
492
381
|
return calculateChecksum(JSON.stringify(normalized));
|
|
493
382
|
}
|
|
383
|
+
var DEFAULT_LLM_MODEL;
|
|
384
|
+
var init_checksumUtils = __esm({
|
|
385
|
+
"src/lib/checksumUtils.ts"() {
|
|
386
|
+
init_logger();
|
|
387
|
+
DEFAULT_LLM_MODEL = "gpt-4";
|
|
388
|
+
}
|
|
389
|
+
});
|
|
494
390
|
|
|
495
391
|
// src/lib/codemieConfigChecksums.ts
|
|
496
392
|
function createExcludeSet(keys) {
|
|
497
393
|
return new Set(keys);
|
|
498
394
|
}
|
|
499
|
-
var DATASOURCE_EXCLUDED_FIELDS = createExcludeSet(["force_reindex"]);
|
|
500
|
-
var WORKFLOW_EXCLUDED_FIELDS = createExcludeSet(["definition"]);
|
|
501
395
|
function buildChecksumObject(src, excluded) {
|
|
502
396
|
const out = {};
|
|
503
397
|
const keys = Object.keys(src).sort();
|
|
@@ -520,6 +414,19 @@ function calculateWorkflowConfigChecksum(workflow) {
|
|
|
520
414
|
const filtered = buildChecksumObject(workflow, WORKFLOW_EXCLUDED_FIELDS);
|
|
521
415
|
return calculateChecksum(JSON.stringify(filtered));
|
|
522
416
|
}
|
|
417
|
+
function calculateSkillConfigChecksum(skill) {
|
|
418
|
+
const filtered = buildChecksumObject(skill, SKILL_EXCLUDED_FIELDS);
|
|
419
|
+
return calculateChecksum(JSON.stringify(filtered));
|
|
420
|
+
}
|
|
421
|
+
var DATASOURCE_EXCLUDED_FIELDS, WORKFLOW_EXCLUDED_FIELDS, SKILL_EXCLUDED_FIELDS;
|
|
422
|
+
var init_codemieConfigChecksums = __esm({
|
|
423
|
+
"src/lib/codemieConfigChecksums.ts"() {
|
|
424
|
+
init_checksumUtils();
|
|
425
|
+
DATASOURCE_EXCLUDED_FIELDS = createExcludeSet(["force_reindex"]);
|
|
426
|
+
WORKFLOW_EXCLUDED_FIELDS = createExcludeSet(["definition"]);
|
|
427
|
+
SKILL_EXCLUDED_FIELDS = createExcludeSet(["file"]);
|
|
428
|
+
}
|
|
429
|
+
});
|
|
523
430
|
|
|
524
431
|
// src/lib/typeGuards.ts
|
|
525
432
|
function hasName(obj) {
|
|
@@ -531,8 +438,34 @@ function hasSettingId(obj) {
|
|
|
531
438
|
function isResolvedIntegration(obj) {
|
|
532
439
|
return typeof obj === "object" && obj !== null && "id" in obj && typeof obj.id === "string";
|
|
533
440
|
}
|
|
534
|
-
|
|
535
|
-
|
|
441
|
+
var init_typeGuards = __esm({
|
|
442
|
+
"src/lib/typeGuards.ts"() {
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
function isChunkSummaryType(type) {
|
|
446
|
+
return CHUNK_SUMMARY_ALIASES.has(type);
|
|
447
|
+
}
|
|
448
|
+
function isFileSummaryType(type) {
|
|
449
|
+
return FILE_SUMMARY_ALIASES.has(type);
|
|
450
|
+
}
|
|
451
|
+
function normalizeCodeIndexType(datasource) {
|
|
452
|
+
if (datasource.code?.indexType) {
|
|
453
|
+
return datasource.code.indexType;
|
|
454
|
+
}
|
|
455
|
+
if (datasource.type === DataSourceType.CODE || datasource.type === DataSourceType.SUMMARY) {
|
|
456
|
+
return datasource.type;
|
|
457
|
+
}
|
|
458
|
+
if (datasource.type === DataSourceType.CHUNK_SUMMARY) {
|
|
459
|
+
return DataSourceType.CHUNK_SUMMARY;
|
|
460
|
+
}
|
|
461
|
+
if (isFileSummaryType(datasource.type)) {
|
|
462
|
+
return DataSourceType.SUMMARY;
|
|
463
|
+
}
|
|
464
|
+
if (isChunkSummaryType(datasource.type)) {
|
|
465
|
+
return DataSourceType.CHUNK_SUMMARY;
|
|
466
|
+
}
|
|
467
|
+
return void 0;
|
|
468
|
+
}
|
|
536
469
|
function validateMcpCommandArgs(s, hasTopLevelCommand) {
|
|
537
470
|
if (hasTopLevelCommand) {
|
|
538
471
|
return true;
|
|
@@ -587,14 +520,15 @@ function convertMcpServers(servers) {
|
|
|
587
520
|
return validServers;
|
|
588
521
|
}
|
|
589
522
|
function assistantResponseToResource(assistant) {
|
|
523
|
+
const safeName = assistant.name || assistant.id || "assistant";
|
|
590
524
|
const slug = assistant.slug || "";
|
|
591
|
-
const promptFileName = slug ||
|
|
525
|
+
const promptFileName = slug || String(safeName).toLowerCase().replaceAll(/\s+/g, "-");
|
|
592
526
|
const mcpServers = convertMcpServers(assistant.mcp_servers);
|
|
593
527
|
const nestedAssistants = assistant.nested_assistants;
|
|
594
528
|
const subAssistants = nestedAssistants?.map((nested) => nested.name).filter((name) => Boolean(name));
|
|
595
529
|
const categories = assistant.categories?.map((category) => category.id);
|
|
596
530
|
return {
|
|
597
|
-
name: assistant.name,
|
|
531
|
+
name: assistant.name || safeName,
|
|
598
532
|
description: assistant.description || "",
|
|
599
533
|
prompt: `system_prompts/${promptFileName}.prompt.md`,
|
|
600
534
|
model: assistant.llm_model_type || DEFAULT_LLM_MODEL,
|
|
@@ -615,6 +549,7 @@ function assistantResponseToResource(assistant) {
|
|
|
615
549
|
}
|
|
616
550
|
function convertCodeDatasource(datasource, base) {
|
|
617
551
|
const desc = datasource.description || "";
|
|
552
|
+
const indexType = normalizeCodeIndexType(datasource);
|
|
618
553
|
if (datasource.code) {
|
|
619
554
|
return {
|
|
620
555
|
...base,
|
|
@@ -622,7 +557,7 @@ function convertCodeDatasource(datasource, base) {
|
|
|
622
557
|
description: desc,
|
|
623
558
|
link: datasource.code.link,
|
|
624
559
|
branch: datasource.code.branch,
|
|
625
|
-
index_type:
|
|
560
|
+
...indexType && { index_type: indexType },
|
|
626
561
|
summarization_model: datasource.code.summarizationModel,
|
|
627
562
|
files_filter: datasource.code.filesFilter
|
|
628
563
|
};
|
|
@@ -631,12 +566,17 @@ function convertCodeDatasource(datasource, base) {
|
|
|
631
566
|
...base,
|
|
632
567
|
type: "code",
|
|
633
568
|
description: desc,
|
|
569
|
+
...indexType && { index_type: indexType },
|
|
634
570
|
link: void 0
|
|
635
571
|
};
|
|
636
572
|
}
|
|
637
573
|
function datasourceResponseToResource(datasource, integrationAlias) {
|
|
574
|
+
const isBedrockDatasource = datasource.type === DataSourceType.BEDROCK;
|
|
575
|
+
const isChunkSummaryDatasource = isChunkSummaryType(datasource.type);
|
|
576
|
+
const isFileSummaryDatasource = isFileSummaryType(datasource.type);
|
|
577
|
+
const isCodeFamilyDatasource = datasource.type === DataSourceType.CODE || datasource.type === DataSourceType.SUMMARY || datasource.type === DataSourceType.CHUNK_SUMMARY || isChunkSummaryDatasource || isFileSummaryDatasource;
|
|
638
578
|
const settingId = integrationAlias ? `$ref:imported.integrations.${integrationAlias}.id` : datasource.setting_id ?? "";
|
|
639
|
-
const allowedWithoutSettingId = /* @__PURE__ */ new Set([
|
|
579
|
+
const allowedWithoutSettingId = /* @__PURE__ */ new Set([DataSourceType.FILE, DataSourceType.GOOGLE, DataSourceType.BEDROCK]);
|
|
640
580
|
if (!settingId && !allowedWithoutSettingId.has(datasource.type)) {
|
|
641
581
|
logger.warn(`\u26A0\uFE0F Datasource "${datasource.name}" is missing setting_id (integration reference)`);
|
|
642
582
|
}
|
|
@@ -647,7 +587,7 @@ function datasourceResponseToResource(datasource, integrationAlias) {
|
|
|
647
587
|
setting_id: settingId || void 0,
|
|
648
588
|
shared_with_project: datasource.shared_with_project
|
|
649
589
|
};
|
|
650
|
-
if (
|
|
590
|
+
if (isCodeFamilyDatasource) {
|
|
651
591
|
return convertCodeDatasource(datasource, base);
|
|
652
592
|
}
|
|
653
593
|
if (datasource.type === DataSourceType.CONFLUENCE && datasource.confluence) {
|
|
@@ -681,6 +621,13 @@ function datasourceResponseToResource(datasource, integrationAlias) {
|
|
|
681
621
|
description: datasource.description || ""
|
|
682
622
|
};
|
|
683
623
|
}
|
|
624
|
+
if (isBedrockDatasource) {
|
|
625
|
+
return {
|
|
626
|
+
...base,
|
|
627
|
+
type: datasource.type,
|
|
628
|
+
description: datasource.description || ""
|
|
629
|
+
};
|
|
630
|
+
}
|
|
684
631
|
logger.warn(` \u26A0\uFE0F Unknown datasource type '${datasource.type}' - saving with basic fields only`);
|
|
685
632
|
return {
|
|
686
633
|
...base,
|
|
@@ -688,15 +635,26 @@ function datasourceResponseToResource(datasource, integrationAlias) {
|
|
|
688
635
|
};
|
|
689
636
|
}
|
|
690
637
|
function workflowResponseToResource(workflow) {
|
|
638
|
+
const safeName = workflow.name || workflow.id || "workflow";
|
|
691
639
|
return {
|
|
692
|
-
name: workflow.name,
|
|
640
|
+
name: workflow.name || safeName,
|
|
693
641
|
description: workflow.description || "",
|
|
694
|
-
definition: `workflows/${
|
|
642
|
+
definition: `workflows/${String(safeName).toLowerCase().replaceAll(/\s+/g, "-")}.yaml`,
|
|
695
643
|
...workflow.mode && { mode: workflow.mode },
|
|
696
644
|
...workflow.shared !== void 0 && { shared: workflow.shared },
|
|
697
645
|
...workflow.icon_url && { icon_url: workflow.icon_url }
|
|
698
646
|
};
|
|
699
647
|
}
|
|
648
|
+
function skillResponseToResource(skill) {
|
|
649
|
+
const fileName = skill.name.toLowerCase().replaceAll(/\s+/g, "-");
|
|
650
|
+
return {
|
|
651
|
+
name: skill.name,
|
|
652
|
+
description: skill.description || "",
|
|
653
|
+
file: `skills/${fileName}.skill.md`,
|
|
654
|
+
...skill.categories && skill.categories.length > 0 && { categories: skill.categories },
|
|
655
|
+
...skill.visibility && skill.visibility !== "project" && { visibility: skill.visibility }
|
|
656
|
+
};
|
|
657
|
+
}
|
|
700
658
|
function isValidCodeParams(params) {
|
|
701
659
|
return typeof params === "object" && params !== null && "link" in params && typeof params.link === "string" && params.link.length > 0;
|
|
702
660
|
}
|
|
@@ -761,6 +719,7 @@ function assistantResourceToCreateParams(assistant, projectName, promptContent)
|
|
|
761
719
|
model,
|
|
762
720
|
sub_assistants: _subAssistants,
|
|
763
721
|
datasource_names: _datasourceNames,
|
|
722
|
+
skills: _skills,
|
|
764
723
|
toolkits,
|
|
765
724
|
mcp_servers: mcpServers,
|
|
766
725
|
...sdkFields
|
|
@@ -781,8 +740,28 @@ function assistantResourceToCreateParams(assistant, projectName, promptContent)
|
|
|
781
740
|
prompt_variables: assistant.prompt_variables || []
|
|
782
741
|
};
|
|
783
742
|
}
|
|
743
|
+
var CHUNK_SUMMARY_ALIASES, FILE_SUMMARY_ALIASES;
|
|
744
|
+
var init_converters = __esm({
|
|
745
|
+
"src/lib/converters.ts"() {
|
|
746
|
+
init_checksumUtils();
|
|
747
|
+
init_logger();
|
|
748
|
+
init_typeGuards();
|
|
749
|
+
CHUNK_SUMMARY_ALIASES = /* @__PURE__ */ new Set(["chunk_summary", "chunk-summary"]);
|
|
750
|
+
FILE_SUMMARY_ALIASES = /* @__PURE__ */ new Set(["file_summary", "file-summary", "file-summay"]);
|
|
751
|
+
}
|
|
752
|
+
});
|
|
784
753
|
|
|
785
754
|
// src/lib/backupTransformers.ts
|
|
755
|
+
var backupTransformers_exports = {};
|
|
756
|
+
__export(backupTransformers_exports, {
|
|
757
|
+
prepareAssistantForYaml: () => prepareAssistantForYaml,
|
|
758
|
+
prepareDatasourceForYaml: () => prepareDatasourceForYaml,
|
|
759
|
+
prepareSkillForYaml: () => prepareSkillForYaml,
|
|
760
|
+
prepareWorkflowForYaml: () => prepareWorkflowForYaml,
|
|
761
|
+
transformMcpServer: () => transformMcpServer,
|
|
762
|
+
transformTool: () => transformTool,
|
|
763
|
+
transformToolkits: () => transformToolkits
|
|
764
|
+
});
|
|
786
765
|
function transformIntegrationSettings(settings, integrationIdToAlias, integrationSpecPaths, contextLabel = "integration") {
|
|
787
766
|
const integrationId = settings.id;
|
|
788
767
|
const alias = settings.alias || integrationId && integrationIdToAlias.get(integrationId);
|
|
@@ -849,94 +828,439 @@ function transformMcpServer(mcp, integrationIdToAlias) {
|
|
|
849
828
|
);
|
|
850
829
|
}
|
|
851
830
|
}
|
|
852
|
-
return result;
|
|
853
|
-
}
|
|
854
|
-
function transformToolkits(toolkits, integrationIdToAlias, integrationSpecPaths) {
|
|
855
|
-
return toolkits?.map(({ toolkit, tools, label, settings_config, is_external, settings }) => ({
|
|
856
|
-
toolkit,
|
|
857
|
-
tools: tools.map((tool) => transformTool(tool, integrationIdToAlias, integrationSpecPaths)),
|
|
858
|
-
label,
|
|
859
|
-
settings_config,
|
|
860
|
-
is_external,
|
|
861
|
-
...settings ? {
|
|
862
|
-
settings: transformIntegrationSettings(settings, integrationIdToAlias, integrationSpecPaths, "toolkit")
|
|
863
|
-
} : {}
|
|
864
|
-
}));
|
|
865
|
-
}
|
|
866
|
-
function prepareAssistantForYaml(assistant, state, integrationIdToAlias, integrationSpecPaths) {
|
|
867
|
-
const transformedToolkits = transformToolkits(assistant.toolkits, integrationIdToAlias, integrationSpecPaths);
|
|
868
|
-
const transformedMcpServers = assistant.mcp_servers?.map((mcp) => transformMcpServer(mcp, integrationIdToAlias));
|
|
869
|
-
const
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
...
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
831
|
+
return result;
|
|
832
|
+
}
|
|
833
|
+
function transformToolkits(toolkits, integrationIdToAlias, integrationSpecPaths) {
|
|
834
|
+
return toolkits?.map(({ toolkit, tools, label, settings_config, is_external, settings }) => ({
|
|
835
|
+
toolkit,
|
|
836
|
+
tools: tools.map((tool) => transformTool(tool, integrationIdToAlias, integrationSpecPaths)),
|
|
837
|
+
label,
|
|
838
|
+
settings_config,
|
|
839
|
+
is_external,
|
|
840
|
+
...settings ? {
|
|
841
|
+
settings: transformIntegrationSettings(settings, integrationIdToAlias, integrationSpecPaths, "toolkit")
|
|
842
|
+
} : {}
|
|
843
|
+
}));
|
|
844
|
+
}
|
|
845
|
+
function prepareAssistantForYaml(assistant, state, integrationIdToAlias, integrationSpecPaths, skillIdToName = /* @__PURE__ */ new Map()) {
|
|
846
|
+
const transformedToolkits = transformToolkits(assistant.toolkits, integrationIdToAlias, integrationSpecPaths);
|
|
847
|
+
const transformedMcpServers = assistant.mcp_servers?.map((mcp) => transformMcpServer(mcp, integrationIdToAlias));
|
|
848
|
+
const assistantData = assistant;
|
|
849
|
+
const resolvedSkillNames = assistantData.skill_ids && assistantData.skill_ids.length > 0 ? assistantData.skill_ids.map((id) => skillIdToName.get(id)).filter((name) => Boolean(name)) : void 0;
|
|
850
|
+
const finalAssistant = {
|
|
851
|
+
...assistantResponseToResource(assistant),
|
|
852
|
+
...transformedToolkits && { toolkits: transformedToolkits },
|
|
853
|
+
...transformedMcpServers && { mcp_servers: transformedMcpServers },
|
|
854
|
+
...resolvedSkillNames && resolvedSkillNames.length > 0 && { skills: resolvedSkillNames }
|
|
855
|
+
};
|
|
856
|
+
state.resources.assistants[assistant.name] = {
|
|
857
|
+
id: assistant.id,
|
|
858
|
+
lastDeployed: (/* @__PURE__ */ new Date()).toISOString(),
|
|
859
|
+
promptChecksum: calculateChecksum(assistant.system_prompt || ""),
|
|
860
|
+
configChecksum: calculateAssistantConfigChecksum(finalAssistant)
|
|
861
|
+
};
|
|
862
|
+
return finalAssistant;
|
|
863
|
+
}
|
|
864
|
+
function prepareDatasourceForYaml(datasource, state, integrationIdToAlias) {
|
|
865
|
+
const settingId = hasSettingId(datasource) ? String(datasource.setting_id || "") : "";
|
|
866
|
+
const integrationAlias = integrationIdToAlias.get(settingId);
|
|
867
|
+
const finalDatasource = datasourceResponseToResource(datasource, integrationAlias);
|
|
868
|
+
state.resources.datasources[datasource.name] = {
|
|
869
|
+
id: datasource.id,
|
|
870
|
+
lastDeployed: (/* @__PURE__ */ new Date()).toISOString(),
|
|
871
|
+
configChecksum: calculateDatasourceConfigChecksum(finalDatasource)
|
|
872
|
+
};
|
|
873
|
+
return finalDatasource;
|
|
874
|
+
}
|
|
875
|
+
function prepareWorkflowForYaml(workflow, state, assistants, backupDir) {
|
|
876
|
+
const resource = workflowResponseToResource(workflow);
|
|
877
|
+
const yamlConfig = workflow.yaml_config;
|
|
878
|
+
let finalYamlContent = yamlConfig || "";
|
|
879
|
+
if (yamlConfig) {
|
|
880
|
+
try {
|
|
881
|
+
const workflowYaml = yaml5.parse(yamlConfig);
|
|
882
|
+
const transformedAssistants = workflowYaml.assistants?.map((assistant) => {
|
|
883
|
+
if (assistant.assistant_id && typeof assistant.assistant_id === "string") {
|
|
884
|
+
const assistantId = assistant.assistant_id;
|
|
885
|
+
const matchedAssistant = assistants.find(({ id }) => id === assistantId);
|
|
886
|
+
if (matchedAssistant && hasName(matchedAssistant)) {
|
|
887
|
+
const { assistant_id: _assistantId, ...rest } = assistant;
|
|
888
|
+
return { ...rest, assistant_name: matchedAssistant.name };
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
return assistant;
|
|
892
|
+
});
|
|
893
|
+
if (transformedAssistants) {
|
|
894
|
+
const transformedYaml = { ...workflowYaml, assistants: transformedAssistants };
|
|
895
|
+
const fileName = `${sanitizeFileName(workflow.name)}.yaml`;
|
|
896
|
+
const filePath = path6.join(backupDir, "workflows", fileName);
|
|
897
|
+
ensureDirectoryExists(filePath);
|
|
898
|
+
finalYamlContent = yaml5.stringify(transformedYaml);
|
|
899
|
+
fs6.writeFileSync(filePath, finalYamlContent, "utf8");
|
|
900
|
+
}
|
|
901
|
+
} catch (error) {
|
|
902
|
+
logger.warn(
|
|
903
|
+
` \u26A0\uFE0F Failed to transform workflow YAML for ${workflow.name}: ${error instanceof Error ? error.message : String(error)}`
|
|
904
|
+
);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
state.resources.workflows[workflow.name] = {
|
|
908
|
+
id: workflow.id,
|
|
909
|
+
lastDeployed: (/* @__PURE__ */ new Date()).toISOString(),
|
|
910
|
+
workflowYamlChecksum: calculateChecksum(finalYamlContent),
|
|
911
|
+
configChecksum: calculateWorkflowConfigChecksum(resource)
|
|
912
|
+
};
|
|
913
|
+
return resource;
|
|
914
|
+
}
|
|
915
|
+
function prepareSkillForYaml(skill, state) {
|
|
916
|
+
const resource = skillResponseToResource(skill);
|
|
917
|
+
if (!state.resources.skills) {
|
|
918
|
+
state.resources.skills = {};
|
|
919
|
+
}
|
|
920
|
+
state.resources.skills[skill.name] = {
|
|
921
|
+
id: skill.id,
|
|
922
|
+
lastDeployed: (/* @__PURE__ */ new Date()).toISOString(),
|
|
923
|
+
contentChecksum: calculateChecksum(skill.content || ""),
|
|
924
|
+
configChecksum: calculateSkillConfigChecksum(resource)
|
|
925
|
+
};
|
|
926
|
+
return resource;
|
|
927
|
+
}
|
|
928
|
+
var init_backupTransformers = __esm({
|
|
929
|
+
"src/lib/backupTransformers.ts"() {
|
|
930
|
+
init_checksumUtils();
|
|
931
|
+
init_codemieConfigChecksums();
|
|
932
|
+
init_converters();
|
|
933
|
+
init_fileUtils();
|
|
934
|
+
init_logger();
|
|
935
|
+
init_typeGuards();
|
|
936
|
+
}
|
|
937
|
+
});
|
|
938
|
+
|
|
939
|
+
// package.json
|
|
940
|
+
var package_default = {
|
|
941
|
+
version: "0.1.445"};
|
|
942
|
+
var appConfigSchema = z.object({
|
|
943
|
+
rootDir: z.string(),
|
|
944
|
+
codemieConfig: z.string(),
|
|
945
|
+
codemieState: z.string(),
|
|
946
|
+
backupsDirectory: z.string()
|
|
947
|
+
});
|
|
948
|
+
var externalConfigSchema = appConfigSchema.partial();
|
|
949
|
+
|
|
950
|
+
// src/appConfig/defaultConfig.ts
|
|
951
|
+
var defaultConfig = {
|
|
952
|
+
rootDir: "./",
|
|
953
|
+
codemieConfig: "codemie.yaml",
|
|
954
|
+
codemieState: ".codemie/state.json",
|
|
955
|
+
backupsDirectory: "backups"
|
|
956
|
+
};
|
|
957
|
+
|
|
958
|
+
// src/appConfig/configLoader.ts
|
|
959
|
+
function loadAppConfig(userConfigPath) {
|
|
960
|
+
if (!userConfigPath) {
|
|
961
|
+
return defaultConfig;
|
|
962
|
+
}
|
|
963
|
+
const userConfig = loadUserConfig(userConfigPath);
|
|
964
|
+
const sanitizedUserConfig = removeEmptyFields(userConfig);
|
|
965
|
+
const merged = { ...defaultConfig, ...sanitizedUserConfig };
|
|
966
|
+
return appConfigSchema.parse(merged);
|
|
967
|
+
}
|
|
968
|
+
function loadUserConfig(configPath) {
|
|
969
|
+
const resolvedPath = path6.resolve(process.cwd(), configPath);
|
|
970
|
+
if (!fs6.existsSync(resolvedPath)) {
|
|
971
|
+
throw new Error(`Config file not found: ${resolvedPath}`);
|
|
972
|
+
}
|
|
973
|
+
let raw;
|
|
974
|
+
try {
|
|
975
|
+
raw = fs6.readFileSync(resolvedPath, "utf8");
|
|
976
|
+
} catch (error) {
|
|
977
|
+
throw new Error(`Failed to read config file: ${resolvedPath}`, { cause: error });
|
|
978
|
+
}
|
|
979
|
+
let json;
|
|
980
|
+
try {
|
|
981
|
+
json = JSON.parse(raw);
|
|
982
|
+
} catch (error) {
|
|
983
|
+
throw new Error(`Malformed JSON in config file: ${resolvedPath}`, { cause: error });
|
|
984
|
+
}
|
|
985
|
+
try {
|
|
986
|
+
return externalConfigSchema.parse(json);
|
|
987
|
+
} catch (error) {
|
|
988
|
+
throw new Error(`Invalid config structure: ${error instanceof Error ? error.message : String(error)}`);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
function removeEmptyFields(userConfig) {
|
|
992
|
+
return Object.fromEntries(
|
|
993
|
+
Object.entries(userConfig).filter(([_, value]) => {
|
|
994
|
+
if (value === null || value === void 0) {
|
|
995
|
+
return false;
|
|
996
|
+
}
|
|
997
|
+
return value.trim() !== "";
|
|
998
|
+
})
|
|
999
|
+
);
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// src/lib/constants.ts
|
|
1003
|
+
var PAGINATION = {
|
|
1004
|
+
DEFAULT_PAGE_SIZE: 100};
|
|
1005
|
+
var TIMEOUTS_MS = {
|
|
1006
|
+
ASSISTANT_FETCH: 3e4,
|
|
1007
|
+
DATASOURCE_FETCH: 3e4,
|
|
1008
|
+
WORKFLOW_FETCH: 3e4,
|
|
1009
|
+
INTEGRATION_FETCH: 3e4,
|
|
1010
|
+
SKILL_FETCH: 3e4
|
|
1011
|
+
};
|
|
1012
|
+
var RATE_LIMITING = {
|
|
1013
|
+
MAX_CONCURRENT_REQUESTS: 5,
|
|
1014
|
+
RETRY_ATTEMPTS: 3,
|
|
1015
|
+
RETRY_DELAY_MS: 1e3
|
|
1016
|
+
};
|
|
1017
|
+
var BACKUP = {
|
|
1018
|
+
TEMP_DIR_PREFIX: ".temp-",
|
|
1019
|
+
TRANSACTION_SAVE_TIMEOUT_MS: 5e3
|
|
1020
|
+
};
|
|
1021
|
+
|
|
1022
|
+
// src/lib/backupTransaction.ts
|
|
1023
|
+
init_fileUtils();
|
|
1024
|
+
init_logger();
|
|
1025
|
+
function busyWaitDelay(ms) {
|
|
1026
|
+
}
|
|
1027
|
+
var BackupTransaction = class {
|
|
1028
|
+
data;
|
|
1029
|
+
transactionPath;
|
|
1030
|
+
isDirty = false;
|
|
1031
|
+
saveTimer;
|
|
1032
|
+
constructor(backupDir, transactionId) {
|
|
1033
|
+
this.transactionPath = path6.join(backupDir, "transaction.json");
|
|
1034
|
+
try {
|
|
1035
|
+
const content = fs6.readFileSync(this.transactionPath, "utf8");
|
|
1036
|
+
this.data = JSON.parse(content);
|
|
1037
|
+
logger.info(`\u{1F4C2} Resuming backup transaction ${this.data.id}`);
|
|
1038
|
+
} catch (error) {
|
|
1039
|
+
const err = error;
|
|
1040
|
+
if (err.code === "ENOENT") {
|
|
1041
|
+
this.data = this.createNewTransaction(backupDir, transactionId);
|
|
1042
|
+
} else {
|
|
1043
|
+
throw error;
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
createNewTransaction(backupDir, transactionId) {
|
|
1048
|
+
const newData = {
|
|
1049
|
+
id: transactionId || this.generateTransactionId(),
|
|
1050
|
+
status: "in-progress",
|
|
1051
|
+
startTime: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1052
|
+
backupDir,
|
|
1053
|
+
resources: {
|
|
1054
|
+
assistants: { total: 0, completed: [], failed: [] },
|
|
1055
|
+
datasources: { total: 0, completed: [], failed: [] },
|
|
1056
|
+
workflows: { total: 0, completed: [], failed: [] },
|
|
1057
|
+
integrations: { total: 0, completed: [], failed: [] },
|
|
1058
|
+
skills: { total: 0, completed: [], failed: [] }
|
|
1059
|
+
}
|
|
1060
|
+
};
|
|
1061
|
+
ensureDirectoryExists(this.transactionPath);
|
|
1062
|
+
this.writeTransactionExclusively(newData);
|
|
1063
|
+
return newData;
|
|
1064
|
+
}
|
|
1065
|
+
writeTransactionExclusively(data) {
|
|
1066
|
+
try {
|
|
1067
|
+
const fd = fs6.openSync(this.transactionPath, "wx");
|
|
1068
|
+
fs6.writeSync(fd, JSON.stringify(data, null, 2));
|
|
1069
|
+
fs6.closeSync(fd);
|
|
1070
|
+
} catch (writeError) {
|
|
1071
|
+
const writeErr = writeError;
|
|
1072
|
+
if (writeErr.code === "EEXIST") {
|
|
1073
|
+
this.readWithRetry();
|
|
1074
|
+
} else {
|
|
1075
|
+
throw writeError;
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
readWithRetry() {
|
|
1080
|
+
let retries = 3;
|
|
1081
|
+
let lastReadError;
|
|
1082
|
+
while (retries > 0) {
|
|
1083
|
+
try {
|
|
1084
|
+
const delayMs = 100 * (4 - retries);
|
|
1085
|
+
if (delayMs > 0) {
|
|
1086
|
+
busyWaitDelay(delayMs);
|
|
1087
|
+
}
|
|
1088
|
+
const content = fs6.readFileSync(this.transactionPath, "utf8");
|
|
1089
|
+
this.data = JSON.parse(content);
|
|
1090
|
+
logger.info(`\u{1F4C2} Resuming backup transaction ${this.data.id} (created by concurrent process)`);
|
|
1091
|
+
break;
|
|
1092
|
+
} catch (readError) {
|
|
1093
|
+
lastReadError = readError instanceof Error ? readError : new Error(String(readError));
|
|
1094
|
+
retries--;
|
|
1095
|
+
if (retries === 0) {
|
|
1096
|
+
throw new Error(
|
|
1097
|
+
`Failed to read transaction file after retries: ${lastReadError.message}. File may be corrupted. Delete ${this.transactionPath} and retry.`
|
|
1098
|
+
);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
generateTransactionId() {
|
|
1104
|
+
return crypto2.randomBytes(8).toString("hex");
|
|
1105
|
+
}
|
|
1106
|
+
/**
|
|
1107
|
+
* Save transaction state to disk (checkpoint) with timeout protection
|
|
1108
|
+
*/
|
|
1109
|
+
async save() {
|
|
1110
|
+
ensureDirectoryExists(this.transactionPath);
|
|
1111
|
+
const savePromise = fs6.promises.writeFile(this.transactionPath, JSON.stringify(this.data, null, 2), "utf8");
|
|
1112
|
+
let timeoutId;
|
|
1113
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
1114
|
+
timeoutId = setTimeout(
|
|
1115
|
+
() => reject(new Error(`Transaction save timeout after ${BACKUP.TRANSACTION_SAVE_TIMEOUT_MS}ms`)),
|
|
1116
|
+
BACKUP.TRANSACTION_SAVE_TIMEOUT_MS
|
|
1117
|
+
);
|
|
1118
|
+
timeoutId.unref();
|
|
1119
|
+
});
|
|
1120
|
+
try {
|
|
1121
|
+
await Promise.race([savePromise, timeoutPromise]);
|
|
1122
|
+
this.isDirty = false;
|
|
1123
|
+
} catch (error) {
|
|
1124
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
1125
|
+
throw new Error(`Failed to save transaction file: ${err.message}`);
|
|
1126
|
+
} finally {
|
|
1127
|
+
if (timeoutId !== void 0) {
|
|
1128
|
+
clearTimeout(timeoutId);
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
/**
|
|
1133
|
+
* Internal synchronous save for batching
|
|
1134
|
+
*/
|
|
1135
|
+
saveSyncInternal() {
|
|
1136
|
+
ensureDirectoryExists(this.transactionPath);
|
|
1137
|
+
fs6.writeFileSync(this.transactionPath, JSON.stringify(this.data, null, 2), "utf8");
|
|
1138
|
+
this.isDirty = false;
|
|
1139
|
+
}
|
|
1140
|
+
/**
|
|
1141
|
+
* Schedule batched save (debounced to avoid excessive disk writes)
|
|
1142
|
+
*/
|
|
1143
|
+
scheduleSave() {
|
|
1144
|
+
if (this.saveTimer) {
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
this.saveTimer = setTimeout(() => {
|
|
1148
|
+
if (this.isDirty) {
|
|
1149
|
+
this.saveSyncInternal();
|
|
1150
|
+
}
|
|
1151
|
+
this.saveTimer = void 0;
|
|
1152
|
+
}, 1e3);
|
|
1153
|
+
this.saveTimer.unref();
|
|
1154
|
+
}
|
|
1155
|
+
/**
|
|
1156
|
+
* Internal method to finalize transaction with a specific status
|
|
1157
|
+
*/
|
|
1158
|
+
end(status) {
|
|
1159
|
+
this.data.status = status;
|
|
1160
|
+
this.data.endTime = (/* @__PURE__ */ new Date()).toISOString();
|
|
1161
|
+
this.flush();
|
|
1162
|
+
}
|
|
1163
|
+
/**
|
|
1164
|
+
* Force immediate save (flush pending changes)
|
|
1165
|
+
*/
|
|
1166
|
+
flush() {
|
|
1167
|
+
if (this.saveTimer) {
|
|
1168
|
+
clearTimeout(this.saveTimer);
|
|
1169
|
+
this.saveTimer = void 0;
|
|
1170
|
+
}
|
|
1171
|
+
if (this.isDirty) {
|
|
1172
|
+
this.saveSyncInternal();
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
/**
|
|
1176
|
+
* Set total count for a resource type
|
|
1177
|
+
*/
|
|
1178
|
+
setTotal(resourceType, total) {
|
|
1179
|
+
this.data.resources[resourceType].total = total;
|
|
1180
|
+
this.isDirty = true;
|
|
1181
|
+
this.scheduleSave();
|
|
1182
|
+
}
|
|
1183
|
+
/**
|
|
1184
|
+
* Mark resource as completed
|
|
1185
|
+
*/
|
|
1186
|
+
markCompleted(resourceType, resourceId) {
|
|
1187
|
+
if (!this.data.resources[resourceType].completed.includes(resourceId)) {
|
|
1188
|
+
this.data.resources[resourceType].completed.push(resourceId);
|
|
1189
|
+
this.isDirty = true;
|
|
1190
|
+
this.scheduleSave();
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
/**
|
|
1194
|
+
* Mark resource as failed
|
|
1195
|
+
*/
|
|
1196
|
+
markFailed(resourceType, resourceId, error) {
|
|
1197
|
+
const failedEntry = { id: resourceId, error };
|
|
1198
|
+
const failedList = this.data.resources[resourceType].failed;
|
|
1199
|
+
if (!failedList.some((f) => f.id === resourceId)) {
|
|
1200
|
+
failedList.push(failedEntry);
|
|
1201
|
+
this.isDirty = true;
|
|
1202
|
+
this.scheduleSave();
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
/**
|
|
1206
|
+
* Check if resource was already backed up
|
|
1207
|
+
*/
|
|
1208
|
+
isCompleted(resourceType, resourceId) {
|
|
1209
|
+
return this.data.resources[resourceType].completed.includes(resourceId);
|
|
1210
|
+
}
|
|
1211
|
+
/**
|
|
1212
|
+
* Mark transaction as completed
|
|
1213
|
+
*/
|
|
1214
|
+
complete() {
|
|
1215
|
+
this.end("completed");
|
|
1216
|
+
}
|
|
1217
|
+
/**
|
|
1218
|
+
* Mark transaction as failed
|
|
1219
|
+
*/
|
|
1220
|
+
fail() {
|
|
1221
|
+
this.end("failed");
|
|
1222
|
+
}
|
|
1223
|
+
/**
|
|
1224
|
+
* Get transaction data (returns deep copy to prevent external modifications)
|
|
1225
|
+
*/
|
|
1226
|
+
getData() {
|
|
1227
|
+
return structuredClone(this.data);
|
|
1228
|
+
}
|
|
1229
|
+
/**
|
|
1230
|
+
* Get summary of backup progress
|
|
1231
|
+
*/
|
|
1232
|
+
getSummary() {
|
|
1233
|
+
const { resources } = this.data;
|
|
1234
|
+
const lines = [];
|
|
1235
|
+
for (const [type, data] of Object.entries(resources)) {
|
|
1236
|
+
const completed = data.completed.length;
|
|
1237
|
+
const failed = data.failed.length;
|
|
1238
|
+
const total = data.total;
|
|
1239
|
+
const percent = total > 0 ? Math.round(completed / total * 100) : 0;
|
|
1240
|
+
lines.push(` ${type}: ${completed}/${total} (${percent}%) ${failed > 0 ? `[${failed} failed]` : ""}`);
|
|
1241
|
+
}
|
|
1242
|
+
return lines.join("\n");
|
|
1243
|
+
}
|
|
1244
|
+
/**
|
|
1245
|
+
* Clean up transaction file after successful completion
|
|
1246
|
+
*/
|
|
1247
|
+
cleanup() {
|
|
1248
|
+
if (fs6.existsSync(this.transactionPath)) {
|
|
1249
|
+
fs6.unlinkSync(this.transactionPath);
|
|
923
1250
|
}
|
|
924
1251
|
}
|
|
925
|
-
|
|
926
|
-
id: workflow.id,
|
|
927
|
-
lastDeployed: (/* @__PURE__ */ new Date()).toISOString(),
|
|
928
|
-
workflowYamlChecksum: calculateChecksum(finalYamlContent),
|
|
929
|
-
configChecksum: calculateWorkflowConfigChecksum(resource)
|
|
930
|
-
};
|
|
931
|
-
return resource;
|
|
932
|
-
}
|
|
1252
|
+
};
|
|
933
1253
|
|
|
934
1254
|
// src/lib/backupYamlGenerator.ts
|
|
1255
|
+
init_backupTransformers();
|
|
1256
|
+
init_logger();
|
|
935
1257
|
function generateCodemieYaml(backup, projectName, backupDir, integrationSpecPaths = /* @__PURE__ */ new Map()) {
|
|
936
1258
|
const integrationIdToAlias = /* @__PURE__ */ new Map();
|
|
937
1259
|
const integrationArray = [];
|
|
1260
|
+
const skillIdToName = new Map(backup.resources.skills.map((skill) => [skill.id, skill.name]));
|
|
938
1261
|
for (const integration of backup.resources.integrations) {
|
|
939
|
-
const
|
|
1262
|
+
const credentialType = integration.credential_type || "integration";
|
|
1263
|
+
const alias = integration.alias || `${String(credentialType).toLowerCase()}_${integration.id.slice(0, 8)}`;
|
|
940
1264
|
integrationIdToAlias.set(integration.id, alias);
|
|
941
1265
|
const specPath = integrationSpecPaths.get(integration.id);
|
|
942
1266
|
let credentialValues = integration.credential_values;
|
|
@@ -992,17 +1316,20 @@ function generateCodemieYaml(backup, projectName, backupDir, integrationSpecPath
|
|
|
992
1316
|
},
|
|
993
1317
|
resources: {
|
|
994
1318
|
assistants: backup.resources.assistants.map(
|
|
995
|
-
(assistant) => prepareAssistantForYaml(assistant, backup.state, integrationIdToAlias, integrationSpecPaths)
|
|
1319
|
+
(assistant) => prepareAssistantForYaml(assistant, backup.state, integrationIdToAlias, integrationSpecPaths, skillIdToName)
|
|
996
1320
|
),
|
|
997
1321
|
datasources: backup.resources.datasources.map(
|
|
998
1322
|
(datasource) => prepareDatasourceForYaml(datasource, backup.state, integrationIdToAlias)
|
|
999
1323
|
),
|
|
1000
1324
|
workflows: backup.resources.workflows.map(
|
|
1001
1325
|
(workflow) => prepareWorkflowForYaml(workflow, backup.state, backup.resources.assistants, backupDir)
|
|
1002
|
-
)
|
|
1326
|
+
),
|
|
1327
|
+
...backup.resources.skills.length > 0 && {
|
|
1328
|
+
skills: backup.resources.skills.map((skill) => prepareSkillForYaml(skill, backup.state))
|
|
1329
|
+
}
|
|
1003
1330
|
}
|
|
1004
1331
|
};
|
|
1005
|
-
return
|
|
1332
|
+
return yaml5.stringify(config);
|
|
1006
1333
|
}
|
|
1007
1334
|
function saveIntegrationOpenApiSpecs(backupData, backupDir) {
|
|
1008
1335
|
const specsDir = path6.join(backupDir, "openapi_specs");
|
|
@@ -1018,8 +1345,8 @@ function saveIntegrationOpenApiSpecs(backupData, backupDir) {
|
|
|
1018
1345
|
if (specEntry && typeof specEntry.value === "string" && specEntry.value) {
|
|
1019
1346
|
const alias = integration.alias || integration.id;
|
|
1020
1347
|
const fileName = String(alias).toLowerCase().replaceAll(/[^a-z0-9]+/g, "-");
|
|
1021
|
-
if (!
|
|
1022
|
-
|
|
1348
|
+
if (!fs6.existsSync(specsDir)) {
|
|
1349
|
+
fs6.mkdirSync(specsDir, { recursive: true });
|
|
1023
1350
|
}
|
|
1024
1351
|
const specContent = specEntry.value;
|
|
1025
1352
|
let fileExtension = ".yaml";
|
|
@@ -1033,7 +1360,7 @@ function saveIntegrationOpenApiSpecs(backupData, backupDir) {
|
|
|
1033
1360
|
}
|
|
1034
1361
|
const specFileName = `${fileName}${fileExtension}`;
|
|
1035
1362
|
const specPath = path6.join(specsDir, specFileName);
|
|
1036
|
-
|
|
1363
|
+
fs6.writeFileSync(specPath, contentToSave, "utf8");
|
|
1037
1364
|
integrationSpecPaths.set(integration.id, `openapi_specs/${specFileName}`);
|
|
1038
1365
|
specsCount++;
|
|
1039
1366
|
}
|
|
@@ -1048,13 +1375,13 @@ function saveBackupFiles(backupData, backupDir, projectName) {
|
|
|
1048
1375
|
const integrationSpecPaths = saveIntegrationOpenApiSpecs(backupData, backupDir);
|
|
1049
1376
|
const codemieYamlPath = path6.join(backupDir, "codemie.yaml");
|
|
1050
1377
|
const codemieYaml = generateCodemieYaml(backupData, projectName, backupDir, integrationSpecPaths);
|
|
1051
|
-
|
|
1378
|
+
fs6.writeFileSync(codemieYamlPath, codemieYaml, "utf8");
|
|
1052
1379
|
logger.info(` \u2713 Saved config file: codemie.yaml`);
|
|
1053
1380
|
const backupJsonPath = path6.join(backupDir, "backup.json");
|
|
1054
|
-
|
|
1381
|
+
fs6.writeFileSync(backupJsonPath, JSON.stringify(backupData, null, 2), "utf8");
|
|
1055
1382
|
logger.info(` \u2713 Saved full backup: backup.json`);
|
|
1056
1383
|
const statePath = path6.join(backupDir, "state.json");
|
|
1057
|
-
|
|
1384
|
+
fs6.writeFileSync(statePath, JSON.stringify(backupData.state, null, 2), "utf8");
|
|
1058
1385
|
logger.info(` \u2713 Saved state file: state.json`);
|
|
1059
1386
|
}
|
|
1060
1387
|
async function createClient(config) {
|
|
@@ -1068,7 +1395,14 @@ async function createClient(config) {
|
|
|
1068
1395
|
password: config.environment.password,
|
|
1069
1396
|
verify_ssl: true
|
|
1070
1397
|
});
|
|
1071
|
-
|
|
1398
|
+
try {
|
|
1399
|
+
await client.initialize();
|
|
1400
|
+
} catch (err) {
|
|
1401
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
1402
|
+
throw new Error(
|
|
1403
|
+
`Failed to initialize Codemie client (auth: ${config.environment.auth_server_url}, api: ${config.environment.codemie_api_url}): ${reason}`
|
|
1404
|
+
);
|
|
1405
|
+
}
|
|
1072
1406
|
return client;
|
|
1073
1407
|
}
|
|
1074
1408
|
var CodemieConfigLoader = class {
|
|
@@ -1082,11 +1416,11 @@ var CodemieConfigLoader = class {
|
|
|
1082
1416
|
loadConfig() {
|
|
1083
1417
|
const { rootDir, codemieConfig } = this.appConfig;
|
|
1084
1418
|
const configPath = path6.join(rootDir, codemieConfig);
|
|
1085
|
-
if (!
|
|
1419
|
+
if (!fs6.existsSync(configPath)) {
|
|
1086
1420
|
throw new Error(`Configuration file not found: ${configPath}`);
|
|
1087
1421
|
}
|
|
1088
|
-
const content =
|
|
1089
|
-
const config =
|
|
1422
|
+
const content = fs6.readFileSync(configPath, "utf8");
|
|
1423
|
+
const config = yaml5.parse(content);
|
|
1090
1424
|
this.resolveImports(config, rootDir);
|
|
1091
1425
|
this.substituteEnvVars(config);
|
|
1092
1426
|
this.applyDatasourceDefaults(config);
|
|
@@ -1100,10 +1434,21 @@ var CodemieConfigLoader = class {
|
|
|
1100
1434
|
loadPrompt(promptPath) {
|
|
1101
1435
|
const { rootDir } = this.appConfig;
|
|
1102
1436
|
const fullPath = path6.join(rootDir, promptPath);
|
|
1103
|
-
if (!
|
|
1437
|
+
if (!fs6.existsSync(fullPath)) {
|
|
1104
1438
|
throw new Error(`Prompt file not found: ${fullPath}`);
|
|
1105
1439
|
}
|
|
1106
|
-
return
|
|
1440
|
+
return fs6.readFileSync(fullPath, "utf8");
|
|
1441
|
+
}
|
|
1442
|
+
/**
|
|
1443
|
+
* Load skill content file (.skill.md)
|
|
1444
|
+
*/
|
|
1445
|
+
loadSkillContent(contentPath) {
|
|
1446
|
+
const { rootDir } = this.appConfig;
|
|
1447
|
+
const fullPath = path6.join(rootDir, contentPath);
|
|
1448
|
+
if (!fs6.existsSync(fullPath)) {
|
|
1449
|
+
throw new Error(`Skill content file not found: ${fullPath}`);
|
|
1450
|
+
}
|
|
1451
|
+
return fs6.readFileSync(fullPath, "utf8");
|
|
1107
1452
|
}
|
|
1108
1453
|
/**
|
|
1109
1454
|
* Load assistant configuration file
|
|
@@ -1111,11 +1456,11 @@ var CodemieConfigLoader = class {
|
|
|
1111
1456
|
loadAssistantConfig(configPath) {
|
|
1112
1457
|
const { rootDir } = this.appConfig;
|
|
1113
1458
|
const fullPath = path6.join(rootDir, configPath);
|
|
1114
|
-
if (!
|
|
1459
|
+
if (!fs6.existsSync(fullPath)) {
|
|
1115
1460
|
throw new Error(`Config file not found: ${fullPath}`);
|
|
1116
1461
|
}
|
|
1117
|
-
const content =
|
|
1118
|
-
return
|
|
1462
|
+
const content = fs6.readFileSync(fullPath, "utf8");
|
|
1463
|
+
return yaml5.parse(content);
|
|
1119
1464
|
}
|
|
1120
1465
|
/**
|
|
1121
1466
|
* Validate that all referenced files exist
|
|
@@ -1126,12 +1471,12 @@ var CodemieConfigLoader = class {
|
|
|
1126
1471
|
if (config.resources.assistants) {
|
|
1127
1472
|
for (const assistant of config.resources.assistants) {
|
|
1128
1473
|
const promptPath = path6.join(rootDir, assistant.prompt);
|
|
1129
|
-
if (!
|
|
1474
|
+
if (!fs6.existsSync(promptPath)) {
|
|
1130
1475
|
errors.push(`Prompt file not found for ${assistant.name}: ${assistant.prompt}`);
|
|
1131
1476
|
}
|
|
1132
1477
|
if (assistant.config) {
|
|
1133
1478
|
const configPath = path6.join(rootDir, assistant.config);
|
|
1134
|
-
if (!
|
|
1479
|
+
if (!fs6.existsSync(configPath)) {
|
|
1135
1480
|
errors.push(`Config file not found for ${assistant.name}: ${assistant.config}`);
|
|
1136
1481
|
}
|
|
1137
1482
|
}
|
|
@@ -1143,6 +1488,17 @@ var CodemieConfigLoader = class {
|
|
|
1143
1488
|
}
|
|
1144
1489
|
}
|
|
1145
1490
|
}
|
|
1491
|
+
if (config.resources.skills) {
|
|
1492
|
+
for (const skill of config.resources.skills) {
|
|
1493
|
+
const contentPath = path6.join(rootDir, skill.file);
|
|
1494
|
+
if (!fs6.existsSync(contentPath)) {
|
|
1495
|
+
errors.push(`Content file not found for skill ${skill.name}: ${skill.file}`);
|
|
1496
|
+
}
|
|
1497
|
+
if (!skill.description) {
|
|
1498
|
+
errors.push(`Missing 'description' for skill: ${skill.name}`);
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1146
1502
|
return {
|
|
1147
1503
|
valid: errors.length === 0,
|
|
1148
1504
|
errors
|
|
@@ -1234,12 +1590,12 @@ Import chain: ${[...visitedFiles].join(" \u2192 ")} \u2192 ${normalizedPath}`
|
|
|
1234
1590
|
* Load YAML file with error handling
|
|
1235
1591
|
*/
|
|
1236
1592
|
loadYamlFile(filePath) {
|
|
1237
|
-
if (!
|
|
1593
|
+
if (!fs6.existsSync(filePath)) {
|
|
1238
1594
|
throw new Error(`Import file not found: ${filePath}`);
|
|
1239
1595
|
}
|
|
1240
1596
|
try {
|
|
1241
|
-
const content =
|
|
1242
|
-
const parsed =
|
|
1597
|
+
const content = fs6.readFileSync(filePath, "utf8");
|
|
1598
|
+
const parsed = yaml5.parse(content);
|
|
1243
1599
|
if (parsed === null || parsed === void 0) {
|
|
1244
1600
|
throw new TypeError(`Import file ${filePath} is empty`);
|
|
1245
1601
|
}
|
|
@@ -1259,28 +1615,28 @@ Import chain: ${[...visitedFiles].join(" \u2192 ")} \u2192 ${normalizedPath}`
|
|
|
1259
1615
|
* @param rootConfig - Root config object (constant reference for resolving paths like "imported.integrations.xxx")
|
|
1260
1616
|
* @param path - Current path in config tree (for error messages, e.g., "resources.assistants[0].toolkits")
|
|
1261
1617
|
*/
|
|
1262
|
-
resolveReferencesRecursive(current, rootConfig,
|
|
1618
|
+
resolveReferencesRecursive(current, rootConfig, path16 = "") {
|
|
1263
1619
|
if (!current || typeof current !== "object") {
|
|
1264
1620
|
return;
|
|
1265
1621
|
}
|
|
1266
1622
|
if (Array.isArray(current)) {
|
|
1267
|
-
this.resolveArrayReferences(current, rootConfig,
|
|
1623
|
+
this.resolveArrayReferences(current, rootConfig, path16);
|
|
1268
1624
|
return;
|
|
1269
1625
|
}
|
|
1270
1626
|
if ("$ref" in current && typeof current.$ref === "string") {
|
|
1271
|
-
this.resolveObjectReference(current, rootConfig,
|
|
1627
|
+
this.resolveObjectReference(current, rootConfig, path16);
|
|
1272
1628
|
return;
|
|
1273
1629
|
}
|
|
1274
|
-
this.resolveObjectProperties(current, rootConfig,
|
|
1630
|
+
this.resolveObjectProperties(current, rootConfig, path16);
|
|
1275
1631
|
}
|
|
1276
1632
|
/**
|
|
1277
1633
|
* Resolve $ref items in arrays and flatten if they point to arrays
|
|
1278
1634
|
* Example: [{ $ref: "context_definitions.repos" }] where repos is [item1, item2]
|
|
1279
1635
|
* becomes [item1, item2]
|
|
1280
1636
|
*/
|
|
1281
|
-
resolveArrayReferences(arr, rootConfig,
|
|
1637
|
+
resolveArrayReferences(arr, rootConfig, path16) {
|
|
1282
1638
|
const result = arr.flatMap((item, i) => {
|
|
1283
|
-
const contextPath = `${
|
|
1639
|
+
const contextPath = `${path16}[${i}]`;
|
|
1284
1640
|
if (!this.isRefObject(item) || item.$ref.startsWith("#")) {
|
|
1285
1641
|
this.resolveReferencesRecursive(item, rootConfig, contextPath);
|
|
1286
1642
|
return [item];
|
|
@@ -1298,9 +1654,9 @@ Import chain: ${[...visitedFiles].join(" \u2192 ")} \u2192 ${normalizedPath}`
|
|
|
1298
1654
|
* Resolve object reference: { $ref: "path" }
|
|
1299
1655
|
* Replaces object with resolved data (in-place mutation)
|
|
1300
1656
|
*/
|
|
1301
|
-
resolveObjectReference(current, rootConfig,
|
|
1657
|
+
resolveObjectReference(current, rootConfig, path16) {
|
|
1302
1658
|
const refPath = current.$ref;
|
|
1303
|
-
const contextPath =
|
|
1659
|
+
const contextPath = path16 || "root";
|
|
1304
1660
|
if (refPath.startsWith("#")) {
|
|
1305
1661
|
return;
|
|
1306
1662
|
}
|
|
@@ -1310,24 +1666,24 @@ Import chain: ${[...visitedFiles].join(" \u2192 ")} \u2192 ${normalizedPath}`
|
|
|
1310
1666
|
delete current[key];
|
|
1311
1667
|
}
|
|
1312
1668
|
Object.assign(current, filteredResolved);
|
|
1313
|
-
this.resolveReferencesRecursive(current, rootConfig,
|
|
1669
|
+
this.resolveReferencesRecursive(current, rootConfig, path16);
|
|
1314
1670
|
}
|
|
1315
1671
|
/**
|
|
1316
1672
|
* Resolve object properties recursively
|
|
1317
1673
|
* Handles both nested objects and "$ref:path" string references
|
|
1318
1674
|
*/
|
|
1319
|
-
resolveObjectProperties(current, rootConfig,
|
|
1675
|
+
resolveObjectProperties(current, rootConfig, path16) {
|
|
1320
1676
|
for (const [key, value] of Object.entries(current)) {
|
|
1321
1677
|
if (typeof value === "string" && value.startsWith("$ref:")) {
|
|
1322
1678
|
const refPath = value.slice(5);
|
|
1323
1679
|
if (refPath.startsWith("#")) {
|
|
1324
1680
|
continue;
|
|
1325
1681
|
}
|
|
1326
|
-
const contextPath =
|
|
1682
|
+
const contextPath = path16 ? `${path16}.${key}` : key;
|
|
1327
1683
|
const resolved = this.resolveReference(rootConfig, refPath, contextPath);
|
|
1328
1684
|
current[key] = resolved;
|
|
1329
1685
|
} else {
|
|
1330
|
-
this.resolveReferencesRecursive(value, rootConfig,
|
|
1686
|
+
this.resolveReferencesRecursive(value, rootConfig, path16 ? `${path16}.${key}` : key);
|
|
1331
1687
|
}
|
|
1332
1688
|
}
|
|
1333
1689
|
}
|
|
@@ -1465,6 +1821,11 @@ Import chain: ${[...visitedFiles].join(" \u2192 ")} \u2192 ${normalizedPath}`
|
|
|
1465
1821
|
}
|
|
1466
1822
|
}
|
|
1467
1823
|
};
|
|
1824
|
+
|
|
1825
|
+
// src/backup.ts
|
|
1826
|
+
init_fileUtils();
|
|
1827
|
+
init_logger();
|
|
1828
|
+
init_logger();
|
|
1468
1829
|
function createConcurrentLimiter(maxConcurrent = RATE_LIMITING.MAX_CONCURRENT_REQUESTS) {
|
|
1469
1830
|
return pLimit(maxConcurrent);
|
|
1470
1831
|
}
|
|
@@ -1484,8 +1845,7 @@ async function withRetry(fn, operation, maxAttempts = RATE_LIMITING.RETRY_ATTEMP
|
|
|
1484
1845
|
const delayMs = RATE_LIMITING.RETRY_DELAY_MS * 2 ** (attempt - 1);
|
|
1485
1846
|
logger.warn(` \u26A0\uFE0F Retry ${attempt}/${maxAttempts} for ${operation} after ${delayMs}ms...`);
|
|
1486
1847
|
await new Promise((resolve6) => {
|
|
1487
|
-
|
|
1488
|
-
timerId.unref();
|
|
1848
|
+
setTimeout(resolve6, delayMs);
|
|
1489
1849
|
});
|
|
1490
1850
|
}
|
|
1491
1851
|
}
|
|
@@ -1509,6 +1869,7 @@ async function withTimeout(promise, timeoutMs, operation) {
|
|
|
1509
1869
|
}
|
|
1510
1870
|
|
|
1511
1871
|
// src/backup.ts
|
|
1872
|
+
init_backupTransformers();
|
|
1512
1873
|
async function* streamResources(fetchPage, resourceType) {
|
|
1513
1874
|
let page = 0;
|
|
1514
1875
|
let hasMore = true;
|
|
@@ -1546,7 +1907,7 @@ async function saveAssistantToBackup(assistant, client, backupData, backupDir) {
|
|
|
1546
1907
|
const fileName = `${full.slug || sanitizeFileName(full.name)}.prompt.md`;
|
|
1547
1908
|
const filePath = path6.join(backupDir, "system_prompts", fileName);
|
|
1548
1909
|
ensureDirectoryExists(filePath);
|
|
1549
|
-
|
|
1910
|
+
fs6.writeFileSync(filePath, full.system_prompt, "utf8");
|
|
1550
1911
|
}
|
|
1551
1912
|
}
|
|
1552
1913
|
async function backupAssistants(client, backupData, backupDir, transaction) {
|
|
@@ -1586,21 +1947,42 @@ async function backupAssistants(client, backupData, backupDir, transaction) {
|
|
|
1586
1947
|
}
|
|
1587
1948
|
async function processDatasourceBackup(datasource, client, backupData, transaction) {
|
|
1588
1949
|
logger.info(` \u2022 ${datasource.name} (${datasource.id})`);
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1950
|
+
let full;
|
|
1951
|
+
try {
|
|
1952
|
+
full = await withTimeout(
|
|
1953
|
+
client.datasources.get(datasource.id),
|
|
1954
|
+
TIMEOUTS_MS.DATASOURCE_FETCH,
|
|
1955
|
+
`Timeout fetching datasource ${datasource.id}`
|
|
1956
|
+
);
|
|
1957
|
+
} catch (error) {
|
|
1958
|
+
logger.warn(
|
|
1959
|
+
` \u26A0\uFE0F Failed to fetch full datasource details for ${datasource.name}. Using list response as fallback: ${error instanceof Error ? error.message : String(error)}`
|
|
1960
|
+
);
|
|
1961
|
+
full = datasource;
|
|
1962
|
+
}
|
|
1594
1963
|
backupData.resources.datasources.push(full);
|
|
1595
1964
|
transaction.markCompleted("datasources", datasource.id);
|
|
1596
1965
|
}
|
|
1597
|
-
|
|
1966
|
+
function normalizeProjectName(projectName) {
|
|
1967
|
+
return String(projectName || "").trim().toLowerCase();
|
|
1968
|
+
}
|
|
1969
|
+
function filterResourcesByProject(resources, projectName) {
|
|
1970
|
+
const normalized = normalizeProjectName(projectName);
|
|
1971
|
+
if (!normalized) {
|
|
1972
|
+
return resources;
|
|
1973
|
+
}
|
|
1974
|
+
return resources.filter((r) => normalizeProjectName(r?.project_name) === normalized);
|
|
1975
|
+
}
|
|
1976
|
+
async function backupDatasources(client, backupData, transaction, projectName) {
|
|
1598
1977
|
logger.info("\u{1F4CA} Fetching datasources...");
|
|
1599
|
-
const
|
|
1978
|
+
const allDatasources = [];
|
|
1600
1979
|
for await (const datasource of streamResources((params) => client.datasources.list(params), "datasources")) {
|
|
1601
|
-
|
|
1980
|
+
allDatasources.push(datasource);
|
|
1602
1981
|
}
|
|
1603
|
-
|
|
1982
|
+
const datasources = filterResourcesByProject(allDatasources, projectName);
|
|
1983
|
+
logger.info(
|
|
1984
|
+
` Found ${datasources.length} datasource(s) for project '${projectName}' (from ${allDatasources.length} total)`
|
|
1985
|
+
);
|
|
1604
1986
|
transaction.setTotal("datasources", datasources.length);
|
|
1605
1987
|
const limit = createConcurrentLimiter();
|
|
1606
1988
|
for (const datasource of datasources) {
|
|
@@ -1653,7 +2035,7 @@ async function backupWorkflows(client, backupData, backupDir, transaction) {
|
|
|
1653
2035
|
const fileName = `${sanitizeFileName(full.name)}.yaml`;
|
|
1654
2036
|
const filePath = path6.join(backupDir, "workflows", fileName);
|
|
1655
2037
|
ensureDirectoryExists(filePath);
|
|
1656
|
-
|
|
2038
|
+
fs6.writeFileSync(filePath, full.yaml_config, "utf8");
|
|
1657
2039
|
}
|
|
1658
2040
|
transaction.markCompleted("workflows", workflow.id);
|
|
1659
2041
|
} catch (error) {
|
|
@@ -1669,9 +2051,9 @@ async function backupWorkflows(client, backupData, backupDir, transaction) {
|
|
|
1669
2051
|
logger.info(`\u2713 Backed up ${transaction.getData().resources.workflows.completed.length} workflow(s)
|
|
1670
2052
|
`);
|
|
1671
2053
|
}
|
|
1672
|
-
async function backupIntegrations(client, backupData) {
|
|
2054
|
+
async function backupIntegrations(client, backupData, projectName) {
|
|
1673
2055
|
logger.info("\u{1F50C} Fetching integrations...");
|
|
1674
|
-
const
|
|
2056
|
+
const allProjectIntegrations = await withTimeout(
|
|
1675
2057
|
client.integrations.list({
|
|
1676
2058
|
per_page: PAGINATION.DEFAULT_PAGE_SIZE,
|
|
1677
2059
|
page: 0,
|
|
@@ -1680,7 +2062,10 @@ async function backupIntegrations(client, backupData) {
|
|
|
1680
2062
|
TIMEOUTS_MS.INTEGRATION_FETCH,
|
|
1681
2063
|
"Timeout fetching project integrations"
|
|
1682
2064
|
);
|
|
1683
|
-
|
|
2065
|
+
const projectIntegrations = filterResourcesByProject(allProjectIntegrations, projectName);
|
|
2066
|
+
logger.info(
|
|
2067
|
+
` Found ${projectIntegrations.length} project integration(s) for project '${projectName}' (from ${allProjectIntegrations.length} total)`
|
|
2068
|
+
);
|
|
1684
2069
|
for (const integration of projectIntegrations) {
|
|
1685
2070
|
logger.info(` \u2022 ${integration.alias || integration.credential_type} (${integration.id}) [project]`);
|
|
1686
2071
|
backupData.resources.integrations.push(integration);
|
|
@@ -1702,10 +2087,54 @@ async function backupIntegrations(client, backupData) {
|
|
|
1702
2087
|
logger.info(`\u2713 Backed up ${projectIntegrations.length + userIntegrations.length} integration(s)
|
|
1703
2088
|
`);
|
|
1704
2089
|
}
|
|
2090
|
+
async function backupSkills(client, backupData, backupDir, transaction) {
|
|
2091
|
+
logger.info("\u{1F9E0} Fetching skills...");
|
|
2092
|
+
const allSkills = [];
|
|
2093
|
+
for await (const skill of streamResources((params) => client.skills.list(params), "skills")) {
|
|
2094
|
+
allSkills.push(skill);
|
|
2095
|
+
}
|
|
2096
|
+
logger.info(` Found ${allSkills.length} skill(s)`);
|
|
2097
|
+
transaction.setTotal("skills", allSkills.length);
|
|
2098
|
+
const limit = createConcurrentLimiter();
|
|
2099
|
+
for (const skill of allSkills) {
|
|
2100
|
+
if (transaction.isCompleted("skills", skill.id)) {
|
|
2101
|
+
logger.info(` \u21B7 Skipping ${skill.name} (already backed up)`);
|
|
2102
|
+
continue;
|
|
2103
|
+
}
|
|
2104
|
+
try {
|
|
2105
|
+
await limit(
|
|
2106
|
+
() => withRetry(async () => {
|
|
2107
|
+
logger.info(` \u2022 ${skill.name} (${skill.id})`);
|
|
2108
|
+
const full = await withTimeout(
|
|
2109
|
+
client.skills.get(skill.id),
|
|
2110
|
+
TIMEOUTS_MS.SKILL_FETCH,
|
|
2111
|
+
`Timeout fetching skill ${skill.id}`
|
|
2112
|
+
);
|
|
2113
|
+
backupData.resources.skills.push(full);
|
|
2114
|
+
const fileName = `${sanitizeFileName(skill.name)}.skill.md`;
|
|
2115
|
+
const filePath = path6.join(backupDir, "skills", fileName);
|
|
2116
|
+
ensureDirectoryExists(filePath);
|
|
2117
|
+
fs6.writeFileSync(filePath, full.content || "", "utf8");
|
|
2118
|
+
}, `Backup skill ${skill.name}`)
|
|
2119
|
+
);
|
|
2120
|
+
transaction.markCompleted("skills", skill.id);
|
|
2121
|
+
} catch (error) {
|
|
2122
|
+
const errorDetails = error instanceof Error ? { message: error.message, stack: error.stack, name: error.name } : { message: String(error) };
|
|
2123
|
+
logger.error(` \u274C Failed to backup ${skill.name}:`);
|
|
2124
|
+
logger.error(` ${errorDetails.message}`);
|
|
2125
|
+
if (errorDetails.stack) {
|
|
2126
|
+
logger.error(` Stack trace: ${errorDetails.stack.split("\n").slice(1, 3).join("\n ")}`);
|
|
2127
|
+
}
|
|
2128
|
+
transaction.markFailed("skills", skill.id, errorDetails.message);
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
logger.info(`\u2713 Backed up ${transaction.getData().resources.skills.completed.length} skill(s)
|
|
2132
|
+
`);
|
|
2133
|
+
}
|
|
1705
2134
|
function getUniqueBackupDir(baseDir, timestamp) {
|
|
1706
2135
|
let counter = 0;
|
|
1707
2136
|
let finalBackupDir = path6.join(baseDir, timestamp);
|
|
1708
|
-
while (
|
|
2137
|
+
while (fs6.existsSync(finalBackupDir)) {
|
|
1709
2138
|
counter++;
|
|
1710
2139
|
finalBackupDir = path6.join(baseDir, `${timestamp}-${counter}`);
|
|
1711
2140
|
}
|
|
@@ -1714,7 +2143,7 @@ function getUniqueBackupDir(baseDir, timestamp) {
|
|
|
1714
2143
|
return { finalDir: finalBackupDir, tempDir: tempBackupDir };
|
|
1715
2144
|
}
|
|
1716
2145
|
function performCleanup(tempBackupDir, transaction) {
|
|
1717
|
-
if (
|
|
2146
|
+
if (fs6.existsSync(tempBackupDir)) {
|
|
1718
2147
|
try {
|
|
1719
2148
|
logger.info(`\u{1F9F9} Rolling back: cleaning up ${tempBackupDir}...`);
|
|
1720
2149
|
cleanupDirectory(tempBackupDir);
|
|
@@ -1753,8 +2182,8 @@ async function backupResources(options) {
|
|
|
1753
2182
|
logger.info("\u{1F50C} Connecting to Codemie API...");
|
|
1754
2183
|
const client = await createClient(config);
|
|
1755
2184
|
logger.info("\u2713 Connected to Codemie API\n");
|
|
1756
|
-
if (!
|
|
1757
|
-
|
|
2185
|
+
if (!fs6.existsSync(tempBackupDir)) {
|
|
2186
|
+
fs6.mkdirSync(tempBackupDir, { recursive: true });
|
|
1758
2187
|
}
|
|
1759
2188
|
logger.info(`\u{1F4C1} Temporary backup directory: ${tempBackupDir}
|
|
1760
2189
|
`);
|
|
@@ -1769,7 +2198,8 @@ async function backupResources(options) {
|
|
|
1769
2198
|
assistants: [],
|
|
1770
2199
|
datasources: [],
|
|
1771
2200
|
workflows: [],
|
|
1772
|
-
integrations: []
|
|
2201
|
+
integrations: [],
|
|
2202
|
+
skills: []
|
|
1773
2203
|
},
|
|
1774
2204
|
state: {
|
|
1775
2205
|
version: "1.0",
|
|
@@ -1778,21 +2208,23 @@ async function backupResources(options) {
|
|
|
1778
2208
|
resources: {
|
|
1779
2209
|
assistants: {},
|
|
1780
2210
|
datasources: {},
|
|
1781
|
-
workflows: {}
|
|
2211
|
+
workflows: {},
|
|
2212
|
+
skills: {}
|
|
1782
2213
|
}
|
|
1783
2214
|
}
|
|
1784
2215
|
};
|
|
1785
|
-
await
|
|
1786
|
-
await
|
|
2216
|
+
await backupSkills(client, backupData, tempBackupDir, transaction);
|
|
2217
|
+
await backupIntegrations(client, backupData, config.project.name);
|
|
2218
|
+
await backupDatasources(client, backupData, transaction, config.project.name);
|
|
1787
2219
|
await backupWorkflows(client, backupData, tempBackupDir, transaction);
|
|
1788
|
-
await
|
|
2220
|
+
await backupAssistants(client, backupData, tempBackupDir, transaction);
|
|
1789
2221
|
const stats = transaction.getData();
|
|
1790
|
-
const totalFailed = stats.resources.assistants.failed.length + stats.resources.datasources.failed.length + stats.resources.workflows.failed.length;
|
|
2222
|
+
const totalFailed = stats.resources.assistants.failed.length + stats.resources.datasources.failed.length + stats.resources.workflows.failed.length + stats.resources.skills.failed.length;
|
|
1791
2223
|
if (totalFailed > 0) {
|
|
1792
2224
|
logger.warn(`
|
|
1793
2225
|
\u26A0\uFE0F Backup completed with ${totalFailed} failed resource(s)`);
|
|
1794
2226
|
logger.warn("Review transaction.json for details\n");
|
|
1795
|
-
const totalResources = stats.resources.assistants.total + stats.resources.datasources.total + stats.resources.workflows.total;
|
|
2227
|
+
const totalResources = stats.resources.assistants.total + stats.resources.datasources.total + stats.resources.workflows.total + stats.resources.skills.total;
|
|
1796
2228
|
const failureRate = totalResources > 0 ? totalFailed / totalResources * 100 : 0;
|
|
1797
2229
|
if (failureRate > 20) {
|
|
1798
2230
|
throw new Error(
|
|
@@ -1814,6 +2246,7 @@ async function backupResources(options) {
|
|
|
1814
2246
|
logger.info(` \u{1F4CA} Datasources: ${backupData.resources.datasources.length}`);
|
|
1815
2247
|
logger.info(` \u{1F504} Workflows: ${backupData.resources.workflows.length}`);
|
|
1816
2248
|
logger.info(` \u{1F50C} Integrations: ${backupData.resources.integrations.length}`);
|
|
2249
|
+
logger.info(` \u{1F9E0} Skills: ${backupData.resources.skills.length}`);
|
|
1817
2250
|
logger.info(`
|
|
1818
2251
|
\u{1F4C1} Location: ${finalBackupDir}
|
|
1819
2252
|
`);
|
|
@@ -1833,12 +2266,15 @@ async function main(options) {
|
|
|
1833
2266
|
try {
|
|
1834
2267
|
await backupResources(options);
|
|
1835
2268
|
process.exit(0);
|
|
1836
|
-
} catch {
|
|
2269
|
+
} catch (error) {
|
|
2270
|
+
logger.error("\n\u274C Backup failed:");
|
|
2271
|
+
logger.error(error instanceof Error ? error.message : String(error));
|
|
1837
2272
|
process.exit(1);
|
|
1838
2273
|
}
|
|
1839
2274
|
}
|
|
1840
2275
|
|
|
1841
2276
|
// src/lib/paginationUtils.ts
|
|
2277
|
+
init_logger();
|
|
1842
2278
|
async function findResourceByName(listFn, name, resourceType) {
|
|
1843
2279
|
let page = 0;
|
|
1844
2280
|
const perPage = 100;
|
|
@@ -1871,6 +2307,10 @@ async function findAssistantByName(client, name) {
|
|
|
1871
2307
|
const assistant = await findResourceByName((params) => client.assistants.list(params), name, "assistant");
|
|
1872
2308
|
return assistant?.id || null;
|
|
1873
2309
|
}
|
|
2310
|
+
async function findSkillByName(client, name) {
|
|
2311
|
+
const skill = await findResourceByName((params) => client.skills.list(params), name, "skill");
|
|
2312
|
+
return skill?.id || null;
|
|
2313
|
+
}
|
|
1874
2314
|
|
|
1875
2315
|
// src/lib/assistantHelpers.ts
|
|
1876
2316
|
async function createAssistantAndGetId(client, params, slug) {
|
|
@@ -1889,12 +2329,18 @@ async function createAssistantAndGetId(client, params, slug) {
|
|
|
1889
2329
|
return assistantId;
|
|
1890
2330
|
}
|
|
1891
2331
|
|
|
2332
|
+
// src/deploy.ts
|
|
2333
|
+
init_checksumUtils();
|
|
2334
|
+
|
|
1892
2335
|
// src/lib/cleanupManager.ts
|
|
2336
|
+
init_logger();
|
|
1893
2337
|
var CleanupManager = class {
|
|
1894
2338
|
constructor(client, stateManager) {
|
|
1895
2339
|
this.client = client;
|
|
1896
2340
|
this.stateManager = stateManager;
|
|
1897
2341
|
}
|
|
2342
|
+
client;
|
|
2343
|
+
stateManager;
|
|
1898
2344
|
/**
|
|
1899
2345
|
* Check if an error indicates that a resource was not found on the platform
|
|
1900
2346
|
* Handles both proper 404 responses
|
|
@@ -1935,10 +2381,12 @@ var CleanupManager = class {
|
|
|
1935
2381
|
const configAssistantNames = new Set((config.resources.assistants || []).map(({ name }) => name));
|
|
1936
2382
|
const configDatasourceNames = new Set((config.resources.datasources || []).map(({ name }) => name));
|
|
1937
2383
|
const configWorkflowNames = new Set((config.resources.workflows || []).map(({ name }) => name));
|
|
2384
|
+
const configSkillNames = new Set((config.resources.skills || []).map(({ name }) => name));
|
|
1938
2385
|
return {
|
|
1939
2386
|
assistants: managedResources.assistants.filter((name) => !configAssistantNames.has(name)),
|
|
1940
2387
|
datasources: managedResources.datasources.filter((name) => !configDatasourceNames.has(name)),
|
|
1941
|
-
workflows: managedResources.workflows.filter((name) => !configWorkflowNames.has(name))
|
|
2388
|
+
workflows: managedResources.workflows.filter((name) => !configWorkflowNames.has(name)),
|
|
2389
|
+
skills: managedResources.skills.filter((name) => !configSkillNames.has(name))
|
|
1942
2390
|
};
|
|
1943
2391
|
}
|
|
1944
2392
|
/**
|
|
@@ -1952,7 +2400,8 @@ var CleanupManager = class {
|
|
|
1952
2400
|
deleted: {
|
|
1953
2401
|
assistants: [],
|
|
1954
2402
|
datasources: [],
|
|
1955
|
-
workflows: []
|
|
2403
|
+
workflows: [],
|
|
2404
|
+
skills: []
|
|
1956
2405
|
},
|
|
1957
2406
|
errors: []
|
|
1958
2407
|
};
|
|
@@ -2031,13 +2480,36 @@ var CleanupManager = class {
|
|
|
2031
2480
|
);
|
|
2032
2481
|
}
|
|
2033
2482
|
}
|
|
2483
|
+
for (const name of orphaned.skills || []) {
|
|
2484
|
+
try {
|
|
2485
|
+
if (!this.stateManager.isManagedResource("skill", name)) {
|
|
2486
|
+
logger.info(` \u26A0\uFE0F Skipping ${name} - not in state (safety check)`);
|
|
2487
|
+
continue;
|
|
2488
|
+
}
|
|
2489
|
+
const id = this.stateManager.getIdByName("skill", name);
|
|
2490
|
+
if (!id) {
|
|
2491
|
+
logger.info(` \u26A0\uFE0F Skipping ${name} - no ID in state`);
|
|
2492
|
+
continue;
|
|
2493
|
+
}
|
|
2494
|
+
await this.deleteResourceSafely("skill", name, id, () => this.client.skills.delete(id));
|
|
2495
|
+
this.stateManager.deleteSkillState(name);
|
|
2496
|
+
result.deleted.skills.push(name);
|
|
2497
|
+
} catch (error) {
|
|
2498
|
+
result.errors.push({
|
|
2499
|
+
type: "skill",
|
|
2500
|
+
name,
|
|
2501
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2502
|
+
});
|
|
2503
|
+
logger.error(` \u274C Failed to delete skill ${name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2034
2506
|
return result;
|
|
2035
2507
|
}
|
|
2036
2508
|
/**
|
|
2037
2509
|
* Get summary of orphaned resources
|
|
2038
2510
|
*/
|
|
2039
2511
|
getOrphanedSummary(orphaned) {
|
|
2040
|
-
const total = orphaned.assistants.length + orphaned.datasources.length + orphaned.workflows.length;
|
|
2512
|
+
const total = orphaned.assistants.length + orphaned.datasources.length + orphaned.workflows.length + (orphaned.skills?.length ?? 0);
|
|
2041
2513
|
if (total === 0) {
|
|
2042
2514
|
return "No orphaned resources found";
|
|
2043
2515
|
}
|
|
@@ -2051,10 +2523,18 @@ var CleanupManager = class {
|
|
|
2051
2523
|
if (orphaned.workflows.length > 0) {
|
|
2052
2524
|
parts.push(`${orphaned.workflows.length} workflow(s)`);
|
|
2053
2525
|
}
|
|
2526
|
+
if ((orphaned.skills?.length ?? 0) > 0) {
|
|
2527
|
+
parts.push(`${orphaned.skills.length} skill(s)`);
|
|
2528
|
+
}
|
|
2054
2529
|
return `Found ${total} orphaned resource(s): ${parts.join(", ")}`;
|
|
2055
2530
|
}
|
|
2056
2531
|
};
|
|
2057
2532
|
|
|
2533
|
+
// src/deploy.ts
|
|
2534
|
+
init_codemieConfigChecksums();
|
|
2535
|
+
init_converters();
|
|
2536
|
+
init_logger();
|
|
2537
|
+
|
|
2058
2538
|
// src/lib/resourceExistenceChecker.ts
|
|
2059
2539
|
async function checkResourceExists(getState, getResource) {
|
|
2060
2540
|
const existingState = getState();
|
|
@@ -2086,6 +2566,16 @@ function checkWorkflowExists(client, name, stateManager) {
|
|
|
2086
2566
|
(id) => client.workflows.get(id)
|
|
2087
2567
|
);
|
|
2088
2568
|
}
|
|
2569
|
+
function checkSkillExists(client, name, stateManager) {
|
|
2570
|
+
return checkResourceExists(
|
|
2571
|
+
() => stateManager.getSkillState(name),
|
|
2572
|
+
(id) => client.skills.get(id)
|
|
2573
|
+
);
|
|
2574
|
+
}
|
|
2575
|
+
|
|
2576
|
+
// src/lib/stateManager.ts
|
|
2577
|
+
init_checksumUtils();
|
|
2578
|
+
init_codemieConfigChecksums();
|
|
2089
2579
|
var StateManager = class {
|
|
2090
2580
|
statePath;
|
|
2091
2581
|
constructor(appConfig) {
|
|
@@ -2096,16 +2586,17 @@ var StateManager = class {
|
|
|
2096
2586
|
* Load state file
|
|
2097
2587
|
*/
|
|
2098
2588
|
loadState() {
|
|
2099
|
-
if (!
|
|
2589
|
+
if (!fs6.existsSync(this.statePath)) {
|
|
2100
2590
|
return this.createEmptyState();
|
|
2101
2591
|
}
|
|
2102
|
-
const content =
|
|
2592
|
+
const content = fs6.readFileSync(this.statePath, "utf8");
|
|
2103
2593
|
const state = JSON.parse(content);
|
|
2104
2594
|
if (!state.resources) {
|
|
2105
2595
|
state.resources = {
|
|
2106
2596
|
assistants: {},
|
|
2107
2597
|
datasources: {},
|
|
2108
|
-
workflows: {}
|
|
2598
|
+
workflows: {},
|
|
2599
|
+
skills: {}
|
|
2109
2600
|
};
|
|
2110
2601
|
}
|
|
2111
2602
|
if (!state.resources.assistants) {
|
|
@@ -2117,6 +2608,9 @@ var StateManager = class {
|
|
|
2117
2608
|
if (!state.resources.workflows) {
|
|
2118
2609
|
state.resources.workflows = {};
|
|
2119
2610
|
}
|
|
2611
|
+
if (!state.resources.skills) {
|
|
2612
|
+
state.resources.skills = {};
|
|
2613
|
+
}
|
|
2120
2614
|
return state;
|
|
2121
2615
|
}
|
|
2122
2616
|
/**
|
|
@@ -2124,11 +2618,11 @@ var StateManager = class {
|
|
|
2124
2618
|
*/
|
|
2125
2619
|
saveState(state) {
|
|
2126
2620
|
const dir = path6.dirname(this.statePath);
|
|
2127
|
-
if (!
|
|
2128
|
-
|
|
2621
|
+
if (!fs6.existsSync(dir)) {
|
|
2622
|
+
fs6.mkdirSync(dir, { recursive: true });
|
|
2129
2623
|
}
|
|
2130
2624
|
state.lastSync = (/* @__PURE__ */ new Date()).toISOString();
|
|
2131
|
-
|
|
2625
|
+
fs6.writeFileSync(this.statePath, JSON.stringify(state, null, 2));
|
|
2132
2626
|
}
|
|
2133
2627
|
/**
|
|
2134
2628
|
* Create empty state structure
|
|
@@ -2141,7 +2635,8 @@ var StateManager = class {
|
|
|
2141
2635
|
resources: {
|
|
2142
2636
|
assistants: {},
|
|
2143
2637
|
datasources: {},
|
|
2144
|
-
workflows: {}
|
|
2638
|
+
workflows: {},
|
|
2639
|
+
skills: {}
|
|
2145
2640
|
}
|
|
2146
2641
|
};
|
|
2147
2642
|
}
|
|
@@ -2236,19 +2731,20 @@ var StateManager = class {
|
|
|
2236
2731
|
}
|
|
2237
2732
|
/**
|
|
2238
2733
|
* Get all managed resources (for cleanup/destroy)
|
|
2239
|
-
* Returns: { assistants: [name1, name2], datasources: [name1], workflows: [name1] }
|
|
2734
|
+
* Returns: { assistants: [name1, name2], datasources: [name1], workflows: [name1], skills: [name1] }
|
|
2240
2735
|
*/
|
|
2241
2736
|
getAllManagedResources() {
|
|
2242
2737
|
const state = this.loadState();
|
|
2243
2738
|
return {
|
|
2244
2739
|
assistants: Object.keys(state.resources.assistants),
|
|
2245
2740
|
datasources: Object.keys(state.resources.datasources),
|
|
2246
|
-
workflows: Object.keys(state.resources.workflows)
|
|
2741
|
+
workflows: Object.keys(state.resources.workflows),
|
|
2742
|
+
skills: Object.keys(state.resources.skills || {})
|
|
2247
2743
|
};
|
|
2248
2744
|
}
|
|
2249
2745
|
/**
|
|
2250
2746
|
* Check if a resource is managed by IaC (exists in state.json)
|
|
2251
|
-
* @param type Resource type ('assistant', 'datasource', 'workflow')
|
|
2747
|
+
* @param type Resource type ('assistant', 'datasource', 'workflow', 'skill')
|
|
2252
2748
|
* @param name Resource name
|
|
2253
2749
|
*/
|
|
2254
2750
|
isManagedResource(type, name) {
|
|
@@ -2263,6 +2759,9 @@ var StateManager = class {
|
|
|
2263
2759
|
case "workflow": {
|
|
2264
2760
|
return name in state.resources.workflows;
|
|
2265
2761
|
}
|
|
2762
|
+
case "skill": {
|
|
2763
|
+
return name in (state.resources.skills || {});
|
|
2764
|
+
}
|
|
2266
2765
|
default: {
|
|
2267
2766
|
return false;
|
|
2268
2767
|
}
|
|
@@ -2283,11 +2782,47 @@ var StateManager = class {
|
|
|
2283
2782
|
case "workflow": {
|
|
2284
2783
|
return state.resources.workflows[name]?.id;
|
|
2285
2784
|
}
|
|
2785
|
+
case "skill": {
|
|
2786
|
+
return (state.resources.skills || {})[name]?.id;
|
|
2787
|
+
}
|
|
2286
2788
|
default: {
|
|
2287
2789
|
return void 0;
|
|
2288
2790
|
}
|
|
2289
2791
|
}
|
|
2290
2792
|
}
|
|
2793
|
+
/**
|
|
2794
|
+
* Update skill state (keyed by NAME)
|
|
2795
|
+
*/
|
|
2796
|
+
updateSkillState(name, id, content, skillResource) {
|
|
2797
|
+
const state = this.loadState();
|
|
2798
|
+
if (!state.resources.skills) {
|
|
2799
|
+
state.resources.skills = {};
|
|
2800
|
+
}
|
|
2801
|
+
state.resources.skills[name] = {
|
|
2802
|
+
id,
|
|
2803
|
+
lastDeployed: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2804
|
+
contentChecksum: calculateChecksum(content),
|
|
2805
|
+
configChecksum: calculateSkillConfigChecksum(skillResource)
|
|
2806
|
+
};
|
|
2807
|
+
this.saveState(state);
|
|
2808
|
+
}
|
|
2809
|
+
/**
|
|
2810
|
+
* Get skill state by NAME
|
|
2811
|
+
*/
|
|
2812
|
+
getSkillState(name) {
|
|
2813
|
+
const state = this.loadState();
|
|
2814
|
+
return (state.resources.skills || {})[name];
|
|
2815
|
+
}
|
|
2816
|
+
/**
|
|
2817
|
+
* Delete skill state by NAME
|
|
2818
|
+
*/
|
|
2819
|
+
deleteSkillState(name) {
|
|
2820
|
+
const state = this.loadState();
|
|
2821
|
+
if (state.resources.skills) {
|
|
2822
|
+
delete state.resources.skills[name];
|
|
2823
|
+
}
|
|
2824
|
+
this.saveState(state);
|
|
2825
|
+
}
|
|
2291
2826
|
};
|
|
2292
2827
|
|
|
2293
2828
|
// src/deploy.ts
|
|
@@ -2380,69 +2915,268 @@ async function deployAssistants(config, client, loader, stateManager) {
|
|
|
2380
2915
|
});
|
|
2381
2916
|
logger.info(` \u2713 Resolved "${dsName}" \u2192 ${datasource.name} (${contextType})`);
|
|
2382
2917
|
}
|
|
2383
|
-
resolvedContext = [...resolvedContext, ...datasourceContextEntries];
|
|
2918
|
+
resolvedContext = [...resolvedContext, ...datasourceContextEntries];
|
|
2919
|
+
}
|
|
2920
|
+
let resolvedSkillIds = [];
|
|
2921
|
+
if (assistant.skills && assistant.skills.length > 0) {
|
|
2922
|
+
logger.info(` Resolving ${assistant.skills.length} skill name(s)...`);
|
|
2923
|
+
const resolvedIds = [];
|
|
2924
|
+
for (const skillName of assistant.skills) {
|
|
2925
|
+
const skillState = stateManager.getSkillState(skillName);
|
|
2926
|
+
if (!skillState) {
|
|
2927
|
+
throw new Error(`Skill "${skillName}" not found in state. Ensure the skill is deployed first.`);
|
|
2928
|
+
}
|
|
2929
|
+
resolvedIds.push(skillState.id);
|
|
2930
|
+
logger.info(` \u2713 Resolved "${skillName}" \u2192 ${skillState.id}`);
|
|
2931
|
+
}
|
|
2932
|
+
resolvedSkillIds = resolvedIds;
|
|
2933
|
+
}
|
|
2934
|
+
const assistantWithResolved = {
|
|
2935
|
+
...assistant,
|
|
2936
|
+
assistant_ids: resolvedAssistantIds,
|
|
2937
|
+
context: resolvedContext,
|
|
2938
|
+
skill_ids: resolvedSkillIds
|
|
2939
|
+
};
|
|
2940
|
+
const apiParams = assistantResourceToCreateParams(assistantWithResolved, config.project.name, promptContent);
|
|
2941
|
+
const existingState = stateManager.getAssistantState(assistant.name);
|
|
2942
|
+
if (existingState) {
|
|
2943
|
+
const existsOnPlatform = await checkAssistantExists(client, assistant.name, stateManager);
|
|
2944
|
+
if (existsOnPlatform) {
|
|
2945
|
+
const hasChanged = existingState.promptChecksum !== calculateChecksum(promptContent) || existingState.configChecksum !== configChecksum;
|
|
2946
|
+
if (hasChanged) {
|
|
2947
|
+
logger.info(` Updating assistant (ID: ${existingState.id})...`);
|
|
2948
|
+
if (process.env.DEBUG_API) {
|
|
2949
|
+
logger.debug("\n=== DEBUG: Update API Params ===");
|
|
2950
|
+
logger.debug(JSON.stringify(apiParams, null, 2));
|
|
2951
|
+
logger.debug("================================\n");
|
|
2952
|
+
}
|
|
2953
|
+
await client.assistants.update(existingState.id, apiParams);
|
|
2954
|
+
logger.info(`\u2713 Updated assistant: ${assistant.name} (${existingState.id})`);
|
|
2955
|
+
stateManager.updateAssistantState(assistant.name, existingState.id, promptContent, assistant, buildConfig);
|
|
2956
|
+
stats.updated++;
|
|
2957
|
+
} else {
|
|
2958
|
+
logger.info(` \u2713 No changes detected (ID: ${existingState.id})`);
|
|
2959
|
+
stats.unchanged++;
|
|
2960
|
+
}
|
|
2961
|
+
} else {
|
|
2962
|
+
logger.info(` \u26A0\uFE0F Assistant ID from state not found on platform, will create new`);
|
|
2963
|
+
logger.info(` Creating new assistant...`);
|
|
2964
|
+
if (process.env.DEBUG_API) {
|
|
2965
|
+
logger.debug("\n=== DEBUG: API Params ===");
|
|
2966
|
+
logger.debug(JSON.stringify(apiParams, null, 2));
|
|
2967
|
+
logger.debug("=========================\n");
|
|
2968
|
+
}
|
|
2969
|
+
let assistantId;
|
|
2970
|
+
try {
|
|
2971
|
+
assistantId = await createAssistantAndGetId(client, apiParams, assistant.slug);
|
|
2972
|
+
logger.info(`\u2713 Created assistant: ${assistant.name} (${assistantId})`);
|
|
2973
|
+
stats.created++;
|
|
2974
|
+
} catch (createError) {
|
|
2975
|
+
logger.warn(` \u26A0\uFE0F Assistant create failed. Checking if it already exists on platform...`);
|
|
2976
|
+
let existingId = null;
|
|
2977
|
+
if (assistant.slug) {
|
|
2978
|
+
try {
|
|
2979
|
+
const existingBySlug = await client.assistants.getBySlug(assistant.slug);
|
|
2980
|
+
existingId = existingBySlug?.id || null;
|
|
2981
|
+
} catch {
|
|
2982
|
+
}
|
|
2983
|
+
}
|
|
2984
|
+
if (!existingId) {
|
|
2985
|
+
existingId = await findAssistantByName(client, assistant.name);
|
|
2986
|
+
}
|
|
2987
|
+
if (!existingId) {
|
|
2988
|
+
throw createError;
|
|
2989
|
+
}
|
|
2990
|
+
logger.info(` Found existing assistant ID: ${existingId}. Updating...`);
|
|
2991
|
+
await client.assistants.update(existingId, apiParams);
|
|
2992
|
+
assistantId = existingId;
|
|
2993
|
+
logger.info(`\u2713 Recovered and updated assistant: ${assistant.name} (${assistantId})`);
|
|
2994
|
+
stats.updated++;
|
|
2995
|
+
}
|
|
2996
|
+
stateManager.updateAssistantState(assistant.name, assistantId, promptContent, assistant, buildConfig);
|
|
2997
|
+
}
|
|
2998
|
+
} else {
|
|
2999
|
+
logger.info(` Creating new assistant...`);
|
|
3000
|
+
if (process.env.DEBUG_API) {
|
|
3001
|
+
logger.debug("\n=== DEBUG: API Params ===");
|
|
3002
|
+
logger.debug(JSON.stringify(apiParams, null, 2));
|
|
3003
|
+
logger.debug("=========================\n");
|
|
3004
|
+
}
|
|
3005
|
+
let assistantId;
|
|
3006
|
+
try {
|
|
3007
|
+
assistantId = await createAssistantAndGetId(client, apiParams, assistant.slug);
|
|
3008
|
+
logger.info(`\u2713 Created assistant: ${assistant.name} (${assistantId})`);
|
|
3009
|
+
stats.created++;
|
|
3010
|
+
} catch (createError) {
|
|
3011
|
+
logger.warn(` \u26A0\uFE0F Assistant create failed. Checking if it already exists on platform...`);
|
|
3012
|
+
let existingId = null;
|
|
3013
|
+
if (assistant.slug) {
|
|
3014
|
+
try {
|
|
3015
|
+
const existingBySlug = await client.assistants.getBySlug(assistant.slug);
|
|
3016
|
+
existingId = existingBySlug?.id || null;
|
|
3017
|
+
} catch {
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
3020
|
+
if (!existingId) {
|
|
3021
|
+
existingId = await findAssistantByName(client, assistant.name);
|
|
3022
|
+
}
|
|
3023
|
+
if (!existingId) {
|
|
3024
|
+
throw createError;
|
|
3025
|
+
}
|
|
3026
|
+
logger.info(` Found existing assistant ID: ${existingId}. Updating...`);
|
|
3027
|
+
await client.assistants.update(existingId, apiParams);
|
|
3028
|
+
assistantId = existingId;
|
|
3029
|
+
logger.info(`\u2713 Recovered and updated assistant: ${assistant.name} (${assistantId})`);
|
|
3030
|
+
stats.updated++;
|
|
3031
|
+
}
|
|
3032
|
+
stateManager.updateAssistantState(assistant.name, assistantId, promptContent, assistant, buildConfig);
|
|
2384
3033
|
}
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
3034
|
+
logger.info("");
|
|
3035
|
+
} catch (error) {
|
|
3036
|
+
logger.error(` \u274C Failed to deploy ${assistant.name}:`);
|
|
3037
|
+
if (error instanceof Error) {
|
|
3038
|
+
logger.error(` ${error.message}`);
|
|
3039
|
+
logger.debug(` Stack:`, error.stack);
|
|
3040
|
+
if ("statusCode" in error) {
|
|
3041
|
+
const apiError = error;
|
|
3042
|
+
logger.error(` Status: ${apiError.statusCode}`);
|
|
3043
|
+
logger.error(` Data: ${JSON.stringify(apiError.response, null, 2)}`);
|
|
3044
|
+
} else if ("response" in error) {
|
|
3045
|
+
const axiosError = error;
|
|
3046
|
+
logger.error(` Status: ${axiosError.response?.status}`);
|
|
3047
|
+
logger.error(` Data: ${JSON.stringify(axiosError.response?.data, null, 2)}`);
|
|
3048
|
+
}
|
|
3049
|
+
} else {
|
|
3050
|
+
logger.error(` ${String(error)}`);
|
|
3051
|
+
}
|
|
3052
|
+
logger.info("");
|
|
3053
|
+
stats.failed++;
|
|
3054
|
+
}
|
|
3055
|
+
}
|
|
3056
|
+
return stats;
|
|
3057
|
+
}
|
|
3058
|
+
async function deploySkills(config, client, loader, stateManager) {
|
|
3059
|
+
const stats = { created: 0, updated: 0, unchanged: 0, failed: 0 };
|
|
3060
|
+
logger.info("\u{1F3AF} Processing skills...\n");
|
|
3061
|
+
if (!config.resources.skills) {
|
|
3062
|
+
return stats;
|
|
3063
|
+
}
|
|
3064
|
+
for (const skill of config.resources.skills) {
|
|
3065
|
+
try {
|
|
3066
|
+
logger.info(`Processing: ${skill.name}`);
|
|
3067
|
+
const content = loader.loadSkillContent(skill.file);
|
|
3068
|
+
const contentChecksum = calculateChecksum(content);
|
|
3069
|
+
const configChecksum = calculateSkillConfigChecksum(skill);
|
|
3070
|
+
const visibility = skill.visibility ?? "project";
|
|
3071
|
+
const existingState = stateManager.getSkillState(skill.name);
|
|
2392
3072
|
if (existingState) {
|
|
2393
|
-
const existsOnPlatform = await
|
|
3073
|
+
const existsOnPlatform = await checkSkillExists(client, skill.name, stateManager);
|
|
2394
3074
|
if (existsOnPlatform) {
|
|
2395
|
-
const hasChanged = existingState.
|
|
3075
|
+
const hasChanged = existingState.contentChecksum !== contentChecksum || existingState.configChecksum !== configChecksum;
|
|
2396
3076
|
if (hasChanged) {
|
|
2397
|
-
logger.info(` Updating
|
|
3077
|
+
logger.info(` Updating skill (ID: ${existingState.id})...`);
|
|
2398
3078
|
if (process.env.DEBUG_API) {
|
|
2399
3079
|
logger.debug("\n=== DEBUG: Update API Params ===");
|
|
2400
|
-
logger.debug(
|
|
3080
|
+
logger.debug(
|
|
3081
|
+
JSON.stringify(
|
|
3082
|
+
{ name: skill.name, description: skill.description, categories: skill.categories },
|
|
3083
|
+
null,
|
|
3084
|
+
2
|
|
3085
|
+
)
|
|
3086
|
+
);
|
|
2401
3087
|
logger.debug("================================\n");
|
|
2402
3088
|
}
|
|
2403
|
-
await client.
|
|
2404
|
-
|
|
2405
|
-
|
|
3089
|
+
await client.skills.update(existingState.id, {
|
|
3090
|
+
name: skill.name,
|
|
3091
|
+
description: skill.description,
|
|
3092
|
+
content,
|
|
3093
|
+
categories: skill.categories
|
|
3094
|
+
});
|
|
3095
|
+
logger.info(`\u2713 Updated skill: ${skill.name} (${existingState.id})`);
|
|
3096
|
+
stateManager.updateSkillState(skill.name, existingState.id, content, skill);
|
|
2406
3097
|
stats.updated++;
|
|
2407
3098
|
} else {
|
|
2408
3099
|
logger.info(` \u2713 No changes detected (ID: ${existingState.id})`);
|
|
2409
3100
|
stats.unchanged++;
|
|
2410
3101
|
}
|
|
2411
3102
|
} else {
|
|
2412
|
-
logger.info(` \u26A0\uFE0F
|
|
2413
|
-
logger.info(` Creating new
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
3103
|
+
logger.info(` \u26A0\uFE0F Skill ID from state not found on platform, will create new`);
|
|
3104
|
+
logger.info(` Creating new skill...`);
|
|
3105
|
+
const result = await client.skills.create({
|
|
3106
|
+
name: skill.name,
|
|
3107
|
+
description: skill.description,
|
|
3108
|
+
content,
|
|
3109
|
+
project: config.project.name,
|
|
3110
|
+
visibility,
|
|
3111
|
+
categories: skill.categories
|
|
3112
|
+
});
|
|
3113
|
+
logger.info(`\u2713 Created skill: ${skill.name} (${result.id})`);
|
|
3114
|
+
stateManager.updateSkillState(skill.name, result.id, content, skill);
|
|
2422
3115
|
stats.created++;
|
|
2423
3116
|
}
|
|
2424
3117
|
} else {
|
|
2425
|
-
logger.info(` Creating new
|
|
3118
|
+
logger.info(` Creating new skill...`);
|
|
2426
3119
|
if (process.env.DEBUG_API) {
|
|
2427
3120
|
logger.debug("\n=== DEBUG: API Params ===");
|
|
2428
|
-
logger.debug(
|
|
3121
|
+
logger.debug(
|
|
3122
|
+
JSON.stringify(
|
|
3123
|
+
{ name: skill.name, description: skill.description, visibility, categories: skill.categories },
|
|
3124
|
+
null,
|
|
3125
|
+
2
|
|
3126
|
+
)
|
|
3127
|
+
);
|
|
2429
3128
|
logger.debug("=========================\n");
|
|
2430
3129
|
}
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
3130
|
+
let skillId;
|
|
3131
|
+
try {
|
|
3132
|
+
const result = await client.skills.create({
|
|
3133
|
+
name: skill.name,
|
|
3134
|
+
description: skill.description,
|
|
3135
|
+
content,
|
|
3136
|
+
project: config.project.name,
|
|
3137
|
+
visibility,
|
|
3138
|
+
categories: skill.categories
|
|
3139
|
+
});
|
|
3140
|
+
skillId = result.id;
|
|
3141
|
+
logger.info(`\u2713 Created skill: ${skill.name} (${skillId})`);
|
|
3142
|
+
stats.created++;
|
|
3143
|
+
} catch (createError) {
|
|
3144
|
+
const msg = createError instanceof Error ? createError.message : String(createError);
|
|
3145
|
+
if (!msg.includes("already exists")) {
|
|
3146
|
+
throw createError;
|
|
3147
|
+
}
|
|
3148
|
+
logger.warn(` \u26A0\uFE0F Skill already exists on platform (no local state). Fetching ID to recover...`);
|
|
3149
|
+
const existingId = await findSkillByName(client, skill.name);
|
|
3150
|
+
if (!existingId) {
|
|
3151
|
+
throw new Error(`Skill "${skill.name}" reported as existing but could not be found by name.`);
|
|
3152
|
+
}
|
|
3153
|
+
logger.info(` Found existing skill ID: ${existingId}. Updating...`);
|
|
3154
|
+
await client.skills.update(existingId, {
|
|
3155
|
+
name: skill.name,
|
|
3156
|
+
description: skill.description,
|
|
3157
|
+
content,
|
|
3158
|
+
categories: skill.categories
|
|
3159
|
+
});
|
|
3160
|
+
skillId = existingId;
|
|
3161
|
+
logger.info(`\u2713 Recovered and updated skill: ${skill.name} (${skillId})`);
|
|
3162
|
+
stats.updated++;
|
|
3163
|
+
}
|
|
3164
|
+
stateManager.updateSkillState(skill.name, skillId, content, skill);
|
|
2435
3165
|
}
|
|
2436
3166
|
logger.info("");
|
|
2437
3167
|
} catch (error) {
|
|
2438
|
-
logger.error(` \u274C Failed to deploy ${
|
|
3168
|
+
logger.error(` \u274C Failed to deploy ${skill.name}:`);
|
|
2439
3169
|
if (error instanceof Error) {
|
|
2440
3170
|
logger.error(` ${error.message}`);
|
|
2441
3171
|
logger.debug(` Stack:`, error.stack);
|
|
2442
|
-
if ("
|
|
3172
|
+
if ("statusCode" in error) {
|
|
3173
|
+
const apiError = error;
|
|
3174
|
+
logger.error(` Status: ${apiError.statusCode}`);
|
|
3175
|
+
logger.error(` Data: ${JSON.stringify(apiError.response, null, 2)}`);
|
|
3176
|
+
} else if ("response" in error) {
|
|
2443
3177
|
const axiosError = error;
|
|
2444
3178
|
logger.error(` Status: ${axiosError.response?.status}`);
|
|
2445
|
-
logger.error(` Data
|
|
3179
|
+
logger.error(` Data: ${JSON.stringify(axiosError.response?.data, null, 2)}`);
|
|
2446
3180
|
}
|
|
2447
3181
|
} else {
|
|
2448
3182
|
logger.error(` ${String(error)}`);
|
|
@@ -2464,6 +3198,24 @@ async function createDatasourceAndGetId(client, createParams, datasourceName) {
|
|
|
2464
3198
|
logger.info(` Found ID: ${datasourceId}`);
|
|
2465
3199
|
return datasourceId;
|
|
2466
3200
|
}
|
|
3201
|
+
async function createOrRecoverDatasource(client, createParams, datasourceName) {
|
|
3202
|
+
try {
|
|
3203
|
+
return await createDatasourceAndGetId(client, createParams, datasourceName);
|
|
3204
|
+
} catch (createError) {
|
|
3205
|
+
if (createError instanceof Error && createError.message.toLowerCase().includes("already exists")) {
|
|
3206
|
+
logger.warn(` \u26A0\uFE0F Datasource "${datasourceName}" already exists on platform. Recovering existing ID...`);
|
|
3207
|
+
const existingId = await findDatasourceByName(client, datasourceName);
|
|
3208
|
+
if (!existingId) {
|
|
3209
|
+
throw new Error(
|
|
3210
|
+
`Datasource "${datasourceName}" already exists but could not be found by name. Manual intervention required.`
|
|
3211
|
+
);
|
|
3212
|
+
}
|
|
3213
|
+
logger.info(` Found existing ID: ${existingId}`);
|
|
3214
|
+
return existingId;
|
|
3215
|
+
}
|
|
3216
|
+
throw createError;
|
|
3217
|
+
}
|
|
3218
|
+
}
|
|
2467
3219
|
async function deployDatasources(config, client, stateManager) {
|
|
2468
3220
|
const stats = { created: 0, updated: 0, unchanged: 0, failed: 0 };
|
|
2469
3221
|
logger.info("\u{1F4CA} Processing datasources...\n");
|
|
@@ -2490,12 +3242,24 @@ async function deployDatasources(config, client, stateManager) {
|
|
|
2490
3242
|
...createParams,
|
|
2491
3243
|
...datasource.force_reindex && { full_reindex: true }
|
|
2492
3244
|
};
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
3245
|
+
try {
|
|
3246
|
+
await client.datasources.update(updateParams);
|
|
3247
|
+
logger.info(
|
|
3248
|
+
`\u2713 ${datasource.force_reindex && !hasChanged ? "Reindexed" : "Updated"} datasource: ${datasource.name} (${existingState.id})`
|
|
3249
|
+
);
|
|
3250
|
+
stateManager.updateDatasourceState(datasource.name, existingState.id, datasource);
|
|
3251
|
+
stats.updated++;
|
|
3252
|
+
} catch (updateError) {
|
|
3253
|
+
if (updateError instanceof Error && updateError.message.includes("unknown not found")) {
|
|
3254
|
+
logger.warn(
|
|
3255
|
+
` \u26A0\uFE0F Datasource "${datasource.name}" update returned 404 \u2014 likely externally managed and read-only. Skipping update.`
|
|
3256
|
+
);
|
|
3257
|
+
stateManager.updateDatasourceState(datasource.name, existingState.id, datasource);
|
|
3258
|
+
stats.unchanged++;
|
|
3259
|
+
} else {
|
|
3260
|
+
throw updateError;
|
|
3261
|
+
}
|
|
3262
|
+
}
|
|
2499
3263
|
} else {
|
|
2500
3264
|
logger.info(` \u2713 No changes detected (ID: ${existingState.id})`);
|
|
2501
3265
|
stats.unchanged++;
|
|
@@ -2503,13 +3267,13 @@ async function deployDatasources(config, client, stateManager) {
|
|
|
2503
3267
|
} else {
|
|
2504
3268
|
logger.info(` \u26A0\uFE0F Datasource ID from state not found on platform, will create new`);
|
|
2505
3269
|
logger.info(` Creating new datasource...`);
|
|
2506
|
-
const datasourceId = await
|
|
3270
|
+
const datasourceId = await createOrRecoverDatasource(client, createParams, datasource.name);
|
|
2507
3271
|
stateManager.updateDatasourceState(datasource.name, datasourceId, datasource);
|
|
2508
3272
|
stats.created++;
|
|
2509
3273
|
}
|
|
2510
3274
|
} else {
|
|
2511
3275
|
logger.info(` Creating new datasource...`);
|
|
2512
|
-
const datasourceId = await
|
|
3276
|
+
const datasourceId = await createOrRecoverDatasource(client, createParams, datasource.name);
|
|
2513
3277
|
stateManager.updateDatasourceState(datasource.name, datasourceId, datasource);
|
|
2514
3278
|
stats.created++;
|
|
2515
3279
|
}
|
|
@@ -2543,11 +3307,11 @@ async function deployWorkflows(config, client, stateManager, rootDir = process.c
|
|
|
2543
3307
|
try {
|
|
2544
3308
|
logger.info(`Processing: ${workflow.name}`);
|
|
2545
3309
|
const yamlPath = path6.join(rootDir, workflow.definition);
|
|
2546
|
-
if (!
|
|
3310
|
+
if (!fs6.existsSync(yamlPath)) {
|
|
2547
3311
|
throw new Error(`Workflow definition file not found: ${workflow.definition}`);
|
|
2548
3312
|
}
|
|
2549
|
-
const yamlConfigContent =
|
|
2550
|
-
const workflowYaml =
|
|
3313
|
+
const yamlConfigContent = fs6.readFileSync(yamlPath, "utf8");
|
|
3314
|
+
const workflowYaml = yaml5.parse(yamlConfigContent);
|
|
2551
3315
|
const referencesToResolve = [];
|
|
2552
3316
|
for (const assistant of workflowYaml.assistants) {
|
|
2553
3317
|
const isInline = !assistant.assistant_name && assistant.model && assistant.system_prompt;
|
|
@@ -2588,7 +3352,7 @@ async function deployWorkflows(config, client, stateManager, rootDir = process.c
|
|
|
2588
3352
|
});
|
|
2589
3353
|
}
|
|
2590
3354
|
}
|
|
2591
|
-
const yamlConfig =
|
|
3355
|
+
const yamlConfig = yaml5.stringify(workflowYaml);
|
|
2592
3356
|
const workflowYamlChecksum = calculateChecksum(yamlConfigContent);
|
|
2593
3357
|
const configChecksum = calculateWorkflowConfigChecksum(workflow);
|
|
2594
3358
|
const existingState = stateManager.getWorkflowState(workflow.name);
|
|
@@ -2707,7 +3471,7 @@ async function deployResources(options) {
|
|
|
2707
3471
|
logger.info("\u{1F9F9} Checking for orphaned resources...");
|
|
2708
3472
|
const cleanupManager = new CleanupManager(client, stateManager);
|
|
2709
3473
|
const orphaned = cleanupManager.findOrphanedResources(config);
|
|
2710
|
-
const totalOrphaned = orphaned.assistants.length + orphaned.datasources.length + orphaned.workflows.length;
|
|
3474
|
+
const totalOrphaned = orphaned.assistants.length + orphaned.datasources.length + orphaned.workflows.length + orphaned.skills.length;
|
|
2711
3475
|
let deleted = 0;
|
|
2712
3476
|
if (totalOrphaned > 0) {
|
|
2713
3477
|
logger.info(`
|
|
@@ -2721,12 +3485,15 @@ async function deployResources(options) {
|
|
|
2721
3485
|
if (orphaned.workflows.length > 0) {
|
|
2722
3486
|
logger.info(` \u2022 ${orphaned.workflows.length} workflow(s)`);
|
|
2723
3487
|
}
|
|
3488
|
+
if (orphaned.skills.length > 0) {
|
|
3489
|
+
logger.info(` \u2022 ${orphaned.skills.length} skill(s)`);
|
|
3490
|
+
}
|
|
2724
3491
|
if (process.env.SAMPLE_DEPLOY === "1") {
|
|
2725
3492
|
logger.info("\n\u{1F50E} SAMPLE_DEPLOY=1 -> Skipping orphan deletion (simulation / partial deploy mode)\n");
|
|
2726
3493
|
} else if (prune) {
|
|
2727
3494
|
logger.info("\n\u{1F5D1}\uFE0F Deleting orphaned resources (removed from config)...\n");
|
|
2728
3495
|
const cleanupResult = await cleanupManager.deleteOrphanedResources(orphaned);
|
|
2729
|
-
deleted = cleanupResult.deleted.assistants.length + cleanupResult.deleted.datasources.length + cleanupResult.deleted.workflows.length;
|
|
3496
|
+
deleted = cleanupResult.deleted.assistants.length + cleanupResult.deleted.datasources.length + cleanupResult.deleted.workflows.length + cleanupResult.deleted.skills.length;
|
|
2730
3497
|
if (deleted > 0) {
|
|
2731
3498
|
logger.info(`
|
|
2732
3499
|
\u2713 Deleted ${deleted} orphaned resource(s)
|
|
@@ -2753,6 +3520,11 @@ async function deployResources(options) {
|
|
|
2753
3520
|
updated += datasourceStats.updated;
|
|
2754
3521
|
unchanged += datasourceStats.unchanged;
|
|
2755
3522
|
failed += datasourceStats.failed;
|
|
3523
|
+
const skillStats = await deploySkills(config, client, loader, stateManager);
|
|
3524
|
+
created += skillStats.created;
|
|
3525
|
+
updated += skillStats.updated;
|
|
3526
|
+
unchanged += skillStats.unchanged;
|
|
3527
|
+
failed += skillStats.failed;
|
|
2756
3528
|
const assistantStats = await deployAssistants(config, client, loader, stateManager);
|
|
2757
3529
|
created += assistantStats.created;
|
|
2758
3530
|
updated += assistantStats.updated;
|
|
@@ -2790,10 +3562,37 @@ async function main2(options) {
|
|
|
2790
3562
|
try {
|
|
2791
3563
|
await deployResources(options);
|
|
2792
3564
|
process.exit(0);
|
|
2793
|
-
} catch {
|
|
3565
|
+
} catch (error) {
|
|
3566
|
+
logger.error("\n\u274C Deployment failed:");
|
|
3567
|
+
logger.error(error instanceof Error ? error.message : String(error));
|
|
2794
3568
|
process.exit(1);
|
|
2795
3569
|
}
|
|
2796
3570
|
}
|
|
3571
|
+
init_logger();
|
|
3572
|
+
|
|
3573
|
+
// src/lib/terminalPromptHelpers.ts
|
|
3574
|
+
init_commandSpinner();
|
|
3575
|
+
async function promptUser(question, defaultValue) {
|
|
3576
|
+
const rl = readline.createInterface({
|
|
3577
|
+
input: process.stdin,
|
|
3578
|
+
output: process.stdout
|
|
3579
|
+
});
|
|
3580
|
+
const displayQuestion = defaultValue ? `${question} (${defaultValue}): ` : `${question}: `;
|
|
3581
|
+
const answer = await pauseSpinnerWhile(
|
|
3582
|
+
() => new Promise((resolve6) => {
|
|
3583
|
+
rl.question(displayQuestion, resolve6);
|
|
3584
|
+
})
|
|
3585
|
+
);
|
|
3586
|
+
rl.close();
|
|
3587
|
+
const trimmedAnswer = answer.trim();
|
|
3588
|
+
return trimmedAnswer || defaultValue || "";
|
|
3589
|
+
}
|
|
3590
|
+
async function confirmOverwrite(filename) {
|
|
3591
|
+
const answer = await promptUser(`File '${filename}' already exists. Overwrite? (y/n)`);
|
|
3592
|
+
return answer.toLowerCase() === "y";
|
|
3593
|
+
}
|
|
3594
|
+
|
|
3595
|
+
// src/destroy.ts
|
|
2797
3596
|
async function destroyResources(options) {
|
|
2798
3597
|
logger.info("\u{1F5D1}\uFE0F Destroy all managed resources\n");
|
|
2799
3598
|
logger.info("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n");
|
|
@@ -2805,7 +3604,7 @@ async function destroyResources(options) {
|
|
|
2805
3604
|
logger.info(`Project: ${config.project.name}
|
|
2806
3605
|
`);
|
|
2807
3606
|
const managed = stateManager.getAllManagedResources();
|
|
2808
|
-
const total = managed.assistants.length + managed.datasources.length + managed.workflows.length;
|
|
3607
|
+
const total = managed.assistants.length + managed.datasources.length + managed.workflows.length + managed.skills.length;
|
|
2809
3608
|
if (total === 0) {
|
|
2810
3609
|
logger.info("\u2713 No managed resources found in state\n");
|
|
2811
3610
|
logger.info("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n");
|
|
@@ -2831,21 +3630,19 @@ async function destroyResources(options) {
|
|
|
2831
3630
|
logger.info(` \u2022 ${name}`);
|
|
2832
3631
|
}
|
|
2833
3632
|
}
|
|
3633
|
+
if (managed.skills.length > 0) {
|
|
3634
|
+
logger.info(` \u{1F3AF} Skills: ${managed.skills.length}`);
|
|
3635
|
+
for (const name of managed.skills) {
|
|
3636
|
+
logger.info(` \u2022 ${name}`);
|
|
3637
|
+
}
|
|
3638
|
+
}
|
|
2834
3639
|
logger.warn("\n\u26A0\uFE0F WARNING: This will DELETE all resources listed above!");
|
|
2835
3640
|
logger.warn("\u26A0\uFE0F These resources were created through IaC (in state.json)\n");
|
|
2836
3641
|
logger.info("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n");
|
|
2837
3642
|
if (force) {
|
|
2838
3643
|
logger.info("\u{1F680} --force flag detected, skipping confirmation\n");
|
|
2839
3644
|
} else {
|
|
2840
|
-
const
|
|
2841
|
-
const rl = readline2.createInterface({
|
|
2842
|
-
input: process.stdin,
|
|
2843
|
-
output: process.stdout
|
|
2844
|
-
});
|
|
2845
|
-
const answer = await new Promise((resolve6) => {
|
|
2846
|
-
rl.question('Type "destroy" to confirm deletion: ', resolve6);
|
|
2847
|
-
});
|
|
2848
|
-
rl.close();
|
|
3645
|
+
const answer = await promptUser('Type "destroy" to confirm deletion');
|
|
2849
3646
|
if (answer.trim().toLowerCase() !== "destroy") {
|
|
2850
3647
|
logger.info("\n\u274C Destruction cancelled\n");
|
|
2851
3648
|
return;
|
|
@@ -2855,7 +3652,7 @@ async function destroyResources(options) {
|
|
|
2855
3652
|
const client = await createClient(config);
|
|
2856
3653
|
const cleanupManager = new CleanupManager(client, stateManager);
|
|
2857
3654
|
const result = await cleanupManager.deleteOrphanedResources(managed);
|
|
2858
|
-
const totalDeleted = result.deleted.assistants.length + result.deleted.datasources.length + result.deleted.workflows.length;
|
|
3655
|
+
const totalDeleted = result.deleted.assistants.length + result.deleted.datasources.length + result.deleted.workflows.length + result.deleted.skills.length;
|
|
2859
3656
|
logger.info("\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n");
|
|
2860
3657
|
logger.info("\u{1F4CA} Destruction Summary:\n");
|
|
2861
3658
|
logger.info(` \u2705 Deleted: ${totalDeleted}`);
|
|
@@ -2888,27 +3685,349 @@ async function main3(options) {
|
|
|
2888
3685
|
process.exit(1);
|
|
2889
3686
|
}
|
|
2890
3687
|
}
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
3688
|
+
|
|
3689
|
+
// src/import.ts
|
|
3690
|
+
init_converters();
|
|
3691
|
+
init_fileUtils();
|
|
3692
|
+
init_logger();
|
|
3693
|
+
init_checksumUtils();
|
|
3694
|
+
init_codemieConfigChecksums();
|
|
3695
|
+
var VALID_RESOURCE_TYPES = ["assistant", "datasource", "workflow", "skill"];
|
|
3696
|
+
async function findAssistant(client, slug) {
|
|
3697
|
+
try {
|
|
3698
|
+
const assistant = await withTimeout(
|
|
3699
|
+
client.assistants.getBySlug(slug),
|
|
3700
|
+
TIMEOUTS_MS.ASSISTANT_FETCH,
|
|
3701
|
+
`Timeout fetching assistant by slug "${slug}"`
|
|
3702
|
+
);
|
|
3703
|
+
if (assistant) {
|
|
3704
|
+
return assistant;
|
|
3705
|
+
}
|
|
3706
|
+
} catch {
|
|
3707
|
+
}
|
|
3708
|
+
const match = await findResourceByName2((params) => client.assistants.list(params), slug, "assistant");
|
|
3709
|
+
return withTimeout(
|
|
3710
|
+
client.assistants.get(match.id),
|
|
3711
|
+
TIMEOUTS_MS.ASSISTANT_FETCH,
|
|
3712
|
+
`Timeout fetching assistant "${match.id}"`
|
|
3713
|
+
);
|
|
3714
|
+
}
|
|
3715
|
+
async function findDatasource(client, name) {
|
|
3716
|
+
const match = await findResourceByName2((params) => client.datasources.list(params), name, "datasource");
|
|
3717
|
+
return withTimeout(
|
|
3718
|
+
client.datasources.get(match.id),
|
|
3719
|
+
TIMEOUTS_MS.DATASOURCE_FETCH,
|
|
3720
|
+
`Timeout fetching datasource "${match.id}"`
|
|
3721
|
+
);
|
|
3722
|
+
}
|
|
3723
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
3724
|
+
async function findWorkflow(client, name) {
|
|
3725
|
+
if (UUID_RE.test(name)) {
|
|
3726
|
+
return withTimeout(client.workflows.get(name), TIMEOUTS_MS.WORKFLOW_FETCH, `Timeout fetching workflow "${name}"`);
|
|
3727
|
+
}
|
|
3728
|
+
let match = null;
|
|
3729
|
+
try {
|
|
3730
|
+
match = await findResourceByName2((params) => client.workflows.list(params), name, "workflow");
|
|
3731
|
+
} catch {
|
|
3732
|
+
}
|
|
3733
|
+
if (match) {
|
|
3734
|
+
return withTimeout(
|
|
3735
|
+
client.workflows.get(match.id),
|
|
3736
|
+
TIMEOUTS_MS.WORKFLOW_FETCH,
|
|
3737
|
+
`Timeout fetching workflow "${match.id}"`
|
|
3738
|
+
);
|
|
3739
|
+
}
|
|
3740
|
+
throw new Error(
|
|
3741
|
+
`workflow "${name}" not found on platform via name search. If you know its ID, pass the UUID directly instead of the name.`
|
|
3742
|
+
);
|
|
3743
|
+
}
|
|
3744
|
+
async function findSkill(client, name) {
|
|
3745
|
+
const match = await findResourceByName2((params) => client.skills.list(params), name, "skill");
|
|
3746
|
+
return withTimeout(client.skills.get(match.id), TIMEOUTS_MS.SKILL_FETCH, `Timeout fetching skill "${match.id}"`);
|
|
3747
|
+
}
|
|
3748
|
+
async function findResourceByName2(fetchPage, name, resourceType) {
|
|
3749
|
+
const allResources = [];
|
|
3750
|
+
let page = 0;
|
|
3751
|
+
let hasMore = true;
|
|
3752
|
+
while (hasMore) {
|
|
3753
|
+
const resources = await fetchPage({
|
|
3754
|
+
per_page: PAGINATION.DEFAULT_PAGE_SIZE,
|
|
3755
|
+
page
|
|
3756
|
+
});
|
|
3757
|
+
allResources.push(...resources);
|
|
3758
|
+
if (resources.length < PAGINATION.DEFAULT_PAGE_SIZE) {
|
|
3759
|
+
hasMore = false;
|
|
3760
|
+
} else {
|
|
3761
|
+
page++;
|
|
3762
|
+
}
|
|
3763
|
+
}
|
|
3764
|
+
const lowerName = name.toLowerCase();
|
|
3765
|
+
const matches = allResources.filter((r) => r.name.toLowerCase() === lowerName);
|
|
3766
|
+
if (matches.length === 0) {
|
|
3767
|
+
throw new Error(
|
|
3768
|
+
`${resourceType} "${name}" not found on platform. Found ${allResources.length} ${resourceType}(s) total.`
|
|
3769
|
+
);
|
|
3770
|
+
}
|
|
3771
|
+
if (matches.length > 1) {
|
|
3772
|
+
const names = matches.map((m) => ` - ${m.name} (${m.id})`).join("\n");
|
|
3773
|
+
throw new Error(`Multiple ${resourceType}s matching "${name}" found:
|
|
3774
|
+
${names}
|
|
3775
|
+
Please use a more specific name.`);
|
|
3776
|
+
}
|
|
3777
|
+
return matches[0];
|
|
3778
|
+
}
|
|
3779
|
+
function checkResourceExists2(config, resourceType, name) {
|
|
3780
|
+
const pluralType = resourceType === "assistant" ? "assistants" : resourceType === "datasource" ? "datasources" : resourceType === "workflow" ? "workflows" : "skills";
|
|
3781
|
+
const resources = config.resources[pluralType] || [];
|
|
3782
|
+
return resources.some((r) => r.name.toLowerCase() === name.toLowerCase());
|
|
3783
|
+
}
|
|
3784
|
+
function writeResourceFiles(rootDir, resourceType, resource, apiResponse) {
|
|
3785
|
+
const safeName = sanitizeFileName(resource.name);
|
|
3786
|
+
if (resourceType === "workflow") {
|
|
3787
|
+
const workflow = apiResponse;
|
|
3788
|
+
const workflowResource = resource;
|
|
3789
|
+
if (workflow.yaml_config) {
|
|
3790
|
+
const defFileName = `${safeName}.yaml`;
|
|
3791
|
+
const defPath = path6.join(rootDir, "workflows", defFileName);
|
|
3792
|
+
ensureDirectoryExists(defPath);
|
|
3793
|
+
fs6.writeFileSync(defPath, workflow.yaml_config, "utf8");
|
|
3794
|
+
logger.info(` \u{1F4CB} Saved workflow definition: workflows/${defFileName}`);
|
|
3795
|
+
workflowResource.definition = `workflows/${defFileName}`;
|
|
3796
|
+
}
|
|
3797
|
+
const configPath = path6.join(rootDir, "configs", "workflows", `${safeName}.yaml`);
|
|
3798
|
+
const relativeConfigPath = `configs/workflows/${safeName}.yaml`;
|
|
3799
|
+
ensureDirectoryExists(configPath);
|
|
3800
|
+
const yamlContent2 = yaml5.stringify(workflowResource, { lineWidth: 120 });
|
|
3801
|
+
fs6.writeFileSync(configPath, yamlContent2, "utf8");
|
|
3802
|
+
logger.info(` \u{1F4C4} Saved workflow config: ${relativeConfigPath}`);
|
|
3803
|
+
return relativeConfigPath;
|
|
3804
|
+
}
|
|
3805
|
+
const pluralType = resourceType === "assistant" ? "assistants" : resourceType === "datasource" ? "datasources" : "skills";
|
|
3806
|
+
const resourceDir = path6.join(rootDir, "configs", "imported-resources", pluralType);
|
|
3807
|
+
const resourceFilePath = path6.join(resourceDir, `${safeName}.yaml`);
|
|
3808
|
+
const relativeResourcePath = `configs/imported-resources/${pluralType}/${safeName}.yaml`;
|
|
3809
|
+
if (resourceType === "assistant") {
|
|
3810
|
+
const assistant = apiResponse;
|
|
3811
|
+
const assistantResource = resource;
|
|
3812
|
+
if (assistant.system_prompt) {
|
|
3813
|
+
const promptFileName = assistant.slug || safeName;
|
|
3814
|
+
const promptPath = path6.join(rootDir, "system_prompts", `${promptFileName}.prompt.md`);
|
|
3815
|
+
ensureDirectoryExists(promptPath);
|
|
3816
|
+
fs6.writeFileSync(promptPath, assistant.system_prompt, "utf8");
|
|
3817
|
+
logger.info(` \u{1F4DD} Saved prompt: system_prompts/${promptFileName}.prompt.md`);
|
|
3818
|
+
assistantResource.prompt = `system_prompts/${promptFileName}.prompt.md`;
|
|
3819
|
+
}
|
|
3820
|
+
} else if (resourceType === "skill") {
|
|
3821
|
+
const skill = apiResponse;
|
|
3822
|
+
const skillResource = resource;
|
|
3823
|
+
if (skill.content) {
|
|
3824
|
+
const skillFileName = `${safeName}.skill.md`;
|
|
3825
|
+
const skillPath = path6.join(rootDir, "skills", skillFileName);
|
|
3826
|
+
ensureDirectoryExists(skillPath);
|
|
3827
|
+
fs6.writeFileSync(skillPath, skill.content, "utf8");
|
|
3828
|
+
logger.info(` \u{1F9E0} Saved skill content: skills/${skillFileName}`);
|
|
3829
|
+
skillResource.file = `skills/${skillFileName}`;
|
|
3830
|
+
}
|
|
3831
|
+
}
|
|
3832
|
+
ensureDirectoryExists(resourceFilePath);
|
|
3833
|
+
const yamlContent = yaml5.stringify(resource, { lineWidth: 120 });
|
|
3834
|
+
fs6.writeFileSync(resourceFilePath, yamlContent, "utf8");
|
|
3835
|
+
logger.info(` \u{1F4C4} Saved resource config: ${relativeResourcePath}`);
|
|
3836
|
+
return relativeResourcePath;
|
|
3837
|
+
}
|
|
3838
|
+
function addImportToCodemieYaml(rootDir, codemieConfigPath, resourceType, relativeResourcePath) {
|
|
3839
|
+
const configPath = path6.join(rootDir, codemieConfigPath);
|
|
3840
|
+
if (!fs6.existsSync(configPath)) {
|
|
3841
|
+
throw new Error(`Configuration file not found: ${configPath}`);
|
|
3842
|
+
}
|
|
3843
|
+
const content = fs6.readFileSync(configPath, "utf8");
|
|
3844
|
+
const doc = yaml5.parseDocument(content);
|
|
3845
|
+
const pluralType = resourceType === "assistant" ? "assistants" : resourceType === "datasource" ? "datasources" : resourceType === "workflow" ? "workflows" : "skills";
|
|
3846
|
+
let resourcesNode = doc.get("resources");
|
|
3847
|
+
if (!resourcesNode) {
|
|
3848
|
+
resourcesNode = doc.createNode({});
|
|
3849
|
+
doc.set("resources", resourcesNode);
|
|
3850
|
+
}
|
|
3851
|
+
let typeArray = doc.getIn(["resources", pluralType]);
|
|
3852
|
+
if (!typeArray) {
|
|
3853
|
+
typeArray = doc.createNode([]);
|
|
3854
|
+
doc.setIn(["resources", pluralType], typeArray);
|
|
3855
|
+
}
|
|
3856
|
+
const importDirective = { $import: relativeResourcePath };
|
|
3857
|
+
const existingImports = typeArray.items.filter((item) => {
|
|
3858
|
+
if (yaml5.isMap(item)) {
|
|
3859
|
+
const importValue = item.get("$import");
|
|
3860
|
+
return importValue === relativeResourcePath;
|
|
3861
|
+
}
|
|
3862
|
+
return false;
|
|
2899
3863
|
});
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
3864
|
+
if (existingImports.length > 0) {
|
|
3865
|
+
logger.info(` \u21B7 Import directive already exists in ${codemieConfigPath}`);
|
|
3866
|
+
return;
|
|
3867
|
+
}
|
|
3868
|
+
const newNode = doc.createNode(importDirective);
|
|
3869
|
+
typeArray.add(newNode);
|
|
3870
|
+
fs6.writeFileSync(configPath, doc.toString(), "utf8");
|
|
3871
|
+
logger.info(` \u2705 Added $import to ${codemieConfigPath} \u2192 resources.${pluralType}`);
|
|
2903
3872
|
}
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
3873
|
+
function updateState(stateManager, resourceType, resource, apiResponse) {
|
|
3874
|
+
switch (resourceType) {
|
|
3875
|
+
case "assistant": {
|
|
3876
|
+
const assistant = apiResponse;
|
|
3877
|
+
const assistantResource = resource;
|
|
3878
|
+
stateManager.updateAssistantState(resource.name, assistant.id, assistant.system_prompt || "", assistantResource);
|
|
3879
|
+
break;
|
|
3880
|
+
}
|
|
3881
|
+
case "datasource": {
|
|
3882
|
+
const datasource = apiResponse;
|
|
3883
|
+
const datasourceResource = resource;
|
|
3884
|
+
stateManager.updateDatasourceState(resource.name, datasource.id, datasourceResource);
|
|
3885
|
+
break;
|
|
3886
|
+
}
|
|
3887
|
+
case "workflow": {
|
|
3888
|
+
const workflow = apiResponse;
|
|
3889
|
+
const workflowResource = resource;
|
|
3890
|
+
stateManager.updateWorkflowState(
|
|
3891
|
+
resource.name,
|
|
3892
|
+
workflow.id,
|
|
3893
|
+
calculateChecksum(workflow.yaml_config || ""),
|
|
3894
|
+
calculateWorkflowConfigChecksum(workflowResource)
|
|
3895
|
+
);
|
|
3896
|
+
break;
|
|
3897
|
+
}
|
|
3898
|
+
case "skill": {
|
|
3899
|
+
const skill = apiResponse;
|
|
3900
|
+
const skillResource = resource;
|
|
3901
|
+
stateManager.updateSkillState(resource.name, skill.id, skill.content || "", skillResource);
|
|
3902
|
+
break;
|
|
3903
|
+
}
|
|
3904
|
+
}
|
|
2907
3905
|
}
|
|
2908
|
-
|
|
2909
|
-
|
|
3906
|
+
function buildIntegrationAliasMap(config) {
|
|
3907
|
+
const map = /* @__PURE__ */ new Map();
|
|
3908
|
+
for (const integration of config.imported?.integrations || []) {
|
|
3909
|
+
if (integration.id && integration.alias) {
|
|
3910
|
+
map.set(integration.id, integration.alias);
|
|
3911
|
+
}
|
|
3912
|
+
}
|
|
3913
|
+
return map;
|
|
3914
|
+
}
|
|
3915
|
+
async function importResource(options) {
|
|
3916
|
+
const { appConfig, resourceType, slug } = options;
|
|
3917
|
+
if (!VALID_RESOURCE_TYPES.includes(resourceType)) {
|
|
3918
|
+
throw new Error(`Invalid resource type: "${resourceType}". Must be one of: ${VALID_RESOURCE_TYPES.join(", ")}`);
|
|
3919
|
+
}
|
|
3920
|
+
const configLoader = new CodemieConfigLoader(appConfig);
|
|
3921
|
+
const config = configLoader.loadConfig();
|
|
3922
|
+
const rootDir = appConfig.rootDir;
|
|
3923
|
+
const stateManager = new StateManager(appConfig);
|
|
3924
|
+
if (checkResourceExists2(config, resourceType, slug)) {
|
|
3925
|
+
throw new Error(
|
|
3926
|
+
`A ${resourceType} named "${slug}" already exists in the configuration. Remove it first or use a different name.`
|
|
3927
|
+
);
|
|
3928
|
+
}
|
|
3929
|
+
logger.info(`
|
|
3930
|
+
\u{1F50D} Importing ${resourceType}: "${slug}"
|
|
3931
|
+
`);
|
|
3932
|
+
logger.info(" Connecting to CodeMie platform...");
|
|
3933
|
+
const client = await createClient(config);
|
|
3934
|
+
const integrationAliasMap = buildIntegrationAliasMap(config);
|
|
3935
|
+
let resource;
|
|
3936
|
+
let apiResponse;
|
|
3937
|
+
switch (resourceType) {
|
|
3938
|
+
case "assistant": {
|
|
3939
|
+
logger.info(` Searching for assistant "${slug}"...`);
|
|
3940
|
+
const assistant = await findAssistant(client, slug);
|
|
3941
|
+
logger.info(` \u2713 Found: ${assistant.name} (${assistant.id})`);
|
|
3942
|
+
apiResponse = assistant;
|
|
3943
|
+
resource = assistantResponseToResource(assistant);
|
|
3944
|
+
if (integrationAliasMap.size > 0) {
|
|
3945
|
+
const { transformToolkits: transformToolkits2, transformMcpServer: transformMcpServer2 } = await Promise.resolve().then(() => (init_backupTransformers(), backupTransformers_exports));
|
|
3946
|
+
const transformedToolkits = transformToolkits2(assistant.toolkits, integrationAliasMap);
|
|
3947
|
+
const transformedMcpServers = assistant.mcp_servers?.map((mcp) => transformMcpServer2(mcp, integrationAliasMap));
|
|
3948
|
+
if (transformedToolkits) {
|
|
3949
|
+
resource.toolkits = transformedToolkits;
|
|
3950
|
+
}
|
|
3951
|
+
if (transformedMcpServers) {
|
|
3952
|
+
resource.mcp_servers = transformedMcpServers;
|
|
3953
|
+
}
|
|
3954
|
+
}
|
|
3955
|
+
const assistantData = assistant;
|
|
3956
|
+
if (assistantData.skill_ids && assistantData.skill_ids.length > 0) {
|
|
3957
|
+
logger.info(` Resolving ${assistantData.skill_ids.length} skill ID(s)...`);
|
|
3958
|
+
const skillNames = [];
|
|
3959
|
+
for (const skillId of assistantData.skill_ids) {
|
|
3960
|
+
try {
|
|
3961
|
+
const skill = await withTimeout(
|
|
3962
|
+
client.skills.get(skillId),
|
|
3963
|
+
TIMEOUTS_MS.ASSISTANT_FETCH,
|
|
3964
|
+
`Timeout fetching skill "${skillId}"`
|
|
3965
|
+
);
|
|
3966
|
+
skillNames.push(skill.name);
|
|
3967
|
+
logger.info(` \u2713 Resolved skill ${skillId.slice(0, 8)}... \u2192 "${skill.name}"`);
|
|
3968
|
+
} catch {
|
|
3969
|
+
logger.warn(` \u26A0\uFE0F Could not resolve skill ID "${skillId}" \u2014 skipping`);
|
|
3970
|
+
}
|
|
3971
|
+
}
|
|
3972
|
+
if (skillNames.length > 0) {
|
|
3973
|
+
resource.skills = skillNames;
|
|
3974
|
+
}
|
|
3975
|
+
}
|
|
3976
|
+
break;
|
|
3977
|
+
}
|
|
3978
|
+
case "datasource": {
|
|
3979
|
+
logger.info(` Searching for datasource "${slug}"...`);
|
|
3980
|
+
const datasource = await findDatasource(client, slug);
|
|
3981
|
+
logger.info(` \u2713 Found: ${datasource.name} (${datasource.id})`);
|
|
3982
|
+
apiResponse = datasource;
|
|
3983
|
+
const settingId = datasource.setting_id || "";
|
|
3984
|
+
const integrationAlias = integrationAliasMap.get(settingId);
|
|
3985
|
+
resource = datasourceResponseToResource(datasource, integrationAlias);
|
|
3986
|
+
break;
|
|
3987
|
+
}
|
|
3988
|
+
case "workflow": {
|
|
3989
|
+
logger.info(` Searching for workflow "${slug}"...`);
|
|
3990
|
+
const workflow = await findWorkflow(client, slug);
|
|
3991
|
+
logger.info(` \u2713 Found: ${workflow.name} (${workflow.id})`);
|
|
3992
|
+
if (workflow.name !== slug && checkResourceExists2(config, resourceType, workflow.name)) {
|
|
3993
|
+
throw new Error(
|
|
3994
|
+
`A ${resourceType} named "${workflow.name}" already exists in the configuration. Remove it first or use a different name.`
|
|
3995
|
+
);
|
|
3996
|
+
}
|
|
3997
|
+
apiResponse = workflow;
|
|
3998
|
+
resource = workflowResponseToResource(workflow);
|
|
3999
|
+
break;
|
|
4000
|
+
}
|
|
4001
|
+
case "skill": {
|
|
4002
|
+
logger.info(` Searching for skill "${slug}"...`);
|
|
4003
|
+
const skill = await findSkill(client, slug);
|
|
4004
|
+
logger.info(` \u2713 Found: ${skill.name} (${skill.id})`);
|
|
4005
|
+
apiResponse = skill;
|
|
4006
|
+
resource = skillResponseToResource(skill);
|
|
4007
|
+
break;
|
|
4008
|
+
}
|
|
4009
|
+
}
|
|
4010
|
+
const relativeResourcePath = writeResourceFiles(rootDir, resourceType, resource, apiResponse);
|
|
4011
|
+
addImportToCodemieYaml(rootDir, appConfig.codemieConfig, resourceType, relativeResourcePath);
|
|
4012
|
+
updateState(stateManager, resourceType, resource, apiResponse);
|
|
4013
|
+
logger.info(`
|
|
4014
|
+
\u2705 Successfully imported ${resourceType} "${resource.name}"
|
|
4015
|
+
`);
|
|
4016
|
+
}
|
|
4017
|
+
async function main4(options) {
|
|
4018
|
+
try {
|
|
4019
|
+
await importResource(options);
|
|
4020
|
+
} catch (error) {
|
|
4021
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4022
|
+
logger.error(`
|
|
4023
|
+
\u274C Import failed: ${errorMessage}
|
|
4024
|
+
`);
|
|
4025
|
+
process.exitCode = 1;
|
|
4026
|
+
}
|
|
4027
|
+
}
|
|
4028
|
+
init_logger();
|
|
2910
4029
|
function fileExists(filePath) {
|
|
2911
|
-
return
|
|
4030
|
+
return fs6.existsSync(filePath);
|
|
2912
4031
|
}
|
|
2913
4032
|
function generateEnvTemplate() {
|
|
2914
4033
|
return `# Codemie API Configuration
|
|
@@ -2994,7 +4113,7 @@ async function saveConfigFile(config, defaultFilename = "config.json") {
|
|
|
2994
4113
|
throw new Error(`Cancelled: Configuration file not saved.`);
|
|
2995
4114
|
}
|
|
2996
4115
|
}
|
|
2997
|
-
|
|
4116
|
+
fs6.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
2998
4117
|
return configFilename;
|
|
2999
4118
|
}
|
|
3000
4119
|
function suggestConfigUsage(configFileName) {
|
|
@@ -3008,6 +4127,7 @@ function suggestConfigUsage(configFileName) {
|
|
|
3008
4127
|
}
|
|
3009
4128
|
|
|
3010
4129
|
// src/init.ts
|
|
4130
|
+
init_logger();
|
|
3011
4131
|
async function initProject(options) {
|
|
3012
4132
|
logger.info("\u{1F680} Initialize Codemie IaC Project\n\n");
|
|
3013
4133
|
try {
|
|
@@ -3066,7 +4186,7 @@ async function initProject(options) {
|
|
|
3066
4186
|
logger.info("\n\u{1F4C1} Creating configuration files...\n");
|
|
3067
4187
|
if (shouldCreateEnv) {
|
|
3068
4188
|
const envContent = generateEnvTemplate();
|
|
3069
|
-
|
|
4189
|
+
fs6.writeFileSync(envPath, envContent, "utf-8");
|
|
3070
4190
|
logger.info(`\u2713 Created .env`);
|
|
3071
4191
|
logger.warn(` \u26A0\uFE0F Remember to fill in your actual credentials!`);
|
|
3072
4192
|
logger.warn(` \u26A0\uFE0F DO NOT commit .env to Git!
|
|
@@ -3074,7 +4194,7 @@ async function initProject(options) {
|
|
|
3074
4194
|
}
|
|
3075
4195
|
if (shouldCreateConfig) {
|
|
3076
4196
|
const configContent = generateMinimalCodemieYaml(projectName, projectDescription);
|
|
3077
|
-
|
|
4197
|
+
fs6.writeFileSync(configPath, configContent, "utf-8");
|
|
3078
4198
|
logger.info(`\u2713 Created ${appConfig.codemieConfig}`);
|
|
3079
4199
|
logger.info(` See examples/ directory for detailed configuration examples
|
|
3080
4200
|
|
|
@@ -3101,15 +4221,24 @@ async function initProject(options) {
|
|
|
3101
4221
|
process.exit(1);
|
|
3102
4222
|
}
|
|
3103
4223
|
}
|
|
3104
|
-
async function
|
|
4224
|
+
async function main5(options) {
|
|
3105
4225
|
await initProject(options);
|
|
3106
4226
|
}
|
|
4227
|
+
|
|
4228
|
+
// src/cli/index.ts
|
|
4229
|
+
init_commandSpinner();
|
|
4230
|
+
init_logger();
|
|
4231
|
+
|
|
4232
|
+
// src/lib/previewUtils.ts
|
|
4233
|
+
init_checksumUtils();
|
|
4234
|
+
init_codemieConfigChecksums();
|
|
4235
|
+
init_converters();
|
|
3107
4236
|
async function previewResource(resource, resourceType, getState, checkExists, calculateChecksums) {
|
|
3108
4237
|
const existingState = getState(resource.name);
|
|
3109
4238
|
if (existingState) {
|
|
3110
4239
|
const existsOnPlatform = await checkExists();
|
|
3111
4240
|
if (existsOnPlatform) {
|
|
3112
|
-
const { hasChanged, updateDetails } = calculateChecksums();
|
|
4241
|
+
const { hasChanged, updateDetails } = await calculateChecksums();
|
|
3113
4242
|
return hasChanged ? {
|
|
3114
4243
|
type: "update",
|
|
3115
4244
|
resourceType,
|
|
@@ -3129,7 +4258,7 @@ async function previewResource(resource, resourceType, getState, checkExists, ca
|
|
|
3129
4258
|
};
|
|
3130
4259
|
}
|
|
3131
4260
|
} else {
|
|
3132
|
-
const { createDetails } = calculateChecksums();
|
|
4261
|
+
const { createDetails } = await calculateChecksums();
|
|
3133
4262
|
return {
|
|
3134
4263
|
type: "create",
|
|
3135
4264
|
resourceType,
|
|
@@ -3167,6 +4296,42 @@ function previewOrphanedResources(orphaned, stateManager) {
|
|
|
3167
4296
|
details: workflowState ? `Removed from config (ID: ${workflowState.id})` : "Removed from config"
|
|
3168
4297
|
});
|
|
3169
4298
|
}
|
|
4299
|
+
for (const name of orphaned.skills || []) {
|
|
4300
|
+
const skillState = stateManager.getSkillState(name);
|
|
4301
|
+
changes.push({
|
|
4302
|
+
type: "delete",
|
|
4303
|
+
resourceType: "skill",
|
|
4304
|
+
name,
|
|
4305
|
+
details: skillState ? `Removed from config (ID: ${skillState.id})` : "Removed from config"
|
|
4306
|
+
});
|
|
4307
|
+
}
|
|
4308
|
+
return changes;
|
|
4309
|
+
}
|
|
4310
|
+
async function previewSkills(skills, loader, stateManager, client) {
|
|
4311
|
+
const changes = [];
|
|
4312
|
+
for (const skill of skills) {
|
|
4313
|
+
const content = loader.loadSkillContent(skill.file);
|
|
4314
|
+
const contentChecksum = calculateChecksum(content);
|
|
4315
|
+
const configChecksum = calculateSkillConfigChecksum(skill);
|
|
4316
|
+
const change = await previewResource(
|
|
4317
|
+
skill,
|
|
4318
|
+
"skill",
|
|
4319
|
+
(name) => stateManager.getSkillState(name),
|
|
4320
|
+
() => checkSkillExists(client, skill.name, stateManager),
|
|
4321
|
+
() => {
|
|
4322
|
+
const existingState = stateManager.getSkillState(skill.name);
|
|
4323
|
+
const hasChanged = existingState ? existingState.contentChecksum !== contentChecksum || existingState.configChecksum !== configChecksum : false;
|
|
4324
|
+
return {
|
|
4325
|
+
hasChanged,
|
|
4326
|
+
updateDetails: "Content or configuration changed"
|
|
4327
|
+
};
|
|
4328
|
+
}
|
|
4329
|
+
);
|
|
4330
|
+
if (!change.details && change.type === "create") {
|
|
4331
|
+
change.details = skill.visibility === "public" ? "Visibility: public" : "Visibility: project";
|
|
4332
|
+
}
|
|
4333
|
+
changes.push(change);
|
|
4334
|
+
}
|
|
3170
4335
|
return changes;
|
|
3171
4336
|
}
|
|
3172
4337
|
async function previewAssistants(assistants, loader, stateManager, client) {
|
|
@@ -3205,10 +4370,19 @@ async function previewDatasources(datasources, stateManager, client) {
|
|
|
3205
4370
|
"datasource",
|
|
3206
4371
|
(name) => stateManager.getDatasourceState(name),
|
|
3207
4372
|
() => checkDatasourceExists(client, datasource.name, stateManager),
|
|
3208
|
-
() => {
|
|
4373
|
+
async () => {
|
|
3209
4374
|
const configChecksum = calculateDatasourceConfigChecksum(datasource);
|
|
3210
4375
|
const existingState = stateManager.getDatasourceState(datasource.name);
|
|
3211
|
-
|
|
4376
|
+
let hasChanged = existingState ? existingState.configChecksum !== configChecksum : false;
|
|
4377
|
+
if (hasChanged && existingState?.id) {
|
|
4378
|
+
try {
|
|
4379
|
+
const datasourceOnPlatform = await client.datasources.get(existingState.id);
|
|
4380
|
+
const normalizedPlatformDatasource = datasourceResponseToResource(datasourceOnPlatform);
|
|
4381
|
+
const platformChecksum = calculateDatasourceConfigChecksum(normalizedPlatformDatasource);
|
|
4382
|
+
hasChanged = platformChecksum !== configChecksum;
|
|
4383
|
+
} catch {
|
|
4384
|
+
}
|
|
4385
|
+
}
|
|
3212
4386
|
if (hasChanged || datasource.force_reindex) {
|
|
3213
4387
|
return {
|
|
3214
4388
|
hasChanged: true,
|
|
@@ -3229,10 +4403,10 @@ async function previewWorkflows(workflows, stateManager, client, rootDir = proce
|
|
|
3229
4403
|
const changes = [];
|
|
3230
4404
|
for (const workflow of workflows) {
|
|
3231
4405
|
const yamlPath = path6.join(rootDir, workflow.definition);
|
|
3232
|
-
if (!
|
|
4406
|
+
if (!fs6.existsSync(yamlPath)) {
|
|
3233
4407
|
throw new Error(`Workflow definition file not found: ${workflow.definition}`);
|
|
3234
4408
|
}
|
|
3235
|
-
const yamlConfig =
|
|
4409
|
+
const yamlConfig = fs6.readFileSync(yamlPath, "utf8");
|
|
3236
4410
|
const change = await previewResource(
|
|
3237
4411
|
workflow,
|
|
3238
4412
|
"workflow",
|
|
@@ -3267,6 +4441,10 @@ async function previewChanges(appConfig, existingClient) {
|
|
|
3267
4441
|
const cleanupManager = new CleanupManager(client, stateManager);
|
|
3268
4442
|
const orphaned = cleanupManager.findOrphanedResources(config);
|
|
3269
4443
|
changes.push(...previewOrphanedResources(orphaned, stateManager));
|
|
4444
|
+
if (config.resources.skills) {
|
|
4445
|
+
const skillChanges = await previewSkills(config.resources.skills, loader, stateManager, client);
|
|
4446
|
+
changes.push(...skillChanges);
|
|
4447
|
+
}
|
|
3270
4448
|
if (config.resources.assistants) {
|
|
3271
4449
|
const assistantChanges = await previewAssistants(config.resources.assistants, loader, stateManager, client);
|
|
3272
4450
|
changes.push(...assistantChanges);
|
|
@@ -3294,7 +4472,7 @@ async function previewChanges(appConfig, existingClient) {
|
|
|
3294
4472
|
}
|
|
3295
4473
|
};
|
|
3296
4474
|
}
|
|
3297
|
-
async function
|
|
4475
|
+
async function main6(options) {
|
|
3298
4476
|
logger.info("\u{1F4CB} Generating deployment preview...\n");
|
|
3299
4477
|
try {
|
|
3300
4478
|
const { appConfig } = options;
|
|
@@ -3402,10 +4580,10 @@ var DependencyValidator = class {
|
|
|
3402
4580
|
const errors = [];
|
|
3403
4581
|
const visited = /* @__PURE__ */ new Set();
|
|
3404
4582
|
const recursionStack = /* @__PURE__ */ new Set();
|
|
3405
|
-
const dfs = (name,
|
|
4583
|
+
const dfs = (name, path16) => {
|
|
3406
4584
|
if (recursionStack.has(name)) {
|
|
3407
|
-
const cycleStart =
|
|
3408
|
-
const cycle = [...
|
|
4585
|
+
const cycleStart = path16.indexOf(name);
|
|
4586
|
+
const cycle = [...path16.slice(cycleStart), name];
|
|
3409
4587
|
errors.push(`Cyclic dependency detected: ${cycle.join(" \u2192 ")}`);
|
|
3410
4588
|
return true;
|
|
3411
4589
|
}
|
|
@@ -3414,14 +4592,14 @@ var DependencyValidator = class {
|
|
|
3414
4592
|
}
|
|
3415
4593
|
visited.add(name);
|
|
3416
4594
|
recursionStack.add(name);
|
|
3417
|
-
|
|
4595
|
+
path16.push(name);
|
|
3418
4596
|
const assistant = assistantMap.get(name);
|
|
3419
4597
|
const subAssistantRefs = assistant?.sub_assistants || [];
|
|
3420
4598
|
for (const subRef of subAssistantRefs) {
|
|
3421
4599
|
if (!assistantMap.has(subRef)) {
|
|
3422
4600
|
continue;
|
|
3423
4601
|
}
|
|
3424
|
-
if (dfs(subRef, [...
|
|
4602
|
+
if (dfs(subRef, [...path16])) {
|
|
3425
4603
|
return true;
|
|
3426
4604
|
}
|
|
3427
4605
|
}
|
|
@@ -3480,7 +4658,7 @@ var DependencyValidator = class {
|
|
|
3480
4658
|
}
|
|
3481
4659
|
for (const workflow of workflows) {
|
|
3482
4660
|
try {
|
|
3483
|
-
const workflowYaml =
|
|
4661
|
+
const workflowYaml = yaml5.parse(workflow.definition);
|
|
3484
4662
|
if (!workflowYaml.assistants) {
|
|
3485
4663
|
continue;
|
|
3486
4664
|
}
|
|
@@ -3504,6 +4682,9 @@ var DependencyValidator = class {
|
|
|
3504
4682
|
}
|
|
3505
4683
|
};
|
|
3506
4684
|
|
|
4685
|
+
// src/validate.ts
|
|
4686
|
+
init_logger();
|
|
4687
|
+
|
|
3507
4688
|
// src/lib/validationUtils.ts
|
|
3508
4689
|
function isAssistantWithSlug(obj) {
|
|
3509
4690
|
return typeof obj === "object" && obj !== null && "slug" in obj && "name" in obj && typeof obj.slug === "string" && typeof obj.name === "string";
|
|
@@ -3586,11 +4767,11 @@ async function validateConfig(options) {
|
|
|
3586
4767
|
if (config.resources.workflows && config.resources.workflows.length > 0) {
|
|
3587
4768
|
const workflowsWithContent = config.resources.workflows.map((workflow) => {
|
|
3588
4769
|
const workflowPath = path6.resolve(workflow.definition);
|
|
3589
|
-
if (!
|
|
4770
|
+
if (!fs6.existsSync(workflowPath)) {
|
|
3590
4771
|
errors.push(`Workflow definition file not found: ${workflow.definition}`);
|
|
3591
4772
|
return { ...workflow, definition: "" };
|
|
3592
4773
|
}
|
|
3593
|
-
const definition =
|
|
4774
|
+
const definition = fs6.readFileSync(workflowPath, "utf8");
|
|
3594
4775
|
return { ...workflow, definition };
|
|
3595
4776
|
});
|
|
3596
4777
|
const workflowErrors = DependencyValidator.validateWorkflowAssistantReferences(
|
|
@@ -3609,7 +4790,7 @@ async function validateConfig(options) {
|
|
|
3609
4790
|
}
|
|
3610
4791
|
return { success: errors.length === 0, errors };
|
|
3611
4792
|
}
|
|
3612
|
-
async function
|
|
4793
|
+
async function main7(options) {
|
|
3613
4794
|
const { checkApi = false, appConfig } = options;
|
|
3614
4795
|
logger.info("\u{1F50D} Validating configuration...");
|
|
3615
4796
|
if (checkApi) {
|
|
@@ -3656,31 +4837,47 @@ async function main6(options) {
|
|
|
3656
4837
|
program.name("codemie").description("Infrastructure as Code for Codemie platform").version(package_default.version);
|
|
3657
4838
|
program.command("init").description("Initialize a new Codemie IaC project").action(() => {
|
|
3658
4839
|
const appConfig = loadAppConfig();
|
|
3659
|
-
void
|
|
4840
|
+
void main5({ appConfig });
|
|
3660
4841
|
});
|
|
3661
|
-
program.command("deploy").description("Deploy resources to Codemie platform").option("-p, --prune", "Delete orphaned resources").option("-c, --config <path>", "Configuration file").action((options) => {
|
|
4842
|
+
program.command("deploy").description("Deploy resources to Codemie platform").option("-p, --prune", "Delete orphaned resources").option("-c, --config <path>", "Configuration file").action(async (options) => {
|
|
3662
4843
|
const configPath = options.config ? path6__default.resolve(options.config) : void 0;
|
|
3663
4844
|
const appConfig = loadAppConfig(configPath);
|
|
3664
|
-
|
|
4845
|
+
await runCommandWithSpinner("Deploying Codemie resources", () => main2({ appConfig, prune: options.prune }));
|
|
3665
4846
|
});
|
|
3666
|
-
program.command("backup").description("Backup all resources from Codemie platform").option("-c, --config <path>", "Configuration file").action((options) => {
|
|
4847
|
+
program.command("backup").description("Backup all resources from Codemie platform").option("-c, --config <path>", "Configuration file").action(async (options) => {
|
|
3667
4848
|
const configPath = options.config ? path6__default.resolve(options.config) : void 0;
|
|
3668
4849
|
const appConfig = loadAppConfig(configPath);
|
|
3669
|
-
|
|
4850
|
+
await runCommandWithSpinner("Backing up Codemie resources", () => main({ appConfig }));
|
|
3670
4851
|
});
|
|
3671
|
-
program.command("destroy").description("Destroy all IaC-managed resources").option("-f, --force", "Skip confirmation prompt").option("-c, --config <path>", "Configuration file").action((options) => {
|
|
4852
|
+
program.command("destroy").description("Destroy all IaC-managed resources").option("-f, --force", "Skip confirmation prompt").option("-c, --config <path>", "Configuration file").action(async (options) => {
|
|
3672
4853
|
const configPath = options.config ? path6__default.resolve(options.config) : void 0;
|
|
3673
4854
|
const appConfig = loadAppConfig(configPath);
|
|
3674
|
-
|
|
4855
|
+
await runCommandWithSpinner("Destroying Codemie resources", () => main3({ appConfig, force: options.force }));
|
|
3675
4856
|
});
|
|
3676
|
-
program.command("preview").description("Preview changes without applying them").option("-c, --config <path>", "Configuration file").action((options) => {
|
|
4857
|
+
program.command("preview").description("Preview changes without applying them").option("-c, --config <path>", "Configuration file").action(async (options) => {
|
|
3677
4858
|
const appConfigPath = options.config ? path6__default.resolve(options.config) : void 0;
|
|
3678
4859
|
const appConfig = loadAppConfig(appConfigPath);
|
|
3679
|
-
|
|
4860
|
+
await runCommandWithSpinner("Generating Codemie preview", () => main6({ appConfig }));
|
|
4861
|
+
});
|
|
4862
|
+
program.command("validate").description("Validate configuration").option("-a, --check-api", "Check for API conflicts").option("-c, --config <path>", "Configuration file").action(async (options) => {
|
|
4863
|
+
const configPath = options.config ? path6__default.resolve(options.config) : void 0;
|
|
4864
|
+
const appConfig = loadAppConfig(configPath);
|
|
4865
|
+
await runCommandWithSpinner(
|
|
4866
|
+
"Validating Codemie configuration",
|
|
4867
|
+
() => main7({ appConfig, checkApi: options.checkApi })
|
|
4868
|
+
);
|
|
3680
4869
|
});
|
|
3681
|
-
program.command("
|
|
4870
|
+
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 to import").option("-c, --config <path>", "Configuration file").action(async (type, slug, options) => {
|
|
4871
|
+
const validTypes = ["assistant", "datasource", "workflow", "skill"];
|
|
4872
|
+
if (!validTypes.includes(type)) {
|
|
4873
|
+
process.exitCode = 1;
|
|
4874
|
+
return;
|
|
4875
|
+
}
|
|
3682
4876
|
const configPath = options.config ? path6__default.resolve(options.config) : void 0;
|
|
3683
4877
|
const appConfig = loadAppConfig(configPath);
|
|
3684
|
-
|
|
4878
|
+
await runCommandWithSpinner(
|
|
4879
|
+
`Importing ${type}`,
|
|
4880
|
+
() => main4({ appConfig, resourceType: type, slug })
|
|
4881
|
+
);
|
|
3685
4882
|
});
|
|
3686
4883
|
program.parse();
|