opencode-studio-server 1.19.0 → 1.21.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 +122 -104
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -1027,37 +1027,30 @@ app.post('/api/ohmyopencode', (req, res) => {
|
|
|
1027
1027
|
return res.status(400).json({ error: 'Missing preferences.agents' });
|
|
1028
1028
|
}
|
|
1029
1029
|
|
|
1030
|
-
// Save preferences to studio.json
|
|
1031
1030
|
const studio = loadStudioConfig();
|
|
1032
1031
|
studio.ohmy = preferences;
|
|
1033
1032
|
saveStudioConfig(studio);
|
|
1034
1033
|
|
|
1035
|
-
// Load current oh-my-opencode.json or start fresh
|
|
1036
1034
|
const currentConfig = loadOhMyOpenCodeConfig() || {};
|
|
1037
1035
|
const warnings = [];
|
|
1038
1036
|
|
|
1039
|
-
// For each agent, pick first available model and apply thinking/reasoning
|
|
1040
1037
|
for (const [agentName, agentPrefs] of Object.entries(preferences.agents)) {
|
|
1041
1038
|
const choices = agentPrefs.choices || [];
|
|
1042
1039
|
const available = choices.find(c => c.available);
|
|
1043
1040
|
if (available) {
|
|
1044
|
-
// Set the model in oh-my-opencode.json
|
|
1045
1041
|
if (!currentConfig.agents) currentConfig.agents = {};
|
|
1046
1042
|
const agentConfig = { model: available.model };
|
|
1047
1043
|
|
|
1048
|
-
|
|
1049
|
-
if (agentPrefs.thinking && agentPrefs.thinking.type === 'enabled') {
|
|
1044
|
+
if (available.thinking && available.thinking.type === 'enabled') {
|
|
1050
1045
|
agentConfig.thinking = { type: 'enabled' };
|
|
1051
1046
|
}
|
|
1052
1047
|
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
agentConfig.reasoning = { effort: agentPrefs.reasoning.effort };
|
|
1048
|
+
if (available.reasoning && available.reasoning.effort) {
|
|
1049
|
+
agentConfig.reasoning = { effort: available.reasoning.effort };
|
|
1056
1050
|
}
|
|
1057
1051
|
|
|
1058
1052
|
currentConfig.agents[agentName] = agentConfig;
|
|
1059
1053
|
} else if (choices.length > 0) {
|
|
1060
|
-
// No available model, keep existing or warn
|
|
1061
1054
|
warnings.push(`No available model for agent "${agentName}"`);
|
|
1062
1055
|
}
|
|
1063
1056
|
}
|
|
@@ -1144,40 +1137,34 @@ async function ensureGitHubRepo(token, repoName) {
|
|
|
1144
1137
|
throw new Error(`Failed to check repo: ${err}`);
|
|
1145
1138
|
}
|
|
1146
1139
|
|
|
1147
|
-
function
|
|
1148
|
-
const
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
const
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
files[blobKey] = blob;
|
|
1172
|
-
tree.push(blob);
|
|
1173
|
-
}
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
return { sha: null, tree };
|
|
1177
|
-
}
|
|
1140
|
+
function collectBlobs(rootDir, basePath = '', blobs = []) {
|
|
1141
|
+
const dir = basePath || rootDir;
|
|
1142
|
+
if (!fs.existsSync(dir)) return blobs;
|
|
1143
|
+
|
|
1144
|
+
for (const name of fs.readdirSync(dir)) {
|
|
1145
|
+
const fullPath = path.join(dir, name);
|
|
1146
|
+
const stat = fs.statSync(fullPath);
|
|
1147
|
+
|
|
1148
|
+
if (stat.isDirectory()) {
|
|
1149
|
+
if (name === 'node_modules' || name === '.git' || name === '.next') continue;
|
|
1150
|
+
collectBlobs(rootDir, fullPath, blobs);
|
|
1151
|
+
} else {
|
|
1152
|
+
if (name.endsWith('.log') || name.endsWith('.tmp')) continue;
|
|
1153
|
+
const content = fs.readFileSync(fullPath, 'utf8');
|
|
1154
|
+
blobs.push({
|
|
1155
|
+
path: path.relative(rootDir, fullPath).replace(/\\/g, '/'),
|
|
1156
|
+
mode: '100644',
|
|
1157
|
+
type: 'blob',
|
|
1158
|
+
content
|
|
1159
|
+
});
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
return blobs;
|
|
1163
|
+
}
|
|
1178
1164
|
|
|
1179
|
-
async function createGitHubBlob(token, blob) {
|
|
1180
|
-
const
|
|
1165
|
+
async function createGitHubBlob(token, repoName, blob) {
|
|
1166
|
+
const [owner, repo] = repoName.split('/');
|
|
1167
|
+
const response = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/blobs`, {
|
|
1181
1168
|
method: 'POST',
|
|
1182
1169
|
headers: {
|
|
1183
1170
|
'Authorization': `Bearer ${token}`,
|
|
@@ -1198,32 +1185,35 @@ async function createGitHubBlob(token, blob) {
|
|
|
1198
1185
|
return data.sha;
|
|
1199
1186
|
}
|
|
1200
1187
|
|
|
1201
|
-
async function createGitHubTree(token, repoName, treeItems) {
|
|
1202
|
-
const [owner, repo] = repoName.split('/');
|
|
1203
|
-
const
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1188
|
+
async function createGitHubTree(token, repoName, treeItems, baseSha = null) {
|
|
1189
|
+
const [owner, repo] = repoName.split('/');
|
|
1190
|
+
const body = {
|
|
1191
|
+
tree: treeItems.map(item => ({
|
|
1192
|
+
path: item.path,
|
|
1193
|
+
mode: item.mode,
|
|
1194
|
+
type: item.type,
|
|
1195
|
+
sha: item.sha
|
|
1196
|
+
}))
|
|
1197
|
+
};
|
|
1198
|
+
if (baseSha) body.base_tree = baseSha;
|
|
1199
|
+
|
|
1200
|
+
const response = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/trees`, {
|
|
1201
|
+
method: 'POST',
|
|
1202
|
+
headers: {
|
|
1203
|
+
'Authorization': `Bearer ${token}`,
|
|
1204
|
+
'Content-Type': 'application/json'
|
|
1205
|
+
},
|
|
1206
|
+
body: JSON.stringify(body)
|
|
1207
|
+
});
|
|
1208
|
+
|
|
1209
|
+
if (!response.ok) {
|
|
1210
|
+
const err = await response.text();
|
|
1211
|
+
throw new Error(`Failed to create tree: ${err}`);
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
const data = await response.json();
|
|
1215
|
+
return data.sha;
|
|
1216
|
+
}
|
|
1227
1217
|
|
|
1228
1218
|
async function createGitHubCommit(token, repoName, treeSha, message) {
|
|
1229
1219
|
const [owner, repo] = repoName.split('/');
|
|
@@ -1260,25 +1250,49 @@ async function createGitHubCommit(token, repoName, treeSha, message) {
|
|
|
1260
1250
|
return data.sha;
|
|
1261
1251
|
}
|
|
1262
1252
|
|
|
1263
|
-
async function updateGitHubRef(token, repoName, commitSha, branch = 'main') {
|
|
1264
|
-
const [owner, repo] = repoName.split('/');
|
|
1265
|
-
|
|
1266
|
-
const
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
}
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
}
|
|
1253
|
+
async function updateGitHubRef(token, repoName, commitSha, branch = 'main') {
|
|
1254
|
+
const [owner, repo] = repoName.split('/');
|
|
1255
|
+
|
|
1256
|
+
const checkRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`, {
|
|
1257
|
+
headers: { 'Authorization': `Bearer ${token}` }
|
|
1258
|
+
});
|
|
1259
|
+
|
|
1260
|
+
if (checkRes.status === 404) {
|
|
1261
|
+
const createRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/refs`, {
|
|
1262
|
+
method: 'POST',
|
|
1263
|
+
headers: {
|
|
1264
|
+
'Authorization': `Bearer ${token}`,
|
|
1265
|
+
'Content-Type': 'application/json'
|
|
1266
|
+
},
|
|
1267
|
+
body: JSON.stringify({
|
|
1268
|
+
ref: `refs/heads/${branch}`,
|
|
1269
|
+
sha: commitSha
|
|
1270
|
+
})
|
|
1271
|
+
});
|
|
1272
|
+
|
|
1273
|
+
if (!createRes.ok) {
|
|
1274
|
+
const err = await createRes.text();
|
|
1275
|
+
throw new Error(`Failed to create ref: ${err}`);
|
|
1276
|
+
}
|
|
1277
|
+
return;
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
const response = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`, {
|
|
1281
|
+
method: 'PATCH',
|
|
1282
|
+
headers: {
|
|
1283
|
+
'Authorization': `Bearer ${token}`,
|
|
1284
|
+
'Content-Type': 'application/json'
|
|
1285
|
+
},
|
|
1286
|
+
body: JSON.stringify({
|
|
1287
|
+
sha: commitSha
|
|
1288
|
+
})
|
|
1289
|
+
});
|
|
1290
|
+
|
|
1291
|
+
if (!response.ok) {
|
|
1292
|
+
const err = await response.text();
|
|
1293
|
+
throw new Error(`Failed to update ref: ${err}`);
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1282
1296
|
|
|
1283
1297
|
app.get('/api/github/backup/status', async (req, res) => {
|
|
1284
1298
|
try {
|
|
@@ -1335,22 +1349,26 @@ app.post('/api/github/backup', async (req, res) => {
|
|
|
1335
1349
|
const opencodeConfig = getConfigPath();
|
|
1336
1350
|
if (!opencodeConfig) return res.status(400).json({ error: 'No opencode config path found' });
|
|
1337
1351
|
|
|
1338
|
-
const opencodeDir = path.dirname(opencodeConfig);
|
|
1339
|
-
const studioDir = path.join(HOME_DIR, '.config', 'opencode-studio');
|
|
1340
|
-
|
|
1341
|
-
const
|
|
1342
|
-
const
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1352
|
+
const opencodeDir = path.dirname(opencodeConfig);
|
|
1353
|
+
const studioDir = path.join(HOME_DIR, '.config', 'opencode-studio');
|
|
1354
|
+
|
|
1355
|
+
const opencodeBlobs = collectBlobs(opencodeDir);
|
|
1356
|
+
const studioBlobs = collectBlobs(studioDir);
|
|
1357
|
+
|
|
1358
|
+
for (const blob of opencodeBlobs) {
|
|
1359
|
+
blob.sha = await createGitHubBlob(token, repoName, blob);
|
|
1360
|
+
blob.path = `opencode/${blob.path}`;
|
|
1361
|
+
delete blob.content;
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
for (const blob of studioBlobs) {
|
|
1365
|
+
blob.sha = await createGitHubBlob(token, repoName, blob);
|
|
1366
|
+
blob.path = `opencode-studio/${blob.path}`;
|
|
1367
|
+
delete blob.content;
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
const allBlobs = [...opencodeBlobs, ...studioBlobs];
|
|
1371
|
+
const rootTreeSha = await createGitHubTree(token, repoName, allBlobs);
|
|
1354
1372
|
|
|
1355
1373
|
const timestamp = new Date().toISOString();
|
|
1356
1374
|
const commitMessage = `OpenCode Studio backup ${timestamp}`;
|