opencode-aicodewith-auth 0.1.3 → 0.1.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/index.js +536 -64
- package/dist/lib/hooks/auto-update/cache.d.ts +1 -0
- package/dist/lib/hooks/auto-update/checker.d.ts +10 -0
- package/dist/lib/hooks/auto-update/constants.d.ts +15 -0
- package/dist/lib/hooks/auto-update/index.d.ts +12 -0
- package/dist/lib/hooks/auto-update/types.d.ts +29 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -59,9 +59,9 @@ function createAicodewith(options = {}) {
|
|
|
59
59
|
var aicodewith = createAicodewith();
|
|
60
60
|
|
|
61
61
|
// index.ts
|
|
62
|
-
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
63
|
-
import
|
|
64
|
-
import
|
|
62
|
+
import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2, access } from "fs/promises";
|
|
63
|
+
import path4 from "path";
|
|
64
|
+
import os3 from "os";
|
|
65
65
|
|
|
66
66
|
// lib/constants.ts
|
|
67
67
|
var PLUGIN_NAME = "opencode-aicodewith-auth";
|
|
@@ -993,82 +993,539 @@ async function handleSuccessResponse(response, isStreaming) {
|
|
|
993
993
|
});
|
|
994
994
|
}
|
|
995
995
|
|
|
996
|
+
// lib/hooks/auto-update/index.ts
|
|
997
|
+
import { spawn } from "child_process";
|
|
998
|
+
|
|
999
|
+
// lib/hooks/auto-update/checker.ts
|
|
1000
|
+
import * as fs2 from "fs";
|
|
1001
|
+
import * as path2 from "path";
|
|
1002
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1003
|
+
|
|
1004
|
+
// lib/hooks/auto-update/constants.ts
|
|
1005
|
+
import * as path from "path";
|
|
1006
|
+
import * as os from "os";
|
|
1007
|
+
import * as fs from "fs";
|
|
1008
|
+
var PACKAGE_NAME = "opencode-aicodewith-auth";
|
|
1009
|
+
var NPM_REGISTRY_URL = `https://registry.npmjs.org/-/package/${PACKAGE_NAME}/dist-tags`;
|
|
1010
|
+
var NPM_FETCH_TIMEOUT = 5000;
|
|
1011
|
+
function getCacheDir() {
|
|
1012
|
+
if (process.platform === "win32") {
|
|
1013
|
+
return path.join(process.env.LOCALAPPDATA ?? os.homedir(), "opencode");
|
|
1014
|
+
}
|
|
1015
|
+
return path.join(os.homedir(), ".cache", "opencode");
|
|
1016
|
+
}
|
|
1017
|
+
var CACHE_DIR3 = getCacheDir();
|
|
1018
|
+
var INSTALLED_PACKAGE_JSON = path.join(CACHE_DIR3, "node_modules", PACKAGE_NAME, "package.json");
|
|
1019
|
+
function getUserConfigDir() {
|
|
1020
|
+
if (process.platform === "win32") {
|
|
1021
|
+
const crossPlatformDir = path.join(os.homedir(), ".config");
|
|
1022
|
+
const appdataDir = process.env.APPDATA ?? path.join(os.homedir(), "AppData", "Roaming");
|
|
1023
|
+
const crossPlatformConfig = path.join(crossPlatformDir, "opencode", "opencode.json");
|
|
1024
|
+
const crossPlatformConfigJsonc = path.join(crossPlatformDir, "opencode", "opencode.jsonc");
|
|
1025
|
+
if (fs.existsSync(crossPlatformConfig) || fs.existsSync(crossPlatformConfigJsonc)) {
|
|
1026
|
+
return crossPlatformDir;
|
|
1027
|
+
}
|
|
1028
|
+
return appdataDir;
|
|
1029
|
+
}
|
|
1030
|
+
return process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), ".config");
|
|
1031
|
+
}
|
|
1032
|
+
function getWindowsAppdataDir() {
|
|
1033
|
+
if (process.platform !== "win32")
|
|
1034
|
+
return null;
|
|
1035
|
+
return process.env.APPDATA ?? path.join(os.homedir(), "AppData", "Roaming");
|
|
1036
|
+
}
|
|
1037
|
+
var USER_CONFIG_DIR = getUserConfigDir();
|
|
1038
|
+
var USER_OPENCODE_CONFIG = path.join(USER_CONFIG_DIR, "opencode", "opencode.json");
|
|
1039
|
+
var USER_OPENCODE_CONFIG_JSONC = path.join(USER_CONFIG_DIR, "opencode", "opencode.jsonc");
|
|
1040
|
+
|
|
1041
|
+
// lib/hooks/auto-update/checker.ts
|
|
1042
|
+
import * as os2 from "os";
|
|
1043
|
+
var LOG_PREFIX = `[${PACKAGE_NAME}:auto-update]`;
|
|
1044
|
+
function log(...args) {
|
|
1045
|
+
if (process.env.DEBUG?.includes(PACKAGE_NAME) || process.env.DEBUG?.includes("auto-update")) {
|
|
1046
|
+
console.log(LOG_PREFIX, ...args);
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
function stripJsonComments(json) {
|
|
1050
|
+
return json.replace(/\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g, (m, g) => g ? "" : m).replace(/,(\s*[}\]])/g, "$1");
|
|
1051
|
+
}
|
|
1052
|
+
function getConfigPaths(directory) {
|
|
1053
|
+
const paths = [
|
|
1054
|
+
path2.join(directory, ".opencode", "opencode.json"),
|
|
1055
|
+
path2.join(directory, ".opencode", "opencode.jsonc"),
|
|
1056
|
+
USER_OPENCODE_CONFIG,
|
|
1057
|
+
USER_OPENCODE_CONFIG_JSONC
|
|
1058
|
+
];
|
|
1059
|
+
if (process.platform === "win32") {
|
|
1060
|
+
const crossPlatformDir = path2.join(os2.homedir(), ".config");
|
|
1061
|
+
const appdataDir = getWindowsAppdataDir();
|
|
1062
|
+
if (appdataDir) {
|
|
1063
|
+
const alternateDir = USER_CONFIG_DIR === crossPlatformDir ? appdataDir : crossPlatformDir;
|
|
1064
|
+
const alternateConfig = path2.join(alternateDir, "opencode", "opencode.json");
|
|
1065
|
+
const alternateConfigJsonc = path2.join(alternateDir, "opencode", "opencode.jsonc");
|
|
1066
|
+
if (!paths.includes(alternateConfig)) {
|
|
1067
|
+
paths.push(alternateConfig);
|
|
1068
|
+
}
|
|
1069
|
+
if (!paths.includes(alternateConfigJsonc)) {
|
|
1070
|
+
paths.push(alternateConfigJsonc);
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
return paths;
|
|
1075
|
+
}
|
|
1076
|
+
function getLocalDevPath(directory) {
|
|
1077
|
+
for (const configPath of getConfigPaths(directory)) {
|
|
1078
|
+
try {
|
|
1079
|
+
if (!fs2.existsSync(configPath))
|
|
1080
|
+
continue;
|
|
1081
|
+
const content = fs2.readFileSync(configPath, "utf-8");
|
|
1082
|
+
const config = JSON.parse(stripJsonComments(content));
|
|
1083
|
+
const plugins = config.plugin ?? [];
|
|
1084
|
+
for (const entry of plugins) {
|
|
1085
|
+
if (entry.startsWith("file://") && entry.includes(PACKAGE_NAME)) {
|
|
1086
|
+
try {
|
|
1087
|
+
return fileURLToPath2(entry);
|
|
1088
|
+
} catch {
|
|
1089
|
+
return entry.replace("file://", "");
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
} catch {
|
|
1094
|
+
continue;
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
return null;
|
|
1098
|
+
}
|
|
1099
|
+
function findPackageJsonUp(startPath) {
|
|
1100
|
+
try {
|
|
1101
|
+
const stat = fs2.statSync(startPath);
|
|
1102
|
+
let dir = stat.isDirectory() ? startPath : path2.dirname(startPath);
|
|
1103
|
+
for (let i = 0;i < 10; i++) {
|
|
1104
|
+
const pkgPath = path2.join(dir, "package.json");
|
|
1105
|
+
if (fs2.existsSync(pkgPath)) {
|
|
1106
|
+
try {
|
|
1107
|
+
const content = fs2.readFileSync(pkgPath, "utf-8");
|
|
1108
|
+
const pkg = JSON.parse(content);
|
|
1109
|
+
if (pkg.name === PACKAGE_NAME)
|
|
1110
|
+
return pkgPath;
|
|
1111
|
+
} catch {}
|
|
1112
|
+
}
|
|
1113
|
+
const parent = path2.dirname(dir);
|
|
1114
|
+
if (parent === dir)
|
|
1115
|
+
break;
|
|
1116
|
+
dir = parent;
|
|
1117
|
+
}
|
|
1118
|
+
} catch {}
|
|
1119
|
+
return null;
|
|
1120
|
+
}
|
|
1121
|
+
function getLocalDevVersion(directory) {
|
|
1122
|
+
const localPath = getLocalDevPath(directory);
|
|
1123
|
+
if (!localPath)
|
|
1124
|
+
return null;
|
|
1125
|
+
try {
|
|
1126
|
+
const pkgPath = findPackageJsonUp(localPath);
|
|
1127
|
+
if (!pkgPath)
|
|
1128
|
+
return null;
|
|
1129
|
+
const content = fs2.readFileSync(pkgPath, "utf-8");
|
|
1130
|
+
const pkg = JSON.parse(content);
|
|
1131
|
+
return pkg.version ?? null;
|
|
1132
|
+
} catch {
|
|
1133
|
+
return null;
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
function findPluginEntry(directory) {
|
|
1137
|
+
for (const configPath of getConfigPaths(directory)) {
|
|
1138
|
+
try {
|
|
1139
|
+
if (!fs2.existsSync(configPath))
|
|
1140
|
+
continue;
|
|
1141
|
+
const content = fs2.readFileSync(configPath, "utf-8");
|
|
1142
|
+
const config = JSON.parse(stripJsonComments(content));
|
|
1143
|
+
const plugins = config.plugin ?? [];
|
|
1144
|
+
for (const entry of plugins) {
|
|
1145
|
+
if (entry === PACKAGE_NAME) {
|
|
1146
|
+
return { entry, isPinned: false, pinnedVersion: null, configPath };
|
|
1147
|
+
}
|
|
1148
|
+
if (entry.startsWith(`${PACKAGE_NAME}@`)) {
|
|
1149
|
+
const pinnedVersion = entry.slice(PACKAGE_NAME.length + 1);
|
|
1150
|
+
const isPinned = pinnedVersion !== "latest";
|
|
1151
|
+
return { entry, isPinned, pinnedVersion: isPinned ? pinnedVersion : null, configPath };
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
} catch {
|
|
1155
|
+
continue;
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
return null;
|
|
1159
|
+
}
|
|
1160
|
+
function getCachedVersion() {
|
|
1161
|
+
try {
|
|
1162
|
+
if (fs2.existsSync(INSTALLED_PACKAGE_JSON)) {
|
|
1163
|
+
const content = fs2.readFileSync(INSTALLED_PACKAGE_JSON, "utf-8");
|
|
1164
|
+
const pkg = JSON.parse(content);
|
|
1165
|
+
if (pkg.version)
|
|
1166
|
+
return pkg.version;
|
|
1167
|
+
}
|
|
1168
|
+
} catch {}
|
|
1169
|
+
try {
|
|
1170
|
+
const currentDir = path2.dirname(fileURLToPath2(import.meta.url));
|
|
1171
|
+
const pkgPath = findPackageJsonUp(currentDir);
|
|
1172
|
+
if (pkgPath) {
|
|
1173
|
+
const content = fs2.readFileSync(pkgPath, "utf-8");
|
|
1174
|
+
const pkg = JSON.parse(content);
|
|
1175
|
+
if (pkg.version)
|
|
1176
|
+
return pkg.version;
|
|
1177
|
+
}
|
|
1178
|
+
} catch (err) {
|
|
1179
|
+
log("Failed to resolve version from current directory:", err);
|
|
1180
|
+
}
|
|
1181
|
+
return null;
|
|
1182
|
+
}
|
|
1183
|
+
function updatePinnedVersion(configPath, oldEntry, newVersion) {
|
|
1184
|
+
try {
|
|
1185
|
+
const content = fs2.readFileSync(configPath, "utf-8");
|
|
1186
|
+
const newEntry = `${PACKAGE_NAME}@${newVersion}`;
|
|
1187
|
+
const pluginMatch = content.match(/"plugin"\s*:\s*\[/);
|
|
1188
|
+
if (!pluginMatch || pluginMatch.index === undefined) {
|
|
1189
|
+
log(`No "plugin" array found in ${configPath}`);
|
|
1190
|
+
return false;
|
|
1191
|
+
}
|
|
1192
|
+
const startIdx = pluginMatch.index + pluginMatch[0].length;
|
|
1193
|
+
let bracketCount = 1;
|
|
1194
|
+
let endIdx = startIdx;
|
|
1195
|
+
for (let i = startIdx;i < content.length && bracketCount > 0; i++) {
|
|
1196
|
+
if (content[i] === "[")
|
|
1197
|
+
bracketCount++;
|
|
1198
|
+
else if (content[i] === "]")
|
|
1199
|
+
bracketCount--;
|
|
1200
|
+
endIdx = i;
|
|
1201
|
+
}
|
|
1202
|
+
const before = content.slice(0, startIdx);
|
|
1203
|
+
const pluginArrayContent = content.slice(startIdx, endIdx);
|
|
1204
|
+
const after = content.slice(endIdx);
|
|
1205
|
+
const escapedOldEntry = oldEntry.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1206
|
+
const regex = new RegExp(`["']${escapedOldEntry}["']`);
|
|
1207
|
+
if (!regex.test(pluginArrayContent)) {
|
|
1208
|
+
log(`Entry "${oldEntry}" not found in plugin array of ${configPath}`);
|
|
1209
|
+
return false;
|
|
1210
|
+
}
|
|
1211
|
+
const updatedPluginArray = pluginArrayContent.replace(regex, `"${newEntry}"`);
|
|
1212
|
+
const updatedContent = before + updatedPluginArray + after;
|
|
1213
|
+
if (updatedContent === content) {
|
|
1214
|
+
log(`No changes made to ${configPath}`);
|
|
1215
|
+
return false;
|
|
1216
|
+
}
|
|
1217
|
+
fs2.writeFileSync(configPath, updatedContent, "utf-8");
|
|
1218
|
+
log(`Updated ${configPath}: ${oldEntry} \u2192 ${newEntry}`);
|
|
1219
|
+
return true;
|
|
1220
|
+
} catch (err) {
|
|
1221
|
+
log(`Failed to update config file ${configPath}:`, err);
|
|
1222
|
+
return false;
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
async function getLatestVersion() {
|
|
1226
|
+
const controller = new AbortController;
|
|
1227
|
+
const timeoutId = setTimeout(() => controller.abort(), NPM_FETCH_TIMEOUT);
|
|
1228
|
+
try {
|
|
1229
|
+
const response = await fetch(NPM_REGISTRY_URL, {
|
|
1230
|
+
signal: controller.signal,
|
|
1231
|
+
headers: { Accept: "application/json" }
|
|
1232
|
+
});
|
|
1233
|
+
if (!response.ok)
|
|
1234
|
+
return null;
|
|
1235
|
+
const data = await response.json();
|
|
1236
|
+
return data.latest ?? null;
|
|
1237
|
+
} catch {
|
|
1238
|
+
return null;
|
|
1239
|
+
} finally {
|
|
1240
|
+
clearTimeout(timeoutId);
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
// lib/hooks/auto-update/cache.ts
|
|
1245
|
+
import * as fs3 from "fs";
|
|
1246
|
+
import * as path3 from "path";
|
|
1247
|
+
function stripTrailingCommas(json) {
|
|
1248
|
+
return json.replace(/,(\s*[}\]])/g, "$1");
|
|
1249
|
+
}
|
|
1250
|
+
function removeFromBunLock(packageName) {
|
|
1251
|
+
const lockPath = path3.join(CACHE_DIR3, "bun.lock");
|
|
1252
|
+
if (!fs3.existsSync(lockPath))
|
|
1253
|
+
return false;
|
|
1254
|
+
try {
|
|
1255
|
+
const content = fs3.readFileSync(lockPath, "utf-8");
|
|
1256
|
+
const lock = JSON.parse(stripTrailingCommas(content));
|
|
1257
|
+
let modified = false;
|
|
1258
|
+
if (lock.workspaces?.[""]?.dependencies?.[packageName]) {
|
|
1259
|
+
delete lock.workspaces[""].dependencies[packageName];
|
|
1260
|
+
modified = true;
|
|
1261
|
+
}
|
|
1262
|
+
if (lock.packages?.[packageName]) {
|
|
1263
|
+
delete lock.packages[packageName];
|
|
1264
|
+
modified = true;
|
|
1265
|
+
}
|
|
1266
|
+
if (modified) {
|
|
1267
|
+
fs3.writeFileSync(lockPath, JSON.stringify(lock, null, 2));
|
|
1268
|
+
log(`Removed from bun.lock: ${packageName}`);
|
|
1269
|
+
}
|
|
1270
|
+
return modified;
|
|
1271
|
+
} catch {
|
|
1272
|
+
return false;
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
function invalidatePackage(packageName = PACKAGE_NAME) {
|
|
1276
|
+
try {
|
|
1277
|
+
const pkgDir = path3.join(CACHE_DIR3, "node_modules", packageName);
|
|
1278
|
+
const pkgJsonPath = path3.join(CACHE_DIR3, "package.json");
|
|
1279
|
+
let packageRemoved = false;
|
|
1280
|
+
let dependencyRemoved = false;
|
|
1281
|
+
let lockRemoved = false;
|
|
1282
|
+
if (fs3.existsSync(pkgDir)) {
|
|
1283
|
+
fs3.rmSync(pkgDir, { recursive: true, force: true });
|
|
1284
|
+
log(`Package removed: ${pkgDir}`);
|
|
1285
|
+
packageRemoved = true;
|
|
1286
|
+
}
|
|
1287
|
+
if (fs3.existsSync(pkgJsonPath)) {
|
|
1288
|
+
const content = fs3.readFileSync(pkgJsonPath, "utf-8");
|
|
1289
|
+
const pkgJson = JSON.parse(content);
|
|
1290
|
+
if (pkgJson.dependencies?.[packageName]) {
|
|
1291
|
+
delete pkgJson.dependencies[packageName];
|
|
1292
|
+
fs3.writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2));
|
|
1293
|
+
log(`Dependency removed from package.json: ${packageName}`);
|
|
1294
|
+
dependencyRemoved = true;
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
lockRemoved = removeFromBunLock(packageName);
|
|
1298
|
+
if (!packageRemoved && !dependencyRemoved && !lockRemoved) {
|
|
1299
|
+
log(`Package not found, nothing to invalidate: ${packageName}`);
|
|
1300
|
+
return false;
|
|
1301
|
+
}
|
|
1302
|
+
return true;
|
|
1303
|
+
} catch (err) {
|
|
1304
|
+
log("Failed to invalidate package:", err);
|
|
1305
|
+
return false;
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
// lib/hooks/auto-update/index.ts
|
|
1310
|
+
function createAutoUpdateHook(ctx, options = {}) {
|
|
1311
|
+
const { autoUpdate = true } = options;
|
|
1312
|
+
let hasChecked = false;
|
|
1313
|
+
return {
|
|
1314
|
+
event: async ({ event }) => {
|
|
1315
|
+
if (event.type !== "session.created")
|
|
1316
|
+
return;
|
|
1317
|
+
if (hasChecked)
|
|
1318
|
+
return;
|
|
1319
|
+
const props = event.properties;
|
|
1320
|
+
if (props?.info?.parentID)
|
|
1321
|
+
return;
|
|
1322
|
+
hasChecked = true;
|
|
1323
|
+
const localDevVersion = getLocalDevVersion(ctx.directory);
|
|
1324
|
+
if (localDevVersion) {
|
|
1325
|
+
log("Local development mode, skipping update check");
|
|
1326
|
+
return;
|
|
1327
|
+
}
|
|
1328
|
+
runBackgroundUpdateCheck(ctx, autoUpdate).catch((err) => {
|
|
1329
|
+
log("Background update check failed:", err);
|
|
1330
|
+
});
|
|
1331
|
+
}
|
|
1332
|
+
};
|
|
1333
|
+
}
|
|
1334
|
+
async function runBackgroundUpdateCheck(ctx, autoUpdate) {
|
|
1335
|
+
const pluginInfo = findPluginEntry(ctx.directory);
|
|
1336
|
+
if (!pluginInfo) {
|
|
1337
|
+
log("Plugin not found in config");
|
|
1338
|
+
return;
|
|
1339
|
+
}
|
|
1340
|
+
const cachedVersion = getCachedVersion();
|
|
1341
|
+
const currentVersion = cachedVersion ?? pluginInfo.pinnedVersion;
|
|
1342
|
+
if (!currentVersion) {
|
|
1343
|
+
log("No version found (cached or pinned)");
|
|
1344
|
+
return;
|
|
1345
|
+
}
|
|
1346
|
+
const latestVersion = await getLatestVersion();
|
|
1347
|
+
if (!latestVersion) {
|
|
1348
|
+
log("Failed to fetch latest version");
|
|
1349
|
+
return;
|
|
1350
|
+
}
|
|
1351
|
+
if (currentVersion === latestVersion) {
|
|
1352
|
+
log("Already on latest version");
|
|
1353
|
+
return;
|
|
1354
|
+
}
|
|
1355
|
+
log(`Update available: ${currentVersion} \u2192 ${latestVersion}`);
|
|
1356
|
+
if (!autoUpdate) {
|
|
1357
|
+
await showUpdateAvailableToast(ctx, currentVersion, latestVersion);
|
|
1358
|
+
log("Auto-update disabled, notification only");
|
|
1359
|
+
return;
|
|
1360
|
+
}
|
|
1361
|
+
if (pluginInfo.isPinned) {
|
|
1362
|
+
const updated = updatePinnedVersion(pluginInfo.configPath, pluginInfo.entry, latestVersion);
|
|
1363
|
+
if (!updated) {
|
|
1364
|
+
await showUpdateAvailableToast(ctx, currentVersion, latestVersion);
|
|
1365
|
+
log("Failed to update pinned version in config");
|
|
1366
|
+
return;
|
|
1367
|
+
}
|
|
1368
|
+
log(`Config updated: ${pluginInfo.entry} \u2192 ${PACKAGE_NAME}@${latestVersion}`);
|
|
1369
|
+
}
|
|
1370
|
+
invalidatePackage(PACKAGE_NAME);
|
|
1371
|
+
const installSuccess = await runBunInstallSafe();
|
|
1372
|
+
if (installSuccess) {
|
|
1373
|
+
await showAutoUpdatedToast(ctx, currentVersion, latestVersion);
|
|
1374
|
+
log(`Update installed: ${currentVersion} \u2192 ${latestVersion}`);
|
|
1375
|
+
} else {
|
|
1376
|
+
await showUpdateAvailableToast(ctx, currentVersion, latestVersion);
|
|
1377
|
+
log("bun install failed; update not installed (falling back to notification-only)");
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
async function runBunInstallSafe() {
|
|
1381
|
+
return new Promise((resolve) => {
|
|
1382
|
+
try {
|
|
1383
|
+
const child = spawn("bun", ["install"], {
|
|
1384
|
+
cwd: CACHE_DIR3,
|
|
1385
|
+
stdio: "ignore",
|
|
1386
|
+
detached: true
|
|
1387
|
+
});
|
|
1388
|
+
child.unref();
|
|
1389
|
+
child.on("close", (code) => {
|
|
1390
|
+
resolve(code === 0);
|
|
1391
|
+
});
|
|
1392
|
+
child.on("error", () => {
|
|
1393
|
+
resolve(false);
|
|
1394
|
+
});
|
|
1395
|
+
setTimeout(() => resolve(false), 30000);
|
|
1396
|
+
} catch (err) {
|
|
1397
|
+
log("bun install error:", err);
|
|
1398
|
+
resolve(false);
|
|
1399
|
+
}
|
|
1400
|
+
});
|
|
1401
|
+
}
|
|
1402
|
+
async function showUpdateAvailableToast(ctx, currentVersion, latestVersion) {
|
|
1403
|
+
await ctx.client.tui.showToast({
|
|
1404
|
+
body: {
|
|
1405
|
+
title: `${PACKAGE_NAME} Update Available`,
|
|
1406
|
+
message: `v${currentVersion} \u2192 v${latestVersion}
|
|
1407
|
+
Restart OpenCode to apply.`,
|
|
1408
|
+
variant: "info",
|
|
1409
|
+
duration: 8000
|
|
1410
|
+
}
|
|
1411
|
+
}).catch(() => {});
|
|
1412
|
+
log(`Update available toast shown: v${latestVersion}`);
|
|
1413
|
+
}
|
|
1414
|
+
async function showAutoUpdatedToast(ctx, oldVersion, newVersion) {
|
|
1415
|
+
await ctx.client.tui.showToast({
|
|
1416
|
+
body: {
|
|
1417
|
+
title: `${PACKAGE_NAME} Updated!`,
|
|
1418
|
+
message: `v${oldVersion} \u2192 v${newVersion}
|
|
1419
|
+
Restart OpenCode to apply.`,
|
|
1420
|
+
variant: "success",
|
|
1421
|
+
duration: 8000
|
|
1422
|
+
}
|
|
1423
|
+
}).catch(() => {});
|
|
1424
|
+
log(`Auto-updated toast shown: v${oldVersion} \u2192 v${newVersion}`);
|
|
1425
|
+
}
|
|
1426
|
+
// lib/provider-config.json
|
|
1427
|
+
var provider_config_default = {
|
|
1428
|
+
name: "AICodewith",
|
|
1429
|
+
env: ["AICODEWITH_API_KEY"],
|
|
1430
|
+
api: "https://api.openai.com/v1",
|
|
1431
|
+
models: {
|
|
1432
|
+
"gpt-5.2-codex": {
|
|
1433
|
+
name: "GPT-5.2 Codex",
|
|
1434
|
+
modalities: { input: ["text", "image"], output: ["text"] }
|
|
1435
|
+
},
|
|
1436
|
+
"gpt-5.2": {
|
|
1437
|
+
name: "GPT-5.2",
|
|
1438
|
+
modalities: { input: ["text", "image"], output: ["text"] }
|
|
1439
|
+
},
|
|
1440
|
+
"claude-sonnet-4-5-20250929": {
|
|
1441
|
+
name: "Claude Sonnet 4.5",
|
|
1442
|
+
modalities: { input: ["text", "image"], output: ["text"] }
|
|
1443
|
+
},
|
|
1444
|
+
"claude-opus-4-5-20251101": {
|
|
1445
|
+
name: "Claude Opus 4.5",
|
|
1446
|
+
modalities: { input: ["text", "image"], output: ["text"] }
|
|
1447
|
+
},
|
|
1448
|
+
"gemini-3-pro-high": {
|
|
1449
|
+
name: "Gemini 3 Pro",
|
|
1450
|
+
modalities: { input: ["text", "image"], output: ["text"] }
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
};
|
|
1454
|
+
|
|
996
1455
|
// index.ts
|
|
997
1456
|
var CODEX_MODEL_PREFIXES = ["gpt-", "codex"];
|
|
998
|
-
var
|
|
999
|
-
var PROVIDER_NAME = "AICodewith";
|
|
1457
|
+
var PACKAGE_NAME2 = "opencode-aicodewith-auth";
|
|
1000
1458
|
var PLUGIN_ENTRY = import.meta.url;
|
|
1001
1459
|
var PROVIDER_EXT = import.meta.url.endsWith(".ts") ? ".ts" : ".js";
|
|
1002
1460
|
var PROVIDER_NPM = new URL(`./provider${PROVIDER_EXT}`, import.meta.url).href;
|
|
1003
|
-
var DEFAULT_API = "https://api.openai.com/v1";
|
|
1004
|
-
var DEFAULT_ENV = ["AICODEWITH_API_KEY"];
|
|
1005
1461
|
var DEFAULT_OUTPUT_TOKEN_MAX = 32000;
|
|
1006
|
-
var
|
|
1007
|
-
var
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
"claude-opus-4-5-20251101": { name: "Claude Opus 4.5", modalities: IMAGE_MODALITIES },
|
|
1012
|
-
"gemini-3-pro-high": { name: "Gemini 3 Pro", modalities: IMAGE_MODALITIES }
|
|
1013
|
-
};
|
|
1014
|
-
var ALLOWED_MODEL_IDS = Object.keys(MODEL_CONFIGS);
|
|
1015
|
-
var ALLOWED_MODEL_SET = new Set(ALLOWED_MODEL_IDS);
|
|
1016
|
-
var homeDir = process.env.OPENCODE_TEST_HOME || os.homedir();
|
|
1017
|
-
var configRoot = process.env.XDG_CONFIG_HOME || path.join(homeDir, ".config");
|
|
1018
|
-
var configDir = path.join(configRoot, "opencode");
|
|
1019
|
-
var configPath = path.join(configDir, "opencode.json");
|
|
1462
|
+
var homeDir = process.env.OPENCODE_TEST_HOME || os3.homedir();
|
|
1463
|
+
var configRoot = process.env.XDG_CONFIG_HOME || path4.join(homeDir, ".config");
|
|
1464
|
+
var configDir = path4.join(configRoot, "opencode");
|
|
1465
|
+
var configPathJson = path4.join(configDir, "opencode.json");
|
|
1466
|
+
var configPathJsonc = path4.join(configDir, "opencode.jsonc");
|
|
1020
1467
|
var ensureConfigPromise;
|
|
1021
|
-
var
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1468
|
+
var fileExists = async (filePath) => {
|
|
1469
|
+
try {
|
|
1470
|
+
await access(filePath);
|
|
1471
|
+
return true;
|
|
1472
|
+
} catch {
|
|
1473
|
+
return false;
|
|
1474
|
+
}
|
|
1475
|
+
};
|
|
1476
|
+
var stripJsonComments2 = (content) => {
|
|
1477
|
+
return content.replace(/\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g, (m, g) => g ? "" : m).replace(/,(\s*[}\]])/g, "$1");
|
|
1478
|
+
};
|
|
1479
|
+
var readJsonOrJsonc = async (filePath) => {
|
|
1028
1480
|
try {
|
|
1029
1481
|
const text = await readFile2(filePath, "utf-8");
|
|
1030
|
-
|
|
1482
|
+
const stripped = filePath.endsWith(".jsonc") ? stripJsonComments2(text) : text;
|
|
1483
|
+
return JSON.parse(stripped);
|
|
1031
1484
|
} catch {
|
|
1032
1485
|
return;
|
|
1033
1486
|
}
|
|
1034
1487
|
};
|
|
1035
|
-
var
|
|
1488
|
+
var deepEqual = (a, b) => {
|
|
1489
|
+
if (a === b)
|
|
1490
|
+
return true;
|
|
1491
|
+
if (typeof a !== typeof b)
|
|
1492
|
+
return false;
|
|
1493
|
+
if (a === null || b === null)
|
|
1494
|
+
return a === b;
|
|
1495
|
+
if (typeof a !== "object")
|
|
1496
|
+
return false;
|
|
1497
|
+
const aObj = a;
|
|
1498
|
+
const bObj = b;
|
|
1499
|
+
const aKeys = Object.keys(aObj);
|
|
1500
|
+
const bKeys = Object.keys(bObj);
|
|
1501
|
+
if (aKeys.length !== bKeys.length)
|
|
1502
|
+
return false;
|
|
1503
|
+
for (const key of aKeys) {
|
|
1504
|
+
if (!Object.prototype.hasOwnProperty.call(bObj, key))
|
|
1505
|
+
return false;
|
|
1506
|
+
if (!deepEqual(aObj[key], bObj[key]))
|
|
1507
|
+
return false;
|
|
1508
|
+
}
|
|
1509
|
+
return true;
|
|
1510
|
+
};
|
|
1511
|
+
var isPackageEntry = (value) => value === PACKAGE_NAME2 || value.startsWith(`${PACKAGE_NAME2}@`);
|
|
1036
1512
|
var ensurePluginEntry = (list) => {
|
|
1037
1513
|
if (!Array.isArray(list))
|
|
1038
1514
|
return [PLUGIN_ENTRY];
|
|
1039
1515
|
const hasPlugin = list.some((entry) => typeof entry === "string" && (entry === PLUGIN_ENTRY || isPackageEntry(entry)));
|
|
1040
1516
|
return hasPlugin ? list : [...list, PLUGIN_ENTRY];
|
|
1041
1517
|
};
|
|
1518
|
+
var buildStandardProviderConfig = () => ({
|
|
1519
|
+
...provider_config_default,
|
|
1520
|
+
npm: PROVIDER_NPM
|
|
1521
|
+
});
|
|
1042
1522
|
var applyProviderConfig = (config) => {
|
|
1043
1523
|
let changed = false;
|
|
1044
1524
|
const providerMap = config.provider && typeof config.provider === "object" ? config.provider : {};
|
|
1045
|
-
const
|
|
1046
|
-
const
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
next.name = PROVIDER_NAME;
|
|
1050
|
-
changed = true;
|
|
1051
|
-
}
|
|
1052
|
-
if (!Array.isArray(next.env)) {
|
|
1053
|
-
next.env = DEFAULT_ENV;
|
|
1054
|
-
changed = true;
|
|
1055
|
-
}
|
|
1056
|
-
if (!next.npm || typeof next.npm === "string" && isPackageEntry(next.npm)) {
|
|
1057
|
-
next.npm = PROVIDER_NPM;
|
|
1058
|
-
changed = true;
|
|
1059
|
-
}
|
|
1060
|
-
if (!next.api) {
|
|
1061
|
-
next.api = DEFAULT_API;
|
|
1062
|
-
changed = true;
|
|
1063
|
-
}
|
|
1064
|
-
const hasExtraModels = Object.keys(existingModels).some((id) => !ALLOWED_MODEL_SET.has(id));
|
|
1065
|
-
const hasMissingModels = ALLOWED_MODEL_IDS.some((id) => !Object.prototype.hasOwnProperty.call(existingModels, id));
|
|
1066
|
-
if (!next.models || hasExtraModels || hasMissingModels) {
|
|
1067
|
-
next.models = toModelMap(ALLOWED_MODEL_IDS, existingModels);
|
|
1068
|
-
changed = true;
|
|
1069
|
-
}
|
|
1070
|
-
providerMap[PROVIDER_ID] = next;
|
|
1071
|
-
if (config.provider !== providerMap) {
|
|
1525
|
+
const existingProvider = providerMap[PROVIDER_ID];
|
|
1526
|
+
const standardProvider = buildStandardProviderConfig();
|
|
1527
|
+
if (!deepEqual(existingProvider, standardProvider)) {
|
|
1528
|
+
providerMap[PROVIDER_ID] = standardProvider;
|
|
1072
1529
|
config.provider = providerMap;
|
|
1073
1530
|
changed = true;
|
|
1074
1531
|
}
|
|
@@ -1083,7 +1540,20 @@ var ensureConfigFile = async () => {
|
|
|
1083
1540
|
if (ensureConfigPromise)
|
|
1084
1541
|
return ensureConfigPromise;
|
|
1085
1542
|
ensureConfigPromise = (async () => {
|
|
1086
|
-
const
|
|
1543
|
+
const jsoncExists = await fileExists(configPathJsonc);
|
|
1544
|
+
const jsonExists = await fileExists(configPathJson);
|
|
1545
|
+
let configPath;
|
|
1546
|
+
let config;
|
|
1547
|
+
if (jsoncExists) {
|
|
1548
|
+
configPath = configPathJsonc;
|
|
1549
|
+
config = await readJsonOrJsonc(configPath) ?? {};
|
|
1550
|
+
} else if (jsonExists) {
|
|
1551
|
+
configPath = configPathJson;
|
|
1552
|
+
config = await readJsonOrJsonc(configPath) ?? {};
|
|
1553
|
+
} else {
|
|
1554
|
+
configPath = configPathJson;
|
|
1555
|
+
config = { $schema: "https://opencode.ai/config.json" };
|
|
1556
|
+
}
|
|
1087
1557
|
if (!config || typeof config !== "object")
|
|
1088
1558
|
return;
|
|
1089
1559
|
const changed = applyProviderConfig(config);
|
|
@@ -1146,14 +1616,14 @@ var ensureGeminiSseParam = (url) => {
|
|
|
1146
1616
|
};
|
|
1147
1617
|
var buildGeminiUrl = (originalUrl, streaming) => {
|
|
1148
1618
|
const original = new URL(originalUrl);
|
|
1149
|
-
let
|
|
1150
|
-
if (!
|
|
1151
|
-
|
|
1619
|
+
let path5 = original.pathname;
|
|
1620
|
+
if (!path5.includes("/v1beta/") && !path5.includes("/v1/")) {
|
|
1621
|
+
path5 = `/v1beta${path5.startsWith("/") ? "" : "/"}${path5}`;
|
|
1152
1622
|
}
|
|
1153
1623
|
const base = new URL(AICODEWITH_GEMINI_BASE_URL);
|
|
1154
1624
|
const basePath = base.pathname.replace(/\/$/, "");
|
|
1155
1625
|
const target = new URL(base.origin);
|
|
1156
|
-
target.pathname = `${basePath}${
|
|
1626
|
+
target.pathname = `${basePath}${path5}`;
|
|
1157
1627
|
target.search = original.search;
|
|
1158
1628
|
const url = target.toString();
|
|
1159
1629
|
return streaming ? ensureGeminiSseParam(url) : url;
|
|
@@ -1188,10 +1658,11 @@ var getOutputTokenLimit = (input, output) => {
|
|
|
1188
1658
|
}
|
|
1189
1659
|
return DEFAULT_OUTPUT_TOKEN_MAX;
|
|
1190
1660
|
};
|
|
1191
|
-
var AicodewithCodexAuthPlugin = async (
|
|
1661
|
+
var AicodewithCodexAuthPlugin = async (ctx) => {
|
|
1192
1662
|
await ensureConfigFile().catch((error) => {
|
|
1193
|
-
console.warn(`[${
|
|
1663
|
+
console.warn(`[${PACKAGE_NAME2}] Failed to update opencode config: ${error instanceof Error ? error.message : error}`);
|
|
1194
1664
|
});
|
|
1665
|
+
const autoUpdateHook = createAutoUpdateHook(ctx, { autoUpdate: true });
|
|
1195
1666
|
const authHook = {
|
|
1196
1667
|
provider: PROVIDER_ID,
|
|
1197
1668
|
loader: async (getAuth, _provider) => {
|
|
@@ -1267,6 +1738,7 @@ var AicodewithCodexAuthPlugin = async (_ctx) => {
|
|
|
1267
1738
|
config: async (config) => {
|
|
1268
1739
|
applyProviderConfig(config);
|
|
1269
1740
|
},
|
|
1741
|
+
event: autoUpdateHook.event,
|
|
1270
1742
|
"chat.params": async (input, output) => {
|
|
1271
1743
|
if (input.model.providerID !== PROVIDER_ID)
|
|
1272
1744
|
return;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function invalidatePackage(packageName?: string): boolean;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { PluginEntryInfo } from "./types";
|
|
2
|
+
declare function log(...args: unknown[]): void;
|
|
3
|
+
export declare function isLocalDevMode(directory: string): boolean;
|
|
4
|
+
export declare function getLocalDevPath(directory: string): string | null;
|
|
5
|
+
export declare function getLocalDevVersion(directory: string): string | null;
|
|
6
|
+
export declare function findPluginEntry(directory: string): PluginEntryInfo | null;
|
|
7
|
+
export declare function getCachedVersion(): string | null;
|
|
8
|
+
export declare function updatePinnedVersion(configPath: string, oldEntry: string, newVersion: string): boolean;
|
|
9
|
+
export declare function getLatestVersion(): Promise<string | null>;
|
|
10
|
+
export { log };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file constants.ts
|
|
3
|
+
* @input None
|
|
4
|
+
* @output Auto-update configuration constants
|
|
5
|
+
* @pos Constants for package paths, npm registry, cache directories
|
|
6
|
+
*/
|
|
7
|
+
export declare const PACKAGE_NAME = "opencode-aicodewith-auth";
|
|
8
|
+
export declare const NPM_REGISTRY_URL = "https://registry.npmjs.org/-/package/opencode-aicodewith-auth/dist-tags";
|
|
9
|
+
export declare const NPM_FETCH_TIMEOUT = 5000;
|
|
10
|
+
export declare const CACHE_DIR: string;
|
|
11
|
+
export declare const INSTALLED_PACKAGE_JSON: string;
|
|
12
|
+
export declare function getWindowsAppdataDir(): string | null;
|
|
13
|
+
export declare const USER_CONFIG_DIR: string;
|
|
14
|
+
export declare const USER_OPENCODE_CONFIG: string;
|
|
15
|
+
export declare const USER_OPENCODE_CONFIG_JSONC: string;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { PluginInput } from "@opencode-ai/plugin";
|
|
2
|
+
import type { AutoUpdateOptions } from "./types";
|
|
3
|
+
export declare function createAutoUpdateHook(ctx: PluginInput, options?: AutoUpdateOptions): {
|
|
4
|
+
event: ({ event }: {
|
|
5
|
+
event: {
|
|
6
|
+
type: string;
|
|
7
|
+
properties?: unknown;
|
|
8
|
+
};
|
|
9
|
+
}) => Promise<void>;
|
|
10
|
+
};
|
|
11
|
+
export type { AutoUpdateOptions } from "./types";
|
|
12
|
+
export { invalidatePackage } from "./cache";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface NpmDistTags {
|
|
2
|
+
latest: string;
|
|
3
|
+
[key: string]: string;
|
|
4
|
+
}
|
|
5
|
+
export interface OpencodeConfig {
|
|
6
|
+
plugin?: string[];
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
}
|
|
9
|
+
export interface PackageJson {
|
|
10
|
+
version: string;
|
|
11
|
+
name?: string;
|
|
12
|
+
[key: string]: unknown;
|
|
13
|
+
}
|
|
14
|
+
export interface UpdateCheckResult {
|
|
15
|
+
needsUpdate: boolean;
|
|
16
|
+
currentVersion: string | null;
|
|
17
|
+
latestVersion: string | null;
|
|
18
|
+
isLocalDev: boolean;
|
|
19
|
+
isPinned: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface AutoUpdateOptions {
|
|
22
|
+
autoUpdate?: boolean;
|
|
23
|
+
}
|
|
24
|
+
export interface PluginEntryInfo {
|
|
25
|
+
entry: string;
|
|
26
|
+
isPinned: boolean;
|
|
27
|
+
pinnedVersion: string | null;
|
|
28
|
+
configPath: string;
|
|
29
|
+
}
|
package/package.json
CHANGED