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 +167 -29
- package/dist/index.js +101 -13
- package/package.json +1 -1
- package/templates/.claude/hooks/hooks.json +10 -0
- package/templates/.claude/hooks/scripts/omcustom-auto-update.sh +170 -0
- package/templates/.claude/skills/omcustom-feedback/SKILL.md +36 -77
- package/templates/manifest.json +1 -1
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.
|
|
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/
|
|
25824
|
-
|
|
25825
|
-
import {
|
|
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
|
|
26407
|
-
import { platform } from "node:os";
|
|
26408
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
26525
|
+
const os = deps.getPlatform();
|
|
26432
26526
|
try {
|
|
26433
26527
|
if (os === "darwin") {
|
|
26434
26528
|
try {
|
|
26435
26529
|
info("rtk.installing_brew");
|
|
26436
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
27937
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
1333
|
+
const os = deps.getPlatform();
|
|
1271
1334
|
try {
|
|
1272
1335
|
if (os === "darwin") {
|
|
1273
1336
|
try {
|
|
1274
1337
|
info("rtk.installing_brew");
|
|
1275
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
@@ -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
|
|
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
|
|
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
|
|
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
|
-
**
|
|
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
|
|
96
|
+
### Phase 4A: GitHub Issue Creation (Route A — gh + authenticated)
|
|
106
97
|
|
|
107
|
-
1.
|
|
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
|
-
|
|
108
|
+
3. Ask for confirmation before creating
|
|
116
109
|
|
|
117
|
-
|
|
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
|
-
|
|
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 "
|
|
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
|
-
|
|
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
|
-
|
|
160
|
+
7. Return the issue URL to the user
|
|
200
161
|
|
|
201
|
-
### Phase 4D: Local Fallback (
|
|
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
|
|
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 |
|
|
231
|
-
|
|
232
|
-
| bug | bug |
|
|
233
|
-
| feature | enhancement |
|
|
234
|
-
| improvement | enhancement |
|
|
235
|
-
| question | question |
|
|
236
|
-
| (auto-detect fails) | (none) |
|
|
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
|
-
-
|
|
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
|
package/templates/manifest.json
CHANGED