codexapp 0.1.56 → 0.1.58

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.html CHANGED
@@ -4,8 +4,8 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Codex Web Local</title>
7
- <script type="module" crossorigin src="/assets/index-C0kJJe0e.js"></script>
8
- <link rel="stylesheet" crossorigin href="/assets/index-BwkNEgMe.css">
7
+ <script type="module" crossorigin src="/assets/index-6_XI7mdm.js"></script>
8
+ <link rel="stylesheet" crossorigin href="/assets/index-DDedrRjM.css">
9
9
  </head>
10
10
  <body class="bg-slate-950">
11
11
  <div id="app"></div>
package/dist-cli/index.js CHANGED
@@ -430,6 +430,8 @@ var SKILLS_SYNC_MANIFEST_PATH = "installed-skills.json";
430
430
  var SYNC_UPSTREAM_SKILLS_OWNER = "OpenClawAndroid";
431
431
  var SYNC_UPSTREAM_SKILLS_REPO = "skills";
432
432
  var PRIVATE_SYNC_BRANCH = "main";
433
+ var PUBLIC_UPSTREAM_BRANCH_ANDROID = "android";
434
+ var PUBLIC_UPSTREAM_BRANCH_DEFAULT = "main";
433
435
  var HUB_SKILLS_OWNER = "openclaw";
434
436
  var HUB_SKILLS_REPO = "skills";
435
437
  var startupSkillsSyncInitialized = false;
@@ -559,7 +561,7 @@ function isAndroidLikeRuntime() {
559
561
  return proot.length > 0;
560
562
  }
561
563
  function getPreferredPublicUpstreamBranch() {
562
- return isAndroidLikeRuntime() ? "android" : "main";
564
+ return isAndroidLikeRuntime() ? PUBLIC_UPSTREAM_BRANCH_ANDROID : PUBLIC_UPSTREAM_BRANCH_DEFAULT;
563
565
  }
564
566
  function isUpstreamSkillsRepo(repoOwner, repoName) {
565
567
  return repoOwner.toLowerCase() === SYNC_UPSTREAM_SKILLS_OWNER.toLowerCase() && repoName.toLowerCase() === SYNC_UPSTREAM_SKILLS_REPO.toLowerCase();
@@ -824,25 +826,50 @@ async function syncInstalledSkillsFolderToRepo(token, repoOwner, repoName, _inst
824
826
  async function pushWithNonFastForwardRetry(repoDir2, branch2) {
825
827
  const maxAttempts = 3;
826
828
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
829
+ await runCommand("git", ["fetch", "origin"], { cwd: repoDir2 });
830
+ let hasLatestRemote = false;
831
+ try {
832
+ await runCommand("git", ["merge-base", "--is-ancestor", `origin/${branch2}`, "HEAD"], { cwd: repoDir2 });
833
+ hasLatestRemote = true;
834
+ } catch {
835
+ hasLatestRemote = false;
836
+ }
837
+ if (!hasLatestRemote) {
838
+ try {
839
+ await runCommand("git", ["pull", "--no-rebase", "--no-ff", "origin", branch2], { cwd: repoDir2 });
840
+ } catch {
841
+ await resolveMergeConflictsByNewerCommit(repoDir2, branch2);
842
+ }
843
+ await runCommand("git", ["add", "."], { cwd: repoDir2 });
844
+ const statusAfterReconcile = (await runCommandWithOutput("git", ["status", "--porcelain"], { cwd: repoDir2 })).trim();
845
+ if (statusAfterReconcile) {
846
+ await runCommand("git", ["commit", "-m", "Reconcile skills sync before push retry"], { cwd: repoDir2 });
847
+ }
848
+ }
827
849
  try {
828
850
  await runCommand("git", ["push", "origin", `HEAD:${branch2}`], { cwd: repoDir2 });
851
+ const state = await readSkillsSyncState();
852
+ const pushedHead = await runCommandWithOutput("git", ["rev-parse", "HEAD"], { cwd: repoDir2 });
853
+ await writeSkillsSyncState({
854
+ ...state,
855
+ lastPushCommitSha: pushedHead.trim(),
856
+ lastSyncAttemptCount: attempt,
857
+ lastSyncError: "",
858
+ lastSyncAtIso: (/* @__PURE__ */ new Date()).toISOString()
859
+ });
829
860
  return;
830
861
  } catch (error) {
831
862
  if (!isNonFastForwardPushError(error) || attempt >= maxAttempts) {
863
+ const state = await readSkillsSyncState();
864
+ await writeSkillsSyncState({
865
+ ...state,
866
+ lastSyncAttemptCount: attempt,
867
+ lastSyncError: getErrorMessage(error, "push failed"),
868
+ lastSyncAtIso: (/* @__PURE__ */ new Date()).toISOString()
869
+ });
832
870
  throw error;
833
871
  }
834
872
  }
835
- await runCommand("git", ["fetch", "origin"], { cwd: repoDir2 });
836
- try {
837
- await runCommand("git", ["pull", "--no-rebase", "--no-ff", "origin", branch2], { cwd: repoDir2 });
838
- } catch {
839
- await resolveMergeConflictsByNewerCommit(repoDir2, branch2);
840
- }
841
- await runCommand("git", ["add", "."], { cwd: repoDir2 });
842
- const statusAfterReconcile = (await runCommandWithOutput("git", ["status", "--porcelain"], { cwd: repoDir2 })).trim();
843
- if (statusAfterReconcile) {
844
- await runCommand("git", ["commit", "-m", "Reconcile skills sync before push retry"], { cwd: repoDir2 });
845
- }
846
873
  }
847
874
  throw new Error("Failed to push after non-fast-forward retries");
848
875
  }
@@ -963,9 +990,8 @@ async function ensureCodexAgentsSymlinkToSkillsAgents() {
963
990
  }
964
991
  await symlink(relativeTarget, codexAgentsPath);
965
992
  }
