oh-my-customcode 0.69.0 → 0.70.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/dist/cli/index.js CHANGED
@@ -9325,7 +9325,7 @@ var init_package = __esm(() => {
9325
9325
  workspaces: [
9326
9326
  "packages/*"
9327
9327
  ],
9328
- version: "0.69.0",
9328
+ version: "0.70.0",
9329
9329
  description: "Batteries-included agent harness for Claude Code",
9330
9330
  type: "module",
9331
9331
  bin: {
@@ -24826,7 +24826,9 @@ var en_default = {
24826
24826
  projectLatestSuffix: "(latest)",
24827
24827
  newVersionAvailable: "[Info] oh-my-customcode v{{latest}} available (current: v{{current}}). Run 'npm i -g oh-my-customcode' to upgrade.",
24828
24828
  rtkMissing: "[RTK] RTK is not installed. Attempting installation...",
24829
- rtkInstalled: "[RTK] ✓ RTK installed — 60-90% token savings activated"
24829
+ rtkInstalled: "[RTK] ✓ RTK installed — 60-90% token savings activated",
24830
+ codexMissing: "[Codex] Codex CLI is not installed. Attempting installation...",
24831
+ codexInstalled: "[Codex] ✓ Codex CLI installed"
24830
24832
  },
24831
24833
  list: {
24832
24834
  description: "List installed components",
@@ -24930,6 +24932,10 @@ var en_default = {
24930
24932
  rtk: {
24931
24933
  pass: "RTK installed",
24932
24934
  warn: "RTK not installed — token savings unavailable"
24935
+ },
24936
+ codex: {
24937
+ pass: "Codex CLI installed",
24938
+ warn: "Codex CLI not installed — AI-assisted development unavailable"
24933
24939
  }
24934
24940
  }
24935
24941
  },
@@ -24993,7 +24999,11 @@ var en_default = {
24993
24999
  rtk_installing: "Installing RTK...",
24994
25000
  rtk_success: "RTK installed successfully",
24995
25001
  rtk_already: "RTK already installed",
24996
- rtk_install_failed: "RTK installation failed"
25002
+ rtk_install_failed: "RTK installation failed",
25003
+ codex_installing: "Installing Codex CLI...",
25004
+ codex_success: "Codex CLI installed successfully",
25005
+ codex_already: "Codex CLI already installed",
25006
+ codex_install_failed: "Codex CLI installation failed"
24997
25007
  },
24998
25008
  init: {
24999
25009
  description: "Initialize oh-my-customcode in the current directory",
@@ -25223,7 +25233,9 @@ var ko_default = {
25223
25233
  projectLatestSuffix: "(최신)",
25224
25234
  newVersionAvailable: "[정보] oh-my-customcode v{{latest}} 사용 가능 (현재: v{{current}}). 'npm i -g oh-my-customcode'를 실행하여 업그레이드하세요.",
25225
25235
  rtkMissing: "[RTK] RTK가 설치되지 않았습니다. 설치를 시도합니다...",
25226
- rtkInstalled: "[RTK] ✓ RTK 설치 완료 — 토큰 60-90% 절감 활성화"
25236
+ rtkInstalled: "[RTK] ✓ RTK 설치 완료 — 토큰 60-90% 절감 활성화",
25237
+ codexMissing: "[Codex] Codex CLI가 설치되지 않았습니다. 설치를 시도합니다...",
25238
+ codexInstalled: "[Codex] ✓ Codex CLI 설치 완료"
25227
25239
  },
25228
25240
  list: {
25229
25241
  description: "설치된 컴포넌트 목록 표시",
@@ -25327,6 +25339,10 @@ var ko_default = {
25327
25339
  rtk: {
25328
25340
  pass: "RTK 설치됨",
25329
25341
  warn: "RTK 미설치 — 토큰 절감 불가"
25342
+ },
25343
+ codex: {
25344
+ pass: "Codex CLI 설치됨",
25345
+ warn: "Codex CLI 미설치 — AI 개발 지원 불가"
25330
25346
  }
25331
25347
  }
25332
25348
  },
@@ -25390,7 +25406,11 @@ var ko_default = {
25390
25406
  rtk_installing: "RTK 설치 중...",
25391
25407
  rtk_success: "RTK 설치 완료",
25392
25408
  rtk_already: "RTK 이미 설치됨",
25393
- rtk_install_failed: "RTK 설치 실패"
25409
+ rtk_install_failed: "RTK 설치 실패",
25410
+ codex_installing: "Codex CLI 설치 중...",
25411
+ codex_success: "Codex CLI 설치 완료",
25412
+ codex_already: "Codex CLI 이미 설치됨",
25413
+ codex_install_failed: "Codex CLI 설치 실패"
25394
25414
  },
25395
25415
  init: {
25396
25416
  description: "현재 디렉토리에 oh-my-customcode 초기화",
@@ -25820,9 +25840,9 @@ var $stringify = publicApi.stringify;
25820
25840
  var $visit = visit.visit;
25821
25841
  var $visitAsync = visit.visitAsync;
25822
25842
 
25823
- // src/core/config.ts
25824
- init_fs();
25825
- import { join as join3 } from "node:path";
25843
+ // src/core/codex-installer.ts
25844
+ import { execSync as execSync3 } from "node:child_process";
25845
+ import { platform } from "node:os";
25826
25846
 
25827
25847
  // src/utils/logger.ts
25828
25848
  var currentOptions = {
@@ -26043,7 +26063,77 @@ function success(messageKey, params) {
26043
26063
  }
26044
26064
  }
26045
26065
 
26066
+ // src/core/codex-installer.ts
26067
+ var defaultDeps = {
26068
+ exec: execSync3,
26069
+ getPlatform: platform
26070
+ };
26071
+ function isCodexInstalled(deps = defaultDeps) {
26072
+ try {
26073
+ deps.exec("which codex", { stdio: "pipe", timeout: 3000 });
26074
+ return true;
26075
+ } catch {
26076
+ return false;
26077
+ }
26078
+ }
26079
+ function getCodexVersion(deps = defaultDeps) {
26080
+ try {
26081
+ return deps.exec("codex --version", {
26082
+ encoding: "utf-8",
26083
+ stdio: "pipe",
26084
+ timeout: 3000
26085
+ }).trim();
26086
+ } catch {
26087
+ return null;
26088
+ }
26089
+ }
26090
+ function installCodex(deps = defaultDeps) {
26091
+ if (process.env.CI || false || false) {
26092
+ return false;
26093
+ }
26094
+ if (isCodexInstalled(deps)) {
26095
+ info("codex.already_installed");
26096
+ return true;
26097
+ }
26098
+ const os = deps.getPlatform();
26099
+ try {
26100
+ if (os === "darwin") {
26101
+ try {
26102
+ info("codex.installing_brew");
26103
+ deps.exec("brew install openai-codex", {
26104
+ stdio: "inherit",
26105
+ timeout: 120000
26106
+ });
26107
+ return isCodexInstalled(deps);
26108
+ } catch {
26109
+ info("codex.installing_npm");
26110
+ deps.exec("npm install -g @openai/codex", {
26111
+ stdio: "inherit",
26112
+ timeout: 120000
26113
+ });
26114
+ return isCodexInstalled(deps);
26115
+ }
26116
+ } else if (os === "linux") {
26117
+ info("codex.installing_npm");
26118
+ deps.exec("npm install -g @openai/codex", {
26119
+ stdio: "inherit",
26120
+ timeout: 120000
26121
+ });
26122
+ return isCodexInstalled(deps);
26123
+ } else {
26124
+ warn("codex.unsupported_os", { os });
26125
+ return false;
26126
+ }
26127
+ } catch (err) {
26128
+ const message = err instanceof Error ? err.message : String(err);
26129
+ warn("codex.install_failed", { error: message });
26130
+ return false;
26131
+ }
26132
+ }
26133
+
26046
26134
  // src/core/config.ts
26135
+ init_fs();
26136
+ import { join as join3 } from "node:path";
26047
26137
  var CONFIG_FILE = ".omcustomrc.json";
26048
26138
  var CURRENT_CONFIG_VERSION = 1;
26049
26139
  function getDefaultConfig() {
@@ -26403,56 +26493,60 @@ async function generateAndWriteLockfileForDir(targetDir) {
26403
26493
  }
26404
26494
 
26405
26495
  // src/core/rtk-installer.ts
26406
- import { execSync as execSync3 } from "node:child_process";
26407
- import { platform } from "node:os";
26408
- function isRtkInstalled() {
26496
+ import { execSync as execSync4 } from "node:child_process";
26497
+ import { platform as platform2 } from "node:os";
26498
+ var defaultDeps2 = {
26499
+ exec: execSync4,
26500
+ getPlatform: platform2
26501
+ };
26502
+ function isRtkInstalled(deps = defaultDeps2) {
26409
26503
  try {
26410
- execSync3("which rtk", { stdio: "pipe", timeout: 3000 });
26504
+ deps.exec("which rtk", { stdio: "pipe", timeout: 3000 });
26411
26505
  return true;
26412
26506
  } catch {
26413
26507
  return false;
26414
26508
  }
26415
26509
  }
26416
- function getRtkVersion() {
26510
+ function getRtkVersion(deps = defaultDeps2) {
26417
26511
  try {
26418
- return execSync3("rtk --version", { encoding: "utf-8", stdio: "pipe", timeout: 3000 }).trim();
26512
+ return deps.exec("rtk --version", { encoding: "utf-8", stdio: "pipe", timeout: 3000 }).trim();
26419
26513
  } catch {
26420
26514
  return null;
26421
26515
  }
26422
26516
  }
26423
- function installRtk() {
26517
+ function installRtk(deps = defaultDeps2) {
26424
26518
  if (process.env.CI || false || false) {
26425
26519
  return false;
26426
26520
  }
26427
- if (isRtkInstalled()) {
26521
+ if (isRtkInstalled(deps)) {
26428
26522
  info("rtk.already_installed");
26429
26523
  return true;
26430
26524
  }
26431
- const os = platform();
26525
+ const os = deps.getPlatform();
26432
26526
  try {
26433
26527
  if (os === "darwin") {
26434
26528
  try {
26435
26529
  info("rtk.installing_brew");
26436
- execSync3("brew install rtk-ai/tap/rtk", {
26530
+ deps.exec("brew install rtk-ai/tap/rtk", {
26437
26531
  stdio: "inherit",
26438
26532
  timeout: 120000
26439
26533
  });
26440
26534
  return true;
26441
26535
  } catch {
26442
26536
  info("rtk.installing_curl");
26443
- execSync3("curl -fsSL https://raw.githubusercontent.com/rtk-ai/rtk/refs/heads/master/install.sh | sh", {
26537
+ deps.exec("curl -fsSL https://raw.githubusercontent.com/rtk-ai/rtk/refs/heads/master/install.sh | sh", {
26444
26538
  stdio: "inherit",
26445
26539
  timeout: 120000
26446
26540
  });
26447
- return isRtkInstalled();
26541
+ return isRtkInstalled(deps);
26448
26542
  }
26449
26543
  } else if (os === "linux") {
26450
26544
  info("rtk.installing_curl");
26451
- execSync3("curl -fsSL https://raw.githubusercontent.com/rtk-ai/rtk/refs/heads/master/install.sh | sh", {
26545
+ deps.exec("curl -fsSL https://raw.githubusercontent.com/rtk-ai/rtk/refs/heads/master/install.sh | sh", {
26452
26546
  stdio: "inherit",
26453
26547
  timeout: 120000
26454
26548
  });
26455
- return isRtkInstalled();
26549
+ return isRtkInstalled(deps);
26456
26550
  } else {
26457
26551
  warn("rtk.unsupported_os", { os });
26458
26552
  return false;
@@ -26780,6 +26874,23 @@ async function checkRtk() {
26780
26874
  fixable: false
26781
26875
  };
26782
26876
  }
26877
+ async function checkCodex() {
26878
+ if (!isCodexInstalled()) {
26879
+ return {
26880
+ name: "Codex",
26881
+ status: "warn",
26882
+ message: "Codex CLI not installed — install manually: npm install -g @openai/codex",
26883
+ fixable: true
26884
+ };
26885
+ }
26886
+ const version = getCodexVersion();
26887
+ return {
26888
+ name: "Codex",
26889
+ status: "pass",
26890
+ message: `Codex CLI OK (${version ?? "unknown version"})`,
26891
+ fixable: false
26892
+ };
26893
+ }
26783
26894
  async function checkContexts(targetDir, rootDir = ".claude") {
26784
26895
  const contextsDir = path.join(targetDir, rootDir, "contexts");
26785
26896
  const exists2 = await isDirectory(contextsDir);
@@ -26883,7 +26994,8 @@ async function fixSingleIssue(check, targetDir, rootDir = ".claude") {
26883
26994
  const fixedCount = await fixBrokenSymlinks(targetDir, fullPaths);
26884
26995
  return fixedCount > 0;
26885
26996
  },
26886
- RTK: async () => Promise.resolve(installRtk())
26997
+ RTK: async () => Promise.resolve(installRtk()),
26998
+ Codex: async () => Promise.resolve(installCodex())
26887
26999
  };
26888
27000
  const fixer = fixMap[check.name];
26889
27001
  return fixer ? fixer() : false;
@@ -27032,7 +27144,8 @@ async function runAllChecks(targetDir, layout, packageVersion, includeUpdates) {
27032
27144
  checkHooks(targetDir, layout.rootDir),
27033
27145
  checkContexts(targetDir, layout.rootDir),
27034
27146
  checkCustomComponents(targetDir, layout.rootDir),
27035
- checkRtk()
27147
+ checkRtk(),
27148
+ checkCodex()
27036
27149
  ]);
27037
27150
  const frameworkCheck = await checkFrameworkDrift(targetDir, packageVersion);
27038
27151
  const checksWithFramework = frameworkCheck ? [...baseChecks, frameworkCheck] : baseChecks;
@@ -27693,6 +27806,19 @@ function installRtkIfNeeded(result) {
27693
27806
  info("install.rtk_already");
27694
27807
  }
27695
27808
  }
27809
+ function installCodexIfNeeded(result) {
27810
+ if (!isCodexInstalled()) {
27811
+ info("install.codex_installing");
27812
+ const codexInstalled = installCodex();
27813
+ if (codexInstalled) {
27814
+ info("install.codex_success");
27815
+ } else {
27816
+ result.warnings.push("Codex CLI installation failed — install manually: npm install -g @openai/codex");
27817
+ }
27818
+ } else {
27819
+ info("install.codex_already");
27820
+ }
27821
+ }
27696
27822
  async function install(options) {
27697
27823
  const result = createInstallResult(options.targetDir);
27698
27824
  try {
@@ -27731,6 +27857,7 @@ async function install(options) {
27731
27857
  info("install.lockfile_generated", { files: String(lockfileResult.fileCount) });
27732
27858
  }
27733
27859
  installRtkIfNeeded(result);
27860
+ installCodexIfNeeded(result);
27734
27861
  result.success = true;
27735
27862
  success("install.success");
27736
27863
  } catch (err) {
@@ -27914,7 +28041,7 @@ async function backupExistingInstallation(targetDir) {
27914
28041
 
27915
28042
  // src/core/mcp-config.ts
27916
28043
  init_fs();
27917
- import { execSync as execSync4 } from "node:child_process";
28044
+ import { execSync as execSync5 } from "node:child_process";
27918
28045
  import { writeFile } from "node:fs/promises";
27919
28046
  import { join as join8 } from "node:path";
27920
28047
  async function generateMCPConfig(targetDir) {
@@ -27926,15 +28053,15 @@ async function generateMCPConfig(targetDir) {
27926
28053
  return;
27927
28054
  }
27928
28055
  try {
27929
- execSync4("uv --version", { stdio: "pipe" });
28056
+ execSync5("uv --version", { stdio: "pipe" });
27930
28057
  } catch {
27931
28058
  warn("uv (Python package manager) not found. Install it with: curl -LsSf https://astral.sh/uv/install.sh | sh");
27932
28059
  warn("Skipping ontology-rag MCP configuration. You can set it up manually later.");
27933
28060
  return;
27934
28061
  }
27935
28062
  try {
27936
- execSync4("uv venv .venv", { cwd: targetDir, stdio: "pipe" });
27937
- execSync4('uv pip install "ontology-rag @ git+https://github.com/baekenough/oh-my-customcode.git#subdirectory=packages/ontology-rag"', { cwd: targetDir, stdio: "pipe" });
28063
+ execSync5("uv venv .venv", { cwd: targetDir, stdio: "pipe" });
28064
+ execSync5('uv pip install "ontology-rag @ git+https://github.com/baekenough/oh-my-customcode.git#subdirectory=packages/ontology-rag"', { cwd: targetDir, stdio: "pipe" });
27938
28065
  } catch (error2) {
27939
28066
  const msg = error2 instanceof Error ? error2.message : String(error2);
27940
28067
  warn(`Failed to setup ontology-rag: ${msg}`);
@@ -27977,7 +28104,7 @@ async function generateMCPConfig(targetDir) {
27977
28104
  }
27978
28105
  async function checkUvAvailable() {
27979
28106
  try {
27980
- execSync4("uv --version", { stdio: "pipe" });
28107
+ execSync5("uv --version", { stdio: "pipe" });
27981
28108
  return true;
27982
28109
  } catch {
27983
28110
  return false;
@@ -30306,6 +30433,16 @@ function checkAndInstallRtkAfterUpdate() {
30306
30433
  }
30307
30434
  }
30308
30435
  }
30436
+ function checkAndInstallCodexAfterUpdate() {
30437
+ if (!isCodexInstalled()) {
30438
+ warn("update.codex_missing");
30439
+ console.log(i18n.t("cli.update.codexMissing"));
30440
+ const codexInstalled = installCodex();
30441
+ if (codexInstalled) {
30442
+ console.log(i18n.t("cli.update.codexInstalled"));
30443
+ }
30444
+ }
30445
+ }
30309
30446
  async function update(options) {
30310
30447
  const result = createUpdateResult();
30311
30448
  try {
@@ -30354,6 +30491,7 @@ async function update(options) {
30354
30491
  });
30355
30492
  }
30356
30493
  checkAndInstallRtkAfterUpdate();
30494
+ checkAndInstallCodexAfterUpdate();
30357
30495
  } catch (err) {
30358
30496
  const message = err instanceof Error ? err.message : String(err);
30359
30497
  result.error = message;
package/dist/index.js CHANGED
@@ -914,6 +914,65 @@ import {
914
914
  } from "node:fs/promises";
915
915
  import { basename as basename2, join as join5 } from "node:path";
916
916
 
917
+ // src/core/codex-installer.ts
918
+ import { execSync } from "node:child_process";
919
+ import { platform } from "node:os";
920
+ var defaultDeps = {
921
+ exec: execSync,
922
+ getPlatform: platform
923
+ };
924
+ function isCodexInstalled(deps = defaultDeps) {
925
+ try {
926
+ deps.exec("which codex", { stdio: "pipe", timeout: 3000 });
927
+ return true;
928
+ } catch {
929
+ return false;
930
+ }
931
+ }
932
+ function installCodex(deps = defaultDeps) {
933
+ if (process.env.CI || false || false) {
934
+ return false;
935
+ }
936
+ if (isCodexInstalled(deps)) {
937
+ info("codex.already_installed");
938
+ return true;
939
+ }
940
+ const os = deps.getPlatform();
941
+ try {
942
+ if (os === "darwin") {
943
+ try {
944
+ info("codex.installing_brew");
945
+ deps.exec("brew install openai-codex", {
946
+ stdio: "inherit",
947
+ timeout: 120000
948
+ });
949
+ return isCodexInstalled(deps);
950
+ } catch {
951
+ info("codex.installing_npm");
952
+ deps.exec("npm install -g @openai/codex", {
953
+ stdio: "inherit",
954
+ timeout: 120000
955
+ });
956
+ return isCodexInstalled(deps);
957
+ }
958
+ } else if (os === "linux") {
959
+ info("codex.installing_npm");
960
+ deps.exec("npm install -g @openai/codex", {
961
+ stdio: "inherit",
962
+ timeout: 120000
963
+ });
964
+ return isCodexInstalled(deps);
965
+ } else {
966
+ warn("codex.unsupported_os", { os });
967
+ return false;
968
+ }
969
+ } catch (err) {
970
+ const message = err instanceof Error ? err.message : String(err);
971
+ warn("codex.install_failed", { error: message });
972
+ return false;
973
+ }
974
+ }
975
+
917
976
  // src/core/file-preservation.ts
918
977
  init_fs();
919
978
  import { basename, join as join3 } from "node:path";
@@ -1249,49 +1308,53 @@ async function generateAndWriteLockfileForDir(targetDir) {
1249
1308
  }
1250
1309
 
1251
1310
  // src/core/rtk-installer.ts
1252
- import { execSync } from "node:child_process";
1253
- import { platform } from "node:os";
1254
- function isRtkInstalled() {
1311
+ import { execSync as execSync2 } from "node:child_process";
1312
+ import { platform as platform2 } from "node:os";
1313
+ var defaultDeps2 = {
1314
+ exec: execSync2,
1315
+ getPlatform: platform2
1316
+ };
1317
+ function isRtkInstalled(deps = defaultDeps2) {
1255
1318
  try {
1256
- execSync("which rtk", { stdio: "pipe", timeout: 3000 });
1319
+ deps.exec("which rtk", { stdio: "pipe", timeout: 3000 });
1257
1320
  return true;
1258
1321
  } catch {
1259
1322
  return false;
1260
1323
  }
1261
1324
  }
1262
- function installRtk() {
1325
+ function installRtk(deps = defaultDeps2) {
1263
1326
  if (process.env.CI || false || false) {
1264
1327
  return false;
1265
1328
  }
1266
- if (isRtkInstalled()) {
1329
+ if (isRtkInstalled(deps)) {
1267
1330
  info("rtk.already_installed");
1268
1331
  return true;
1269
1332
  }
1270
- const os = platform();
1333
+ const os = deps.getPlatform();
1271
1334
  try {
1272
1335
  if (os === "darwin") {
1273
1336
  try {
1274
1337
  info("rtk.installing_brew");
1275
- execSync("brew install rtk-ai/tap/rtk", {
1338
+ deps.exec("brew install rtk-ai/tap/rtk", {
1276
1339
  stdio: "inherit",
1277
1340
  timeout: 120000
1278
1341
  });
1279
1342
  return true;
1280
1343
  } catch {
1281
1344
  info("rtk.installing_curl");
1282
- execSync("curl -fsSL https://raw.githubusercontent.com/rtk-ai/rtk/refs/heads/master/install.sh | sh", {
1345
+ deps.exec("curl -fsSL https://raw.githubusercontent.com/rtk-ai/rtk/refs/heads/master/install.sh | sh", {
1283
1346
  stdio: "inherit",
1284
1347
  timeout: 120000
1285
1348
  });
1286
- return isRtkInstalled();
1349
+ return isRtkInstalled(deps);
1287
1350
  }
1288
1351
  } else if (os === "linux") {
1289
1352
  info("rtk.installing_curl");
1290
- execSync("curl -fsSL https://raw.githubusercontent.com/rtk-ai/rtk/refs/heads/master/install.sh | sh", {
1353
+ deps.exec("curl -fsSL https://raw.githubusercontent.com/rtk-ai/rtk/refs/heads/master/install.sh | sh", {
1291
1354
  stdio: "inherit",
1292
1355
  timeout: 120000
1293
1356
  });
1294
- return isRtkInstalled();
1357
+ return isRtkInstalled(deps);
1295
1358
  } else {
1296
1359
  warn("rtk.unsupported_os", { os });
1297
1360
  return false;
@@ -1492,6 +1555,19 @@ function installRtkIfNeeded(result) {
1492
1555
  info("install.rtk_already");
1493
1556
  }
1494
1557
  }
1558
+ function installCodexIfNeeded(result) {
1559
+ if (!isCodexInstalled()) {
1560
+ info("install.codex_installing");
1561
+ const codexInstalled = installCodex();
1562
+ if (codexInstalled) {
1563
+ info("install.codex_success");
1564
+ } else {
1565
+ result.warnings.push("Codex CLI installation failed — install manually: npm install -g @openai/codex");
1566
+ }
1567
+ } else {
1568
+ info("install.codex_already");
1569
+ }
1570
+ }
1495
1571
  async function install(options) {
1496
1572
  const result = createInstallResult(options.targetDir);
1497
1573
  try {
@@ -1530,6 +1606,7 @@ async function install(options) {
1530
1606
  info("install.lockfile_generated", { files: String(lockfileResult.fileCount) });
1531
1607
  }
1532
1608
  installRtkIfNeeded(result);
1609
+ installCodexIfNeeded(result);
1533
1610
  result.success = true;
1534
1611
  success("install.success");
1535
1612
  } catch (err) {
@@ -1743,7 +1820,7 @@ var package_default = {
1743
1820
  workspaces: [
1744
1821
  "packages/*"
1745
1822
  ],
1746
- version: "0.69.0",
1823
+ version: "0.70.0",
1747
1824
  description: "Batteries-included agent harness for Claude Code",
1748
1825
  type: "module",
1749
1826
  bin: {
@@ -4588,6 +4665,16 @@ function checkAndInstallRtkAfterUpdate() {
4588
4665
  }
4589
4666
  }
4590
4667
  }
4668
+ function checkAndInstallCodexAfterUpdate() {
4669
+ if (!isCodexInstalled()) {
4670
+ warn("update.codex_missing");
4671
+ console.log(i18n.t("cli.update.codexMissing"));
4672
+ const codexInstalled = installCodex();
4673
+ if (codexInstalled) {
4674
+ console.log(i18n.t("cli.update.codexInstalled"));
4675
+ }
4676
+ }
4677
+ }
4591
4678
  async function update(options) {
4592
4679
  const result = createUpdateResult();
4593
4680
  try {
@@ -4636,6 +4723,7 @@ async function update(options) {
4636
4723
  });
4637
4724
  }
4638
4725
  checkAndInstallRtkAfterUpdate();
4726
+ checkAndInstallCodexAfterUpdate();
4639
4727
  } catch (err) {
4640
4728
  const message = err instanceof Error ? err.message : String(err);
4641
4729
  result.error = message;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "workspaces": [
4
4
  "packages/*"
5
5
  ],
6
- "version": "0.69.0",
6
+ "version": "0.70.0",
7
7
  "description": "Batteries-included agent harness for Claude Code",
8
8
  "type": "module",
9
9
  "bin": {
@@ -116,6 +116,16 @@
116
116
  }
117
117
  ],
118
118
  "SessionStart": [
119
+ {
120
+ "matcher": "*",
121
+ "hooks": [
122
+ {
123
+ "type": "command",
124
+ "command": ".claude/hooks/scripts/omcustom-auto-update.sh"
125
+ }
126
+ ],
127
+ "description": "Interactive oh-my-customcode update check at session start (#752)"
128
+ },
119
129
  {
120
130
  "matcher": "*",
121
131
  "hooks": [
@@ -0,0 +1,170 @@
1
+ #!/usr/bin/env bash
2
+ # SessionStart auto-update hook — interactive omcustom update check
3
+ # Trigger: SessionStart (runs BEFORE session-env-check.sh)
4
+ # Purpose: Check for oh-my-customcode updates, prompt user, optionally update
5
+ # Protocol: stdin JSON -> stdout pass-through, exit 0 ALWAYS
6
+ # Design: GitHub issue #752
7
+
8
+ # Pass through stdin immediately — capture for later output
9
+ input=$(cat)
10
+
11
+ # --- Guard: skip conditions ---
12
+
13
+ # Skip if explicitly disabled
14
+ if [ "${OMCUSTOM_SKIP_AUTO_UPDATE:-}" = "true" ]; then
15
+ echo "$input"
16
+ exit 0
17
+ fi
18
+
19
+ # Skip if /dev/tty not available (CI, Docker, non-interactive)
20
+ if ! [ -c /dev/tty ] 2>/dev/null; then
21
+ echo "$input"
22
+ exit 0
23
+ fi
24
+
25
+ # --- Configuration ---
26
+ CACHE_DIR="$HOME/.oh-my-customcode"
27
+ CACHE_FILE="${CACHE_DIR}/self-update-cache.json"
28
+ CACHE_MAX_AGE=3600 # 1 hour in seconds
29
+ NPM_TIMEOUT=3 # seconds for npm view
30
+ INPUT_TIMEOUT=10 # seconds for user prompt
31
+ PACKAGE_NAME="oh-my-customcode"
32
+
33
+ # --- Helper: semantic version compare ---
34
+ # Returns 0 if $1 < $2 (update available)
35
+ version_lt() {
36
+ local older
37
+ older=$(printf '%s\n' "$1" "$2" | sort -V | head -1)
38
+ [ "$older" = "$1" ] && [ "$1" != "$2" ]
39
+ }
40
+
41
+ # --- Step 1: Get current installed version ---
42
+ CURRENT_VERSION=""
43
+
44
+ # Try .omcustomrc.json in current directory
45
+ if [ -f ".omcustomrc.json" ]; then
46
+ CURRENT_VERSION=$(grep -o '"version"[[:space:]]*:[[:space:]]*"[^"]*"' .omcustomrc.json 2>/dev/null | head -1 | grep -o '"[^"]*"$' | tr -d '"')
47
+ fi
48
+
49
+ # Fallback: try npm list
50
+ if [ -z "$CURRENT_VERSION" ]; then
51
+ CURRENT_VERSION=$(npm list -g "$PACKAGE_NAME" --depth=0 2>/dev/null | grep -o "${PACKAGE_NAME}@[^ ]*" | cut -d'@' -f2)
52
+ fi
53
+
54
+ # Cannot determine current version — skip
55
+ if [ -z "$CURRENT_VERSION" ]; then
56
+ echo "$input"
57
+ exit 0
58
+ fi
59
+
60
+ # --- Step 2: Get latest version (cache-first, network fallback) ---
61
+ LATEST_VERSION=""
62
+ CACHE_HIT=false
63
+
64
+ # Ensure cache directory exists
65
+ mkdir -p "$CACHE_DIR" 2>/dev/null
66
+
67
+ # Check cache freshness
68
+ if [ -f "$CACHE_FILE" ]; then
69
+ CACHE_TIMESTAMP=$(grep -o '"timestamp"[[:space:]]*:[[:space:]]*[0-9]*' "$CACHE_FILE" 2>/dev/null | grep -o '[0-9]*$')
70
+ CACHED_VERSION=$(grep -o '"version"[[:space:]]*:[[:space:]]*"[^"]*"' "$CACHE_FILE" 2>/dev/null | grep -o '"[^"]*"$' | tr -d '"')
71
+
72
+ if [ -n "$CACHE_TIMESTAMP" ] && [ -n "$CACHED_VERSION" ]; then
73
+ NOW=$(date +%s)
74
+ AGE=$((NOW - CACHE_TIMESTAMP))
75
+
76
+ if [ "$AGE" -lt "$CACHE_MAX_AGE" ]; then
77
+ LATEST_VERSION="$CACHED_VERSION"
78
+ CACHE_HIT=true
79
+ fi
80
+ fi
81
+ fi
82
+
83
+ # Cache miss or stale — fetch from npm registry
84
+ if [ "$CACHE_HIT" = false ]; then
85
+ if command -v npm >/dev/null 2>&1; then
86
+ LATEST_VERSION=$(timeout "$NPM_TIMEOUT" npm view "$PACKAGE_NAME" version 2>/dev/null || echo "")
87
+
88
+ # Update cache on successful fetch
89
+ if [ -n "$LATEST_VERSION" ]; then
90
+ NOW=$(date +%s)
91
+ cat > "$CACHE_FILE" << EOF
92
+ {
93
+ "version": "${LATEST_VERSION}",
94
+ "timestamp": ${NOW},
95
+ "source": "npm-registry"
96
+ }
97
+ EOF
98
+ fi
99
+ fi
100
+ fi
101
+
102
+ # Could not determine latest version — skip
103
+ if [ -z "$LATEST_VERSION" ]; then
104
+ echo "$input"
105
+ exit 0
106
+ fi
107
+
108
+ # --- Step 3: Compare versions ---
109
+ if ! version_lt "$CURRENT_VERSION" "$LATEST_VERSION"; then
110
+ # Already up to date
111
+ echo "$input"
112
+ exit 0
113
+ fi
114
+
115
+ # --- Step 4: Prompt user for update ---
116
+ echo "" >&2
117
+ echo "--- [oh-my-customcode Update Available] ---" >&2
118
+ echo " New version: v${LATEST_VERSION} (current: v${CURRENT_VERSION})" >&2
119
+ echo "" >&2
120
+
121
+ # Interactive prompt via /dev/tty (SessionStart stdin is JSON pipe)
122
+ printf " Update oh-my-customcode to v%s? [y/N] " "$LATEST_VERSION" >/dev/tty 2>/dev/null
123
+ if read -r -t "$INPUT_TIMEOUT" answer </dev/tty 2>/dev/null; then
124
+ case "$answer" in
125
+ [yY]|[yY][eE][sS])
126
+ echo " Installing oh-my-customcode@${LATEST_VERSION}..." >&2
127
+ if npm install -g "${PACKAGE_NAME}@latest" >&2 2>&1; then
128
+ echo " ✓ Updated to v${LATEST_VERSION}" >&2
129
+
130
+ # Check if project harness should be updated too
131
+ if [ -f ".omcustomrc.json" ]; then
132
+ printf " Update project harness too? [y/N] " >/dev/tty 2>/dev/null
133
+ if read -r -t "$INPUT_TIMEOUT" harness_answer </dev/tty 2>/dev/null; then
134
+ case "$harness_answer" in
135
+ [yY]|[yY][eE][sS])
136
+ echo " Updating project harness..." >&2
137
+ if command -v omcustom >/dev/null 2>&1; then
138
+ omcustom update --force >&2 2>&1 || echo " ⚠ Harness update failed (non-blocking)" >&2
139
+ echo " ✓ Project harness updated" >&2
140
+ else
141
+ echo " ⚠ omcustom command not found after install" >&2
142
+ fi
143
+ ;;
144
+ *)
145
+ echo " Skipped harness update" >&2
146
+ ;;
147
+ esac
148
+ else
149
+ echo "" >&2
150
+ echo " Timed out — skipped harness update" >&2
151
+ fi
152
+ fi
153
+ else
154
+ echo " ⚠ Update failed (non-blocking, continuing session)" >&2
155
+ fi
156
+ ;;
157
+ *)
158
+ echo " Skipped update" >&2
159
+ ;;
160
+ esac
161
+ else
162
+ echo "" >&2
163
+ echo " Timed out — skipped update" >&2
164
+ fi
165
+
166
+ echo "------------------------------------" >&2
167
+
168
+ # Always pass through and exit 0
169
+ echo "$input"
170
+ exit 0
@@ -9,11 +9,11 @@ argument-hint: "[description or leave empty for interactive] [--anonymous]"
9
9
 
10
10
  # Feedback Submitter
11
11
 
12
- Submit feedback about oh-my-customcode (bugs, features, improvements, questions) directly from the CLI session. Supports anonymous submission via Airflow DAG when gh CLI is unavailable or when anonymity is requested.
12
+ Submit feedback about oh-my-customcode (bugs, features, improvements, questions) directly from the CLI session. Supports anonymous submission with `[Anonymous Feedback]` title prefix when `--anonymous` flag is used.
13
13
 
14
14
  ## Purpose
15
15
 
16
- Lowers the barrier for submitting feedback by allowing users to create GitHub issues or submit anonymously — without leaving their terminal session. All feedback is filed to the `baekenough/oh-my-customcode` repository.
16
+ Lowers the barrier for submitting feedback by allowing users to create GitHub issues — without leaving their terminal session. All feedback is filed to the `baekenough/oh-my-customcode` repository.
17
17
 
18
18
  ## Usage
19
19
 
@@ -59,22 +59,13 @@ if [ "$GH_AVAILABLE" = "true" ]; then
59
59
  else
60
60
  GH_AUTHED=false
61
61
  fi
62
-
63
- # Check curl availability
64
- command -v curl >/dev/null 2>&1 && CURL_AVAILABLE=true || CURL_AVAILABLE=false
65
62
  ```
66
63
 
67
- **Route A**: `gh` available + authenticated + NOT anonymous
64
+ **Route A**: `gh` available + authenticated
68
65
  - Use GitHub Issue creation (see Phase 4A)
66
+ - If `--anonymous`: adds `[Anonymous Feedback]` prefix and `anonymous` label
69
67
 
70
- **Route B**: anonymous OR not authenticated (gh available)
71
- - Use `curl` to Airflow REST API (see Phase 4C)
72
- - Phase 4B (workflow_dispatch) reserved for future use
73
-
74
- **Route C**: `gh` NOT available (or Route B curl fallback)
75
- - Use `curl` to Airflow REST API (see Phase 4C)
76
-
77
- **Fallback**: Neither `gh` nor `curl` available
68
+ **Fallback**: `gh` NOT available or not authenticated
78
69
  - Save feedback locally and inform the user (see Phase 4D)
79
70
 
80
71
  ### Phase 3: Environment Collection
@@ -102,24 +93,30 @@ For anonymous submissions, do NOT include the project name. Offer to include pro
102
93
  - Ask: "Include environment info (version, OS) in the anonymous report? [Y/n]"
103
94
  - If declined, set `PROJECT_CONTEXT=""`
104
95
 
105
- ### Phase 4A: GitHub Issue Creation (Route A — gh + authenticated + not anonymous)
96
+ ### Phase 4A: GitHub Issue Creation (Route A — gh + authenticated)
106
97
 
107
- 1. Show the user a preview of the issue to be created:
98
+ 1. If `ANONYMOUS=true`, prepend `[Anonymous Feedback] ` to the title and add `anonymous` to the label list.
99
+
100
+ 2. Show the user a preview of the issue to be created:
108
101
  ```
109
102
  [Preview]
110
103
  ├── Title: {title}
111
104
  ├── Category: {category}
112
- ├── Labels: feedback, {category-label}
105
+ ├── Labels: feedback, {category-label}[, anonymous]
113
106
  └── Repo: baekenough/oh-my-customcode
114
107
  ```
115
- 2. Ask for confirmation before creating
108
+ 3. Ask for confirmation before creating
116
109
 
117
- 3. Ensure labels exist (defensive):
110
+ 4. Ensure labels exist (defensive):
118
111
  ```bash
119
112
  gh label create feedback --description "User feedback via /omcustom-feedback" --color 0E8A16 --repo baekenough/oh-my-customcode 2>/dev/null || true
113
+ # If anonymous, ensure the anonymous label exists
114
+ if [ "$ANONYMOUS" = "true" ]; then
115
+ gh label create anonymous --description "Anonymous feedback submission" --color C5DEF5 --repo baekenough/oh-my-customcode 2>/dev/null || true
116
+ fi
120
117
  ```
121
118
 
122
- 4. Create the issue using `--body-file` for safe markdown handling:
119
+ 5. Create the issue using `--body-file` for safe markdown handling:
123
120
  ```bash
124
121
  # Write body to temp file to avoid shell escaping issues
125
122
  cat > /tmp/omcustom-feedback-body.md << 'FEEDBACK_EOF'
@@ -141,64 +138,28 @@ For anonymous submissions, do NOT include the project name. Offer to include pro
141
138
  *Submitted via `/omcustom-feedback`*
142
139
  FEEDBACK_EOF
143
140
 
141
+ # Build label string
142
+ LABELS="feedback,${CATEGORY_LABEL}"
143
+ if [ "$ANONYMOUS" = "true" ]; then
144
+ LABELS="${LABELS},anonymous"
145
+ fi
146
+
144
147
  # Create issue
145
148
  gh issue create \
146
149
  --repo baekenough/oh-my-customcode \
147
150
  --title "{title}" \
148
- --label "feedback,{category_label}" \
151
+ --label "$LABELS" \
149
152
  --body-file /tmp/omcustom-feedback-body.md
150
153
 
151
154
  # Clean up
152
155
  rm -f /tmp/omcustom-feedback-body.md
153
156
  ```
154
157
 
155
- 5. If label creation fails AND issue creation fails due to labels, retry without labels as fallback
156
-
157
- 6. Return the issue URL to the user
158
-
159
- ### Phase 4B: Anonymous via GitHub Actions (Route B — FUTURE)
160
-
161
- > **Note**: This route is reserved for future implementation. The `feedback-submission.yml` workflow does not yet exist. All anonymous/unauthenticated submissions currently fall through to Route C (Airflow REST API).
162
-
163
- ```bash
164
- gh workflow run feedback-submission.yml \
165
- --repo baekenough/oh-my-customcode \
166
- -f title="$TITLE" \
167
- -f body="$BODY" \
168
- -f feedback_type="$TYPE" \
169
- -f anonymous=true \
170
- -f project_context="$PROJECT_CONTEXT"
171
- ```
172
-
173
- On success, inform the user:
174
- ```
175
- [Done] Anonymous feedback submitted via GitHub Actions workflow.
176
- ```
177
-
178
- If `gh workflow run` fails (e.g., permissions), fall through to Route C.
179
-
180
- ### Phase 4C: Anonymous via Airflow REST API (Route C — no gh)
181
-
182
- ```bash
183
- # Build JSON payload safely with jq
184
- PAYLOAD=$(jq -n --arg title "$TITLE" --arg body "$BODY" --arg type "$TYPE" --arg ctx "$PROJECT_CONTEXT" \
185
- '{"conf":{"title":$title,"body":$body,"feedback_type":$type,"anonymous":true,"submitter":"","project_context":$ctx}}')
186
-
187
- curl -X POST "https://airflow.baekenough.com/api/v2/dags/omc_feedback_collector/dagRuns" \
188
- -H "Content-Type: application/json" \
189
- -d "$PAYLOAD" \
190
- --silent --show-error \
191
- --max-time 15
192
- ```
193
-
194
- On HTTP 200/201, inform the user:
195
- ```
196
- [Done] Anonymous feedback submitted via Airflow.
197
- ```
158
+ 6. If label creation fails AND issue creation fails due to labels, retry without labels as fallback
198
159
 
199
- On failure (non-2xx or network error), fall through to Fallback.
160
+ 7. Return the issue URL to the user
200
161
 
201
- ### Phase 4D: Local Fallback (no gh, no curl, or all routes failed)
162
+ ### Phase 4D: Local Fallback (gh not available, not authenticated, or issue creation failed)
202
163
 
203
164
  ```bash
204
165
  mkdir -p ~/.omcustom/feedback
@@ -222,25 +183,23 @@ Inform the user:
222
183
  [Saved] Feedback saved locally to ~/.omcustom/feedback/{timestamp}.json
223
184
  Submit manually when connectivity is available:
224
185
  - GitHub Issues: https://github.com/baekenough/oh-my-customcode/issues/new
225
- - Or run /omcustom:feedback again when gh or curl is available
186
+ - Or run /omcustom:feedback again when gh is available
226
187
  ```
227
188
 
228
189
  ### Category-to-Label Mapping
229
190
 
230
- | Category | GitHub Label | Airflow feedback_type |
231
- |----------|--------------|-----------------------|
232
- | bug | bug | bug |
233
- | feature | enhancement | feature |
234
- | improvement | enhancement | improvement |
235
- | question | question | question |
236
- | (auto-detect fails) | (none) | general |
191
+ | Category | GitHub Label |
192
+ |----------|--------------|
193
+ | bug | bug |
194
+ | feature | enhancement |
195
+ | improvement | enhancement |
196
+ | question | question |
197
+ | (auto-detect fails) | (none) |
237
198
 
238
199
  ## Notes
239
200
 
240
201
  - Route A creates a visible GitHub issue attributed to the user's gh account
241
- - Routes B and C submit anonymously submitter identity is not recorded
242
- - Route B requires `gh` but NOT authentication to the repo (public workflow)
243
- - Route C requires `curl` (available on macOS/Linux by default)
202
+ - When `--anonymous` is used, the title is prefixed with `[Anonymous Feedback]` and the `anonymous` label is added
244
203
  - Fallback ensures no feedback is silently lost even in offline environments
245
204
  - `disable-model-invocation: true` ensures this skill only runs when explicitly invoked by the user
246
205
  - Target repo is hardcoded to `baekenough/oh-my-customcode` — feedback is always about omcustom itself
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.69.0",
2
+ "version": "0.70.0",
3
3
  "lastUpdated": "2026-03-24T00:00:00.000Z",
4
4
  "components": [
5
5
  {