openkbs 0.0.53 → 0.0.59
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1490 -202
- package/package.json +2 -1
- package/src/actions.js +1282 -1
- package/src/index.js +77 -1
- package/src/utils.js +5 -2
- package/templates/.openkbs/knowledge/examples/ai-copywriter-agent/app/instructions.txt +44 -9
- package/templates/.openkbs/knowledge/examples/ai-copywriter-agent/src/Events/actions.js +43 -42
- package/templates/.openkbs/knowledge/examples/ai-copywriter-agent/src/Events/handler.js +14 -8
- package/templates/.openkbs/knowledge/examples/ai-copywriter-agent/src/Frontend/contentRender.js +95 -12
- package/templates/.openkbs/knowledge/examples/ai-marketing-agent/README.md +64 -0
- package/templates/.openkbs/knowledge/examples/ai-marketing-agent/app/instructions.txt +160 -0
- package/templates/.openkbs/knowledge/examples/ai-marketing-agent/app/settings.json +7 -0
- package/templates/.openkbs/knowledge/examples/ai-marketing-agent/src/Events/actions.js +258 -0
- package/templates/.openkbs/knowledge/examples/ai-marketing-agent/src/Events/onRequest.js +13 -0
- package/templates/.openkbs/knowledge/examples/ai-marketing-agent/src/Events/onRequest.json +3 -0
- package/templates/.openkbs/knowledge/examples/ai-marketing-agent/src/Events/onResponse.js +13 -0
- package/templates/.openkbs/knowledge/examples/ai-marketing-agent/src/Events/onResponse.json +3 -0
- package/templates/.openkbs/knowledge/examples/ai-marketing-agent/src/Frontend/contentRender.js +170 -0
- package/templates/.openkbs/knowledge/examples/ai-marketing-agent/src/Frontend/contentRender.json +3 -0
- package/templates/.openkbs/knowledge/metadata.json +1 -1
- package/templates/CLAUDE.md +593 -222
- package/templates/app/instructions.txt +13 -1
- package/templates/app/settings.json +5 -6
- package/templates/src/Events/actions.js +43 -9
- package/templates/src/Events/handler.js +24 -25
- package/templates/webpack.contentRender.config.js +8 -2
- package/version.json +3 -3
- package/MODIFY.md +0 -132
package/src/actions.js
CHANGED
|
@@ -17,6 +17,79 @@ const TEMPLATE_DIR = path.join(os.homedir(), '.openkbs', 'templates');
|
|
|
17
17
|
const jwtPath = path.join(os.homedir(), '.openkbs', 'clientJWT');
|
|
18
18
|
const generateTransactionId = () => `${+new Date()}-${Math.floor(100000 + Math.random() * 900000)}`;
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Find settings from settings.json - checks current dir, then functions/ or site/ subdirs
|
|
22
|
+
* Returns full settings object with kbId, region, etc.
|
|
23
|
+
*/
|
|
24
|
+
function findSettings() {
|
|
25
|
+
const paths = [
|
|
26
|
+
path.join(process.cwd(), 'settings.json'),
|
|
27
|
+
path.join(process.cwd(), 'app', 'settings.json'),
|
|
28
|
+
path.join(process.cwd(), 'functions', 'settings.json'),
|
|
29
|
+
path.join(process.cwd(), 'site', 'settings.json')
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
for (const settingsPath of paths) {
|
|
33
|
+
if (fs.existsSync(settingsPath)) {
|
|
34
|
+
try {
|
|
35
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
36
|
+
if (settings.kbId) return settings;
|
|
37
|
+
} catch (e) {}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Find kbId from settings.json - checks current dir, then functions/ or site/ subdirs
|
|
45
|
+
*/
|
|
46
|
+
function findKbId() {
|
|
47
|
+
const settings = findSettings();
|
|
48
|
+
return settings?.kbId || null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Find region from settings.json - checks current dir, then functions/ or site/ subdirs
|
|
53
|
+
* Default: 'us-east-1'
|
|
54
|
+
*/
|
|
55
|
+
function findRegion() {
|
|
56
|
+
const settings = findSettings();
|
|
57
|
+
return settings?.region || 'us-east-1';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// MIME types for common file extensions
|
|
61
|
+
const MIME_TYPES = {
|
|
62
|
+
'.html': 'text/html',
|
|
63
|
+
'.htm': 'text/html',
|
|
64
|
+
'.css': 'text/css',
|
|
65
|
+
'.js': 'application/javascript',
|
|
66
|
+
'.mjs': 'application/javascript',
|
|
67
|
+
'.json': 'application/json',
|
|
68
|
+
'.png': 'image/png',
|
|
69
|
+
'.jpg': 'image/jpeg',
|
|
70
|
+
'.jpeg': 'image/jpeg',
|
|
71
|
+
'.gif': 'image/gif',
|
|
72
|
+
'.svg': 'image/svg+xml',
|
|
73
|
+
'.ico': 'image/x-icon',
|
|
74
|
+
'.webp': 'image/webp',
|
|
75
|
+
'.woff': 'font/woff',
|
|
76
|
+
'.woff2': 'font/woff2',
|
|
77
|
+
'.ttf': 'font/ttf',
|
|
78
|
+
'.eot': 'application/vnd.ms-fontobject',
|
|
79
|
+
'.pdf': 'application/pdf',
|
|
80
|
+
'.txt': 'text/plain',
|
|
81
|
+
'.xml': 'application/xml',
|
|
82
|
+
'.zip': 'application/zip',
|
|
83
|
+
'.mp3': 'audio/mpeg',
|
|
84
|
+
'.mp4': 'video/mp4',
|
|
85
|
+
'.webm': 'video/webm'
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
function getMimeType(filePath) {
|
|
89
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
90
|
+
return MIME_TYPES[ext] || 'application/octet-stream';
|
|
91
|
+
}
|
|
92
|
+
|
|
20
93
|
async function signAction(options) {
|
|
21
94
|
try {
|
|
22
95
|
const userProfile = await getUserProfile();
|
|
@@ -837,6 +910,1209 @@ async function downloadClaudeMdFromS3(claudeMdPath) {
|
|
|
837
910
|
}
|
|
838
911
|
}
|
|
839
912
|
|
|
913
|
+
// ===== Elastic Functions Commands =====
|
|
914
|
+
|
|
915
|
+
async function fnAction(subCommand, args = []) {
|
|
916
|
+
// Find kbId from settings.json (current dir, app/, functions/, site/)
|
|
917
|
+
let kbId = findKbId();
|
|
918
|
+
|
|
919
|
+
if (!kbId) {
|
|
920
|
+
// Fallback to standard KB lookup
|
|
921
|
+
const localKBData = await fetchLocalKBData();
|
|
922
|
+
kbId = localKBData?.kbId;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
if (!kbId) {
|
|
926
|
+
return console.red('No KB found. Create settings.json with {"kbId": "..."} or run from a KB project directory.');
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
const { kbToken } = await fetchKBJWT(kbId);
|
|
930
|
+
|
|
931
|
+
switch (subCommand) {
|
|
932
|
+
case 'list':
|
|
933
|
+
return await fnListAction(kbToken);
|
|
934
|
+
case 'deploy':
|
|
935
|
+
return await fnDeployAction(kbToken, args[0], args.slice(1));
|
|
936
|
+
case 'delete':
|
|
937
|
+
return await fnDeleteAction(kbToken, args[0]);
|
|
938
|
+
case 'logs':
|
|
939
|
+
return await fnLogsAction(kbToken, args[0], args.slice(1));
|
|
940
|
+
case 'env':
|
|
941
|
+
return await fnEnvAction(kbToken, args[0], args.slice(1));
|
|
942
|
+
case 'invoke':
|
|
943
|
+
return await fnInvokeAction(kbToken, args[0], args.slice(1));
|
|
944
|
+
default:
|
|
945
|
+
console.log('Usage: openkbs fn <command> [options]');
|
|
946
|
+
console.log('');
|
|
947
|
+
console.log('Commands:');
|
|
948
|
+
console.log(' list List all elastic functions');
|
|
949
|
+
console.log(' deploy <name> Deploy a function from ./functions/<name>/');
|
|
950
|
+
console.log(' delete <name> Delete a function');
|
|
951
|
+
console.log(' logs <name> View function logs');
|
|
952
|
+
console.log(' env <name> [KEY=value] View or set environment variables');
|
|
953
|
+
console.log(' invoke <name> [payload] Invoke a function');
|
|
954
|
+
console.log('');
|
|
955
|
+
console.log('Options for deploy:');
|
|
956
|
+
console.log(' --region <region> Region (us-east-1, eu-central-1, ap-southeast-1)');
|
|
957
|
+
console.log(' --memory <mb> Memory size (128-3008 MB)');
|
|
958
|
+
console.log(' --timeout <seconds> Timeout (1-900 seconds)');
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
async function fnListAction(kbToken) {
|
|
963
|
+
try {
|
|
964
|
+
const response = await makePostRequest(KB_API_URL, {
|
|
965
|
+
token: kbToken,
|
|
966
|
+
action: 'listElasticFunctions'
|
|
967
|
+
});
|
|
968
|
+
|
|
969
|
+
if (response.error) {
|
|
970
|
+
return console.red('Error:', response.error);
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
const functions = response.functions || [];
|
|
974
|
+
|
|
975
|
+
if (functions.length === 0) {
|
|
976
|
+
console.log('No elastic functions found.');
|
|
977
|
+
console.log('');
|
|
978
|
+
console.log('Create a function:');
|
|
979
|
+
console.log(' 1. Create directory: mkdir -p functions/hello');
|
|
980
|
+
console.log(' 2. Create handler: echo "export const handler = async (event) => ({ body: \'Hello!\' });" > functions/hello/index.mjs');
|
|
981
|
+
console.log(' 3. Deploy: openkbs fn deploy hello --region us-east-1');
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
console.log('Elastic Functions:\n');
|
|
986
|
+
const maxNameLen = Math.max(...functions.map(f => f.functionName.length), 10);
|
|
987
|
+
|
|
988
|
+
functions.forEach(f => {
|
|
989
|
+
const name = f.functionName.padEnd(maxNameLen);
|
|
990
|
+
const region = f.region || 'unknown';
|
|
991
|
+
const url = f.customUrl || f.functionUrl || 'N/A';
|
|
992
|
+
console.log(` ${name} ${region} ${url}`);
|
|
993
|
+
});
|
|
994
|
+
} catch (error) {
|
|
995
|
+
console.red('Error listing functions:', error.message);
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
async function fnDeployAction(kbToken, functionName, args) {
|
|
1000
|
+
if (!functionName) {
|
|
1001
|
+
return console.red('Function name required. Usage: openkbs fn deploy <name>');
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
// Parse arguments - region defaults to settings.json or us-east-1
|
|
1005
|
+
let region = findRegion();
|
|
1006
|
+
let memorySize = 256;
|
|
1007
|
+
let timeout = 30;
|
|
1008
|
+
|
|
1009
|
+
for (let i = 0; i < args.length; i++) {
|
|
1010
|
+
if (args[i] === '--region' && args[i + 1]) {
|
|
1011
|
+
region = args[++i];
|
|
1012
|
+
} else if (args[i] === '--memory' && args[i + 1]) {
|
|
1013
|
+
memorySize = parseInt(args[++i]);
|
|
1014
|
+
} else if (args[i] === '--timeout' && args[i + 1]) {
|
|
1015
|
+
timeout = parseInt(args[++i]);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// Try to find the function directory in order:
|
|
1020
|
+
// 1. ./functionName (if running from functions/ directory)
|
|
1021
|
+
// 2. ./functions/functionName (if running from project root)
|
|
1022
|
+
let functionDir = path.join(process.cwd(), functionName);
|
|
1023
|
+
if (!await fs.pathExists(functionDir)) {
|
|
1024
|
+
functionDir = path.join(process.cwd(), 'functions', functionName);
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
if (!await fs.pathExists(functionDir)) {
|
|
1028
|
+
return console.red(`Function directory not found. Tried:\n - ./${functionName}\n - ./functions/${functionName}`);
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
console.log(`Deploying function '${functionName}' to ${region}...`);
|
|
1032
|
+
|
|
1033
|
+
try {
|
|
1034
|
+
// Check if package.json exists and run npm install
|
|
1035
|
+
const packageJsonPath = path.join(functionDir, 'package.json');
|
|
1036
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
1037
|
+
console.log('Installing dependencies...');
|
|
1038
|
+
execSync('npm install --production', { cwd: functionDir, stdio: 'inherit' });
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
// Create a zip of the function directory
|
|
1042
|
+
const archiver = require('archiver');
|
|
1043
|
+
const { PassThrough } = require('stream');
|
|
1044
|
+
|
|
1045
|
+
const archive = archiver('zip', { zlib: { level: 9 } });
|
|
1046
|
+
const chunks = [];
|
|
1047
|
+
const passThrough = new PassThrough();
|
|
1048
|
+
|
|
1049
|
+
passThrough.on('data', chunk => chunks.push(chunk));
|
|
1050
|
+
|
|
1051
|
+
await new Promise((resolve, reject) => {
|
|
1052
|
+
passThrough.on('end', resolve);
|
|
1053
|
+
passThrough.on('error', reject);
|
|
1054
|
+
archive.on('error', reject);
|
|
1055
|
+
|
|
1056
|
+
archive.pipe(passThrough);
|
|
1057
|
+
archive.directory(functionDir, false);
|
|
1058
|
+
archive.finalize();
|
|
1059
|
+
});
|
|
1060
|
+
|
|
1061
|
+
const zipBuffer = Buffer.concat(chunks);
|
|
1062
|
+
const code = zipBuffer.toString('base64');
|
|
1063
|
+
|
|
1064
|
+
// Check if function exists
|
|
1065
|
+
const listResponse = await makePostRequest(KB_API_URL, {
|
|
1066
|
+
token: kbToken,
|
|
1067
|
+
action: 'listElasticFunctions'
|
|
1068
|
+
});
|
|
1069
|
+
|
|
1070
|
+
const existingFunc = listResponse.functions?.find(f => f.functionName === functionName);
|
|
1071
|
+
|
|
1072
|
+
let response;
|
|
1073
|
+
if (existingFunc) {
|
|
1074
|
+
// Update existing function
|
|
1075
|
+
console.log('Updating existing function...');
|
|
1076
|
+
response = await makePostRequest(KB_API_URL, {
|
|
1077
|
+
token: kbToken,
|
|
1078
|
+
action: 'updateElasticFunction',
|
|
1079
|
+
functionName,
|
|
1080
|
+
code
|
|
1081
|
+
});
|
|
1082
|
+
} else {
|
|
1083
|
+
// Create new function
|
|
1084
|
+
console.log('Creating new function...');
|
|
1085
|
+
response = await makePostRequest(KB_API_URL, {
|
|
1086
|
+
token: kbToken,
|
|
1087
|
+
action: 'createElasticFunction',
|
|
1088
|
+
functionName,
|
|
1089
|
+
code,
|
|
1090
|
+
region,
|
|
1091
|
+
memorySize,
|
|
1092
|
+
timeout
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
if (response.error) {
|
|
1097
|
+
return console.red('Deploy failed:', response.error);
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
console.green('Deploy successful!');
|
|
1101
|
+
if (response.functionUrl) {
|
|
1102
|
+
console.log(`Lambda URL: ${response.functionUrl}`);
|
|
1103
|
+
}
|
|
1104
|
+
if (response.customUrl) {
|
|
1105
|
+
console.log(`Custom URL: ${response.customUrl}`);
|
|
1106
|
+
}
|
|
1107
|
+
} catch (error) {
|
|
1108
|
+
console.red('Deploy failed:', error.message);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
async function fnDeleteAction(kbToken, functionName) {
|
|
1113
|
+
if (!functionName) {
|
|
1114
|
+
return console.red('Function name required. Usage: openkbs fn delete <name>');
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
try {
|
|
1118
|
+
console.log(`Deleting function '${functionName}'...`);
|
|
1119
|
+
|
|
1120
|
+
const response = await makePostRequest(KB_API_URL, {
|
|
1121
|
+
token: kbToken,
|
|
1122
|
+
action: 'deleteElasticFunction',
|
|
1123
|
+
functionName
|
|
1124
|
+
});
|
|
1125
|
+
|
|
1126
|
+
if (response.error) {
|
|
1127
|
+
return console.red('Delete failed:', response.error);
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
console.green(`Function '${functionName}' deleted successfully.`);
|
|
1131
|
+
} catch (error) {
|
|
1132
|
+
console.red('Delete failed:', error.message);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
async function fnLogsAction(kbToken, functionName, args) {
|
|
1137
|
+
if (!functionName) {
|
|
1138
|
+
return console.red('Function name required. Usage: openkbs fn logs <name>');
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
try {
|
|
1142
|
+
let limit = 50;
|
|
1143
|
+
for (let i = 0; i < args.length; i++) {
|
|
1144
|
+
if (args[i] === '--limit' && args[i + 1]) {
|
|
1145
|
+
limit = parseInt(args[++i]);
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
const response = await makePostRequest(KB_API_URL, {
|
|
1150
|
+
token: kbToken,
|
|
1151
|
+
action: 'getElasticFunctionLogs',
|
|
1152
|
+
functionName,
|
|
1153
|
+
limit
|
|
1154
|
+
});
|
|
1155
|
+
|
|
1156
|
+
if (response.error) {
|
|
1157
|
+
return console.red('Error:', response.error);
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
if (!response.events || response.events.length === 0) {
|
|
1161
|
+
console.log('No logs found. Function may not have been invoked yet.');
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
console.log(`Logs for '${functionName}':\n`);
|
|
1166
|
+
response.events.forEach(event => {
|
|
1167
|
+
const time = new Date(event.timestamp).toISOString();
|
|
1168
|
+
console.log(`[${time}] ${event.message}`);
|
|
1169
|
+
});
|
|
1170
|
+
} catch (error) {
|
|
1171
|
+
console.red('Error fetching logs:', error.message);
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
async function fnEnvAction(kbToken, functionName, args) {
|
|
1176
|
+
if (!functionName) {
|
|
1177
|
+
return console.red('Function name required. Usage: openkbs fn env <name> [KEY=value ...]');
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
try {
|
|
1181
|
+
if (args.length === 0) {
|
|
1182
|
+
// Show current env vars
|
|
1183
|
+
const response = await makePostRequest(KB_API_URL, {
|
|
1184
|
+
token: kbToken,
|
|
1185
|
+
action: 'getElasticFunction',
|
|
1186
|
+
functionName
|
|
1187
|
+
});
|
|
1188
|
+
|
|
1189
|
+
if (response.error) {
|
|
1190
|
+
return console.red('Error:', response.error);
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
console.log(`Environment variables for '${functionName}':\n`);
|
|
1194
|
+
const env = response.env || {};
|
|
1195
|
+
if (Object.keys(env).length === 0) {
|
|
1196
|
+
console.log(' (none)');
|
|
1197
|
+
} else {
|
|
1198
|
+
Object.entries(env).forEach(([key, value]) => {
|
|
1199
|
+
console.log(` ${key}=${value}`);
|
|
1200
|
+
});
|
|
1201
|
+
}
|
|
1202
|
+
} else {
|
|
1203
|
+
// Set env vars
|
|
1204
|
+
const env = {};
|
|
1205
|
+
args.forEach(arg => {
|
|
1206
|
+
const [key, ...valueParts] = arg.split('=');
|
|
1207
|
+
if (key && valueParts.length > 0) {
|
|
1208
|
+
env[key] = valueParts.join('=');
|
|
1209
|
+
}
|
|
1210
|
+
});
|
|
1211
|
+
|
|
1212
|
+
if (Object.keys(env).length === 0) {
|
|
1213
|
+
return console.red('Invalid format. Use: openkbs fn env <name> KEY=value');
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
console.log(`Setting environment variables for '${functionName}'...`);
|
|
1217
|
+
|
|
1218
|
+
const response = await makePostRequest(KB_API_URL, {
|
|
1219
|
+
token: kbToken,
|
|
1220
|
+
action: 'setElasticFunctionEnv',
|
|
1221
|
+
functionName,
|
|
1222
|
+
env
|
|
1223
|
+
});
|
|
1224
|
+
|
|
1225
|
+
if (response.error) {
|
|
1226
|
+
return console.red('Error:', response.error);
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
console.green('Environment variables updated.');
|
|
1230
|
+
}
|
|
1231
|
+
} catch (error) {
|
|
1232
|
+
console.red('Error:', error.message);
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
async function fnInvokeAction(kbToken, functionName, args) {
|
|
1237
|
+
if (!functionName) {
|
|
1238
|
+
return console.red('Function name required. Usage: openkbs fn invoke <name> [payload]');
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
try {
|
|
1242
|
+
let payload = {};
|
|
1243
|
+
if (args.length > 0) {
|
|
1244
|
+
try {
|
|
1245
|
+
payload = JSON.parse(args.join(' '));
|
|
1246
|
+
} catch (e) {
|
|
1247
|
+
return console.red('Invalid JSON payload');
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
console.log(`Invoking '${functionName}'...`);
|
|
1252
|
+
|
|
1253
|
+
const response = await makePostRequest(KB_API_URL, {
|
|
1254
|
+
token: kbToken,
|
|
1255
|
+
action: 'invokeElasticFunction',
|
|
1256
|
+
functionName,
|
|
1257
|
+
payload
|
|
1258
|
+
});
|
|
1259
|
+
|
|
1260
|
+
if (response.error) {
|
|
1261
|
+
return console.red('Error:', response.error);
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
console.log('\nResponse:');
|
|
1265
|
+
console.log(JSON.stringify(response.payload, null, 2));
|
|
1266
|
+
|
|
1267
|
+
if (response.functionError) {
|
|
1268
|
+
console.red('\nFunction Error:', response.functionError);
|
|
1269
|
+
}
|
|
1270
|
+
} catch (error) {
|
|
1271
|
+
console.red('Error invoking function:', error.message);
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
// ===== Elastic Storage Commands =====
|
|
1276
|
+
|
|
1277
|
+
async function storageAction(subCommand, args = []) {
|
|
1278
|
+
// Find kbId from settings.json (current dir, app/, functions/, site/)
|
|
1279
|
+
let kbId = findKbId();
|
|
1280
|
+
|
|
1281
|
+
if (!kbId) {
|
|
1282
|
+
const localKBData = await fetchLocalKBData();
|
|
1283
|
+
kbId = localKBData?.kbId;
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
if (!kbId) {
|
|
1287
|
+
return console.red('No KB found. Create settings.json with {"kbId": "..."} or run from a KB project directory.');
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
const { kbToken } = await fetchKBJWT(kbId);
|
|
1291
|
+
|
|
1292
|
+
switch (subCommand) {
|
|
1293
|
+
case 'enable':
|
|
1294
|
+
return await storageEnableAction(kbToken);
|
|
1295
|
+
case 'status':
|
|
1296
|
+
return await storageStatusAction(kbToken);
|
|
1297
|
+
case 'ls':
|
|
1298
|
+
case 'list':
|
|
1299
|
+
return await storageListAction(kbToken, args[0]);
|
|
1300
|
+
case 'put':
|
|
1301
|
+
case 'upload':
|
|
1302
|
+
return await storageUploadAction(kbToken, args[0], args[1]);
|
|
1303
|
+
case 'get':
|
|
1304
|
+
case 'download':
|
|
1305
|
+
return await storageDownloadAction(kbToken, args[0], args[1]);
|
|
1306
|
+
case 'rm':
|
|
1307
|
+
case 'delete':
|
|
1308
|
+
return await storageDeleteAction(kbToken, args[0]);
|
|
1309
|
+
case 'disable':
|
|
1310
|
+
return await storageDisableAction(kbToken, args);
|
|
1311
|
+
case 'cloudfront':
|
|
1312
|
+
case 'cf':
|
|
1313
|
+
return await storageCloudFrontAction(kbToken, args[0], args[1]);
|
|
1314
|
+
case 'public':
|
|
1315
|
+
return await storagePublicAction(kbToken, args[0]);
|
|
1316
|
+
default:
|
|
1317
|
+
console.log('Usage: openkbs storage <command> [options]');
|
|
1318
|
+
console.log('');
|
|
1319
|
+
console.log('Commands:');
|
|
1320
|
+
console.log(' enable Enable elastic storage for this KB');
|
|
1321
|
+
console.log(' status Show storage status and info');
|
|
1322
|
+
console.log(' public <true|false> Make storage publicly readable');
|
|
1323
|
+
console.log(' ls [prefix] List objects in storage');
|
|
1324
|
+
console.log(' put <local> <remote> Upload a file to storage');
|
|
1325
|
+
console.log(' get <remote> <local> Download a file from storage');
|
|
1326
|
+
console.log(' rm <key> Delete an object from storage');
|
|
1327
|
+
console.log(' disable [--force] Disable storage (deletes bucket)');
|
|
1328
|
+
console.log(' cloudfront <path> Add storage to CloudFront at path (e.g., /media)');
|
|
1329
|
+
console.log(' cloudfront remove <path> Remove storage from CloudFront');
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
async function storageEnableAction(kbToken) {
|
|
1334
|
+
try {
|
|
1335
|
+
console.log('Enabling elastic storage...');
|
|
1336
|
+
|
|
1337
|
+
const response = await makePostRequest(KB_API_URL, {
|
|
1338
|
+
token: kbToken,
|
|
1339
|
+
action: 'enableElasticStorage'
|
|
1340
|
+
});
|
|
1341
|
+
|
|
1342
|
+
if (response.error) {
|
|
1343
|
+
return console.red('Error:', response.error);
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
if (response.alreadyEnabled) {
|
|
1347
|
+
console.yellow('Storage is already enabled.');
|
|
1348
|
+
} else {
|
|
1349
|
+
console.green('Storage enabled successfully!');
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
console.log(` Bucket: ${response.bucket}`);
|
|
1353
|
+
console.log(` Region: ${response.region}`);
|
|
1354
|
+
|
|
1355
|
+
if (response.functionsUpdated > 0) {
|
|
1356
|
+
console.log(` Updated ${response.functionsUpdated} function(s) with STORAGE_BUCKET env var`);
|
|
1357
|
+
}
|
|
1358
|
+
} catch (error) {
|
|
1359
|
+
console.red('Error enabling storage:', error.message);
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
async function storageStatusAction(kbToken) {
|
|
1364
|
+
try {
|
|
1365
|
+
const response = await makePostRequest(KB_API_URL, {
|
|
1366
|
+
token: kbToken,
|
|
1367
|
+
action: 'getElasticStorage'
|
|
1368
|
+
});
|
|
1369
|
+
|
|
1370
|
+
if (response.error) {
|
|
1371
|
+
return console.red('Error:', response.error);
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
if (!response.enabled) {
|
|
1375
|
+
console.log('Elastic Storage: disabled');
|
|
1376
|
+
console.log('');
|
|
1377
|
+
console.log('Enable with: openkbs storage enable');
|
|
1378
|
+
return;
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
console.log('Elastic Storage: enabled');
|
|
1382
|
+
console.log(` Bucket: ${response.bucket}`);
|
|
1383
|
+
console.log(` Region: ${response.region}`);
|
|
1384
|
+
console.log(` Public: ${response.public ? 'yes' : 'no'}`);
|
|
1385
|
+
if (response.publicUrl) {
|
|
1386
|
+
console.log(` Public URL: ${response.publicUrl}`);
|
|
1387
|
+
}
|
|
1388
|
+
} catch (error) {
|
|
1389
|
+
console.red('Error getting storage status:', error.message);
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
async function storageListAction(kbToken, prefix = '') {
|
|
1394
|
+
try {
|
|
1395
|
+
const response = await makePostRequest(KB_API_URL, {
|
|
1396
|
+
token: kbToken,
|
|
1397
|
+
action: 'listStorageObjects',
|
|
1398
|
+
prefix: prefix || ''
|
|
1399
|
+
});
|
|
1400
|
+
|
|
1401
|
+
if (response.error) {
|
|
1402
|
+
return console.red('Error:', response.error);
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
const objects = response.objects || [];
|
|
1406
|
+
|
|
1407
|
+
if (objects.length === 0) {
|
|
1408
|
+
console.log('No objects found.');
|
|
1409
|
+
return;
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
console.log(`Objects${prefix ? ` (prefix: ${prefix})` : ''}:\n`);
|
|
1413
|
+
|
|
1414
|
+
objects.forEach(obj => {
|
|
1415
|
+
const size = formatBytes(obj.size);
|
|
1416
|
+
const date = new Date(obj.lastModified).toISOString().split('T')[0];
|
|
1417
|
+
console.log(` ${date} ${size.padStart(10)} ${obj.key}`);
|
|
1418
|
+
});
|
|
1419
|
+
|
|
1420
|
+
if (response.isTruncated) {
|
|
1421
|
+
console.log('\n (more objects exist, use prefix to filter)');
|
|
1422
|
+
}
|
|
1423
|
+
} catch (error) {
|
|
1424
|
+
console.red('Error listing objects:', error.message);
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
async function storageUploadAction(kbToken, localPath, remoteKey) {
|
|
1429
|
+
if (!localPath) {
|
|
1430
|
+
return console.red('Usage: openkbs storage put <local-file> [remote-key]');
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
const fullLocalPath = path.resolve(localPath);
|
|
1434
|
+
|
|
1435
|
+
if (!fs.existsSync(fullLocalPath)) {
|
|
1436
|
+
return console.red(`File not found: ${fullLocalPath}`);
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
// Use filename if remote key not specified
|
|
1440
|
+
if (!remoteKey) {
|
|
1441
|
+
remoteKey = path.basename(localPath);
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
try {
|
|
1445
|
+
console.log(`Uploading ${localPath} to ${remoteKey}...`);
|
|
1446
|
+
|
|
1447
|
+
// Get presigned upload URL
|
|
1448
|
+
const response = await makePostRequest(KB_API_URL, {
|
|
1449
|
+
token: kbToken,
|
|
1450
|
+
action: 'getStorageUploadUrl',
|
|
1451
|
+
storageKey: remoteKey
|
|
1452
|
+
});
|
|
1453
|
+
|
|
1454
|
+
if (response.error) {
|
|
1455
|
+
return console.red('Error:', response.error);
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
// Upload file using presigned URL
|
|
1459
|
+
const fileContent = fs.readFileSync(fullLocalPath);
|
|
1460
|
+
await fetch(response.uploadUrl, {
|
|
1461
|
+
method: 'PUT',
|
|
1462
|
+
body: fileContent
|
|
1463
|
+
});
|
|
1464
|
+
|
|
1465
|
+
console.green(`Uploaded: ${remoteKey}`);
|
|
1466
|
+
|
|
1467
|
+
if (response.publicUrl) {
|
|
1468
|
+
console.log(`Public URL: ${response.publicUrl}`);
|
|
1469
|
+
}
|
|
1470
|
+
} catch (error) {
|
|
1471
|
+
console.red('Upload failed:', error.message);
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
async function storageDownloadAction(kbToken, remoteKey, localPath) {
|
|
1476
|
+
if (!remoteKey) {
|
|
1477
|
+
return console.red('Usage: openkbs storage get <remote-key> [local-file]');
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
// Use remote filename if local path not specified
|
|
1481
|
+
if (!localPath) {
|
|
1482
|
+
localPath = path.basename(remoteKey);
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
try {
|
|
1486
|
+
console.log(`Downloading ${remoteKey} to ${localPath}...`);
|
|
1487
|
+
|
|
1488
|
+
// Get presigned download URL
|
|
1489
|
+
const response = await makePostRequest(KB_API_URL, {
|
|
1490
|
+
token: kbToken,
|
|
1491
|
+
action: 'getStorageDownloadUrl',
|
|
1492
|
+
storageKey: remoteKey
|
|
1493
|
+
});
|
|
1494
|
+
|
|
1495
|
+
if (response.error) {
|
|
1496
|
+
return console.red('Error:', response.error);
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
// Download file
|
|
1500
|
+
const fetchResponse = await fetch(response.downloadUrl);
|
|
1501
|
+
if (!fetchResponse.ok) {
|
|
1502
|
+
return console.red(`Download failed: ${fetchResponse.statusText}`);
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
const buffer = await fetchResponse.arrayBuffer();
|
|
1506
|
+
fs.writeFileSync(localPath, Buffer.from(buffer));
|
|
1507
|
+
|
|
1508
|
+
console.green(`Downloaded: ${localPath}`);
|
|
1509
|
+
} catch (error) {
|
|
1510
|
+
console.red('Download failed:', error.message);
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
async function storageDeleteAction(kbToken, key) {
|
|
1515
|
+
if (!key) {
|
|
1516
|
+
return console.red('Usage: openkbs storage rm <key>');
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
try {
|
|
1520
|
+
console.log(`Deleting ${key}...`);
|
|
1521
|
+
|
|
1522
|
+
const response = await makePostRequest(KB_API_URL, {
|
|
1523
|
+
token: kbToken,
|
|
1524
|
+
action: 'deleteStorageObject',
|
|
1525
|
+
storageKey: key
|
|
1526
|
+
});
|
|
1527
|
+
|
|
1528
|
+
if (response.error) {
|
|
1529
|
+
return console.red('Error:', response.error);
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
console.green(`Deleted: ${key}`);
|
|
1533
|
+
} catch (error) {
|
|
1534
|
+
console.red('Delete failed:', error.message);
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
async function storageDisableAction(kbToken, args) {
|
|
1539
|
+
const force = args.includes('--force');
|
|
1540
|
+
|
|
1541
|
+
try {
|
|
1542
|
+
console.log('Disabling elastic storage...');
|
|
1543
|
+
|
|
1544
|
+
if (!force) {
|
|
1545
|
+
console.yellow('Warning: This will delete the storage bucket.');
|
|
1546
|
+
console.yellow('Use --force to delete all objects and the bucket.');
|
|
1547
|
+
return;
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
const response = await makePostRequest(KB_API_URL, {
|
|
1551
|
+
token: kbToken,
|
|
1552
|
+
action: 'deleteElasticStorage',
|
|
1553
|
+
force: true
|
|
1554
|
+
});
|
|
1555
|
+
|
|
1556
|
+
if (response.error) {
|
|
1557
|
+
return console.red('Error:', response.error);
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
console.green('Storage disabled successfully.');
|
|
1561
|
+
|
|
1562
|
+
if (response.functionsUpdated > 0) {
|
|
1563
|
+
console.log(`Removed STORAGE_BUCKET from ${response.functionsUpdated} function(s)`);
|
|
1564
|
+
}
|
|
1565
|
+
} catch (error) {
|
|
1566
|
+
console.red('Error disabling storage:', error.message);
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
async function storagePublicAction(kbToken, value) {
|
|
1571
|
+
if (!value || !['true', 'false'].includes(value.toLowerCase())) {
|
|
1572
|
+
return console.red('Usage: openkbs storage public <true|false>');
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
const makePublic = value.toLowerCase() === 'true';
|
|
1576
|
+
|
|
1577
|
+
try {
|
|
1578
|
+
console.log(makePublic ? 'Making storage public...' : 'Making storage private...');
|
|
1579
|
+
|
|
1580
|
+
const response = await makePostRequest(KB_API_URL, {
|
|
1581
|
+
token: kbToken,
|
|
1582
|
+
action: 'setElasticStoragePublic',
|
|
1583
|
+
isPublic: makePublic
|
|
1584
|
+
});
|
|
1585
|
+
|
|
1586
|
+
if (response.error) {
|
|
1587
|
+
return console.red('Error:', response.error);
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
if (makePublic) {
|
|
1591
|
+
console.green('Storage is now public!');
|
|
1592
|
+
console.log(`Public URL: ${response.publicUrl}`);
|
|
1593
|
+
} else {
|
|
1594
|
+
console.green('Storage is now private.');
|
|
1595
|
+
}
|
|
1596
|
+
} catch (error) {
|
|
1597
|
+
console.red('Error:', error.message);
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
async function storageCloudFrontAction(kbToken, pathOrRemove, pathArg) {
|
|
1602
|
+
// Handle "storage cloudfront remove <path>" vs "storage cloudfront <path>"
|
|
1603
|
+
let pathPrefix;
|
|
1604
|
+
let enable = true;
|
|
1605
|
+
|
|
1606
|
+
if (pathOrRemove === 'remove' || pathOrRemove === 'rm') {
|
|
1607
|
+
if (!pathArg) {
|
|
1608
|
+
return console.red('Usage: openkbs storage cloudfront remove <path>');
|
|
1609
|
+
}
|
|
1610
|
+
pathPrefix = pathArg;
|
|
1611
|
+
enable = false;
|
|
1612
|
+
} else {
|
|
1613
|
+
if (!pathOrRemove) {
|
|
1614
|
+
console.log('Usage: openkbs storage cloudfront <path>');
|
|
1615
|
+
console.log(' openkbs storage cloudfront remove <path>');
|
|
1616
|
+
console.log('');
|
|
1617
|
+
console.log('Examples:');
|
|
1618
|
+
console.log(' openkbs storage cloudfront media # Makes storage available at /media/*');
|
|
1619
|
+
console.log(' openkbs storage cloudfront files # Makes storage available at /files/*');
|
|
1620
|
+
console.log(' openkbs storage cloudfront remove media # Remove from CloudFront');
|
|
1621
|
+
return;
|
|
1622
|
+
}
|
|
1623
|
+
pathPrefix = pathOrRemove;
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
try {
|
|
1627
|
+
if (enable) {
|
|
1628
|
+
console.log(`Adding storage to CloudFront at /${pathPrefix}/*...`);
|
|
1629
|
+
} else {
|
|
1630
|
+
console.log(`Removing storage from CloudFront at /${pathPrefix}/*...`);
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
const response = await makePostRequest(KB_API_URL, {
|
|
1634
|
+
token: kbToken,
|
|
1635
|
+
action: 'setStorageCloudFront',
|
|
1636
|
+
pathPrefix,
|
|
1637
|
+
enable
|
|
1638
|
+
});
|
|
1639
|
+
|
|
1640
|
+
if (response.error) {
|
|
1641
|
+
return console.red('Error:', response.error);
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
if (enable) {
|
|
1645
|
+
console.green('Storage added to CloudFront!');
|
|
1646
|
+
console.log(` Custom URL: ${response.customUrl}`);
|
|
1647
|
+
console.log(` Path: /${response.path}/*`);
|
|
1648
|
+
console.yellow('\n Note: CloudFront changes take 2-5 minutes to propagate.');
|
|
1649
|
+
} else {
|
|
1650
|
+
console.green('Storage removed from CloudFront.');
|
|
1651
|
+
console.yellow(' Note: CloudFront changes take 2-5 minutes to propagate.');
|
|
1652
|
+
}
|
|
1653
|
+
} catch (error) {
|
|
1654
|
+
console.red('Error:', error.message);
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
function formatBytes(bytes) {
|
|
1659
|
+
if (bytes === 0) return '0 B';
|
|
1660
|
+
const k = 1024;
|
|
1661
|
+
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
1662
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
1663
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
// ===== Elastic Postgres Commands =====
|
|
1667
|
+
|
|
1668
|
+
async function postgresAction(subCommand, args = []) {
|
|
1669
|
+
// Find kbId from settings.json (current dir, app/, functions/, site/)
|
|
1670
|
+
let kbId = findKbId();
|
|
1671
|
+
|
|
1672
|
+
if (!kbId) {
|
|
1673
|
+
const localKBData = await fetchLocalKBData();
|
|
1674
|
+
kbId = localKBData?.kbId;
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
if (!kbId) {
|
|
1678
|
+
return console.red('No KB found. Create settings.json with {"kbId": "..."} or run from a KB project directory.');
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
const { kbToken } = await fetchKBJWT(kbId);
|
|
1682
|
+
|
|
1683
|
+
switch (subCommand) {
|
|
1684
|
+
case 'enable':
|
|
1685
|
+
return await postgresEnableAction(kbToken);
|
|
1686
|
+
case 'status':
|
|
1687
|
+
return await postgresStatusAction(kbToken);
|
|
1688
|
+
case 'connection':
|
|
1689
|
+
case 'conn':
|
|
1690
|
+
return await postgresConnectionAction(kbToken);
|
|
1691
|
+
case 'disable':
|
|
1692
|
+
return await postgresDisableAction(kbToken);
|
|
1693
|
+
default:
|
|
1694
|
+
console.log('Usage: openkbs postgres <command>');
|
|
1695
|
+
console.log('');
|
|
1696
|
+
console.log('Commands:');
|
|
1697
|
+
console.log(' enable Enable Postgres database for this KB');
|
|
1698
|
+
console.log(' status Show Postgres status and info');
|
|
1699
|
+
console.log(' connection Show connection string');
|
|
1700
|
+
console.log(' disable Disable Postgres (deletes database)');
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
async function postgresEnableAction(kbToken) {
|
|
1705
|
+
try {
|
|
1706
|
+
const region = findRegion();
|
|
1707
|
+
console.log(`Enabling Elastic Postgres in ${region}...`);
|
|
1708
|
+
|
|
1709
|
+
const response = await makePostRequest(KB_API_URL, {
|
|
1710
|
+
token: kbToken,
|
|
1711
|
+
action: 'enableElasticPostgres',
|
|
1712
|
+
region
|
|
1713
|
+
});
|
|
1714
|
+
|
|
1715
|
+
if (response.error) {
|
|
1716
|
+
return console.red('Error:', response.error);
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
if (response.alreadyEnabled) {
|
|
1720
|
+
console.yellow('Postgres already enabled.');
|
|
1721
|
+
} else {
|
|
1722
|
+
console.green('Postgres enabled successfully!');
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
console.log(` Host: ${response.host}`);
|
|
1726
|
+
console.log(` Port: ${response.port}`);
|
|
1727
|
+
console.log(` Database: ${response.dbName}`);
|
|
1728
|
+
console.log(` Region: ${response.region}`);
|
|
1729
|
+
|
|
1730
|
+
if (response.functionsUpdated > 0) {
|
|
1731
|
+
console.log(` Updated ${response.functionsUpdated} function(s) with DATABASE_URL`);
|
|
1732
|
+
}
|
|
1733
|
+
} catch (error) {
|
|
1734
|
+
console.red('Error enabling Postgres:', error.message);
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
async function postgresStatusAction(kbToken) {
|
|
1739
|
+
try {
|
|
1740
|
+
const response = await makePostRequest(KB_API_URL, {
|
|
1741
|
+
token: kbToken,
|
|
1742
|
+
action: 'getElasticPostgres'
|
|
1743
|
+
});
|
|
1744
|
+
|
|
1745
|
+
if (response.error) {
|
|
1746
|
+
return console.red('Error:', response.error);
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
if (!response.enabled) {
|
|
1750
|
+
console.yellow('Postgres is not enabled.');
|
|
1751
|
+
console.log('Run: openkbs postgres enable');
|
|
1752
|
+
return;
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
console.green('Postgres Status: Enabled');
|
|
1756
|
+
console.log(` Host: ${response.host}`);
|
|
1757
|
+
console.log(` Port: ${response.port}`);
|
|
1758
|
+
console.log(` Database: ${response.dbName}`);
|
|
1759
|
+
console.log(` Region: ${response.region}`);
|
|
1760
|
+
console.log(` Project: ${response.projectId}`);
|
|
1761
|
+
} catch (error) {
|
|
1762
|
+
console.red('Error getting Postgres status:', error.message);
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
async function postgresConnectionAction(kbToken) {
|
|
1767
|
+
try {
|
|
1768
|
+
const response = await makePostRequest(KB_API_URL, {
|
|
1769
|
+
token: kbToken,
|
|
1770
|
+
action: 'getElasticPostgresConnection'
|
|
1771
|
+
});
|
|
1772
|
+
|
|
1773
|
+
if (response.error) {
|
|
1774
|
+
return console.red('Error:', response.error);
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
console.log('Connection String:');
|
|
1778
|
+
console.log(response.connectionString);
|
|
1779
|
+
} catch (error) {
|
|
1780
|
+
console.red('Error getting connection:', error.message);
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
async function postgresDisableAction(kbToken) {
|
|
1785
|
+
try {
|
|
1786
|
+
console.log('Disabling Elastic Postgres...');
|
|
1787
|
+
console.yellow('Warning: This will permanently delete the database and all data!');
|
|
1788
|
+
|
|
1789
|
+
const response = await makePostRequest(KB_API_URL, {
|
|
1790
|
+
token: kbToken,
|
|
1791
|
+
action: 'deleteElasticPostgres'
|
|
1792
|
+
});
|
|
1793
|
+
|
|
1794
|
+
if (response.error) {
|
|
1795
|
+
return console.red('Error:', response.error);
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
console.green('Postgres disabled successfully.');
|
|
1799
|
+
|
|
1800
|
+
if (response.functionsUpdated > 0) {
|
|
1801
|
+
console.log(`Removed DATABASE_URL from ${response.functionsUpdated} function(s)`);
|
|
1802
|
+
}
|
|
1803
|
+
} catch (error) {
|
|
1804
|
+
console.red('Error disabling Postgres:', error.message);
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
// ===== Site Commands =====
|
|
1809
|
+
|
|
1810
|
+
async function siteAction(subCommand, args = []) {
|
|
1811
|
+
// Find kbId and site directory
|
|
1812
|
+
let kbId = findKbId();
|
|
1813
|
+
let siteDir = process.cwd();
|
|
1814
|
+
|
|
1815
|
+
// If no settings.json in current dir, check site/ subdirectory
|
|
1816
|
+
if (!fs.existsSync(path.join(process.cwd(), 'settings.json'))) {
|
|
1817
|
+
const siteDirPath = path.join(process.cwd(), 'site');
|
|
1818
|
+
const siteSettingsPath = path.join(siteDirPath, 'settings.json');
|
|
1819
|
+
if (fs.existsSync(siteSettingsPath)) {
|
|
1820
|
+
siteDir = siteDirPath;
|
|
1821
|
+
try {
|
|
1822
|
+
const settings = JSON.parse(fs.readFileSync(siteSettingsPath, 'utf8'));
|
|
1823
|
+
kbId = settings.kbId;
|
|
1824
|
+
} catch (e) {}
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
if (!kbId) {
|
|
1829
|
+
const localKBData = await fetchLocalKBData();
|
|
1830
|
+
kbId = localKBData?.kbId;
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
if (!kbId) {
|
|
1834
|
+
return console.red('No KB found. Create settings.json with {"kbId": "..."} in current dir or site/ subdirectory.');
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
const { kbToken } = await fetchKBJWT(kbId);
|
|
1838
|
+
|
|
1839
|
+
switch (subCommand) {
|
|
1840
|
+
case 'deploy':
|
|
1841
|
+
return await siteDeployAction(kbToken, kbId, siteDir, args);
|
|
1842
|
+
default:
|
|
1843
|
+
console.log('Site management commands:\n');
|
|
1844
|
+
console.log(' openkbs site deploy Upload all files to S3');
|
|
1845
|
+
console.log('\nRun from a folder containing settings.json with kbId, or from parent with site/ subdirectory');
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
async function siteDeployAction(kbToken, kbId, siteDir, args) {
|
|
1850
|
+
|
|
1851
|
+
// Walk directory and get all files (excluding settings.json and hidden files)
|
|
1852
|
+
const walkDir = async (dir, baseDir = dir) => {
|
|
1853
|
+
const files = [];
|
|
1854
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
1855
|
+
|
|
1856
|
+
for (const entry of entries) {
|
|
1857
|
+
const fullPath = path.join(dir, entry.name);
|
|
1858
|
+
const relativePath = path.relative(baseDir, fullPath);
|
|
1859
|
+
|
|
1860
|
+
// Skip hidden files, settings.json, and node_modules
|
|
1861
|
+
if (entry.name.startsWith('.') ||
|
|
1862
|
+
entry.name === 'settings.json' ||
|
|
1863
|
+
entry.name === 'node_modules') {
|
|
1864
|
+
continue;
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
if (entry.isDirectory()) {
|
|
1868
|
+
files.push(...await walkDir(fullPath, baseDir));
|
|
1869
|
+
} else {
|
|
1870
|
+
files.push(relativePath);
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
return files;
|
|
1874
|
+
};
|
|
1875
|
+
|
|
1876
|
+
try {
|
|
1877
|
+
console.log(`Uploading site files for KB ${kbId}...`);
|
|
1878
|
+
|
|
1879
|
+
const files = await walkDir(siteDir);
|
|
1880
|
+
|
|
1881
|
+
if (files.length === 0) {
|
|
1882
|
+
return console.yellow('No files found to upload.');
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
console.log(`Found ${files.length} files to upload.`);
|
|
1886
|
+
|
|
1887
|
+
let uploaded = 0;
|
|
1888
|
+
for (const file of files) {
|
|
1889
|
+
const filePath = path.join(siteDir, file);
|
|
1890
|
+
const fileContent = fs.readFileSync(filePath);
|
|
1891
|
+
|
|
1892
|
+
// Get presigned URL for 'files' namespace with correct Content-Type
|
|
1893
|
+
const contentType = getMimeType(file);
|
|
1894
|
+
const response = await makePostRequest(KB_API_URL, {
|
|
1895
|
+
token: kbToken,
|
|
1896
|
+
namespace: 'files',
|
|
1897
|
+
kbId,
|
|
1898
|
+
fileName: file,
|
|
1899
|
+
fileType: contentType,
|
|
1900
|
+
presignedOperation: 'putObject',
|
|
1901
|
+
action: 'createPresignedURL'
|
|
1902
|
+
});
|
|
1903
|
+
|
|
1904
|
+
if (response.error) {
|
|
1905
|
+
console.red(`Failed to get presigned URL for ${file}:`, response.error);
|
|
1906
|
+
continue;
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
// Upload file with correct Content-Type
|
|
1910
|
+
await fetch(response, {
|
|
1911
|
+
method: 'PUT',
|
|
1912
|
+
body: fileContent,
|
|
1913
|
+
headers: { 'Content-Type': contentType }
|
|
1914
|
+
});
|
|
1915
|
+
uploaded++;
|
|
1916
|
+
console.log(`Uploaded: ${file} (${contentType})`);
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
console.green(`\nUpload complete! ${uploaded}/${files.length} files uploaded.`);
|
|
1920
|
+
console.log(`Files accessible at: https://files.openkbs.com/${kbId}/`);
|
|
1921
|
+
|
|
1922
|
+
} catch (error) {
|
|
1923
|
+
console.red('Upload failed:', error.message);
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1927
|
+
// ===== Elastic Pulse Commands =====
|
|
1928
|
+
|
|
1929
|
+
async function pulseAction(subCommand, args = []) {
|
|
1930
|
+
// Find kbId from settings.json (current dir, app/, functions/, site/)
|
|
1931
|
+
let kbId = findKbId();
|
|
1932
|
+
|
|
1933
|
+
if (!kbId) {
|
|
1934
|
+
const localKBData = await fetchLocalKBData();
|
|
1935
|
+
kbId = localKBData?.kbId;
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
if (!kbId) {
|
|
1939
|
+
return console.red('No KB found. Create settings.json with {"kbId": "..."} or run from a KB project directory.');
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
const { kbToken } = await fetchKBJWT(kbId);
|
|
1943
|
+
|
|
1944
|
+
switch (subCommand) {
|
|
1945
|
+
case 'enable':
|
|
1946
|
+
return await pulseEnableAction(kbToken);
|
|
1947
|
+
case 'status':
|
|
1948
|
+
return await pulseStatusAction(kbToken);
|
|
1949
|
+
case 'disable':
|
|
1950
|
+
return await pulseDisableAction(kbToken);
|
|
1951
|
+
case 'channels':
|
|
1952
|
+
return await pulseChannelsAction(kbToken);
|
|
1953
|
+
case 'presence':
|
|
1954
|
+
return await pulsePresenceAction(kbToken, args[0]);
|
|
1955
|
+
case 'publish':
|
|
1956
|
+
case 'send':
|
|
1957
|
+
return await pulsePublishAction(kbToken, args[0], args.slice(1).join(' '));
|
|
1958
|
+
default:
|
|
1959
|
+
console.log('Usage: openkbs pulse <command>');
|
|
1960
|
+
console.log('');
|
|
1961
|
+
console.log('Commands:');
|
|
1962
|
+
console.log(' enable Enable Pulse (WebSocket) for this KB');
|
|
1963
|
+
console.log(' status Show Pulse status and endpoint');
|
|
1964
|
+
console.log(' disable Disable Pulse');
|
|
1965
|
+
console.log(' channels List active channels');
|
|
1966
|
+
console.log(' presence <channel> Show connected clients in channel');
|
|
1967
|
+
console.log(' publish <channel> <message> Send message to channel');
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
async function pulseEnableAction(kbToken) {
|
|
1972
|
+
try {
|
|
1973
|
+
console.log('Enabling Elastic Pulse...');
|
|
1974
|
+
|
|
1975
|
+
const response = await makePostRequest(KB_API_URL, {
|
|
1976
|
+
token: kbToken,
|
|
1977
|
+
action: 'enableElasticPulse'
|
|
1978
|
+
});
|
|
1979
|
+
|
|
1980
|
+
if (response.error) {
|
|
1981
|
+
return console.red('Error:', response.error);
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1984
|
+
if (response.alreadyEnabled) {
|
|
1985
|
+
console.yellow('Pulse already enabled.');
|
|
1986
|
+
} else {
|
|
1987
|
+
console.green('Pulse enabled successfully!');
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
console.log(` Endpoint: ${response.endpoint}`);
|
|
1991
|
+
console.log(` Region: ${response.region}`);
|
|
1992
|
+
} catch (error) {
|
|
1993
|
+
console.red('Error enabling Pulse:', error.message);
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
async function pulseStatusAction(kbToken) {
|
|
1998
|
+
try {
|
|
1999
|
+
const response = await makePostRequest(KB_API_URL, {
|
|
2000
|
+
token: kbToken,
|
|
2001
|
+
action: 'getElasticPulse'
|
|
2002
|
+
});
|
|
2003
|
+
|
|
2004
|
+
if (response.error) {
|
|
2005
|
+
return console.red('Error:', response.error);
|
|
2006
|
+
}
|
|
2007
|
+
|
|
2008
|
+
if (!response.enabled) {
|
|
2009
|
+
console.log('Elastic Pulse: disabled');
|
|
2010
|
+
console.log(' Use "openkbs pulse enable" to enable WebSocket messaging.');
|
|
2011
|
+
return;
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
console.log('Elastic Pulse: enabled');
|
|
2015
|
+
console.log(` Endpoint: ${response.endpoint}`);
|
|
2016
|
+
console.log(` Region: ${response.region}`);
|
|
2017
|
+
} catch (error) {
|
|
2018
|
+
console.red('Error getting Pulse status:', error.message);
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
async function pulseDisableAction(kbToken) {
|
|
2023
|
+
try {
|
|
2024
|
+
console.log('Disabling Elastic Pulse...');
|
|
2025
|
+
|
|
2026
|
+
const response = await makePostRequest(KB_API_URL, {
|
|
2027
|
+
token: kbToken,
|
|
2028
|
+
action: 'disableElasticPulse'
|
|
2029
|
+
});
|
|
2030
|
+
|
|
2031
|
+
if (response.error) {
|
|
2032
|
+
return console.red('Error:', response.error);
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
console.green('Pulse disabled successfully.');
|
|
2036
|
+
} catch (error) {
|
|
2037
|
+
console.red('Error disabling Pulse:', error.message);
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
async function pulseChannelsAction(kbToken) {
|
|
2042
|
+
try {
|
|
2043
|
+
const response = await makePostRequest(KB_API_URL, {
|
|
2044
|
+
token: kbToken,
|
|
2045
|
+
action: 'pulseChannels'
|
|
2046
|
+
});
|
|
2047
|
+
|
|
2048
|
+
if (response.error) {
|
|
2049
|
+
return console.red('Error:', response.error);
|
|
2050
|
+
}
|
|
2051
|
+
|
|
2052
|
+
if (!response.channels || response.channels.length === 0) {
|
|
2053
|
+
console.log('No active channels');
|
|
2054
|
+
return;
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2057
|
+
console.log(`Active channels (${response.totalConnections} total connections):\n`);
|
|
2058
|
+
for (const ch of response.channels) {
|
|
2059
|
+
console.log(` ${ch.channel}: ${ch.count} connection(s)`);
|
|
2060
|
+
}
|
|
2061
|
+
} catch (error) {
|
|
2062
|
+
console.red('Error getting channels:', error.message);
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
|
|
2066
|
+
async function pulsePresenceAction(kbToken, channel) {
|
|
2067
|
+
try {
|
|
2068
|
+
const response = await makePostRequest(KB_API_URL, {
|
|
2069
|
+
token: kbToken,
|
|
2070
|
+
action: 'pulsePresence',
|
|
2071
|
+
channel: channel || 'default'
|
|
2072
|
+
});
|
|
2073
|
+
|
|
2074
|
+
if (response.error) {
|
|
2075
|
+
return console.red('Error:', response.error);
|
|
2076
|
+
}
|
|
2077
|
+
|
|
2078
|
+
console.log(`Channel: ${response.channel}`);
|
|
2079
|
+
console.log(`Connected: ${response.count}`);
|
|
2080
|
+
|
|
2081
|
+
if (response.members && response.members.length > 0) {
|
|
2082
|
+
console.log('\nMembers:');
|
|
2083
|
+
for (const m of response.members) {
|
|
2084
|
+
const time = new Date(m.connectedAt).toISOString();
|
|
2085
|
+
console.log(` ${m.userId || 'anonymous'} (since ${time})`);
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
} catch (error) {
|
|
2089
|
+
console.red('Error getting presence:', error.message);
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2093
|
+
async function pulsePublishAction(kbToken, channel, message) {
|
|
2094
|
+
if (!channel || !message) {
|
|
2095
|
+
return console.red('Usage: openkbs pulse publish <channel> <message>');
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
try {
|
|
2099
|
+
const response = await makePostRequest(KB_API_URL, {
|
|
2100
|
+
token: kbToken,
|
|
2101
|
+
action: 'pulsePublish',
|
|
2102
|
+
channel,
|
|
2103
|
+
message
|
|
2104
|
+
});
|
|
2105
|
+
|
|
2106
|
+
if (response.error) {
|
|
2107
|
+
return console.red('Error:', response.error);
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2110
|
+
console.green(`Message sent to ${response.sent} client(s) on channel "${response.channel}"`);
|
|
2111
|
+
} catch (error) {
|
|
2112
|
+
console.red('Error publishing message:', error.message);
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
|
|
840
2116
|
module.exports = {
|
|
841
2117
|
signAction,
|
|
842
2118
|
loginAction,
|
|
@@ -857,5 +2133,10 @@ module.exports = {
|
|
|
857
2133
|
updateKnowledgeAction,
|
|
858
2134
|
updateCliAction,
|
|
859
2135
|
publishAction,
|
|
860
|
-
unpublishAction
|
|
2136
|
+
unpublishAction,
|
|
2137
|
+
fnAction,
|
|
2138
|
+
siteAction,
|
|
2139
|
+
storageAction,
|
|
2140
|
+
postgresAction,
|
|
2141
|
+
pulseAction
|
|
861
2142
|
};
|