@raidiant/notifai 2.0.1 → 2.0.2

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/CHANGELOG.md CHANGED
@@ -8,6 +8,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8
8
  This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
9
9
  and [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/).
10
10
 
11
+ ## [2.0.2](https://github.com/Raidiant-io/notifai/compare/v2.0.1...v2.0.2) (2026-08-14)
12
+
13
+
14
+ ### Bug Fixes
15
+
16
+ * **cli:** preserve Codex hook representation ([0c03308](https://github.com/Raidiant-io/notifai/commit/0c03308cdc355bb1a84f8e5ef3daaeba51059fe6))
17
+
11
18
  ## [2.0.1](https://github.com/Raidiant-io/notifai/compare/v2.0.0...v2.0.1) (2026-08-14)
12
19
 
13
20
 
package/dist/commands.js CHANGED
@@ -15,7 +15,7 @@ import { renderConfigExplain, renderConfigList, renderConfigPlain, } from './ui/
15
15
  import { firstBlocker, } from './readiness.js';
16
16
  import { WAITER_CEILING_SECONDS, clearAcknowledgementObligation, handleSessionEnd, handleStop, handleUserPromptSubmit, parseHookInput, pruneAbandonedSessions, readLiveProjectSessionPointers, readMatchingProjectSessionPointer, readProjectSession, readProjectSessionPointer, readSessionState, registerQuestion, } from './hooks.js';
17
17
  import { LOG_EVENTS, activeLogPath, archiveLogPaths, logConfigResolved, logSettingsFrom, logsDiskUsage, nullLogger, readLogRecords, renderRecord, } from './logging.js';
18
- import { BLOCKING_STOP_TIMEOUT_SECONDS, CLAUDE_ASYNC_STOP_TIMEOUT_SECONDS, HARNESSES, applyPlan, buildCursorHookConfig, buildHookConfig, collapseUnusedCodexRepresentation, codexLayerDir, codexLayerPaths, codexRepresentationProblems, codexTrustProblems, codexProjectRoot, detectHarness, detectedHarnesses, findInstallations, handlerEvent, hookDefinitionFiles, inspectCodexLayer, loadCursorSettings, loadSettings, mergeCursorHooks, mergeHooks, removeCursorHooks, removeHooks, settingsFile, } from './install-hooks.js';
18
+ import { BLOCKING_STOP_TIMEOUT_SECONDS, CLAUDE_ASYNC_STOP_TIMEOUT_SECONDS, HARNESSES, applyPlan, buildCursorHookConfig, buildHookConfig, codexLayerDir, codexLayerPaths, codexRepresentationProblems, codexTrustProblems, codexProjectRoot, detectHarness, detectedHarnesses, findInstallations, handlerEvent, hookDefinitionFiles, inspectCodexLayer, loadCursorSettings, loadSettings, mergeCursorHooks, mergeHooks, removeCursorHooks, removeHooks, settingsFile, withCodexLayerTransaction, } from './install-hooks.js';
19
19
  import { HARNESS_CAPABILITIES, HARNESS_LABELS } from './harnesses.js';
20
20
  import { claudeWakeRoute, inspectClaudeInbox, systemClaudeWakeAdapters, } from './claude-wake.js';
21
21
  import { codexWakeRoute, inspectCodexResume, } from './codex-wake.js';
@@ -1817,11 +1817,14 @@ export function hooksInstallCommand(deps, flags) {
1817
1817
  return EXIT.failed;
1818
1818
  }
1819
1819
  }
1820
- const file = settingsFile(harness, wantGlobal, deps.cwd, deps.env, hookPlatform);
1820
+ const codexPaths = harness === 'codex'
1821
+ ? codexLayerPaths(wantGlobal, deps.cwd, deps.env, hookPlatform)
1822
+ : null;
1823
+ const settingsTarget = codexPaths?.configToml ?? settingsFile(harness, wantGlobal, deps.cwd, deps.env, hookPlatform);
1821
1824
  // OpenCode's adapter is a generated plugin module rather than a handler
1822
1825
  // merged into a settings document, so it owns the whole file.
1823
1826
  if (harness === 'opencode') {
1824
- return installOpencodePlugin(deps, file, {
1827
+ return installOpencodePlugin(deps, settingsTarget, {
1825
1828
  adapterPath,
1826
1829
  timeoutSeconds: BLOCKING_STOP_TIMEOUT_SECONDS,
1827
1830
  platform: hookPlatform,
@@ -1830,15 +1833,15 @@ export function hooksInstallCommand(deps, flags) {
1830
1833
  }
1831
1834
  if (harness === 'cursor') {
1832
1835
  try {
1833
- withTargetFileLock(file, () => {
1834
- const document = loadCursorSettings(file);
1836
+ withTargetFileLock(settingsTarget, () => {
1837
+ const document = loadCursorSettings(settingsTarget);
1835
1838
  const result = mergeCursorHooks(document, buildCursorHookConfig({
1836
1839
  adapterPath,
1837
1840
  harness: 'cursor',
1838
1841
  platform: hookPlatform,
1839
1842
  nodePath,
1840
1843
  }), scriptPath);
1841
- applyPlan(file, result.document);
1844
+ applyPlan(settingsTarget, result.document);
1842
1845
  return result;
1843
1846
  });
1844
1847
  }
@@ -1846,27 +1849,27 @@ export function hooksInstallCommand(deps, flags) {
1846
1849
  deps.io.err(String(err));
1847
1850
  return EXIT.failed;
1848
1851
  }
1849
- printHooksInstallClose(deps, harness, file);
1852
+ printHooksInstallClose(deps, harness, settingsTarget);
1850
1853
  return EXIT.ok;
1851
1854
  }
1852
- let foreignStopCount = 0;
1853
- let collapsedFrom = null;
1855
+ const installInto = (file) => {
1856
+ const document = loadSettings(file);
1857
+ const foreignStopCount = foreignStopHandlers(document).length;
1858
+ const result = mergeHooks(document, buildHookConfig({
1859
+ adapterPath,
1860
+ harness,
1861
+ platform: hookPlatform,
1862
+ nodePath,
1863
+ }), scriptPath);
1864
+ applyPlan(file, result.document);
1865
+ return { file, foreignStopCount };
1866
+ };
1867
+ let installed;
1854
1868
  try {
1855
- withTargetFileLock(file, () => {
1856
- const document = loadSettings(file);
1857
- foreignStopCount = foreignStopHandlers(document).length;
1858
- const result = mergeHooks(document, buildHookConfig({
1859
- adapterPath,
1860
- harness,
1861
- platform: hookPlatform,
1862
- nodePath,
1863
- }), scriptPath);
1864
- applyPlan(file, result.document);
1865
- return result;
1866
- });
1867
- if (harness === 'codex') {
1868
- collapsedFrom = collapseUnusedCodexRepresentation(file, scriptPath, codexLayerPaths(flags.global ?? false, deps.cwd, deps.env));
1869
- }
1869
+ installed =
1870
+ codexPaths === null
1871
+ ? withTargetFileLock(settingsTarget, () => installInto(settingsTarget))
1872
+ : withCodexLayerTransaction(codexPaths, (inspection) => installInto(inspection.writeTarget));
1870
1873
  }
1871
1874
  catch (err) {
1872
1875
  deps.io.err(String(err));
@@ -1877,15 +1880,12 @@ export function hooksInstallCommand(deps, flags) {
1877
1880
  if (layer !== null)
1878
1881
  mkdirSync(layer, { recursive: true });
1879
1882
  }
1880
- printHooksInstallClose(deps, harness, file);
1881
- if (collapsedFrom !== null) {
1882
- deps.io.out(`Collapsed ${collapsedFrom} onto ${file} so this layer has one representation.`);
1883
- }
1884
- if (foreignStopCount > 0) {
1883
+ printHooksInstallClose(deps, harness, installed.file);
1884
+ if (installed.foreignStopCount > 0) {
1885
1885
  deps.io.out("This layer already has a Stop handler. Codex runs every matching handler, so Notifai's Stop and the existing one will both fire.");
1886
1886
  }
1887
1887
  if (harness === 'codex') {
1888
- for (const problem of codexRepresentationProblems(deps.cwd, deps.env)) {
1888
+ for (const problem of codexRepresentationProblems(deps.cwd, deps.env, hookPlatform)) {
1889
1889
  deps.io.out(problem);
1890
1890
  }
1891
1891
  }
@@ -1933,7 +1933,11 @@ export function hooksUninstallCommand(deps, flags) {
1933
1933
  if (!harness)
1934
1934
  return EXIT.usage;
1935
1935
  const scriptPath = flags.scriptPath ?? process.argv[1] ?? 'notifai';
1936
- const file = settingsFile(harness, flags.global ?? false, deps.cwd, deps.env, deps.hookPlatform);
1936
+ const global = flags.global ?? false;
1937
+ const codexPaths = harness === 'codex'
1938
+ ? codexLayerPaths(global, deps.cwd, deps.env, deps.hookPlatform)
1939
+ : null;
1940
+ const file = codexPaths?.configToml ?? settingsFile(harness, global, deps.cwd, deps.env, deps.hookPlatform);
1937
1941
  if (harness === 'opencode') {
1938
1942
  try {
1939
1943
  return withTargetFileLock(file, () => {
@@ -1982,33 +1986,47 @@ export function hooksUninstallCommand(deps, flags) {
1982
1986
  : `No Notifai hooks found in ${file}`);
1983
1987
  return EXIT.ok;
1984
1988
  }
1985
- const files = hookDefinitionFiles(harness, flags.global ?? false, deps.cwd, deps.env);
1986
- const existing = files.filter((candidate) => existsSync(candidate));
1987
- if (existing.length === 0) {
1988
- deps.io.out(`Nothing to remove: ${file} does not exist.`);
1989
- return EXIT.ok;
1990
- }
1991
- let removedAny = false;
1992
- try {
1989
+ const removeInstalledHooks = () => {
1990
+ const files = codexPaths === null
1991
+ ? hookDefinitionFiles(harness, global, deps.cwd, deps.env, deps.hookPlatform)
1992
+ : [codexPaths.hooksJson, codexPaths.configToml];
1993
+ const existing = files.filter((candidate) => existsSync(candidate));
1994
+ let removedAny = false;
1993
1995
  for (const candidate of existing) {
1994
- const stripped = withTargetFileLock(candidate, () => {
1996
+ const removeFromCandidate = () => {
1995
1997
  const document = loadSettings(candidate);
1996
1998
  const result = removeHooks(document, scriptPath);
1997
- applyPlan(candidate, result.document);
1999
+ if (result.replaced.length > 0)
2000
+ applyPlan(candidate, result.document);
1998
2001
  return result;
1999
- });
2002
+ };
2003
+ const stripped = codexPaths === null
2004
+ ? withTargetFileLock(candidate, removeFromCandidate)
2005
+ : removeFromCandidate();
2000
2006
  if (stripped.replaced.length > 0) {
2001
2007
  removedAny = true;
2002
2008
  deps.io.out(`Removed Notifai hooks (${stripped.replaced.join(', ')}) from ${candidate}`);
2003
2009
  }
2004
2010
  }
2011
+ return { existing, removedAny };
2012
+ };
2013
+ let result;
2014
+ try {
2015
+ result =
2016
+ codexPaths === null
2017
+ ? removeInstalledHooks()
2018
+ : withCodexLayerTransaction(codexPaths, removeInstalledHooks);
2005
2019
  }
2006
2020
  catch (err) {
2007
2021
  deps.io.err(String(err));
2008
2022
  return EXIT.failed;
2009
2023
  }
2010
- if (!removedAny) {
2011
- deps.io.out(`No Notifai hooks found in ${existing.join(', ')}`);
2024
+ if (result.existing.length === 0) {
2025
+ deps.io.out(`Nothing to remove: ${file} does not exist.`);
2026
+ return EXIT.ok;
2027
+ }
2028
+ if (!result.removedAny) {
2029
+ deps.io.out(`No Notifai hooks found in ${result.existing.join(', ')}`);
2012
2030
  }
2013
2031
  return EXIT.ok;
2014
2032
  }
@@ -3936,23 +3954,12 @@ function hookChecks(deps) {
3936
3954
  .join('; '),
3937
3955
  });
3938
3956
  }
3939
- const representationProblems = codexRepresentationProblems(deps.cwd, deps.env);
3957
+ const representationProblems = codexRepresentationProblems(deps.cwd, deps.env, deps.hookPlatform);
3940
3958
  if (representationProblems.length > 0) {
3941
- const collapseCommand = representationProblems.some((problem) => problem.includes('--global'))
3942
- ? 'notifai hooks install --harness codex --global'
3943
- : 'notifai hooks install --harness codex';
3944
3959
  checks.push({
3945
3960
  name: 'hooks (codex representation)',
3946
3961
  ok: false,
3947
3962
  detail: representationProblems.join('; '),
3948
- ...(representationProblems.some((problem) => problem.includes('to collapse onto'))
3949
- ? {
3950
- remedy: {
3951
- summary: 'collapse this Codex layer onto a single hook file',
3952
- command: collapseCommand,
3953
- },
3954
- }
3955
- : {}),
3956
3963
  });
3957
3964
  }
3958
3965
  if (active !== null && activeInstallations.length > 0) {