966
- async function initializeSkillsSyncOnStartup(appServer) {
967
- if (startupSkillsSyncInitialized) return;
968
- startupSkillsSyncInitialized = true;
993
+ async function runSkillsSyncStartup(appServer) {
994
+ if (startupSyncStatus.inProgress) return;
969
995
  startupSyncStatus.inProgress = true;
970
996
  startupSyncStatus.lastRunAtIso = (/* @__PURE__ */ new Date()).toISOString();
971
997
  startupSyncStatus.lastError = "";
@@ -1016,6 +1042,11 @@ async function initializeSkillsSyncOnStartup(appServer) {
1016
1042
  startupSyncStatus.inProgress = false;
1017
1043
  }
1018
1044
  }
1045
+ async function initializeSkillsSyncOnStartup(appServer) {
1046
+ if (startupSkillsSyncInitialized) return;
1047
+ startupSkillsSyncInitialized = true;
1048
+ await runSkillsSyncStartup(appServer);
1049
+ }
1019
1050
  async function finalizeGithubLoginAndSync(token, username, appServer) {
1020
1051
  const repoName = DEFAULT_SKILLS_SYNC_REPO_NAME;
1021
1052
  await ensurePrivateForkFromUpstream(token, username, repoName);
@@ -1106,6 +1137,13 @@ async function handleSkillsRoutes(req, res, url, context) {
1106
1137
  repoOwner: state.repoOwner ?? "",
1107
1138
  repoName: state.repoName ?? "",
1108
1139
  configured: Boolean(state.githubToken && state.repoOwner && state.repoName),
1140
+ telemetry: {
1141
+ lastPullCommitSha: state.lastPullCommitSha ?? "",
1142
+ lastPushCommitSha: state.lastPushCommitSha ?? "",
1143
+ lastSyncAttemptCount: state.lastSyncAttemptCount ?? 0,
1144
+ lastSyncError: state.lastSyncError ?? "",
1145
+ lastSyncAtIso: state.lastSyncAtIso ?? ""
1146
+ },
1109
1147
  startup: {
1110
1148
  inProgress: startupSyncStatus.inProgress,
1111
1149
  mode: startupSyncStatus.mode,
@@ -1203,6 +1241,15 @@ async function handleSkillsRoutes(req, res, url, context) {
1203
1241
  }
1204
1242
  return true;
1205
1243
  }
1244
+ if (req.method === "POST" && url.pathname === "/codex-api/skills-sync/startup-sync") {
1245
+ try {
1246
+ await runSkillsSyncStartup(appServer);
1247
+ setJson(res, 200, { ok: true });
1248
+ } catch (error) {
1249
+ setJson(res, 502, { error: getErrorMessage(error, "Failed to run startup sync") });
1250
+ }
1251
+ return true;
1252
+ }
1206
1253
  if (req.method === "POST" && url.pathname === "/codex-api/skills-sync/pull") {
1207
1254
  try {
1208
1255
  const state = await readSkillsSyncState();
@@ -1233,35 +1280,21 @@ async function handleSkillsRoutes(req, res, url, context) {
1233
1280
  }
1234
1281
  const localDir = await detectUserSkillsDir(appServer);
1235
1282
  await pullInstalledSkillsFolderFromRepo(state.githubToken, state.repoOwner, state.repoName);
1236
- const installerScript = resolveSkillInstallerScriptPath(getCodexHomeDir());
1237
- if (!installerScript) {
1238
- throw new Error("Skill installer script not found");
1239
- }
1240
- const pythonCommand = resolvePythonCommand();
1241
- if (!pythonCommand) {
1242
- throw new Error("Python 3 is required to install skills");
1243
- }
1244
1283
  const localSkills = await scanInstalledSkillsFromDisk();
1284
+ const missingAfterPull = [];
1245
1285
  for (const skill of remote) {
1246
1286
  const owner = skill.owner || uniqueOwnerByName.get(skill.name) || "";
1247
1287
  if (!owner) continue;
1248
1288
  if (!localSkills.has(skill.name)) {
1249
- await runCommand(pythonCommand.command, [
1250
- ...pythonCommand.args,
1251
- installerScript,
1252
- "--repo",
1253
- `${HUB_SKILLS_OWNER}/${HUB_SKILLS_REPO}`,
1254
- "--path",
1255
- `skills/${owner}/${skill.name}`,
1256
- "--dest",
1257
- localDir,
1258
- "--method",
1259
- "git"
1260
- ]);
1289
+ missingAfterPull.push(`${owner}/${skill.name}`);
1290
+ continue;
1261
1291
  }
1262
1292
  const skillPath = join2(localDir, skill.name);
1263
1293
  await appServer.rpc("skills/config/write", { path: skillPath, enabled: skill.enabled });
1264
1294
  }
1295
+ if (missingAfterPull.length > 0) {
1296
+ throw new Error(`Missing skill folders after pull: ${missingAfterPull.join(", ")}`);
1297
+ }
1265
1298
  const remoteNames = new Set(remote.map((row) => row.name));
1266
1299
  for (const [name, localInfo] of localSkills.entries()) {
1267
1300
  if (!remoteNames.has(name)) {
@@ -1273,7 +1306,15 @@ async function handleSkillsRoutes(req, res, url, context) {
1273
1306
  const owner = item.owner || uniqueOwnerByName.get(item.name) || "";
1274
1307
  if (owner) nextOwners[item.name] = owner;
1275
1308
  }
1276
- await writeSkillsSyncState({ ...state, installedOwners: nextOwners });
1309
+ const pulledHead = await runCommandWithOutput("git", ["rev-parse", "HEAD"], { cwd: getSkillsInstallDir() }).catch(() => "");
1310
+ await writeSkillsSyncState({
1311
+ ...state,
1312
+ installedOwners: nextOwners,
1313
+ lastPullCommitSha: pulledHead.trim(),
1314
+ lastSyncAttemptCount: 1,
1315
+ lastSyncError: "",
1316
+ lastSyncAtIso: (/* @__PURE__ */ new Date()).toISOString()
1317
+ });
1277
1318
  try {
1278
1319
  await appServer.rpc("skills/list", { forceReload: true });
1279
1320
  } catch {
@@ -1374,7 +1415,8 @@ async function handleSkillsRoutes(req, res, url, context) {
1374
1415
  const payload = asRecord(await readJsonBody2(req));
1375
1416
  const name = typeof payload?.name === "string" ? payload.name : "";
1376
1417
  const path = typeof payload?.path === "string" ? payload.path : "";
1377
- const target = path || (name ? join2(getSkillsInstallDir(), name) : "");
1418
+ const normalizedPath = path.endsWith("/SKILL.md") ? path.slice(0, -"/SKILL.md".length) : path;
1419
+ const target = normalizedPath || (name ? join2(getSkillsInstallDir(), name) : "");
1378
1420
  if (!target) {
1379
1421
  setJson(res, 400, { error: "Missing name or path" });
1380
1422
  return true;