@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 +7 -0
- package/dist/commands.js +63 -56
- package/dist/commands.js.map +1 -1
- package/dist/install-hooks.d.ts +14 -15
- package/dist/install-hooks.js +26 -55
- package/dist/install-hooks.js.map +1 -1
- package/package.json +1 -1
- package/src/commands.test.ts +64 -13
- package/src/commands.ts +78 -70
- package/src/install-hooks.test.ts +16 -62
- package/src/install-hooks.ts +32 -56
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,
|
|
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
|
|
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,
|
|
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(
|
|
1834
|
-
const document = loadCursorSettings(
|
|
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(
|
|
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,
|
|
1852
|
+
printHooksInstallClose(deps, harness, settingsTarget);
|
|
1850
1853
|
return EXIT.ok;
|
|
1851
1854
|
}
|
|
1852
|
-
|
|
1853
|
-
|
|
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
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
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 (
|
|
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
|
|
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
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
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
|
|
1996
|
+
const removeFromCandidate = () => {
|
|
1995
1997
|
const document = loadSettings(candidate);
|
|
1996
1998
|
const result = removeHooks(document, scriptPath);
|
|
1997
|
-
|
|
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 (
|
|
2011
|
-
deps.io.out(`
|
|
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) {
|