opencode-studio-server 1.25.0 → 1.27.0
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/index.js +154 -249
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -1103,15 +1103,7 @@ async function ensureGitHubRepo(token, repoName) {
|
|
|
1103
1103
|
});
|
|
1104
1104
|
|
|
1105
1105
|
if (response.ok) {
|
|
1106
|
-
|
|
1107
|
-
const branch = data.default_branch || 'main';
|
|
1108
|
-
const refRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`, {
|
|
1109
|
-
headers: { 'Authorization': `Bearer ${token}` }
|
|
1110
|
-
});
|
|
1111
|
-
if (refRes.status === 404 || refRes.status === 409) {
|
|
1112
|
-
await bootstrapEmptyRepo(token, owner, repo);
|
|
1113
|
-
}
|
|
1114
|
-
return data;
|
|
1106
|
+
return await response.json();
|
|
1115
1107
|
}
|
|
1116
1108
|
|
|
1117
1109
|
if (response.status === 404) {
|
|
@@ -1130,6 +1122,7 @@ async function ensureGitHubRepo(token, repoName) {
|
|
|
1130
1122
|
});
|
|
1131
1123
|
|
|
1132
1124
|
if (createRes.ok) {
|
|
1125
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
1133
1126
|
return await createRes.json();
|
|
1134
1127
|
}
|
|
1135
1128
|
|
|
@@ -1141,183 +1134,34 @@ async function ensureGitHubRepo(token, repoName) {
|
|
|
1141
1134
|
throw new Error(`Failed to check repo: ${err}`);
|
|
1142
1135
|
}
|
|
1143
1136
|
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
'Authorization': `Bearer ${token}`,
|
|
1149
|
-
'Content-Type': 'application/json'
|
|
1150
|
-
},
|
|
1151
|
-
body: JSON.stringify({
|
|
1152
|
-
message: 'Initial commit',
|
|
1153
|
-
content: Buffer.from('# OpenCode Studio Backup\n').toString('base64')
|
|
1154
|
-
})
|
|
1155
|
-
});
|
|
1156
|
-
|
|
1157
|
-
if (!res.ok) {
|
|
1158
|
-
const err = await res.text();
|
|
1159
|
-
throw new Error(`Failed to bootstrap repo: ${err}`);
|
|
1160
|
-
}
|
|
1161
|
-
}
|
|
1162
|
-
|
|
1163
|
-
function collectBlobs(rootDir, basePath = '', blobs = []) {
|
|
1164
|
-
const dir = basePath || rootDir;
|
|
1165
|
-
if (!fs.existsSync(dir)) return blobs;
|
|
1137
|
+
function copyDirContents(src, dest) {
|
|
1138
|
+
if (!fs.existsSync(src)) return;
|
|
1139
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
1140
|
+
const SKIP_DIRS = ['node_modules', '.git', '.next', 'cache'];
|
|
1166
1141
|
const SKIP_EXT = ['.log', '.tmp', '.db', '.sqlite', '.cache', '.pack', '.idx'];
|
|
1167
1142
|
|
|
1168
|
-
for (const name of fs.readdirSync(
|
|
1169
|
-
const
|
|
1170
|
-
const
|
|
1143
|
+
for (const name of fs.readdirSync(src)) {
|
|
1144
|
+
const srcPath = path.join(src, name);
|
|
1145
|
+
const destPath = path.join(dest, name);
|
|
1146
|
+
const stat = fs.statSync(srcPath);
|
|
1171
1147
|
|
|
1172
1148
|
if (stat.isDirectory()) {
|
|
1173
|
-
if (
|
|
1174
|
-
|
|
1149
|
+
if (SKIP_DIRS.includes(name)) continue;
|
|
1150
|
+
copyDirContents(srcPath, destPath);
|
|
1175
1151
|
} else {
|
|
1176
1152
|
if (SKIP_EXT.some(ext => name.endsWith(ext))) continue;
|
|
1177
|
-
|
|
1178
|
-
const content = fs.readFileSync(fullPath, 'utf8');
|
|
1179
|
-
blobs.push({
|
|
1180
|
-
path: path.relative(rootDir, fullPath).replace(/\\/g, '/'),
|
|
1181
|
-
mode: '100644',
|
|
1182
|
-
type: 'blob',
|
|
1183
|
-
content
|
|
1184
|
-
});
|
|
1185
|
-
} catch (e) { }
|
|
1153
|
+
fs.copyFileSync(srcPath, destPath);
|
|
1186
1154
|
}
|
|
1187
1155
|
}
|
|
1188
|
-
return blobs;
|
|
1189
|
-
}
|
|
1190
|
-
|
|
1191
|
-
async function createGitHubBlob(token, repoName, blob) {
|
|
1192
|
-
const [owner, repo] = repoName.split('/');
|
|
1193
|
-
const response = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/blobs`, {
|
|
1194
|
-
method: 'POST',
|
|
1195
|
-
headers: {
|
|
1196
|
-
'Authorization': `Bearer ${token}`,
|
|
1197
|
-
'Content-Type': 'application/json'
|
|
1198
|
-
},
|
|
1199
|
-
body: JSON.stringify({
|
|
1200
|
-
content: Buffer.from(blob.content).toString('base64'),
|
|
1201
|
-
encoding: 'base64'
|
|
1202
|
-
})
|
|
1203
|
-
});
|
|
1204
|
-
|
|
1205
|
-
if (!response.ok) {
|
|
1206
|
-
const err = await response.text();
|
|
1207
|
-
throw new Error(`Failed to create blob: ${err}`);
|
|
1208
|
-
}
|
|
1209
|
-
|
|
1210
|
-
const data = await response.json();
|
|
1211
|
-
return data.sha;
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
async function createGitHubTree(token, repoName, treeItems, baseSha = null) {
|
|
1215
|
-
const [owner, repo] = repoName.split('/');
|
|
1216
|
-
const body = {
|
|
1217
|
-
tree: treeItems.map(item => ({
|
|
1218
|
-
path: item.path,
|
|
1219
|
-
mode: item.mode,
|
|
1220
|
-
type: item.type,
|
|
1221
|
-
sha: item.sha
|
|
1222
|
-
}))
|
|
1223
|
-
};
|
|
1224
|
-
if (baseSha) body.base_tree = baseSha;
|
|
1225
|
-
|
|
1226
|
-
const response = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/trees`, {
|
|
1227
|
-
method: 'POST',
|
|
1228
|
-
headers: {
|
|
1229
|
-
'Authorization': `Bearer ${token}`,
|
|
1230
|
-
'Content-Type': 'application/json'
|
|
1231
|
-
},
|
|
1232
|
-
body: JSON.stringify(body)
|
|
1233
|
-
});
|
|
1234
|
-
|
|
1235
|
-
if (!response.ok) {
|
|
1236
|
-
const err = await response.text();
|
|
1237
|
-
throw new Error(`Failed to create tree: ${err}`);
|
|
1238
|
-
}
|
|
1239
|
-
|
|
1240
|
-
const data = await response.json();
|
|
1241
|
-
return data.sha;
|
|
1242
1156
|
}
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
});
|
|
1250
|
-
|
|
1251
|
-
let parentSha = null;
|
|
1252
|
-
if (headRes.ok) {
|
|
1253
|
-
const headData = await headRes.json();
|
|
1254
|
-
parentSha = headData.object.sha;
|
|
1255
|
-
}
|
|
1256
|
-
|
|
1257
|
-
const response = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/commits`, {
|
|
1258
|
-
method: 'POST',
|
|
1259
|
-
headers: {
|
|
1260
|
-
'Authorization': `Bearer ${token}`,
|
|
1261
|
-
'Content-Type': 'application/json'
|
|
1262
|
-
},
|
|
1263
|
-
body: JSON.stringify({
|
|
1264
|
-
message,
|
|
1265
|
-
tree: treeSha,
|
|
1266
|
-
parents: parentSha ? [parentSha] : []
|
|
1267
|
-
})
|
|
1268
|
-
});
|
|
1269
|
-
|
|
1270
|
-
if (!response.ok) {
|
|
1271
|
-
const err = await response.text();
|
|
1272
|
-
throw new Error(`Failed to create commit: ${err}`);
|
|
1273
|
-
}
|
|
1274
|
-
|
|
1275
|
-
const data = await response.json();
|
|
1276
|
-
return data.sha;
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
async function updateGitHubRef(token, repoName, commitSha, branch = 'main') {
|
|
1280
|
-
const [owner, repo] = repoName.split('/');
|
|
1281
|
-
|
|
1282
|
-
const checkRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`, {
|
|
1283
|
-
headers: { 'Authorization': `Bearer ${token}` }
|
|
1284
|
-
});
|
|
1285
|
-
|
|
1286
|
-
if (checkRes.status === 404) {
|
|
1287
|
-
const createRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/refs`, {
|
|
1288
|
-
method: 'POST',
|
|
1289
|
-
headers: {
|
|
1290
|
-
'Authorization': `Bearer ${token}`,
|
|
1291
|
-
'Content-Type': 'application/json'
|
|
1292
|
-
},
|
|
1293
|
-
body: JSON.stringify({
|
|
1294
|
-
ref: `refs/heads/${branch}`,
|
|
1295
|
-
sha: commitSha
|
|
1296
|
-
})
|
|
1157
|
+
|
|
1158
|
+
function execPromise(cmd, opts = {}) {
|
|
1159
|
+
return new Promise((resolve, reject) => {
|
|
1160
|
+
exec(cmd, opts, (err, stdout, stderr) => {
|
|
1161
|
+
if (err) reject(new Error(stderr || err.message));
|
|
1162
|
+
else resolve(stdout.trim());
|
|
1297
1163
|
});
|
|
1298
|
-
|
|
1299
|
-
if (!createRes.ok) {
|
|
1300
|
-
const err = await createRes.text();
|
|
1301
|
-
throw new Error(`Failed to create ref: ${err}`);
|
|
1302
|
-
}
|
|
1303
|
-
return;
|
|
1304
|
-
}
|
|
1305
|
-
|
|
1306
|
-
const response = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`, {
|
|
1307
|
-
method: 'PATCH',
|
|
1308
|
-
headers: {
|
|
1309
|
-
'Authorization': `Bearer ${token}`,
|
|
1310
|
-
'Content-Type': 'application/json'
|
|
1311
|
-
},
|
|
1312
|
-
body: JSON.stringify({
|
|
1313
|
-
sha: commitSha
|
|
1314
|
-
})
|
|
1315
1164
|
});
|
|
1316
|
-
|
|
1317
|
-
if (!response.ok) {
|
|
1318
|
-
const err = await response.text();
|
|
1319
|
-
throw new Error(`Failed to update ref: ${err}`);
|
|
1320
|
-
}
|
|
1321
1165
|
}
|
|
1322
1166
|
|
|
1323
1167
|
app.get('/api/github/backup/status', async (req, res) => {
|
|
@@ -1331,100 +1175,161 @@ app.get('/api/github/backup/status', async (req, res) => {
|
|
|
1331
1175
|
const user = await getGitHubUser(token);
|
|
1332
1176
|
if (!user) return res.json({ connected: false, config: backupConfig, error: 'Failed to get GitHub user' });
|
|
1333
1177
|
|
|
1334
|
-
if (!backupConfig.repo) {
|
|
1335
|
-
return res.json({ connected: true, user: user.login, config: backupConfig });
|
|
1336
|
-
}
|
|
1178
|
+
if (!backupConfig.repo) {
|
|
1179
|
+
return res.json({ connected: true, user: user.login, config: backupConfig, autoSync: studio.githubAutoSync || false });
|
|
1180
|
+
}
|
|
1337
1181
|
|
|
1338
1182
|
const owner = backupConfig.owner || user.login;
|
|
1339
1183
|
const response = await fetch(`https://api.github.com/repos/${owner}/${backupConfig.repo}`, {
|
|
1340
1184
|
headers: { 'Authorization': `Bearer ${token}` }
|
|
1341
1185
|
});
|
|
1342
1186
|
|
|
1343
|
-
if (!response.ok) {
|
|
1344
|
-
return res.json({ connected: true, user: user.login, config: backupConfig, repoExists: false });
|
|
1345
|
-
}
|
|
1187
|
+
if (!response.ok) {
|
|
1188
|
+
return res.json({ connected: true, user: user.login, config: backupConfig, repoExists: false, autoSync: studio.githubAutoSync || false });
|
|
1189
|
+
}
|
|
1346
1190
|
|
|
1347
|
-
const data = await response.json();
|
|
1348
|
-
res.json({ connected: true, user: user.login, config: backupConfig, repoExists: true, lastUpdated: data.pushed_at });
|
|
1349
|
-
} catch (err) {
|
|
1191
|
+
const data = await response.json();
|
|
1192
|
+
res.json({ connected: true, user: user.login, config: backupConfig, repoExists: true, lastUpdated: data.pushed_at, autoSync: studio.githubAutoSync || false });
|
|
1193
|
+
} catch (err) {
|
|
1350
1194
|
res.json({ connected: false, error: err.message });
|
|
1351
1195
|
}
|
|
1352
1196
|
});
|
|
1353
1197
|
|
|
1354
|
-
app.post('/api/github/backup', async (req, res) => {
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
const
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
const
|
|
1367
|
-
const
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1198
|
+
app.post('/api/github/backup', async (req, res) => {
|
|
1199
|
+
let tempDir = null;
|
|
1200
|
+
try {
|
|
1201
|
+
const token = await getGitHubToken();
|
|
1202
|
+
if (!token) return res.status(400).json({ error: 'Not logged in to gh CLI. Run: gh auth login' });
|
|
1203
|
+
|
|
1204
|
+
const user = await getGitHubUser(token);
|
|
1205
|
+
if (!user) return res.status(400).json({ error: 'Failed to get GitHub user' });
|
|
1206
|
+
|
|
1207
|
+
const { owner, repo, branch } = req.body;
|
|
1208
|
+
const studio = loadStudioConfig();
|
|
1209
|
+
|
|
1210
|
+
const finalOwner = owner || studio.githubBackup?.owner || user.login;
|
|
1211
|
+
const finalRepo = repo || studio.githubBackup?.repo;
|
|
1212
|
+
const finalBranch = branch || studio.githubBackup?.branch || 'main';
|
|
1213
|
+
|
|
1214
|
+
if (!finalRepo) return res.status(400).json({ error: 'No repo name provided' });
|
|
1215
|
+
|
|
1216
|
+
const repoName = `${finalOwner}/${finalRepo}`;
|
|
1217
|
+
|
|
1218
|
+
await ensureGitHubRepo(token, repoName);
|
|
1219
|
+
|
|
1220
|
+
const opencodeConfig = getConfigPath();
|
|
1221
|
+
if (!opencodeConfig) return res.status(400).json({ error: 'No opencode config path found' });
|
|
1222
|
+
|
|
1378
1223
|
const opencodeDir = path.dirname(opencodeConfig);
|
|
1379
1224
|
const studioDir = path.join(HOME_DIR, '.config', 'opencode-studio');
|
|
1380
1225
|
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1226
|
+
tempDir = path.join(os.tmpdir(), `opencode-backup-${Date.now()}`);
|
|
1227
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
1228
|
+
|
|
1229
|
+
await execPromise(`git clone --depth 1 https://x-access-token:${token}@github.com/${repoName}.git .`, { cwd: tempDir });
|
|
1384
1230
|
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1231
|
+
const backupOpencodeDir = path.join(tempDir, 'opencode');
|
|
1232
|
+
const backupStudioDir = path.join(tempDir, 'opencode-studio');
|
|
1233
|
+
|
|
1234
|
+
if (fs.existsSync(backupOpencodeDir)) fs.rmSync(backupOpencodeDir, { recursive: true });
|
|
1235
|
+
if (fs.existsSync(backupStudioDir)) fs.rmSync(backupStudioDir, { recursive: true });
|
|
1236
|
+
|
|
1237
|
+
copyDirContents(opencodeDir, backupOpencodeDir);
|
|
1238
|
+
copyDirContents(studioDir, backupStudioDir);
|
|
1239
|
+
|
|
1240
|
+
await execPromise('git add -A', { cwd: tempDir });
|
|
1241
|
+
|
|
1242
|
+
const timestamp = new Date().toISOString();
|
|
1243
|
+
const commitMessage = `OpenCode Studio backup ${timestamp}`;
|
|
1244
|
+
|
|
1245
|
+
try {
|
|
1246
|
+
await execPromise(`git commit -m "${commitMessage}"`, { cwd: tempDir });
|
|
1247
|
+
await execPromise(`git push origin ${finalBranch}`, { cwd: tempDir });
|
|
1248
|
+
} catch (e) {
|
|
1249
|
+
if (e.message.includes('nothing to commit')) {
|
|
1250
|
+
fs.rmSync(tempDir, { recursive: true });
|
|
1251
|
+
return res.json({ success: true, timestamp, message: 'No changes to backup', url: `https://github.com/${repoName}` });
|
|
1393
1252
|
}
|
|
1253
|
+
throw e;
|
|
1394
1254
|
}
|
|
1395
1255
|
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1256
|
+
fs.rmSync(tempDir, { recursive: true });
|
|
1257
|
+
|
|
1258
|
+
studio.githubBackup = { owner: finalOwner, repo: finalRepo, branch: finalBranch };
|
|
1259
|
+
studio.lastGithubBackup = timestamp;
|
|
1260
|
+
saveStudioConfig(studio);
|
|
1261
|
+
|
|
1262
|
+
res.json({ success: true, timestamp, url: `https://github.com/${repoName}` });
|
|
1263
|
+
} catch (err) {
|
|
1264
|
+
if (tempDir && fs.existsSync(tempDir)) fs.rmSync(tempDir, { recursive: true });
|
|
1265
|
+
console.error('GitHub backup error:', err);
|
|
1266
|
+
res.status(500).json({ error: err.message });
|
|
1267
|
+
}
|
|
1268
|
+
});
|
|
1269
|
+
|
|
1270
|
+
app.post('/api/github/restore', async (req, res) => {
|
|
1271
|
+
let tempDir = null;
|
|
1272
|
+
try {
|
|
1273
|
+
const token = await getGitHubToken();
|
|
1274
|
+
if (!token) return res.status(400).json({ error: 'Not logged in to gh CLI. Run: gh auth login' });
|
|
1275
|
+
|
|
1276
|
+
const user = await getGitHubUser(token);
|
|
1277
|
+
if (!user) return res.status(400).json({ error: 'Failed to get GitHub user' });
|
|
1278
|
+
|
|
1279
|
+
const { owner, repo, branch } = req.body;
|
|
1280
|
+
const studio = loadStudioConfig();
|
|
1281
|
+
|
|
1282
|
+
const finalOwner = owner || studio.githubBackup?.owner || user.login;
|
|
1283
|
+
const finalRepo = repo || studio.githubBackup?.repo;
|
|
1284
|
+
const finalBranch = branch || studio.githubBackup?.branch || 'main';
|
|
1285
|
+
|
|
1286
|
+
if (!finalRepo) return res.status(400).json({ error: 'No repo configured' });
|
|
1287
|
+
|
|
1288
|
+
const repoName = `${finalOwner}/${finalRepo}`;
|
|
1289
|
+
|
|
1290
|
+
const opencodeConfig = getConfigPath();
|
|
1291
|
+
if (!opencodeConfig) return res.status(400).json({ error: 'No opencode config path found' });
|
|
1292
|
+
|
|
1293
|
+
const opencodeDir = path.dirname(opencodeConfig);
|
|
1294
|
+
const studioDir = path.join(HOME_DIR, '.config', 'opencode-studio');
|
|
1295
|
+
|
|
1296
|
+
tempDir = path.join(os.tmpdir(), `opencode-restore-${Date.now()}`);
|
|
1297
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
1298
|
+
|
|
1299
|
+
await execPromise(`git clone --depth 1 -b ${finalBranch} https://x-access-token:${token}@github.com/${repoName}.git .`, { cwd: tempDir });
|
|
1300
|
+
|
|
1301
|
+
const backupOpencodeDir = path.join(tempDir, 'opencode');
|
|
1302
|
+
const backupStudioDir = path.join(tempDir, 'opencode-studio');
|
|
1303
|
+
|
|
1304
|
+
if (!fs.existsSync(backupOpencodeDir)) {
|
|
1305
|
+
fs.rmSync(tempDir, { recursive: true });
|
|
1306
|
+
return res.status(400).json({ error: 'No opencode backup found in repository' });
|
|
1405
1307
|
}
|
|
1406
1308
|
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1309
|
+
copyDirContents(backupOpencodeDir, opencodeDir);
|
|
1310
|
+
if (fs.existsSync(backupStudioDir)) {
|
|
1311
|
+
copyDirContents(backupStudioDir, studioDir);
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
fs.rmSync(tempDir, { recursive: true });
|
|
1315
|
+
|
|
1316
|
+
res.json({ success: true, message: 'Config restored from GitHub' });
|
|
1317
|
+
} catch (err) {
|
|
1318
|
+
if (tempDir && fs.existsSync(tempDir)) fs.rmSync(tempDir, { recursive: true });
|
|
1319
|
+
console.error('GitHub restore error:', err);
|
|
1320
|
+
res.status(500).json({ error: err.message });
|
|
1321
|
+
}
|
|
1322
|
+
});
|
|
1323
|
+
|
|
1324
|
+
app.post('/api/github/autosync', async (req, res) => {
|
|
1325
|
+
const studio = loadStudioConfig();
|
|
1326
|
+
const enabled = req.body.enabled;
|
|
1327
|
+
studio.githubAutoSync = enabled;
|
|
1328
|
+
saveStudioConfig(studio);
|
|
1329
|
+
res.json({ success: true, enabled });
|
|
1330
|
+
});
|
|
1331
|
+
|
|
1332
|
+
const getSkillDir = () => {
|
|
1428
1333
|
const cp = getConfigPath();
|
|
1429
1334
|
return cp ? path.join(path.dirname(cp), 'skill') : null;
|
|
1430
1335
|
};
|