@raidiant/notifai 11.7.1 → 12.0.0-beta.11
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 +53 -0
- package/NOTICE +16 -0
- package/THIRD_PARTY_NOTICES +725 -0
- package/bin/notifai.mjs +11420 -0
- package/data/install.ps1 +318 -0
- package/data/release.json +1 -0
- package/inventory.json +1 -0
- package/npm-adapter-files.json +52 -0
- package/package.json +14 -44
- package/README.md +0 -168
- package/dist/agent-update-notice.d.ts +0 -7
- package/dist/agent-update-notice.js +0 -77
- package/dist/agent-update-notice.js.map +0 -1
- package/dist/atomic-file.d.ts +0 -30
- package/dist/atomic-file.js +0 -148
- package/dist/atomic-file.js.map +0 -1
- package/dist/attendant-update.d.ts +0 -15
- package/dist/attendant-update.js +0 -96
- package/dist/attendant-update.js.map +0 -1
- package/dist/claude-wake.d.ts +0 -108
- package/dist/claude-wake.js +0 -369
- package/dist/claude-wake.js.map +0 -1
- package/dist/cli-bin.d.ts +0 -26
- package/dist/cli-bin.js +0 -220
- package/dist/cli-bin.js.map +0 -1
- package/dist/cli-contract.d.ts +0 -16
- package/dist/cli-contract.js +0 -27
- package/dist/cli-contract.js.map +0 -1
- package/dist/cli-release.d.ts +0 -36
- package/dist/cli-release.js +0 -85
- package/dist/cli-release.js.map +0 -1
- package/dist/client.d.ts +0 -109
- package/dist/client.js +0 -217
- package/dist/client.js.map +0 -1
- package/dist/codex-answer-control.d.ts +0 -14
- package/dist/codex-answer-control.js +0 -41
- package/dist/codex-answer-control.js.map +0 -1
- package/dist/codex-answer-presentation.d.ts +0 -18
- package/dist/codex-answer-presentation.js +0 -113
- package/dist/codex-answer-presentation.js.map +0 -1
- package/dist/codex-input-lifecycle.d.ts +0 -10
- package/dist/codex-input-lifecycle.js +0 -63
- package/dist/codex-input-lifecycle.js.map +0 -1
- package/dist/codex-native-control.d.ts +0 -13
- package/dist/codex-native-control.js +0 -120
- package/dist/codex-native-control.js.map +0 -1
- package/dist/codex-native-turn.d.ts +0 -37
- package/dist/codex-native-turn.js +0 -155
- package/dist/codex-native-turn.js.map +0 -1
- package/dist/codex-question-bindings.d.ts +0 -76
- package/dist/codex-question-bindings.js +0 -151
- package/dist/codex-question-bindings.js.map +0 -1
- package/dist/codex-queue-control.d.ts +0 -21
- package/dist/codex-queue-control.js +0 -59
- package/dist/codex-queue-control.js.map +0 -1
- package/dist/codex-tool-messages.d.ts +0 -7
- package/dist/codex-tool-messages.js +0 -126
- package/dist/codex-tool-messages.js.map +0 -1
- package/dist/codex-wake.d.ts +0 -58
- package/dist/codex-wake.js +0 -135
- package/dist/codex-wake.js.map +0 -1
- package/dist/command-session.d.ts +0 -14
- package/dist/command-session.js +0 -31
- package/dist/command-session.js.map +0 -1
- package/dist/commands-acknowledge.d.ts +0 -13
- package/dist/commands-acknowledge.js +0 -116
- package/dist/commands-acknowledge.js.map +0 -1
- package/dist/commands-agent-sessions.d.ts +0 -6
- package/dist/commands-agent-sessions.js +0 -51
- package/dist/commands-agent-sessions.js.map +0 -1
- package/dist/commands-ask.d.ts +0 -62
- package/dist/commands-ask.js +0 -503
- package/dist/commands-ask.js.map +0 -1
- package/dist/commands-auth.d.ts +0 -76
- package/dist/commands-auth.js +0 -421
- package/dist/commands-auth.js.map +0 -1
- package/dist/commands-close.d.ts +0 -6
- package/dist/commands-close.js +0 -273
- package/dist/commands-close.js.map +0 -1
- package/dist/commands-config.d.ts +0 -31
- package/dist/commands-config.js +0 -272
- package/dist/commands-config.js.map +0 -1
- package/dist/commands-core.d.ts +0 -145
- package/dist/commands-core.js +0 -190
- package/dist/commands-core.js.map +0 -1
- package/dist/commands-devices.d.ts +0 -41
- package/dist/commands-devices.js +0 -144
- package/dist/commands-devices.js.map +0 -1
- package/dist/commands-doctor.d.ts +0 -25
- package/dist/commands-doctor.js +0 -1553
- package/dist/commands-doctor.js.map +0 -1
- package/dist/commands-guidance.d.ts +0 -27
- package/dist/commands-guidance.js +0 -150
- package/dist/commands-guidance.js.map +0 -1
- package/dist/commands-harness-context.d.ts +0 -57
- package/dist/commands-harness-context.js +0 -102
- package/dist/commands-harness-context.js.map +0 -1
- package/dist/commands-hook-attend.d.ts +0 -70
- package/dist/commands-hook-attend.js +0 -655
- package/dist/commands-hook-attend.js.map +0 -1
- package/dist/commands-hook-diagnostics.d.ts +0 -37
- package/dist/commands-hook-diagnostics.js +0 -136
- package/dist/commands-hook-diagnostics.js.map +0 -1
- package/dist/commands-hook-install.d.ts +0 -26
- package/dist/commands-hook-install.js +0 -784
- package/dist/commands-hook-install.js.map +0 -1
- package/dist/commands-hook-run.d.ts +0 -29
- package/dist/commands-hook-run.js +0 -933
- package/dist/commands-hook-run.js.map +0 -1
- package/dist/commands-hook-shape.d.ts +0 -19
- package/dist/commands-hook-shape.js +0 -48
- package/dist/commands-hook-shape.js.map +0 -1
- package/dist/commands-init.d.ts +0 -42
- package/dist/commands-init.js +0 -901
- package/dist/commands-init.js.map +0 -1
- package/dist/commands-io.d.ts +0 -2
- package/dist/commands-io.js +0 -117
- package/dist/commands-io.js.map +0 -1
- package/dist/commands-logs.d.ts +0 -30
- package/dist/commands-logs.js +0 -201
- package/dist/commands-logs.js.map +0 -1
- package/dist/commands-native-acknowledge.d.ts +0 -8
- package/dist/commands-native-acknowledge.js +0 -114
- package/dist/commands-native-acknowledge.js.map +0 -1
- package/dist/commands-project.d.ts +0 -4
- package/dist/commands-project.js +0 -41
- package/dist/commands-project.js.map +0 -1
- package/dist/commands-receive.d.ts +0 -3
- package/dist/commands-receive.js +0 -59
- package/dist/commands-receive.js.map +0 -1
- package/dist/commands-send-support.d.ts +0 -59
- package/dist/commands-send-support.js +0 -258
- package/dist/commands-send-support.js.map +0 -1
- package/dist/commands-send.d.ts +0 -34
- package/dist/commands-send.js +0 -900
- package/dist/commands-send.js.map +0 -1
- package/dist/commands-setup-proof.d.ts +0 -43
- package/dist/commands-setup-proof.js +0 -134
- package/dist/commands-setup-proof.js.map +0 -1
- package/dist/commands-skill.d.ts +0 -21
- package/dist/commands-skill.js +0 -199
- package/dist/commands-skill.js.map +0 -1
- package/dist/commands-sounds.d.ts +0 -12
- package/dist/commands-sounds.js +0 -35
- package/dist/commands-sounds.js.map +0 -1
- package/dist/commands-update-check.d.ts +0 -6
- package/dist/commands-update-check.js +0 -102
- package/dist/commands-update-check.js.map +0 -1
- package/dist/commands-update-resume.d.ts +0 -7
- package/dist/commands-update-resume.js +0 -143
- package/dist/commands-update-resume.js.map +0 -1
- package/dist/commands-update-skill.d.ts +0 -5
- package/dist/commands-update-skill.js +0 -36
- package/dist/commands-update-skill.js.map +0 -1
- package/dist/commands-update.d.ts +0 -11
- package/dist/commands-update.js +0 -286
- package/dist/commands-update.js.map +0 -1
- package/dist/commands.d.ts +0 -26
- package/dist/commands.js +0 -27
- package/dist/commands.js.map +0 -1
- package/dist/config-schema.d.ts +0 -62
- package/dist/config-schema.js +0 -302
- package/dist/config-schema.js.map +0 -1
- package/dist/config.d.ts +0 -181
- package/dist/config.js +0 -357
- package/dist/config.js.map +0 -1
- package/dist/credentials.d.ts +0 -96
- package/dist/credentials.js +0 -342
- package/dist/credentials.js.map +0 -1
- package/dist/file-lock.d.ts +0 -34
- package/dist/file-lock.js +0 -367
- package/dist/file-lock.js.map +0 -1
- package/dist/generated-session-label.d.ts +0 -2
- package/dist/generated-session-label.js +0 -152
- package/dist/generated-session-label.js.map +0 -1
- package/dist/guidance-content.d.ts +0 -53
- package/dist/guidance-content.js +0 -248
- package/dist/guidance-content.js.map +0 -1
- package/dist/guidance-render.d.ts +0 -25
- package/dist/guidance-render.js +0 -47
- package/dist/guidance-render.js.map +0 -1
- package/dist/guidance.d.ts +0 -69
- package/dist/guidance.js +0 -173
- package/dist/guidance.js.map +0 -1
- package/dist/harness-session-title.d.ts +0 -22
- package/dist/harness-session-title.js +0 -74
- package/dist/harness-session-title.js.map +0 -1
- package/dist/harnesses.d.ts +0 -65
- package/dist/harnesses.js +0 -159
- package/dist/harnesses.js.map +0 -1
- package/dist/hermes-attendant.d.ts +0 -38
- package/dist/hermes-attendant.js +0 -337
- package/dist/hermes-attendant.js.map +0 -1
- package/dist/hermes-plugin.d.ts +0 -11
- package/dist/hermes-plugin.js +0 -299
- package/dist/hermes-plugin.js.map +0 -1
- package/dist/hook-acknowledgements.d.ts +0 -70
- package/dist/hook-acknowledgements.js +0 -490
- package/dist/hook-acknowledgements.js.map +0 -1
- package/dist/hook-adapter.d.ts +0 -48
- package/dist/hook-adapter.js +0 -419
- package/dist/hook-adapter.js.map +0 -1
- package/dist/hook-events.d.ts +0 -105
- package/dist/hook-events.js +0 -130
- package/dist/hook-events.js.map +0 -1
- package/dist/hook-gates.d.ts +0 -7
- package/dist/hook-gates.js +0 -20
- package/dist/hook-gates.js.map +0 -1
- package/dist/hook-input.d.ts +0 -10
- package/dist/hook-input.js +0 -64
- package/dist/hook-input.js.map +0 -1
- package/dist/hook-lifecycle.d.ts +0 -112
- package/dist/hook-lifecycle.js +0 -2031
- package/dist/hook-lifecycle.js.map +0 -1
- package/dist/hook-project-sessions.d.ts +0 -28
- package/dist/hook-project-sessions.js +0 -141
- package/dist/hook-project-sessions.js.map +0 -1
- package/dist/hook-question-lock.d.ts +0 -29
- package/dist/hook-question-lock.js +0 -217
- package/dist/hook-question-lock.js.map +0 -1
- package/dist/hook-question-retirement.d.ts +0 -74
- package/dist/hook-question-retirement.js +0 -314
- package/dist/hook-question-retirement.js.map +0 -1
- package/dist/hook-question-state.d.ts +0 -58
- package/dist/hook-question-state.js +0 -239
- package/dist/hook-question-state.js.map +0 -1
- package/dist/hook-session-state.d.ts +0 -127
- package/dist/hook-session-state.js +0 -488
- package/dist/hook-session-state.js.map +0 -1
- package/dist/hook-types.d.ts +0 -476
- package/dist/hook-types.js +0 -4
- package/dist/hook-types.js.map +0 -1
- package/dist/injection-render.d.ts +0 -36
- package/dist/injection-render.js +0 -61
- package/dist/injection-render.js.map +0 -1
- package/dist/install-hooks.d.ts +0 -470
- package/dist/install-hooks.js +0 -1622
- package/dist/install-hooks.js.map +0 -1
- package/dist/integration-health.d.ts +0 -26
- package/dist/integration-health.js +0 -133
- package/dist/integration-health.js.map +0 -1
- package/dist/interactive.d.ts +0 -22
- package/dist/interactive.js +0 -644
- package/dist/interactive.js.map +0 -1
- package/dist/invocation-context.d.ts +0 -47
- package/dist/invocation-context.js +0 -164
- package/dist/invocation-context.js.map +0 -1
- package/dist/local-path.d.ts +0 -7
- package/dist/local-path.js +0 -27
- package/dist/local-path.js.map +0 -1
- package/dist/logging.d.ts +0 -157
- package/dist/logging.js +0 -599
- package/dist/logging.js.map +0 -1
- package/dist/main-run.d.ts +0 -1
- package/dist/main-run.js +0 -42
- package/dist/main-run.js.map +0 -1
- package/dist/main.d.ts +0 -2
- package/dist/main.js +0 -12
- package/dist/main.js.map +0 -1
- package/dist/native-answer-operation.d.ts +0 -70
- package/dist/native-answer-operation.js +0 -247
- package/dist/native-answer-operation.js.map +0 -1
- package/dist/native-skills.d.ts +0 -64
- package/dist/native-skills.js +0 -167
- package/dist/native-skills.js.map +0 -1
- package/dist/node-floor.d.ts +0 -19
- package/dist/node-floor.js +0 -32
- package/dist/node-floor.js.map +0 -1
- package/dist/npm-invocation.d.ts +0 -34
- package/dist/npm-invocation.js +0 -53
- package/dist/npm-invocation.js.map +0 -1
- package/dist/openclaw-continuation-bridge.d.ts +0 -7
- package/dist/openclaw-continuation-bridge.js +0 -64
- package/dist/openclaw-continuation-bridge.js.map +0 -1
- package/dist/openclaw-gateway-readiness.d.ts +0 -1
- package/dist/openclaw-gateway-readiness.js +0 -24
- package/dist/openclaw-gateway-readiness.js.map +0 -1
- package/dist/openclaw-generation.d.ts +0 -21
- package/dist/openclaw-generation.js +0 -115
- package/dist/openclaw-generation.js.map +0 -1
- package/dist/openclaw-message-bridge.d.ts +0 -8
- package/dist/openclaw-message-bridge.js +0 -93
- package/dist/openclaw-message-bridge.js.map +0 -1
- package/dist/openclaw-pending.d.ts +0 -8
- package/dist/openclaw-pending.js +0 -47
- package/dist/openclaw-pending.js.map +0 -1
- package/dist/openclaw-plugin.d.ts +0 -75
- package/dist/openclaw-plugin.js +0 -1353
- package/dist/openclaw-plugin.js.map +0 -1
- package/dist/openclaw-session-access.d.ts +0 -12
- package/dist/openclaw-session-access.js +0 -79
- package/dist/openclaw-session-access.js.map +0 -1
- package/dist/opencode-plugin.d.ts +0 -88
- package/dist/opencode-plugin.js +0 -286
- package/dist/opencode-plugin.js.map +0 -1
- package/dist/orca-session-title.d.ts +0 -11
- package/dist/orca-session-title.js +0 -111
- package/dist/orca-session-title.js.map +0 -1
- package/dist/pack-feedback-slice.d.ts +0 -35
- package/dist/pack-feedback-slice.js +0 -126
- package/dist/pack-feedback-slice.js.map +0 -1
- package/dist/pairing-qr.d.ts +0 -5
- package/dist/pairing-qr.js +0 -24
- package/dist/pairing-qr.js.map +0 -1
- package/dist/pending-pairing.d.ts +0 -38
- package/dist/pending-pairing.js +0 -70
- package/dist/pending-pairing.js.map +0 -1
- package/dist/platform.d.ts +0 -67
- package/dist/platform.js +0 -140
- package/dist/platform.js.map +0 -1
- package/dist/process-identity.d.ts +0 -26
- package/dist/process-identity.js +0 -82
- package/dist/process-identity.js.map +0 -1
- package/dist/program.d.ts +0 -59
- package/dist/program.js +0 -691
- package/dist/program.js.map +0 -1
- package/dist/project-enablement.d.ts +0 -21
- package/dist/project-enablement.js +0 -71
- package/dist/project-enablement.js.map +0 -1
- package/dist/question-settlement-process.d.ts +0 -17
- package/dist/question-settlement-process.js +0 -35
- package/dist/question-settlement-process.js.map +0 -1
- package/dist/question-timing.d.ts +0 -46
- package/dist/question-timing.js +0 -48
- package/dist/question-timing.js.map +0 -1
- package/dist/readiness.d.ts +0 -153
- package/dist/readiness.js +0 -149
- package/dist/readiness.js.map +0 -1
- package/dist/release.d.ts +0 -31
- package/dist/release.js +0 -52
- package/dist/release.js.map +0 -1
- package/dist/send-attempts.d.ts +0 -26
- package/dist/send-attempts.js +0 -139
- package/dist/send-attempts.js.map +0 -1
- package/dist/send.d.ts +0 -115
- package/dist/send.js +0 -421
- package/dist/send.js.map +0 -1
- package/dist/session-activation.d.ts +0 -13
- package/dist/session-activation.js +0 -64
- package/dist/session-activation.js.map +0 -1
- package/dist/session-attendant-probe.d.ts +0 -85
- package/dist/session-attendant-probe.js +0 -173
- package/dist/session-attendant-probe.js.map +0 -1
- package/dist/session-attendant-state.d.ts +0 -70
- package/dist/session-attendant-state.js +0 -256
- package/dist/session-attendant-state.js.map +0 -1
- package/dist/session-attendant.d.ts +0 -143
- package/dist/session-attendant.js +0 -520
- package/dist/session-attendant.js.map +0 -1
- package/dist/session-delivery.d.ts +0 -249
- package/dist/session-delivery.js +0 -576
- package/dist/session-delivery.js.map +0 -1
- package/dist/session-handoff.d.ts +0 -98
- package/dist/session-handoff.js +0 -118
- package/dist/session-handoff.js.map +0 -1
- package/dist/session-input-wakes.d.ts +0 -78
- package/dist/session-input-wakes.js +0 -239
- package/dist/session-input-wakes.js.map +0 -1
- package/dist/session-inputs.d.ts +0 -57
- package/dist/session-inputs.js +0 -434
- package/dist/session-inputs.js.map +0 -1
- package/dist/session-labels.d.ts +0 -56
- package/dist/session-labels.js +0 -396
- package/dist/session-labels.js.map +0 -1
- package/dist/session-message-handoff.d.ts +0 -39
- package/dist/session-message-handoff.js +0 -107
- package/dist/session-message-handoff.js.map +0 -1
- package/dist/setup-destinations.d.ts +0 -23
- package/dist/setup-destinations.js +0 -44
- package/dist/setup-destinations.js.map +0 -1
- package/dist/skill-integrity.d.ts +0 -54
- package/dist/skill-integrity.js +0 -164
- package/dist/skill-integrity.js.map +0 -1
- package/dist/skill-source/manifest.json +0 -40
- package/dist/skill-source/notifai/SKILL.md +0 -350
- package/dist/skill-source/notifai/references/diagnostics.md +0 -36
- package/dist/skill-source/notifai/references/harness-setup.md +0 -384
- package/dist/skill-source/notifai/references/native-questions.md +0 -51
- package/dist/skill-source/notifai/references/notes-and-edits.md +0 -54
- package/dist/skill-source/notifai/references/send-details.md +0 -14
- package/dist/skill-source/notifai/references/updates.md +0 -116
- package/dist/skill-source/notifai/references/writing-examples.md +0 -56
- package/dist/sound-ref.d.ts +0 -23
- package/dist/sound-ref.js +0 -28
- package/dist/sound-ref.js.map +0 -1
- package/dist/ui/banner.d.ts +0 -22
- package/dist/ui/banner.js +0 -87
- package/dist/ui/banner.js.map +0 -1
- package/dist/ui/config-view.d.ts +0 -30
- package/dist/ui/config-view.js +0 -108
- package/dist/ui/config-view.js.map +0 -1
- package/dist/ui/help.d.ts +0 -44
- package/dist/ui/help.js +0 -107
- package/dist/ui/help.js.map +0 -1
- package/dist/ui/theme.d.ts +0 -90
- package/dist/ui/theme.js +0 -244
- package/dist/ui/theme.js.map +0 -1
- package/dist/update-handoff.d.ts +0 -29
- package/dist/update-handoff.js +0 -69
- package/dist/update-handoff.js.map +0 -1
- package/dist/url-policy.d.ts +0 -85
- package/dist/url-policy.js +0 -369
- package/dist/url-policy.js.map +0 -1
- package/dist/version.d.ts +0 -40
- package/dist/version.js +0 -132
- package/dist/version.js.map +0 -1
- package/dist/wake-support.d.ts +0 -40
- package/dist/wake-support.js +0 -95
- package/dist/wake-support.js.map +0 -1
|
@@ -1,784 +0,0 @@
|
|
|
1
|
-
/** Hook installation and uninstallation across supported harnesses. */
|
|
2
|
-
import { existsSync, lstatSync, readFileSync, readdirSync, rmSync, rmdirSync } from 'node:fs';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import { parse as parseToml } from 'smol-toml';
|
|
5
|
-
import { atomicWriteFileSync } from './atomic-file.js';
|
|
6
|
-
import { EXIT } from './commands-core.js';
|
|
7
|
-
import { stopShapeProblems } from './commands-hook-shape.js';
|
|
8
|
-
import { withTargetFileLock } from './file-lock.js';
|
|
9
|
-
import { HARNESS_LABELS, HOOK_INSTALLABLE_HARNESSES, } from './harnesses.js';
|
|
10
|
-
import { installHookAdapter, isNpxAdapterTarget } from './hook-adapter.js';
|
|
11
|
-
import { HOOK_EVENT_COMMAND_RE, HOOK_EVENT_TABLE, HOOK_EVENTS, requiredHookEvents } from './hook-events.js';
|
|
12
|
-
import { NON_ROUTING_BLOCKING_STOP_TIMEOUT_SECONDS, applyPlan, buildCursorHookConfig, buildHookConfig, cleanupEmptiedCodexLayer, codexCoexistenceNotes, codexHomeNote, codexLegacyProjectLayers, codexMachineLayerPaths, codexRepresentationProblems, codexTrustProblems, detectHarness, detectedHarnesses, findInstallations, findLegacyProjectInstallations, findObsoleteNotifaiPluginWiring, handlerEvent, isTomlSettingsPath, loadCursorSettings, loadSettings, machineHookFiles, mergeCursorHooks, mergeHooks, prepareCodexPluginCleanup, preparePlan, removeCursorHooks, removeHooks, settingsFile, stripObsoleteNotifaiPluginEnablement, withCodexLayerTransaction, } from './install-hooks.js';
|
|
13
|
-
import { OPENCLAW_PLUGIN_MANIFEST, OPENCLAW_PLUGIN_PACKAGE, enableOpenclawNotifaiConfigText, isOurOpenclawPlugin, openclawPluginLoadBlockers, openclawPluginLoadWarning, openclawConfigPath, openclawPluginManifest, openclawPluginPackage, openclawPluginSource, parseOpenclawConfig, removeOpenclawLoadPathConfigText, removeOpenclawNotifaiConfigText, } from './openclaw-plugin.js';
|
|
14
|
-
import { isOurOpencodePlugin, opencodePluginSource } from './opencode-plugin.js';
|
|
15
|
-
import { installHermesPlugin, preflightHermesPlugin, uninstallHermesPlugin } from './hermes-plugin.js';
|
|
16
|
-
import { packageVersion } from './release.js';
|
|
17
|
-
import { CLI_PACKAGE_NAME, cliPackageSpec } from './cli-contract.js';
|
|
18
|
-
import { activeNpmCli } from './npm-invocation.js';
|
|
19
|
-
/** True when this process is `npx` / `npm exec`, not a global or linked install. */
|
|
20
|
-
export function runningViaNpx(env, scriptPath) {
|
|
21
|
-
if (env['npm_command'] === 'exec')
|
|
22
|
-
return true;
|
|
23
|
-
return scriptPath.includes(`${path.sep}_npx${path.sep}`);
|
|
24
|
-
}
|
|
25
|
-
function fileHookInstallTarget(target) {
|
|
26
|
-
if (target === undefined || isNpxAdapterTarget(target))
|
|
27
|
-
return undefined;
|
|
28
|
-
return target;
|
|
29
|
-
}
|
|
30
|
-
function resolveHookAdapterTarget(deps, flags) {
|
|
31
|
-
if (deps.hookInstallTarget !== undefined && isNpxAdapterTarget(deps.hookInstallTarget)) {
|
|
32
|
-
return deps.hookInstallTarget;
|
|
33
|
-
}
|
|
34
|
-
const fileTarget = fileHookInstallTarget(deps.hookInstallTarget);
|
|
35
|
-
const execPath = flags.execPath ?? fileTarget?.execPath ?? process.execPath;
|
|
36
|
-
const scriptPath = flags.scriptPath ?? fileTarget?.scriptPath ?? process.argv[1] ?? 'notifai';
|
|
37
|
-
if (runningViaNpx(deps.env, scriptPath)) {
|
|
38
|
-
const version = packageVersion();
|
|
39
|
-
const npmCli = activeNpmCli(deps.env);
|
|
40
|
-
if (version === null) {
|
|
41
|
-
throw new Error(`Could not read this CLI version, so an npx hook target cannot be pinned. Install \`${CLI_PACKAGE_NAME}\` globally and rerun \`notifai hooks install\`.`);
|
|
42
|
-
}
|
|
43
|
-
if (npmCli === null) {
|
|
44
|
-
throw new Error(`This process looks like npx but npm_execpath is missing. Install \`${CLI_PACKAGE_NAME}\` globally and rerun \`notifai hooks install\`.`);
|
|
45
|
-
}
|
|
46
|
-
return { kind: 'npx', execPath, npmCli, spec: cliPackageSpec(version) };
|
|
47
|
-
}
|
|
48
|
-
return { execPath, scriptPath };
|
|
49
|
-
}
|
|
50
|
-
function installationBytes(files) {
|
|
51
|
-
return JSON.stringify(files.map(file => {
|
|
52
|
-
try {
|
|
53
|
-
return [file, readFileSync(file, 'utf8')];
|
|
54
|
-
}
|
|
55
|
-
catch {
|
|
56
|
-
return [file, null];
|
|
57
|
-
}
|
|
58
|
-
}));
|
|
59
|
-
}
|
|
60
|
-
function printHooksInstallClose(deps, harness, file, changed = true) {
|
|
61
|
-
const label = HARNESS_LABELS[harness];
|
|
62
|
-
const approvalNote = harness === 'codex' && codexTrustProblems(findInstallations(deps.env, deps.hookAdapterHome, deps.hookPlatform).filter(entry => entry.harness === 'codex'), deps.env).length === 0
|
|
63
|
-
? 'Your existing Codex hook approvals still match. ' : '';
|
|
64
|
-
const activation = !changed
|
|
65
|
-
? approvalNote + 'Hook installation is unchanged; this write needs no restart. Continue the current Agent Session if its lifecycle and trust are ready; `notifai update --check --json` reports any remaining session requirement.'
|
|
66
|
-
: harness === 'codex'
|
|
67
|
-
? codexTrustProblems(findInstallations(deps.env, deps.hookAdapterHome, deps.hookPlatform).filter((installation) => installation.harness === 'codex'), deps.env).length === 0
|
|
68
|
-
? 'Your existing Codex hook approvals still match. Start one fresh Codex session, send one prompt, and run `notifai doctor`.'
|
|
69
|
-
: 'The changed Notifai handlers need approval. Open `/hooks` in Codex and approve or enable them, then start one fresh Codex session, send one prompt, and run `notifai doctor`.'
|
|
70
|
-
: harness === 'cursor'
|
|
71
|
-
? 'Start one fresh Cursor conversation, send one prompt, finish its first turn, then run `notifai doctor`.'
|
|
72
|
-
: harness === 'opencode'
|
|
73
|
-
? 'Restart OpenCode, start one fresh session, send one prompt, then run `notifai doctor`.'
|
|
74
|
-
: harness === 'openclaw'
|
|
75
|
-
? 'Restart the OpenClaw Gateway, start one fresh Agent Session, send one prompt, then run `notifai doctor`.'
|
|
76
|
-
: `Start one fresh ${label} session, send one prompt, then run \`notifai doctor\`.`;
|
|
77
|
-
if (deps.io.interactive === true && deps.io.note) {
|
|
78
|
-
void deps.io.note(`${file}\n${activation}`, `${label} hooks installed`);
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
deps.io.out(`Installed ${harness} hooks in ${file}`);
|
|
82
|
-
deps.io.out(activation);
|
|
83
|
-
}
|
|
84
|
-
export function hooksInstallCommand(deps, flags) {
|
|
85
|
-
if (flags.harness === undefined) {
|
|
86
|
-
const detected = detectedHarnesses(deps.cwd, deps.env);
|
|
87
|
-
if (detected.length === 0) {
|
|
88
|
-
deps.io.err(`Could not tell which harness you mean — pass --harness <${HOOK_INSTALLABLE_HARNESSES.join('|')}>.`);
|
|
89
|
-
return EXIT.usage;
|
|
90
|
-
}
|
|
91
|
-
let ok = true;
|
|
92
|
-
for (const harness of detected) {
|
|
93
|
-
if (hooksInstallCommand(deps, { ...flags, harness }) !== EXIT.ok)
|
|
94
|
-
ok = false;
|
|
95
|
-
}
|
|
96
|
-
return ok ? EXIT.ok : EXIT.failed;
|
|
97
|
-
}
|
|
98
|
-
const harness = resolveHarness(deps, flags.harness);
|
|
99
|
-
if (!harness)
|
|
100
|
-
return EXIT.usage;
|
|
101
|
-
if (harness === 'hermes') {
|
|
102
|
-
try {
|
|
103
|
-
preflightHermesPlugin(deps.env);
|
|
104
|
-
const target = resolveHookAdapterTarget(deps, flags);
|
|
105
|
-
const adapter = installHookAdapter(target, deps.hookAdapterHome, deps.hookPlatform, deps.env);
|
|
106
|
-
const file = installHermesPlugin(adapter.path, deps.env, (deps.hookPlatform ?? process.platform) === 'win32' ? target.execPath : undefined);
|
|
107
|
-
deps.io.out(`Installed the Notifai Hermes plugin at ${file}. Start a fresh local classic CLI Agent Session; its live attendant can route questions after activation.`);
|
|
108
|
-
return EXIT.ok;
|
|
109
|
-
}
|
|
110
|
-
catch (err) {
|
|
111
|
-
deps.io.err(`Could not install Hermes plugin: ${String(err)}`);
|
|
112
|
-
return EXIT.failed;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
const adapterTarget = resolveHookAdapterTarget(deps, flags);
|
|
116
|
-
const scriptPath = flags.scriptPath ?? fileHookInstallTarget(adapterTarget)?.scriptPath ?? process.argv[1] ?? 'notifai';
|
|
117
|
-
const hookPlatform = deps.hookPlatform ?? process.platform;
|
|
118
|
-
const nodePath = adapterTarget.execPath;
|
|
119
|
-
const codexPaths = harness === 'codex' ? codexMachineLayerPaths(deps.env, hookPlatform) : null;
|
|
120
|
-
let adapterPath;
|
|
121
|
-
try {
|
|
122
|
-
// Refuse unsupported plugin configuration before even updating the adapter.
|
|
123
|
-
// The authoritative plan is made again under the layer lock below.
|
|
124
|
-
if (codexPaths !== null)
|
|
125
|
-
prepareCodexPluginCleanup(codexPaths.configToml);
|
|
126
|
-
if (harness === 'openclaw') {
|
|
127
|
-
const configFile = openclawConfigPath(deps.env, hookPlatform);
|
|
128
|
-
if (existsSync(configFile)) {
|
|
129
|
-
assertOwnedRegularFile(configFile);
|
|
130
|
-
parseOpenclawConfig(readFileSync(configFile, 'utf8'), configFile);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
adapterPath = installHookAdapter(adapterTarget, deps.hookAdapterHome, hookPlatform, deps.env).path;
|
|
134
|
-
}
|
|
135
|
-
catch (err) {
|
|
136
|
-
deps.io.err(`Could not prepare hook installation: ${String(err)}`);
|
|
137
|
-
return EXIT.failed;
|
|
138
|
-
}
|
|
139
|
-
// Codex is the one harness whose layer holds two candidate files, so its
|
|
140
|
-
// transaction anchors on `config.toml` and the inspection names the target.
|
|
141
|
-
const settingsTarget = codexPaths?.configToml ?? settingsFile(harness, deps.env, hookPlatform);
|
|
142
|
-
const comparedFiles = codexPaths === null ? [settingsTarget] : [codexPaths.configToml, codexPaths.hooksJson];
|
|
143
|
-
const beforeInstall = installationBytes(comparedFiles);
|
|
144
|
-
// OpenCode's adapter is a generated plugin module rather than a handler
|
|
145
|
-
// merged into a settings document, so it owns the whole file.
|
|
146
|
-
if (harness === 'opencode') {
|
|
147
|
-
return finishInstall(deps, harness, scriptPath, installOpencodePlugin(deps, settingsTarget, {
|
|
148
|
-
adapterPath,
|
|
149
|
-
timeoutSeconds: NON_ROUTING_BLOCKING_STOP_TIMEOUT_SECONDS,
|
|
150
|
-
platform: hookPlatform,
|
|
151
|
-
nodePath,
|
|
152
|
-
...(flags.narrate === undefined ? {} : { narrate: flags.narrate }),
|
|
153
|
-
}));
|
|
154
|
-
}
|
|
155
|
-
if (harness === 'openclaw') {
|
|
156
|
-
return finishInstall(deps, harness, scriptPath, installOpenclawPlugin(deps, settingsTarget, {
|
|
157
|
-
adapterPath,
|
|
158
|
-
timeoutSeconds: NON_ROUTING_BLOCKING_STOP_TIMEOUT_SECONDS,
|
|
159
|
-
platform: hookPlatform,
|
|
160
|
-
nodePath,
|
|
161
|
-
...(flags.narrate === undefined ? {} : { narrate: flags.narrate }),
|
|
162
|
-
}));
|
|
163
|
-
}
|
|
164
|
-
if (harness === 'grok') {
|
|
165
|
-
return finishInstall(deps, harness, scriptPath, installGrokHooks(deps, settingsTarget, { adapterPath, platform: hookPlatform, nodePath }, flags.narrate));
|
|
166
|
-
}
|
|
167
|
-
if (harness === 'cursor') {
|
|
168
|
-
try {
|
|
169
|
-
withTargetFileLock(settingsTarget, () => {
|
|
170
|
-
const document = loadCursorSettings(settingsTarget);
|
|
171
|
-
const result = mergeCursorHooks(document, buildCursorHookConfig({
|
|
172
|
-
adapterPath,
|
|
173
|
-
harness: 'cursor',
|
|
174
|
-
platform: hookPlatform,
|
|
175
|
-
nodePath,
|
|
176
|
-
}), scriptPath);
|
|
177
|
-
applyPlan(settingsTarget, result.document);
|
|
178
|
-
return result;
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
catch (err) {
|
|
182
|
-
deps.io.err(String(err));
|
|
183
|
-
return EXIT.failed;
|
|
184
|
-
}
|
|
185
|
-
if (flags.narrate !== false)
|
|
186
|
-
printHooksInstallClose(deps, harness, settingsTarget, beforeInstall !== installationBytes(comparedFiles));
|
|
187
|
-
return finishInstall(deps, harness, scriptPath, EXIT.ok);
|
|
188
|
-
}
|
|
189
|
-
const prepareInstall = (file, document = loadSettings(file), previous) => {
|
|
190
|
-
const foreignStopCount = foreignStopHandlers(document).length;
|
|
191
|
-
const result = mergeHooks(document, buildHookConfig({
|
|
192
|
-
adapterPath,
|
|
193
|
-
harness,
|
|
194
|
-
platform: hookPlatform,
|
|
195
|
-
nodePath,
|
|
196
|
-
}), scriptPath, { preserveIdentities: harness === 'codex' });
|
|
197
|
-
// JSON settings can carry leftover native plugin enablement in the same
|
|
198
|
-
// document as hooks. Strip it in this write so a crash cannot leave both
|
|
199
|
-
// firing. Codex plugin cleanup is planned and persisted first below.
|
|
200
|
-
const next = isTomlSettingsPath(file)
|
|
201
|
-
? result.document
|
|
202
|
-
: stripObsoleteNotifaiPluginEnablement(result.document).document;
|
|
203
|
-
return { file, foreignStopCount, apply: preparePlan(file, next, previous) };
|
|
204
|
-
};
|
|
205
|
-
let installed;
|
|
206
|
-
let migratedOwnedInline = false;
|
|
207
|
-
let disabledPlugins = [];
|
|
208
|
-
try {
|
|
209
|
-
installed =
|
|
210
|
-
codexPaths === null
|
|
211
|
-
? withTargetFileLock(settingsTarget, () => {
|
|
212
|
-
const plan = prepareInstall(settingsTarget);
|
|
213
|
-
plan.apply();
|
|
214
|
-
return plan;
|
|
215
|
-
})
|
|
216
|
-
: withCodexLayerTransaction(codexPaths, (inspection) => {
|
|
217
|
-
const cleanup = prepareCodexPluginCleanup(inspection.paths.configToml);
|
|
218
|
-
const documentAfterCleanup = (file) => file === inspection.paths.configToml
|
|
219
|
-
? cleanup.source === null ? {} : parseToml(cleanup.source)
|
|
220
|
-
: loadSettings(file);
|
|
221
|
-
const previousAfterCleanup = (file) => file === inspection.paths.configToml ? cleanup.source : undefined;
|
|
222
|
-
const target = inspection.writeTarget;
|
|
223
|
-
const plan = prepareInstall(target, documentAfterCleanup(target), previousAfterCleanup(target));
|
|
224
|
-
const staleTarget = target === inspection.paths.hooksJson
|
|
225
|
-
? inspection.paths.configToml : inspection.paths.hooksJson;
|
|
226
|
-
const stale = removeHooks(documentAfterCleanup(staleTarget), scriptPath);
|
|
227
|
-
const removeStale = stale.replaced.length === 0 ? null : preparePlan(staleTarget, stale.document, previousAfterCleanup(staleTarget));
|
|
228
|
-
// All syntax/preservation plans are validated before writing. Retire
|
|
229
|
-
// the native plugin FIRST and never restore it on a later failure:
|
|
230
|
-
// an interruption can leave no new wiring, never two firing paths.
|
|
231
|
-
cleanup.apply();
|
|
232
|
-
disabledPlugins = cleanup.removed;
|
|
233
|
-
const before = existsSync(target) ? readFileSync(target, 'utf8') : null;
|
|
234
|
-
plan.apply();
|
|
235
|
-
const written = readFileSync(target, 'utf8');
|
|
236
|
-
try {
|
|
237
|
-
removeStale?.();
|
|
238
|
-
}
|
|
239
|
-
catch (err) {
|
|
240
|
-
// Roll back only our document write, never native enablement or
|
|
241
|
-
// an external writer's configuration.
|
|
242
|
-
if (readFileSync(target, 'utf8') === written) {
|
|
243
|
-
if (before === null)
|
|
244
|
-
rmSync(target, { force: true });
|
|
245
|
-
else
|
|
246
|
-
atomicWriteFileSync(target, before);
|
|
247
|
-
}
|
|
248
|
-
throw err;
|
|
249
|
-
}
|
|
250
|
-
migratedOwnedInline = removeStale !== null && staleTarget === inspection.paths.configToml;
|
|
251
|
-
return plan;
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
catch (err) {
|
|
255
|
-
deps.io.err(String(err));
|
|
256
|
-
return EXIT.failed;
|
|
257
|
-
}
|
|
258
|
-
if (disabledPlugins.length > 0) {
|
|
259
|
-
deps.io.out(`Disabled leftover Notifai native plugin wiring (${disabledPlugins.join(', ')}) so it cannot fire beside document hooks`);
|
|
260
|
-
}
|
|
261
|
-
if (migratedOwnedInline) {
|
|
262
|
-
deps.io.out('Moved Notifai Codex handlers from config.toml to hooks.json. Codex keys approval by source path, so open `/hooks` and approve the new handlers.');
|
|
263
|
-
}
|
|
264
|
-
if (flags.narrate !== false)
|
|
265
|
-
printHooksInstallClose(deps, harness, installed.file, beforeInstall !== installationBytes(comparedFiles));
|
|
266
|
-
if (installed.foreignStopCount > 0) {
|
|
267
|
-
const label = HARNESS_LABELS[harness];
|
|
268
|
-
deps.io.out(`This layer already has a Stop handler that Notifai does not own. ${label} may run it alongside Notifai's handler; Notifai preserves it but has not assessed its behavior.`);
|
|
269
|
-
}
|
|
270
|
-
if (harness === 'codex') {
|
|
271
|
-
for (const problem of codexRepresentationProblems(deps.env, hookPlatform)) {
|
|
272
|
-
deps.io.out(problem);
|
|
273
|
-
}
|
|
274
|
-
for (const note of codexCoexistenceNotes(deps.env, hookPlatform)) {
|
|
275
|
-
deps.io.out(note);
|
|
276
|
-
}
|
|
277
|
-
const home = codexHomeNote(deps.env, hookPlatform);
|
|
278
|
-
if (home !== null)
|
|
279
|
-
deps.io.out(home);
|
|
280
|
-
}
|
|
281
|
-
return finishInstall(deps, harness, scriptPath, EXIT.ok);
|
|
282
|
-
}
|
|
283
|
-
/**
|
|
284
|
-
* Clear this Project's leftover Notifai wiring, once the Machine installation
|
|
285
|
-
* this run just wrote is proven current.
|
|
286
|
-
*
|
|
287
|
-
* Order matters and is the whole reason this is not part of the write above: a
|
|
288
|
-
* legacy Project handler is still routing questions until the Machine one can
|
|
289
|
-
* be shown to have every required event, a sound Stop shape, and no structural
|
|
290
|
-
* problem. Removing it first would leave a window with no working route, and
|
|
291
|
-
* removing it after a failed install would leave none at all.
|
|
292
|
-
*/
|
|
293
|
-
function finishInstall(deps, harness, scriptPath, code) {
|
|
294
|
-
if (code !== EXIT.ok)
|
|
295
|
-
return code;
|
|
296
|
-
if (!machineInstallationIsCurrent(deps, harness))
|
|
297
|
-
return code;
|
|
298
|
-
removeLegacyProjectInstallations(deps, harness, scriptPath);
|
|
299
|
-
return code;
|
|
300
|
-
}
|
|
301
|
-
/** Whether exactly one complete, undamaged Machine installation is in place. */
|
|
302
|
-
function machineInstallationIsCurrent(deps, harness) {
|
|
303
|
-
const installations = findInstallations(deps.env, deps.hookAdapterHome, deps.hookPlatform).filter((installation) => installation.harness === harness);
|
|
304
|
-
if (installations.length !== 1)
|
|
305
|
-
return false;
|
|
306
|
-
const installation = installations[0];
|
|
307
|
-
if ((installation.problems?.length ?? 0) > 0)
|
|
308
|
-
return false;
|
|
309
|
-
if (stopShapeProblems(installation, deps.hookPlatform).length > 0)
|
|
310
|
-
return false;
|
|
311
|
-
const installed = new Set(installation.handlers
|
|
312
|
-
.map((handler) => handlerEvent(handler.command))
|
|
313
|
-
.filter((event) => event !== null && HOOK_EVENTS.includes(event)));
|
|
314
|
-
return requiredHookEvents(harness, deps.hookPlatform).every((event) => installed.has(event));
|
|
315
|
-
}
|
|
316
|
-
function foreignStopHandlers(document) {
|
|
317
|
-
const groups = document.hooks?.['Stop'];
|
|
318
|
-
if (!Array.isArray(groups))
|
|
319
|
-
return [];
|
|
320
|
-
return groups
|
|
321
|
-
.flatMap((group) => group.hooks ?? [])
|
|
322
|
-
.filter((handler) => !HOOK_EVENT_COMMAND_RE.test(handler.command));
|
|
323
|
-
}
|
|
324
|
-
/**
|
|
325
|
-
* Writes the OpenCode plugin, replacing any Notifai plugin already there —
|
|
326
|
-
* including one a different checkout wrote, matched on the managed marker for
|
|
327
|
-
* the same reason command hooks are.
|
|
328
|
-
*/
|
|
329
|
-
function installOpencodePlugin(deps, file, options) {
|
|
330
|
-
const beforeInstall = installationBytes([file]);
|
|
331
|
-
try {
|
|
332
|
-
withTargetFileLock(file, () => {
|
|
333
|
-
if (existsSync(file)) {
|
|
334
|
-
assertOwnedRegularFile(file);
|
|
335
|
-
const existing = readFileSync(file, 'utf8');
|
|
336
|
-
if (!isOurOpencodePlugin(existing)) {
|
|
337
|
-
throw new Error(`${file} exists and was not written by Notifai; move it aside first.`);
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
atomicWriteFileSync(file, opencodePluginSource(options), {
|
|
341
|
-
mode: 0o600,
|
|
342
|
-
preserveMode: false,
|
|
343
|
-
requireCurrentUserOwner: true,
|
|
344
|
-
});
|
|
345
|
-
});
|
|
346
|
-
}
|
|
347
|
-
catch (err) {
|
|
348
|
-
deps.io.err(String(err));
|
|
349
|
-
return EXIT.failed;
|
|
350
|
-
}
|
|
351
|
-
if (options.narrate !== false)
|
|
352
|
-
printHooksInstallClose(deps, 'opencode', file, beforeInstall !== installationBytes([file]));
|
|
353
|
-
return EXIT.ok;
|
|
354
|
-
}
|
|
355
|
-
/** Grok merges global hook files. This entire file is ours; leave foreign files untouched. */
|
|
356
|
-
function installGrokHooks(deps, file, options, narrate) {
|
|
357
|
-
const before = installationBytes([file]);
|
|
358
|
-
try {
|
|
359
|
-
withTargetFileLock(file, () => {
|
|
360
|
-
if (existsSync(file)) {
|
|
361
|
-
assertOwnedRegularFile(file);
|
|
362
|
-
if (!isOurGrokHookFile(readFileSync(file, 'utf8'))) {
|
|
363
|
-
throw new Error(`${file} exists and was not written by Notifai; move it aside first.`);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
atomicWriteFileSync(file, `${JSON.stringify({ hooks: buildHookConfig({
|
|
367
|
-
adapterPath: options.adapterPath,
|
|
368
|
-
harness: 'grok',
|
|
369
|
-
platform: options.platform,
|
|
370
|
-
nodePath: options.nodePath,
|
|
371
|
-
}) }, null, 2)}\n`, {
|
|
372
|
-
mode: 0o600,
|
|
373
|
-
preserveMode: false,
|
|
374
|
-
requireCurrentUserOwner: true,
|
|
375
|
-
});
|
|
376
|
-
});
|
|
377
|
-
}
|
|
378
|
-
catch (err) {
|
|
379
|
-
deps.io.err(String(err));
|
|
380
|
-
return EXIT.failed;
|
|
381
|
-
}
|
|
382
|
-
if (narrate !== false)
|
|
383
|
-
printHooksInstallClose(deps, 'grok', file, before !== installationBytes([file]));
|
|
384
|
-
return EXIT.ok;
|
|
385
|
-
}
|
|
386
|
-
function isOurGrokHookFile(source) {
|
|
387
|
-
let parsed;
|
|
388
|
-
try {
|
|
389
|
-
parsed = JSON.parse(source);
|
|
390
|
-
}
|
|
391
|
-
catch {
|
|
392
|
-
return false;
|
|
393
|
-
}
|
|
394
|
-
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
|
|
395
|
-
return false;
|
|
396
|
-
const document = parsed;
|
|
397
|
-
if (Object.keys(document).length !== 1 || typeof document['hooks'] !== 'object' || document['hooks'] === null || Array.isArray(document['hooks']))
|
|
398
|
-
return false;
|
|
399
|
-
const events = Object.entries(document['hooks']);
|
|
400
|
-
const knownEvents = new Set(HOOK_EVENT_TABLE.flatMap((row) => row.document === null ? [] : [row.document]));
|
|
401
|
-
return events.length > 0 && events.every(([event, groups]) => knownEvents.has(event) && Array.isArray(groups) && groups.length > 0 && groups.every((group) => typeof group === 'object' && group !== null && !Array.isArray(group) &&
|
|
402
|
-
Object.keys(group).every((key) => key === 'matcher' || key === 'hooks') &&
|
|
403
|
-
(group.matcher === undefined || typeof group.matcher === 'string') &&
|
|
404
|
-
Array.isArray(group.hooks) && group.hooks.length > 0 && group.hooks.every((handler) => isOurGrokHookHandler(handler))));
|
|
405
|
-
}
|
|
406
|
-
function isOurGrokHookHandler(handler) {
|
|
407
|
-
if (typeof handler !== 'object' || handler === null || Array.isArray(handler))
|
|
408
|
-
return false;
|
|
409
|
-
const fields = handler;
|
|
410
|
-
const command = fields['command'];
|
|
411
|
-
return Object.keys(fields).every((key) => key === 'type' || key === 'command' || key === 'timeout') &&
|
|
412
|
-
fields['type'] === 'command' && typeof command === 'string' &&
|
|
413
|
-
(fields['timeout'] === undefined || typeof fields['timeout'] === 'number') &&
|
|
414
|
-
HOOK_EVENT_COMMAND_RE.test(command) && command.includes('--owner notifai --harness grok');
|
|
415
|
-
}
|
|
416
|
-
function installOpenclawPlugin(deps, file, options) {
|
|
417
|
-
const pluginDir = path.dirname(file);
|
|
418
|
-
const comparedFiles = [file, path.join(pluginDir, OPENCLAW_PLUGIN_MANIFEST), path.join(pluginDir, OPENCLAW_PLUGIN_PACKAGE), openclawConfigPath(deps.env, deps.hookPlatform)];
|
|
419
|
-
const beforeInstall = installationBytes(comparedFiles);
|
|
420
|
-
let blockers = [];
|
|
421
|
-
try {
|
|
422
|
-
withTargetFileLock(file, () => {
|
|
423
|
-
if (existsSync(file)) {
|
|
424
|
-
assertOwnedRegularFile(file);
|
|
425
|
-
const existing = readFileSync(file, 'utf8');
|
|
426
|
-
if (!isOurOpenclawPlugin(existing)) {
|
|
427
|
-
throw new Error(`${file} exists and was not written by Notifai; move it aside first.`);
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
atomicWriteFileSync(file, openclawPluginSource(options), {
|
|
431
|
-
mode: 0o600,
|
|
432
|
-
preserveMode: false,
|
|
433
|
-
requireCurrentUserOwner: true,
|
|
434
|
-
});
|
|
435
|
-
atomicWriteFileSync(path.join(pluginDir, OPENCLAW_PLUGIN_MANIFEST), openclawPluginManifest(), {
|
|
436
|
-
mode: 0o600,
|
|
437
|
-
preserveMode: false,
|
|
438
|
-
requireCurrentUserOwner: true,
|
|
439
|
-
});
|
|
440
|
-
atomicWriteFileSync(path.join(pluginDir, OPENCLAW_PLUGIN_PACKAGE), openclawPluginPackage(), {
|
|
441
|
-
mode: 0o600,
|
|
442
|
-
preserveMode: false,
|
|
443
|
-
requireCurrentUserOwner: true,
|
|
444
|
-
});
|
|
445
|
-
});
|
|
446
|
-
blockers = writeOpenclawEnablement(deps);
|
|
447
|
-
}
|
|
448
|
-
catch (err) {
|
|
449
|
-
deps.io.err(String(err));
|
|
450
|
-
return EXIT.failed;
|
|
451
|
-
}
|
|
452
|
-
const warning = openclawPluginLoadWarning(blockers);
|
|
453
|
-
if (warning !== null)
|
|
454
|
-
deps.io.err(warning);
|
|
455
|
-
if (options.narrate !== false)
|
|
456
|
-
printHooksInstallClose(deps, 'openclaw', file, beforeInstall !== installationBytes(comparedFiles));
|
|
457
|
-
return EXIT.ok;
|
|
458
|
-
}
|
|
459
|
-
function writeOpenclawEnablement(deps) {
|
|
460
|
-
const configFile = openclawConfigPath(deps.env, deps.hookPlatform);
|
|
461
|
-
return withTargetFileLock(configFile, () => {
|
|
462
|
-
if (existsSync(configFile))
|
|
463
|
-
assertOwnedRegularFile(configFile);
|
|
464
|
-
const source = existsSync(configFile) ? readFileSync(configFile, 'utf8') : '{}\n';
|
|
465
|
-
const config = parseOpenclawConfig(source, configFile);
|
|
466
|
-
const nextSource = enableOpenclawNotifaiConfigText(source, configFile);
|
|
467
|
-
atomicWriteFileSync(configFile, nextSource, {
|
|
468
|
-
mode: 0o600,
|
|
469
|
-
preserveMode: true,
|
|
470
|
-
requireCurrentUserOwner: true,
|
|
471
|
-
});
|
|
472
|
-
return openclawPluginLoadBlockers(config);
|
|
473
|
-
});
|
|
474
|
-
}
|
|
475
|
-
function removeEmptyOpenclawPluginDirectory(pluginDir) {
|
|
476
|
-
try {
|
|
477
|
-
if (!lstatSync(pluginDir).isDirectory() || readdirSync(pluginDir).length !== 0)
|
|
478
|
-
return;
|
|
479
|
-
rmdirSync(pluginDir);
|
|
480
|
-
}
|
|
481
|
-
catch (err) {
|
|
482
|
-
const code = err.code;
|
|
483
|
-
if (code === 'ENOENT' || code === 'ENOTEMPTY' || code === 'EEXIST')
|
|
484
|
-
return;
|
|
485
|
-
throw err;
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
/**
|
|
489
|
-
* Delete a Notifai-written plugin module and everything generated beside it.
|
|
490
|
-
*
|
|
491
|
-
* Returns what happened rather than narrating, because uninstall and migration
|
|
492
|
-
* cleanup say different things about the same outcome.
|
|
493
|
-
*/
|
|
494
|
-
function removeNotifaiPluginFile(harness, file) {
|
|
495
|
-
const pluginDir = path.dirname(file);
|
|
496
|
-
const outcome = withTargetFileLock(file, () => {
|
|
497
|
-
if (!existsSync(file))
|
|
498
|
-
return 'absent';
|
|
499
|
-
assertOwnedRegularFile(file);
|
|
500
|
-
const source = readFileSync(file, 'utf8');
|
|
501
|
-
// We own the whole file, but only if we wrote it.
|
|
502
|
-
const ours = harness === 'openclaw' ? isOurOpenclawPlugin(source) : isOurOpencodePlugin(source);
|
|
503
|
-
if (!ours)
|
|
504
|
-
return 'foreign';
|
|
505
|
-
rmSync(file, { force: true });
|
|
506
|
-
if (harness === 'openclaw') {
|
|
507
|
-
rmSync(path.join(pluginDir, OPENCLAW_PLUGIN_MANIFEST), { force: true });
|
|
508
|
-
rmSync(path.join(pluginDir, OPENCLAW_PLUGIN_PACKAGE), { force: true });
|
|
509
|
-
}
|
|
510
|
-
return 'removed';
|
|
511
|
-
});
|
|
512
|
-
if (harness === 'openclaw' && outcome === 'removed') {
|
|
513
|
-
removeEmptyOpenclawPluginDirectory(pluginDir);
|
|
514
|
-
}
|
|
515
|
-
return outcome;
|
|
516
|
-
}
|
|
517
|
-
/** Apply a minimal JSONC edit to OpenClaw's config. */
|
|
518
|
-
function editOpenclawConfig(deps, edit) {
|
|
519
|
-
const configFile = openclawConfigPath(deps.env, deps.hookPlatform);
|
|
520
|
-
if (!existsSync(configFile))
|
|
521
|
-
return;
|
|
522
|
-
withTargetFileLock(configFile, () => {
|
|
523
|
-
assertOwnedRegularFile(configFile);
|
|
524
|
-
const source = readFileSync(configFile, 'utf8');
|
|
525
|
-
const next = edit(source, configFile);
|
|
526
|
-
if (next === source)
|
|
527
|
-
return;
|
|
528
|
-
atomicWriteFileSync(configFile, next, {
|
|
529
|
-
mode: 0o600,
|
|
530
|
-
preserveMode: true,
|
|
531
|
-
requireCurrentUserOwner: true,
|
|
532
|
-
});
|
|
533
|
-
});
|
|
534
|
-
}
|
|
535
|
-
/** Strip only Notifai's handlers from each settings document that exists. */
|
|
536
|
-
function stripNotifaiHandlers(files, scriptPath, cursor) {
|
|
537
|
-
const existing = files.filter((candidate) => existsSync(candidate));
|
|
538
|
-
const removed = [];
|
|
539
|
-
for (const candidate of existing) {
|
|
540
|
-
const strip = () => {
|
|
541
|
-
if (cursor) {
|
|
542
|
-
const result = removeCursorHooks(loadCursorSettings(candidate), scriptPath);
|
|
543
|
-
if (result.replaced.length > 0)
|
|
544
|
-
applyPlan(candidate, result.document);
|
|
545
|
-
return result;
|
|
546
|
-
}
|
|
547
|
-
const result = removeHooks(loadSettings(candidate), scriptPath);
|
|
548
|
-
if (isTomlSettingsPath(candidate)) {
|
|
549
|
-
if (result.replaced.length > 0)
|
|
550
|
-
applyPlan(candidate, result.document);
|
|
551
|
-
return result;
|
|
552
|
-
}
|
|
553
|
-
const cleaned = stripObsoleteNotifaiPluginEnablement(result.document);
|
|
554
|
-
if (result.replaced.length > 0 || cleaned.removed.length > 0) {
|
|
555
|
-
applyPlan(candidate, cleaned.document);
|
|
556
|
-
}
|
|
557
|
-
return result;
|
|
558
|
-
};
|
|
559
|
-
const result = withTargetFileLock(candidate, strip);
|
|
560
|
-
if (result.replaced.length > 0)
|
|
561
|
-
removed.push({ file: candidate, events: result.replaced });
|
|
562
|
-
}
|
|
563
|
-
return { existing, removed };
|
|
564
|
-
}
|
|
565
|
-
export function hooksUninstallCommand(deps, flags) {
|
|
566
|
-
const harness = resolveHarness(deps, flags.harness);
|
|
567
|
-
if (!harness)
|
|
568
|
-
return EXIT.usage;
|
|
569
|
-
if (harness === 'hermes') {
|
|
570
|
-
try {
|
|
571
|
-
deps.io.out(uninstallHermesPlugin(deps.env)
|
|
572
|
-
? 'Removed the Notifai Hermes plugin through Hermes.'
|
|
573
|
-
: 'No Notifai Hermes plugin is installed.');
|
|
574
|
-
return EXIT.ok;
|
|
575
|
-
}
|
|
576
|
-
catch (err) {
|
|
577
|
-
deps.io.err(`Could not remove Hermes plugin: ${String(err)}`);
|
|
578
|
-
return EXIT.failed;
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
const scriptPath = flags.scriptPath ?? process.argv[1] ?? 'notifai';
|
|
582
|
-
const codexPaths = harness === 'codex' ? codexMachineLayerPaths(deps.env, deps.hookPlatform) : null;
|
|
583
|
-
const file = codexPaths?.configToml ?? settingsFile(harness, deps.env, deps.hookPlatform);
|
|
584
|
-
try {
|
|
585
|
-
if (harness === 'openclaw') {
|
|
586
|
-
const configFile = openclawConfigPath(deps.env, deps.hookPlatform);
|
|
587
|
-
if (existsSync(configFile)) {
|
|
588
|
-
assertOwnedRegularFile(configFile);
|
|
589
|
-
parseOpenclawConfig(readFileSync(configFile, 'utf8'), configFile);
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
if (harness === 'grok') {
|
|
593
|
-
const outcome = withTargetFileLock(file, () => {
|
|
594
|
-
if (!existsSync(file))
|
|
595
|
-
return 'absent';
|
|
596
|
-
assertOwnedRegularFile(file);
|
|
597
|
-
if (!isOurGrokHookFile(readFileSync(file, 'utf8')))
|
|
598
|
-
return 'foreign';
|
|
599
|
-
rmSync(file, { force: true });
|
|
600
|
-
return 'removed';
|
|
601
|
-
});
|
|
602
|
-
if (outcome === 'removed')
|
|
603
|
-
deps.io.out(`Removed the Notifai Grok hooks at ${file}`);
|
|
604
|
-
else if (outcome === 'foreign')
|
|
605
|
-
deps.io.out(`Left ${file} alone: Notifai did not write it.`);
|
|
606
|
-
else
|
|
607
|
-
deps.io.out(`Nothing to remove: ${file} does not exist.`);
|
|
608
|
-
}
|
|
609
|
-
else if (harness === 'opencode' || harness === 'openclaw') {
|
|
610
|
-
const outcome = removeNotifaiPluginFile(harness, file);
|
|
611
|
-
if (outcome === 'removed') {
|
|
612
|
-
if (harness === 'openclaw') {
|
|
613
|
-
editOpenclawConfig(deps, (source, configFile) => removeOpenclawNotifaiConfigText(source, configFile, path.dirname(file)));
|
|
614
|
-
}
|
|
615
|
-
deps.io.out(`Removed the Notifai ${HARNESS_LABELS[harness]} plugin at ${file}`);
|
|
616
|
-
}
|
|
617
|
-
else if (outcome === 'foreign') {
|
|
618
|
-
deps.io.out(`Left ${file} alone: Notifai did not write it.`);
|
|
619
|
-
}
|
|
620
|
-
else {
|
|
621
|
-
deps.io.out(`Nothing to remove: ${file} does not exist.`);
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
else if (harness === 'cursor') {
|
|
625
|
-
const result = stripNotifaiHandlers([file], scriptPath, true);
|
|
626
|
-
if (result.existing.length === 0) {
|
|
627
|
-
deps.io.out(`Nothing to remove: ${file} does not exist.`);
|
|
628
|
-
}
|
|
629
|
-
else if (result.removed.length > 0) {
|
|
630
|
-
for (const entry of result.removed) {
|
|
631
|
-
deps.io.out(`Removed Notifai hooks (${entry.events.join(', ')}) from ${entry.file}`);
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
else {
|
|
635
|
-
deps.io.out(`No Notifai hooks found in ${file}`);
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
else {
|
|
639
|
-
const files = machineHookFiles(harness, deps.env, deps.hookPlatform);
|
|
640
|
-
const result = codexPaths === null
|
|
641
|
-
? stripNotifaiHandlers(files, scriptPath, false)
|
|
642
|
-
: withCodexLayerTransaction(codexPaths, (inspection) => {
|
|
643
|
-
const cleanup = prepareCodexPluginCleanup(inspection.paths.configToml);
|
|
644
|
-
const existing = files.filter((candidate) => existsSync(candidate));
|
|
645
|
-
const plans = existing.map((candidate) => {
|
|
646
|
-
const source = candidate === inspection.paths.configToml ? cleanup.source : undefined;
|
|
647
|
-
const document = source === undefined ? loadSettings(candidate)
|
|
648
|
-
: source === null ? {} : parseToml(source);
|
|
649
|
-
const result = removeHooks(document, scriptPath);
|
|
650
|
-
return {
|
|
651
|
-
file: candidate,
|
|
652
|
-
events: result.replaced,
|
|
653
|
-
apply: result.replaced.length === 0 ? () => { } : preparePlan(candidate, result.document, source),
|
|
654
|
-
};
|
|
655
|
-
});
|
|
656
|
-
cleanup.apply();
|
|
657
|
-
for (const plan of plans)
|
|
658
|
-
plan.apply();
|
|
659
|
-
cleanupEmptiedCodexLayer(inspection.paths);
|
|
660
|
-
return { existing, removed: plans.filter((plan) => plan.events.length > 0) };
|
|
661
|
-
});
|
|
662
|
-
if (result.existing.length === 0) {
|
|
663
|
-
deps.io.out(`Nothing to remove: ${file} does not exist.`);
|
|
664
|
-
}
|
|
665
|
-
else if (result.removed.length > 0) {
|
|
666
|
-
for (const entry of result.removed) {
|
|
667
|
-
deps.io.out(`Removed Notifai hooks (${entry.events.join(', ')}) from ${entry.file}`);
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
else {
|
|
671
|
-
deps.io.out(`No Notifai hooks found in ${result.existing.join(', ')}`);
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
catch (err) {
|
|
676
|
-
deps.io.err(String(err));
|
|
677
|
-
return EXIT.failed;
|
|
678
|
-
}
|
|
679
|
-
// Uninstall means no Notifai lifecycle wiring is left anywhere this Project
|
|
680
|
-
// can reach, not just in the one file the Machine installation used.
|
|
681
|
-
removeLegacyProjectInstallations(deps, harness, scriptPath);
|
|
682
|
-
const leftoverPlugin = findObsoleteNotifaiPluginWiring(deps.env, deps.hookPlatform).filter((entry) => entry.harness === harness);
|
|
683
|
-
if (leftoverPlugin.length > 0) {
|
|
684
|
-
deps.io.err(leftoverPlugin
|
|
685
|
-
.map((entry) => `Obsolete native plugin wiring still enabled (${entry.keys.join(', ')}) in ${entry.file}; document hooks and a leftover plugin must never both fire. Uninstall is not complete while that plugin remains. Disable that Notifai plugin and rerun \`notifai hooks uninstall --harness ${entry.harness}\`.`)
|
|
686
|
-
.join('\n'));
|
|
687
|
-
return EXIT.failed;
|
|
688
|
-
}
|
|
689
|
-
return EXIT.ok;
|
|
690
|
-
}
|
|
691
|
-
/**
|
|
692
|
-
* Remove Notifai's own Project-scoped definitions discoverable from this
|
|
693
|
-
* Project. Foreign handlers sharing those files are never touched, and a file
|
|
694
|
-
* Notifai did not write is never deleted.
|
|
695
|
-
*/
|
|
696
|
-
export function removeLegacyProjectInstallations(deps, harness, scriptPath) {
|
|
697
|
-
const legacy = findLegacyProjectInstallations(deps.cwd, deps.env, deps.hookAdapterHome, deps.hookPlatform).filter((installation) => installation.harness === harness);
|
|
698
|
-
if (legacy.length === 0)
|
|
699
|
-
return;
|
|
700
|
-
try {
|
|
701
|
-
if (harness === 'opencode' || harness === 'openclaw') {
|
|
702
|
-
for (const installation of legacy) {
|
|
703
|
-
if (removeNotifaiPluginFile(harness, installation.file) !== 'removed')
|
|
704
|
-
continue;
|
|
705
|
-
if (harness === 'openclaw') {
|
|
706
|
-
editOpenclawConfig(deps, (source, configFile) => removeOpenclawLoadPathConfigText(source, configFile, path.dirname(installation.file)));
|
|
707
|
-
}
|
|
708
|
-
deps.io.out(`Removed the leftover Project-scoped Notifai ${HARNESS_LABELS[harness]} plugin at ${installation.file}`);
|
|
709
|
-
}
|
|
710
|
-
return;
|
|
711
|
-
}
|
|
712
|
-
const files = legacy.map((installation) => installation.file);
|
|
713
|
-
const result = stripNotifaiHandlers(files, scriptPath, harness === 'cursor');
|
|
714
|
-
for (const entry of result.removed) {
|
|
715
|
-
deps.io.out(`Removed leftover Project-scoped Notifai hooks (${entry.events.join(', ')}) from ${entry.file}`);
|
|
716
|
-
}
|
|
717
|
-
if (harness === 'codex') {
|
|
718
|
-
for (const paths of codexLegacyProjectLayers(deps.cwd))
|
|
719
|
-
cleanupEmptiedCodexLayer(paths);
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
catch (err) {
|
|
723
|
-
deps.io.err(`Could not remove leftover Project-scoped ${harness} hooks: ${String(err)}`);
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
function assertOwnedRegularFile(file) {
|
|
727
|
-
const stat = lstatSync(file);
|
|
728
|
-
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
729
|
-
throw new Error(`${file} is not a regular file; refusing to read or replace it.`);
|
|
730
|
-
}
|
|
731
|
-
const uid = typeof process.getuid === 'function' ? process.getuid() : undefined;
|
|
732
|
-
if (uid !== undefined && stat.uid !== uid) {
|
|
733
|
-
throw new Error(`${file} is owned by uid ${stat.uid}, not the current user.`);
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
function resolveHarness(deps, requested) {
|
|
737
|
-
if (requested !== undefined) {
|
|
738
|
-
if (HOOK_INSTALLABLE_HARNESSES.includes(requested))
|
|
739
|
-
return requested;
|
|
740
|
-
deps.io.err(`Unknown harness "${requested}". Supported: ${HOOK_INSTALLABLE_HARNESSES.join(', ')}.`);
|
|
741
|
-
return null;
|
|
742
|
-
}
|
|
743
|
-
const detected = detectHarness(deps.cwd, deps.env);
|
|
744
|
-
if (!detected) {
|
|
745
|
-
deps.io.err(`Could not tell which harness to install for — pass --harness <${HOOK_INSTALLABLE_HARNESSES.join('|')}>.`);
|
|
746
|
-
return null;
|
|
747
|
-
}
|
|
748
|
-
return detected;
|
|
749
|
-
}
|
|
750
|
-
/**
|
|
751
|
-
* Which harnesses to wire. An explicit `--harness` still wins as a singleton.
|
|
752
|
-
* Otherwise: every detected harness, or a human picker when detection is empty
|
|
753
|
-
* or names more than one.
|
|
754
|
-
*/
|
|
755
|
-
export async function pickHarnessesToInstall(deps, requested) {
|
|
756
|
-
if (requested !== undefined) {
|
|
757
|
-
const harness = resolveHarness(deps, requested);
|
|
758
|
-
return harness === null ? null : [harness];
|
|
759
|
-
}
|
|
760
|
-
const detected = detectedHarnesses(deps.cwd, deps.env);
|
|
761
|
-
if (detected.length === 1)
|
|
762
|
-
return detected;
|
|
763
|
-
if (deps.io.interactive === true && deps.io.multiselect) {
|
|
764
|
-
const picked = await deps.io.multiselect('Which agent harnesses should Notifai wire here?', HOOK_INSTALLABLE_HARNESSES.map((name) => ({
|
|
765
|
-
value: name,
|
|
766
|
-
label: HARNESS_LABELS[name],
|
|
767
|
-
...(detected.includes(name) ? { hint: 'detected on this machine' } : {}),
|
|
768
|
-
})), detected);
|
|
769
|
-
if (picked === null)
|
|
770
|
-
return null;
|
|
771
|
-
const unknown = picked.filter((name) => !HOOK_INSTALLABLE_HARNESSES.includes(name));
|
|
772
|
-
if (unknown.length > 0) {
|
|
773
|
-
deps.io.err(`Unknown harness "${unknown[0]}". Supported: ${HOOK_INSTALLABLE_HARNESSES.join(', ')}.`);
|
|
774
|
-
return null;
|
|
775
|
-
}
|
|
776
|
-
return picked;
|
|
777
|
-
}
|
|
778
|
-
if (detected.length === 0) {
|
|
779
|
-
deps.io.err(`Could not tell which harness to wire. Run: notifai hooks install --harness <${HOOK_INSTALLABLE_HARNESSES.join('|')}>`);
|
|
780
|
-
return null;
|
|
781
|
-
}
|
|
782
|
-
return detected;
|
|
783
|
-
}
|
|
784
|
-
//# sourceMappingURL=commands-hook-install.js.map
|