@openchamber/web 1.4.5 → 1.4.7
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/assets/{ToolOutputDialog-DYNTzpsQ.js → ToolOutputDialog-oDE1UvDN.js} +1 -1
- package/dist/assets/{index-g64tpi5J.js → index-BqCwlsig.js} +2 -2
- package/dist/assets/index-CFHNKWvn.css +1 -0
- package/dist/assets/main-Bi1ZnDPY.js +128 -0
- package/dist/assets/{vendor-.bun-B2HtLj-d.js → vendor-.bun-C07YQe9X.js} +240 -240
- package/dist/index.html +3 -3
- package/package.json +7 -5
- package/server/index.js +165 -347
- package/server/lib/skills-catalog/clawdhub/api.js +129 -0
- package/server/lib/skills-catalog/clawdhub/index.js +30 -0
- package/server/lib/skills-catalog/clawdhub/install.js +200 -0
- package/server/lib/skills-catalog/clawdhub/scan.js +73 -0
- package/server/lib/skills-catalog/curated-sources.js +9 -1
- package/dist/assets/index-hru9kOov.css +0 -1
- package/dist/assets/main-BucgrSbb.js +0 -128
package/server/index.js
CHANGED
|
@@ -9,12 +9,12 @@ import os from 'os';
|
|
|
9
9
|
import crypto from 'crypto';
|
|
10
10
|
import { createUiAuth } from './lib/ui-auth.js';
|
|
11
11
|
import { startCloudflareTunnel, printTunnelWarning, checkCloudflaredAvailable } from './lib/cloudflare-tunnel.js';
|
|
12
|
+
import { createOpencodeServer } from '@opencode-ai/sdk/server';
|
|
12
13
|
|
|
13
14
|
const __filename = fileURLToPath(import.meta.url);
|
|
14
15
|
const __dirname = path.dirname(__filename);
|
|
15
16
|
|
|
16
17
|
const DEFAULT_PORT = 3000;
|
|
17
|
-
const DEFAULT_OPENCODE_PORT = 0;
|
|
18
18
|
const HEALTH_CHECK_INTERVAL = 30000;
|
|
19
19
|
const SHUTDOWN_TIMEOUT = 10000;
|
|
20
20
|
const MODELS_DEV_API_URL = 'https://models.dev/api.json';
|
|
@@ -848,7 +848,6 @@ let openCodeApiDetectionTimer = null;
|
|
|
848
848
|
let isDetectingApiPrefix = false;
|
|
849
849
|
let openCodeApiDetectionPromise = null;
|
|
850
850
|
let lastOpenCodeError = null;
|
|
851
|
-
let openCodePortWaiters = [];
|
|
852
851
|
let isOpenCodeReady = false;
|
|
853
852
|
let openCodeNotReadySince = 0;
|
|
854
853
|
let exitOnShutdown = true;
|
|
@@ -890,12 +889,7 @@ async function isOpenCodeProcessHealthy() {
|
|
|
890
889
|
return false;
|
|
891
890
|
}
|
|
892
891
|
|
|
893
|
-
//
|
|
894
|
-
if (openCodeProcess.exitCode !== null || openCodeProcess.signalCode !== null) {
|
|
895
|
-
return false;
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
// Health check via HTTP
|
|
892
|
+
// Health check via HTTP since SDK object doesn't expose exitCode
|
|
899
893
|
try {
|
|
900
894
|
const response = await fetch(`http://127.0.0.1:${openCodePort}/session`, {
|
|
901
895
|
method: 'GET',
|
|
@@ -907,105 +901,6 @@ async function isOpenCodeProcessHealthy() {
|
|
|
907
901
|
}
|
|
908
902
|
}
|
|
909
903
|
|
|
910
|
-
const OPENCODE_BINARY_ENV =
|
|
911
|
-
process.env.OPENCODE_BINARY ||
|
|
912
|
-
process.env.OPENCHAMBER_BINARY ||
|
|
913
|
-
process.env.OPENCODE_PATH ||
|
|
914
|
-
process.env.OPENCHAMBER_OPENCODE_PATH ||
|
|
915
|
-
null;
|
|
916
|
-
|
|
917
|
-
function buildAugmentedPath() {
|
|
918
|
-
const augmented = new Set();
|
|
919
|
-
|
|
920
|
-
const loginShellPath = getLoginShellPath();
|
|
921
|
-
if (loginShellPath) {
|
|
922
|
-
for (const segment of loginShellPath.split(path.delimiter)) {
|
|
923
|
-
if (segment) {
|
|
924
|
-
augmented.add(segment);
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
|
|
929
|
-
const current = (process.env.PATH || '').split(path.delimiter).filter(Boolean);
|
|
930
|
-
for (const segment of current) {
|
|
931
|
-
augmented.add(segment);
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
return Array.from(augmented).join(path.delimiter);
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
function getLoginShellPath() {
|
|
938
|
-
if (process.platform === 'win32') {
|
|
939
|
-
return null;
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
const shell = process.env.SHELL || '/bin/zsh';
|
|
943
|
-
const shellName = path.basename(shell);
|
|
944
|
-
|
|
945
|
-
// Nushell requires different flag syntax and PATH access
|
|
946
|
-
const isNushell = shellName === 'nu' || shellName === 'nushell';
|
|
947
|
-
const args = isNushell
|
|
948
|
-
? ['-l', '-i', '-c', '$env.PATH | str join (char esep)']
|
|
949
|
-
: ['-lic', 'echo -n "$PATH"'];
|
|
950
|
-
|
|
951
|
-
try {
|
|
952
|
-
const result = spawnSync(shell, args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
953
|
-
if (result.status === 0 && typeof result.stdout === 'string') {
|
|
954
|
-
const value = result.stdout.trim();
|
|
955
|
-
if (value) {
|
|
956
|
-
return value;
|
|
957
|
-
}
|
|
958
|
-
} else if (result.stderr) {
|
|
959
|
-
console.warn(`Failed to read PATH from login shell (${shell}): ${result.stderr}`);
|
|
960
|
-
}
|
|
961
|
-
} catch (error) {
|
|
962
|
-
console.warn(`Error executing login shell (${shell}) for PATH detection: ${error.message}`);
|
|
963
|
-
}
|
|
964
|
-
return null;
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
function resolveBinaryFromPath(binaryName, searchPath) {
|
|
968
|
-
if (!binaryName) {
|
|
969
|
-
return null;
|
|
970
|
-
}
|
|
971
|
-
if (path.isAbsolute(binaryName)) {
|
|
972
|
-
return fs.existsSync(binaryName) ? binaryName : null;
|
|
973
|
-
}
|
|
974
|
-
const directories = searchPath.split(path.delimiter).filter(Boolean);
|
|
975
|
-
for (const directory of directories) {
|
|
976
|
-
try {
|
|
977
|
-
const candidate = path.join(directory, binaryName);
|
|
978
|
-
if (fs.existsSync(candidate)) {
|
|
979
|
-
const stats = fs.statSync(candidate);
|
|
980
|
-
if (stats.isFile()) {
|
|
981
|
-
return candidate;
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
} catch {
|
|
985
|
-
// Ignore resolution errors, continue searching
|
|
986
|
-
}
|
|
987
|
-
}
|
|
988
|
-
return null;
|
|
989
|
-
}
|
|
990
|
-
|
|
991
|
-
function getOpencodeSpawnConfig() {
|
|
992
|
-
const envPath = buildAugmentedPath();
|
|
993
|
-
const resolvedEnv = { ...process.env, PATH: envPath };
|
|
994
|
-
|
|
995
|
-
if (OPENCODE_BINARY_ENV) {
|
|
996
|
-
const explicit = resolveBinaryFromPath(OPENCODE_BINARY_ENV, envPath);
|
|
997
|
-
if (explicit) {
|
|
998
|
-
console.log(`Using OpenCode binary from OPENCODE_BINARY: ${explicit}`);
|
|
999
|
-
return { command: explicit, env: resolvedEnv };
|
|
1000
|
-
}
|
|
1001
|
-
console.warn(
|
|
1002
|
-
`OPENCODE_BINARY path "${OPENCODE_BINARY_ENV}" not found. Falling back to search.`
|
|
1003
|
-
);
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
return { command: 'opencode', env: resolvedEnv };
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
904
|
const ENV_CONFIGURED_OPENCODE_PORT = (() => {
|
|
1010
905
|
const raw =
|
|
1011
906
|
process.env.OPENCODE_PORT ||
|
|
@@ -1048,42 +943,79 @@ function setOpenCodePort(port) {
|
|
|
1048
943
|
}
|
|
1049
944
|
|
|
1050
945
|
lastOpenCodeError = null;
|
|
946
|
+
}
|
|
1051
947
|
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
948
|
+
function getLoginShellPath() {
|
|
949
|
+
if (process.platform === 'win32') {
|
|
950
|
+
return null;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
const shell = process.env.SHELL || '/bin/zsh';
|
|
954
|
+
const shellName = path.basename(shell);
|
|
955
|
+
|
|
956
|
+
// Nushell requires different flag syntax and PATH access
|
|
957
|
+
const isNushell = shellName === 'nu' || shellName === 'nushell';
|
|
958
|
+
const args = isNushell
|
|
959
|
+
? ['-l', '-i', '-c', '$env.PATH | str join (char esep)']
|
|
960
|
+
: ['-lic', 'echo -n "$PATH"'];
|
|
961
|
+
|
|
962
|
+
try {
|
|
963
|
+
const result = spawnSync(shell, args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
964
|
+
if (result.status === 0 && typeof result.stdout === 'string') {
|
|
965
|
+
const value = result.stdout.trim();
|
|
966
|
+
if (value) {
|
|
967
|
+
return value;
|
|
1060
968
|
}
|
|
1061
969
|
}
|
|
970
|
+
} catch (error) {
|
|
971
|
+
// ignore
|
|
1062
972
|
}
|
|
973
|
+
return null;
|
|
1063
974
|
}
|
|
1064
975
|
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
return openCodePort;
|
|
1068
|
-
}
|
|
976
|
+
function buildAugmentedPath() {
|
|
977
|
+
const augmented = new Set();
|
|
1069
978
|
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
979
|
+
const loginShellPath = getLoginShellPath();
|
|
980
|
+
if (loginShellPath) {
|
|
981
|
+
for (const segment of loginShellPath.split(path.delimiter)) {
|
|
982
|
+
if (segment) {
|
|
983
|
+
augmented.add(segment);
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
}
|
|
1075
987
|
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
988
|
+
const current = (process.env.PATH || '').split(path.delimiter).filter(Boolean);
|
|
989
|
+
for (const segment of current) {
|
|
990
|
+
augmented.add(segment);
|
|
991
|
+
}
|
|
1080
992
|
|
|
1081
|
-
|
|
1082
|
-
});
|
|
993
|
+
return Array.from(augmented).join(path.delimiter);
|
|
1083
994
|
}
|
|
1084
995
|
|
|
1085
996
|
const API_PREFIX_CANDIDATES = ['', '/api']; // Simplified - only check root and /api
|
|
1086
997
|
|
|
998
|
+
async function waitForReady(url, timeoutMs = 10000) {
|
|
999
|
+
const start = Date.now();
|
|
1000
|
+
while (Date.now() - start < timeoutMs) {
|
|
1001
|
+
try {
|
|
1002
|
+
const controller = new AbortController();
|
|
1003
|
+
const timeout = setTimeout(() => controller.abort(), 3000);
|
|
1004
|
+
const res = await fetch(`${url.replace(/\/+$/, '')}/config`, {
|
|
1005
|
+
method: 'GET',
|
|
1006
|
+
headers: { Accept: 'application/json' },
|
|
1007
|
+
signal: controller.signal
|
|
1008
|
+
});
|
|
1009
|
+
clearTimeout(timeout);
|
|
1010
|
+
if (res.ok) return true;
|
|
1011
|
+
} catch {
|
|
1012
|
+
// ignore
|
|
1013
|
+
}
|
|
1014
|
+
await new Promise(r => setTimeout(r, 100));
|
|
1015
|
+
}
|
|
1016
|
+
return false;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1087
1019
|
function normalizeApiPrefix(prefix) {
|
|
1088
1020
|
if (!prefix) {
|
|
1089
1021
|
return '';
|
|
@@ -1119,51 +1051,6 @@ function setDetectedOpenCodeApiPrefix(prefix) {
|
|
|
1119
1051
|
}
|
|
1120
1052
|
}
|
|
1121
1053
|
|
|
1122
|
-
function detectPortFromLogMessage(message) {
|
|
1123
|
-
if (openCodePort && ENV_CONFIGURED_OPENCODE_PORT) {
|
|
1124
|
-
return;
|
|
1125
|
-
}
|
|
1126
|
-
|
|
1127
|
-
const regex = /https?:\/\/[^:\s]+:(\d+)/gi;
|
|
1128
|
-
let match;
|
|
1129
|
-
while ((match = regex.exec(message)) !== null) {
|
|
1130
|
-
const port = parseInt(match[1], 10);
|
|
1131
|
-
if (Number.isFinite(port) && port > 0) {
|
|
1132
|
-
setOpenCodePort(port);
|
|
1133
|
-
return;
|
|
1134
|
-
}
|
|
1135
|
-
}
|
|
1136
|
-
|
|
1137
|
-
const fallbackMatch = /(?:^|\s)(?:127\.0\.0\.1|localhost):(\d+)/i.exec(message);
|
|
1138
|
-
if (fallbackMatch) {
|
|
1139
|
-
const port = parseInt(fallbackMatch[1], 10);
|
|
1140
|
-
if (Number.isFinite(port) && port > 0) {
|
|
1141
|
-
setOpenCodePort(port);
|
|
1142
|
-
}
|
|
1143
|
-
}
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
|
-
function detectPrefixFromLogMessage(message) {
|
|
1147
|
-
if (!openCodePort) {
|
|
1148
|
-
return;
|
|
1149
|
-
}
|
|
1150
|
-
|
|
1151
|
-
const urlRegex = /https?:\/\/[^:\s]+:(\d+)(\/[^\s"']*)?/gi;
|
|
1152
|
-
let match;
|
|
1153
|
-
|
|
1154
|
-
while ((match = urlRegex.exec(message)) !== null) {
|
|
1155
|
-
const portMatch = parseInt(match[1], 10);
|
|
1156
|
-
if (portMatch !== openCodePort) {
|
|
1157
|
-
continue;
|
|
1158
|
-
}
|
|
1159
|
-
|
|
1160
|
-
const path = match[2] || '';
|
|
1161
|
-
const normalized = normalizeApiPrefix(path);
|
|
1162
|
-
setDetectedOpenCodeApiPrefix(normalized);
|
|
1163
|
-
return;
|
|
1164
|
-
}
|
|
1165
|
-
}
|
|
1166
|
-
|
|
1167
1054
|
function getCandidateApiPrefixes() {
|
|
1168
1055
|
if (openCodeApiPrefixDetected) {
|
|
1169
1056
|
return [openCodeApiPrefix];
|
|
@@ -1495,136 +1382,57 @@ function parseArgs(argv = process.argv.slice(2)) {
|
|
|
1495
1382
|
}
|
|
1496
1383
|
|
|
1497
1384
|
async function startOpenCode() {
|
|
1498
|
-
const desiredPort = ENV_CONFIGURED_OPENCODE_PORT ??
|
|
1385
|
+
const desiredPort = ENV_CONFIGURED_OPENCODE_PORT ?? 0;
|
|
1499
1386
|
console.log(
|
|
1500
|
-
desiredPort
|
|
1387
|
+
desiredPort > 0
|
|
1501
1388
|
? `Starting OpenCode on requested port ${desiredPort}...`
|
|
1502
1389
|
: 'Starting OpenCode with dynamic port assignment...'
|
|
1503
1390
|
);
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
const { command, env } = getOpencodeSpawnConfig();
|
|
1507
|
-
const args = ['serve', '--port', desiredPort.toString()];
|
|
1508
|
-
console.log(`Launching OpenCode via "${command}" with args ${args.join(' ')}`);
|
|
1391
|
+
// Note: SDK starts in current process CWD. openCodeWorkingDirectory is tracked but not used for spawn in SDK.
|
|
1509
1392
|
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1393
|
+
try {
|
|
1394
|
+
const serverInstance = await createOpencodeServer({
|
|
1395
|
+
hostname: '127.0.0.1',
|
|
1396
|
+
port: desiredPort,
|
|
1397
|
+
timeout: 30000,
|
|
1398
|
+
env: {
|
|
1399
|
+
...process.env,
|
|
1400
|
+
// Pass minimal config to avoid pollution, but inherit PATH etc
|
|
1401
|
+
}
|
|
1402
|
+
});
|
|
1517
1403
|
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
firstSignalResolver = resolve;
|
|
1521
|
-
});
|
|
1522
|
-
let firstSignalSettled = false;
|
|
1523
|
-
const settleFirstSignal = () => {
|
|
1524
|
-
if (firstSignalSettled) {
|
|
1525
|
-
return;
|
|
1526
|
-
}
|
|
1527
|
-
firstSignalSettled = true;
|
|
1528
|
-
clearTimeout(firstSignalTimer);
|
|
1529
|
-
child.stdout.off('data', settleFirstSignal);
|
|
1530
|
-
child.stderr.off('data', settleFirstSignal);
|
|
1531
|
-
child.off('exit', settleFirstSignal);
|
|
1532
|
-
if (firstSignalResolver) {
|
|
1533
|
-
firstSignalResolver();
|
|
1404
|
+
if (!serverInstance || !serverInstance.url) {
|
|
1405
|
+
throw new Error('OpenCode server started but URL is missing');
|
|
1534
1406
|
}
|
|
1535
|
-
};
|
|
1536
|
-
const firstSignalTimer = setTimeout(settleFirstSignal, 750);
|
|
1537
|
-
|
|
1538
|
-
child.stdout.once('data', settleFirstSignal);
|
|
1539
|
-
child.stderr.once('data', settleFirstSignal);
|
|
1540
|
-
child.once('exit', settleFirstSignal);
|
|
1541
|
-
|
|
1542
|
-
child.stdout.on('data', (data) => {
|
|
1543
|
-
const text = data.toString();
|
|
1544
|
-
console.log(`OpenCode: ${text.trim()}`);
|
|
1545
|
-
detectPortFromLogMessage(text);
|
|
1546
|
-
detectPrefixFromLogMessage(text);
|
|
1547
|
-
settleFirstSignal();
|
|
1548
|
-
});
|
|
1549
1407
|
|
|
1550
|
-
|
|
1551
|
-
const
|
|
1552
|
-
|
|
1553
|
-
console.error(`OpenCode Error: ${lastOpenCodeError}`);
|
|
1554
|
-
detectPortFromLogMessage(text);
|
|
1555
|
-
detectPrefixFromLogMessage(text);
|
|
1556
|
-
settleFirstSignal();
|
|
1557
|
-
});
|
|
1408
|
+
const url = new URL(serverInstance.url);
|
|
1409
|
+
const port = parseInt(url.port, 10);
|
|
1410
|
+
const prefix = normalizeApiPrefix(url.pathname);
|
|
1558
1411
|
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
child.off('error', onError);
|
|
1563
|
-
resolve(null);
|
|
1564
|
-
};
|
|
1565
|
-
const onError = (error) => {
|
|
1566
|
-
child.off('spawn', onSpawn);
|
|
1567
|
-
reject(error);
|
|
1568
|
-
};
|
|
1412
|
+
if (await waitForReady(serverInstance.url, 10000)) {
|
|
1413
|
+
setOpenCodePort(port);
|
|
1414
|
+
setDetectedOpenCodeApiPrefix(prefix); // SDK URL typically includes the prefix if any
|
|
1569
1415
|
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1416
|
+
isOpenCodeReady = true;
|
|
1417
|
+
lastOpenCodeError = null;
|
|
1418
|
+
openCodeNotReadySince = 0;
|
|
1419
|
+
|
|
1420
|
+
return serverInstance;
|
|
1421
|
+
} else {
|
|
1422
|
+
try {
|
|
1423
|
+
serverInstance.close();
|
|
1424
|
+
} catch {
|
|
1425
|
+
// ignore
|
|
1426
|
+
}
|
|
1427
|
+
throw new Error('Server started but health check failed (timeout)');
|
|
1428
|
+
}
|
|
1429
|
+
} catch (error) {
|
|
1573
1430
|
lastOpenCodeError = error.message;
|
|
1574
1431
|
openCodePort = null;
|
|
1575
1432
|
syncToHmrState();
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
});
|
|
1579
|
-
|
|
1580
|
-
if (startupError) {
|
|
1581
|
-
if (startupError.code === 'ENOENT') {
|
|
1582
|
-
const enhanced = new Error(
|
|
1583
|
-
`Failed to start OpenCode – executable "${command}" not found. ` +
|
|
1584
|
-
'Set OPENCODE_BINARY to the full path of the opencode CLI or ensure it is on PATH.'
|
|
1585
|
-
);
|
|
1586
|
-
enhanced.code = startupError.code;
|
|
1587
|
-
startupError = enhanced;
|
|
1588
|
-
}
|
|
1589
|
-
throw startupError;
|
|
1433
|
+
console.error(`Failed to start OpenCode: ${error.message}`);
|
|
1434
|
+
throw error;
|
|
1590
1435
|
}
|
|
1591
|
-
|
|
1592
|
-
child.on('exit', (code, signal) => {
|
|
1593
|
-
lastOpenCodeError = `OpenCode exited with code ${code}, signal ${signal ?? 'null'}`;
|
|
1594
|
-
isOpenCodeReady = false;
|
|
1595
|
-
openCodeNotReadySince = Date.now();
|
|
1596
|
-
|
|
1597
|
-
if (!isShuttingDown && !isRestartingOpenCode) {
|
|
1598
|
-
console.log(`OpenCode process exited with code ${code}, signal ${signal}`);
|
|
1599
|
-
|
|
1600
|
-
setTimeout(() => {
|
|
1601
|
-
restartOpenCode().catch((err) => {
|
|
1602
|
-
console.error('Failed to restart OpenCode after exit:', err);
|
|
1603
|
-
});
|
|
1604
|
-
}, 5000);
|
|
1605
|
-
} else if (isRestartingOpenCode) {
|
|
1606
|
-
console.log('OpenCode exit during controlled restart, not triggering auto-restart');
|
|
1607
|
-
}
|
|
1608
|
-
});
|
|
1609
|
-
|
|
1610
|
-
child.on('error', (error) => {
|
|
1611
|
-
lastOpenCodeError = error.message;
|
|
1612
|
-
isOpenCodeReady = false;
|
|
1613
|
-
openCodeNotReadySince = Date.now();
|
|
1614
|
-
console.error(`OpenCode process error: ${error.message}`);
|
|
1615
|
-
if (!isShuttingDown) {
|
|
1616
|
-
|
|
1617
|
-
setTimeout(() => {
|
|
1618
|
-
restartOpenCode().catch((err) => {
|
|
1619
|
-
console.error('Failed to restart OpenCode after error:', err);
|
|
1620
|
-
});
|
|
1621
|
-
}, 5000);
|
|
1622
|
-
}
|
|
1623
|
-
});
|
|
1624
|
-
|
|
1625
|
-
await firstSignalPromise;
|
|
1626
|
-
|
|
1627
|
-
return child;
|
|
1628
1436
|
}
|
|
1629
1437
|
|
|
1630
1438
|
async function restartOpenCode() {
|
|
@@ -1641,60 +1449,16 @@ async function restartOpenCode() {
|
|
|
1641
1449
|
console.log('Restarting OpenCode process...');
|
|
1642
1450
|
|
|
1643
1451
|
if (openCodeProcess) {
|
|
1644
|
-
console.log('
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
processToTerminate.kill('SIGTERM');
|
|
1650
|
-
|
|
1651
|
-
await new Promise((resolve) => {
|
|
1652
|
-
let resolved = false;
|
|
1653
|
-
|
|
1654
|
-
const cleanup = () => {
|
|
1655
|
-
processToTerminate.off('exit', onExit);
|
|
1656
|
-
clearTimeout(forceKillTimer);
|
|
1657
|
-
clearTimeout(hardStopTimer);
|
|
1658
|
-
if (!resolved) {
|
|
1659
|
-
resolved = true;
|
|
1660
|
-
resolve();
|
|
1661
|
-
}
|
|
1662
|
-
};
|
|
1663
|
-
|
|
1664
|
-
const onExit = () => {
|
|
1665
|
-
cleanup();
|
|
1666
|
-
};
|
|
1667
|
-
|
|
1668
|
-
const forceKillTimer = setTimeout(() => {
|
|
1669
|
-
if (resolved) {
|
|
1670
|
-
return;
|
|
1671
|
-
}
|
|
1672
|
-
forcedTermination = true;
|
|
1673
|
-
console.warn('OpenCode process did not exit after SIGTERM, sending SIGKILL');
|
|
1674
|
-
processToTerminate.kill('SIGKILL');
|
|
1675
|
-
}, 3000);
|
|
1676
|
-
|
|
1677
|
-
const hardStopTimer = setTimeout(() => {
|
|
1678
|
-
if (resolved) {
|
|
1679
|
-
return;
|
|
1680
|
-
}
|
|
1681
|
-
console.warn('OpenCode process unresponsive after SIGKILL, continuing restart');
|
|
1682
|
-
cleanup();
|
|
1683
|
-
}, 5000);
|
|
1684
|
-
|
|
1685
|
-
processToTerminate.once('exit', onExit);
|
|
1686
|
-
});
|
|
1687
|
-
|
|
1688
|
-
if (forcedTermination) {
|
|
1689
|
-
console.log('OpenCode process terminated forcefully during restart');
|
|
1690
|
-
}
|
|
1691
|
-
} else {
|
|
1692
|
-
console.log('OpenCode process already stopped before restart command');
|
|
1452
|
+
console.log('Stopping existing OpenCode process...');
|
|
1453
|
+
try {
|
|
1454
|
+
openCodeProcess.close();
|
|
1455
|
+
} catch (error) {
|
|
1456
|
+
console.warn('Error closing OpenCode process:', error);
|
|
1693
1457
|
}
|
|
1694
|
-
|
|
1695
1458
|
openCodeProcess = null;
|
|
1696
1459
|
syncToHmrState();
|
|
1697
|
-
|
|
1460
|
+
|
|
1461
|
+
// Brief delay to allow port release
|
|
1698
1462
|
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
1699
1463
|
}
|
|
1700
1464
|
|
|
@@ -1705,6 +1469,8 @@ async function restartOpenCode() {
|
|
|
1705
1469
|
openCodePort = null;
|
|
1706
1470
|
syncToHmrState();
|
|
1707
1471
|
}
|
|
1472
|
+
|
|
1473
|
+
// Reset detection state
|
|
1708
1474
|
openCodeApiPrefixDetected = false;
|
|
1709
1475
|
if (openCodeApiDetectionTimer) {
|
|
1710
1476
|
clearTimeout(openCodeApiDetectionTimer);
|
|
@@ -1716,13 +1482,10 @@ async function restartOpenCode() {
|
|
|
1716
1482
|
openCodeProcess = await startOpenCode();
|
|
1717
1483
|
syncToHmrState();
|
|
1718
1484
|
|
|
1719
|
-
if (!ENV_CONFIGURED_OPENCODE_PORT) {
|
|
1720
|
-
await waitForOpenCodePort();
|
|
1721
|
-
}
|
|
1722
|
-
|
|
1723
1485
|
if (expressApp) {
|
|
1724
1486
|
setupProxy(expressApp);
|
|
1725
|
-
|
|
1487
|
+
// Ensure prefix is set correctly (SDK usually handles this, but just in case)
|
|
1488
|
+
ensureOpenCodeApiPrefix();
|
|
1726
1489
|
}
|
|
1727
1490
|
})();
|
|
1728
1491
|
|
|
@@ -2928,6 +2691,7 @@ async function main(options = {}) {
|
|
|
2928
2691
|
const { parseSkillRepoSource } = await import('./lib/skills-catalog/source.js');
|
|
2929
2692
|
const { scanSkillsRepository } = await import('./lib/skills-catalog/scan.js');
|
|
2930
2693
|
const { installSkillsFromRepository } = await import('./lib/skills-catalog/install.js');
|
|
2694
|
+
const { scanClawdHub, installSkillsFromClawdHub, isClawdHubSource } = await import('./lib/skills-catalog/clawdhub/index.js');
|
|
2931
2695
|
const { getProfiles, getProfile } = await import('./lib/git-identity-storage.js');
|
|
2932
2696
|
|
|
2933
2697
|
const listGitIdentitiesForResponse = () => {
|
|
@@ -2984,6 +2748,37 @@ async function main(options = {}) {
|
|
|
2984
2748
|
const itemsBySource = {};
|
|
2985
2749
|
|
|
2986
2750
|
for (const src of sources) {
|
|
2751
|
+
// Handle ClawdHub sources separately (API-based, not git-based)
|
|
2752
|
+
if (src.sourceType === 'clawdhub' || isClawdHubSource(src.source)) {
|
|
2753
|
+
const cacheKey = 'clawdhub:registry';
|
|
2754
|
+
let scanResult = !refresh ? getCachedScan(cacheKey) : null;
|
|
2755
|
+
|
|
2756
|
+
if (!scanResult) {
|
|
2757
|
+
const scanned = await scanClawdHub();
|
|
2758
|
+
if (!scanned.ok) {
|
|
2759
|
+
itemsBySource[src.id] = [];
|
|
2760
|
+
continue;
|
|
2761
|
+
}
|
|
2762
|
+
scanResult = scanned;
|
|
2763
|
+
setCachedScan(cacheKey, scanResult);
|
|
2764
|
+
}
|
|
2765
|
+
|
|
2766
|
+
const items = (scanResult.items || []).map((item) => {
|
|
2767
|
+
const installed = installedByName.get(item.skillName);
|
|
2768
|
+
return {
|
|
2769
|
+
...item,
|
|
2770
|
+
sourceId: src.id,
|
|
2771
|
+
installed: installed
|
|
2772
|
+
? { isInstalled: true, scope: installed.scope }
|
|
2773
|
+
: { isInstalled: false },
|
|
2774
|
+
};
|
|
2775
|
+
});
|
|
2776
|
+
|
|
2777
|
+
itemsBySource[src.id] = items;
|
|
2778
|
+
continue;
|
|
2779
|
+
}
|
|
2780
|
+
|
|
2781
|
+
// Handle GitHub sources (git clone based)
|
|
2987
2782
|
const parsed = parseSkillRepoSource(src.source);
|
|
2988
2783
|
if (!parsed.ok) {
|
|
2989
2784
|
itemsBySource[src.id] = [];
|
|
@@ -3093,6 +2888,29 @@ async function main(options = {}) {
|
|
|
3093
2888
|
}
|
|
3094
2889
|
workingDirectory = resolved.directory;
|
|
3095
2890
|
}
|
|
2891
|
+
|
|
2892
|
+
// Handle ClawdHub sources (ZIP download based)
|
|
2893
|
+
if (isClawdHubSource(source)) {
|
|
2894
|
+
const result = await installSkillsFromClawdHub({
|
|
2895
|
+
scope,
|
|
2896
|
+
workingDirectory,
|
|
2897
|
+
userSkillDir: SKILL_DIR,
|
|
2898
|
+
selections,
|
|
2899
|
+
conflictPolicy,
|
|
2900
|
+
conflictDecisions,
|
|
2901
|
+
});
|
|
2902
|
+
|
|
2903
|
+
if (!result.ok) {
|
|
2904
|
+
if (result.error?.kind === 'conflicts') {
|
|
2905
|
+
return res.status(409).json({ ok: false, error: result.error });
|
|
2906
|
+
}
|
|
2907
|
+
return res.status(400).json({ ok: false, error: result.error });
|
|
2908
|
+
}
|
|
2909
|
+
|
|
2910
|
+
return res.json({ ok: true, installed: result.installed || [], skipped: result.skipped || [] });
|
|
2911
|
+
}
|
|
2912
|
+
|
|
2913
|
+
// Handle GitHub sources (git clone based)
|
|
3096
2914
|
const identity = resolveGitIdentity(gitIdentityId);
|
|
3097
2915
|
|
|
3098
2916
|
const result = await installSkillsFromRepository({
|