@raidiant/notifai 10.0.0 → 10.1.1

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.
Files changed (127) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +4 -1
  3. package/dist/cli-bin.d.ts +21 -1
  4. package/dist/cli-bin.js +141 -10
  5. package/dist/cli-bin.js.map +1 -1
  6. package/dist/cli-update-contract.d.ts +7 -0
  7. package/dist/cli-update-contract.js +10 -0
  8. package/dist/cli-update-contract.js.map +1 -0
  9. package/dist/client.d.ts +6 -1
  10. package/dist/client.js +1 -0
  11. package/dist/client.js.map +1 -1
  12. package/dist/codex-wake.d.ts +2 -0
  13. package/dist/codex-wake.js +2 -1
  14. package/dist/codex-wake.js.map +1 -1
  15. package/dist/commands-auth.d.ts +15 -1
  16. package/dist/commands-auth.js +32 -14
  17. package/dist/commands-auth.js.map +1 -1
  18. package/dist/commands-core.d.ts +10 -1
  19. package/dist/commands-core.js +13 -4
  20. package/dist/commands-core.js.map +1 -1
  21. package/dist/commands-devices.d.ts +10 -1
  22. package/dist/commands-devices.js +17 -13
  23. package/dist/commands-devices.js.map +1 -1
  24. package/dist/commands-doctor.js +158 -71
  25. package/dist/commands-doctor.js.map +1 -1
  26. package/dist/commands-harness-context.js +8 -0
  27. package/dist/commands-harness-context.js.map +1 -1
  28. package/dist/commands-hooks.js +169 -15
  29. package/dist/commands-hooks.js.map +1 -1
  30. package/dist/commands-init.d.ts +16 -0
  31. package/dist/commands-init.js +254 -91
  32. package/dist/commands-init.js.map +1 -1
  33. package/dist/commands-send.d.ts +1 -1
  34. package/dist/commands-send.js +121 -35
  35. package/dist/commands-send.js.map +1 -1
  36. package/dist/commands-update.d.ts +10 -0
  37. package/dist/commands-update.js +141 -0
  38. package/dist/commands-update.js.map +1 -0
  39. package/dist/commands.d.ts +2 -1
  40. package/dist/commands.js +2 -1
  41. package/dist/commands.js.map +1 -1
  42. package/dist/harnesses.d.ts +2 -2
  43. package/dist/harnesses.js +22 -2
  44. package/dist/harnesses.js.map +1 -1
  45. package/dist/hook-adapter.d.ts +2 -0
  46. package/dist/hook-adapter.js +12 -1
  47. package/dist/hook-adapter.js.map +1 -1
  48. package/dist/hooks.d.ts +43 -2
  49. package/dist/hooks.js +163 -21
  50. package/dist/hooks.js.map +1 -1
  51. package/dist/install-hooks.js +15 -9
  52. package/dist/install-hooks.js.map +1 -1
  53. package/dist/interactive.js +20 -23
  54. package/dist/interactive.js.map +1 -1
  55. package/dist/main-run.d.ts +1 -0
  56. package/dist/main-run.js +42 -0
  57. package/dist/main-run.js.map +1 -0
  58. package/dist/main.js +10 -31
  59. package/dist/main.js.map +1 -1
  60. package/dist/node-floor.d.ts +18 -0
  61. package/dist/node-floor.js +35 -0
  62. package/dist/node-floor.js.map +1 -0
  63. package/dist/openclaw-plugin.d.ts +49 -0
  64. package/dist/openclaw-plugin.js +350 -0
  65. package/dist/openclaw-plugin.js.map +1 -0
  66. package/dist/program.d.ts +2 -1
  67. package/dist/program.js +31 -13
  68. package/dist/program.js.map +1 -1
  69. package/dist/question-settlement-process.d.ts +15 -0
  70. package/dist/question-settlement-process.js +35 -0
  71. package/dist/question-settlement-process.js.map +1 -0
  72. package/dist/readiness.d.ts +25 -1
  73. package/dist/readiness.js +32 -2
  74. package/dist/readiness.js.map +1 -1
  75. package/dist/session-activation.js +1 -1
  76. package/dist/session-activation.js.map +1 -1
  77. package/dist/setup-destinations.d.ts +23 -0
  78. package/dist/setup-destinations.js +44 -0
  79. package/dist/setup-destinations.js.map +1 -0
  80. package/dist/ui/help.d.ts +4 -5
  81. package/dist/ui/help.js +5 -6
  82. package/dist/ui/help.js.map +1 -1
  83. package/package.json +2 -2
  84. package/src/cli-bin.test.ts +145 -7
  85. package/src/cli-bin.ts +176 -10
  86. package/src/cli-update-contract.ts +9 -0
  87. package/src/cli-update.test.ts +125 -0
  88. package/src/client.ts +7 -0
  89. package/src/codex-wake.ts +5 -1
  90. package/src/commands-auth.ts +47 -13
  91. package/src/commands-core.ts +21 -4
  92. package/src/commands-devices.ts +23 -16
  93. package/src/commands-doctor.ts +197 -75
  94. package/src/commands-harness-context.ts +8 -0
  95. package/src/commands-hooks.ts +210 -18
  96. package/src/commands-init.ts +275 -92
  97. package/src/commands-send.ts +145 -39
  98. package/src/commands-update.ts +180 -0
  99. package/src/commands.test.ts +701 -53
  100. package/src/commands.ts +2 -0
  101. package/src/fixtures/openclaw-2026.7.1-2-agent-local.json +57 -0
  102. package/src/harness-integration.test.ts +82 -0
  103. package/src/harnesses.test.ts +9 -1
  104. package/src/harnesses.ts +24 -2
  105. package/src/hook-adapter.ts +14 -0
  106. package/src/hooks.test.ts +275 -0
  107. package/src/hooks.ts +270 -18
  108. package/src/install-hooks.test.ts +208 -0
  109. package/src/install-hooks.ts +19 -9
  110. package/src/interactive.test.ts +31 -0
  111. package/src/interactive.ts +20 -26
  112. package/src/main-run.ts +45 -0
  113. package/src/main.ts +9 -33
  114. package/src/node-floor.test.ts +27 -0
  115. package/src/node-floor.ts +37 -0
  116. package/src/openclaw-plugin.ts +409 -0
  117. package/src/program.test.ts +35 -0
  118. package/src/program.ts +37 -12
  119. package/src/question-settlement-process.ts +47 -0
  120. package/src/readiness.ts +35 -2
  121. package/src/session-activation.ts +1 -1
  122. package/src/setup-destinations.test.ts +41 -0
  123. package/src/setup-destinations.ts +50 -0
  124. package/src/skill.test.ts +13 -2
  125. package/src/ui/help.test.ts +16 -0
  126. package/src/ui/help.ts +6 -7
  127. package/src/commands-devices.test.ts +0 -15
@@ -2,13 +2,19 @@ import {
2
2
  CAPABILITIES_V1,
3
3
  REPLY_MAX_WINDOW_SECONDS,
4
4
  validateDraft,
5
+ type EvidenceSnapshot,
5
6
  type ListRepliesResponse,
6
7
  type ReplyView,
7
8
  type SubmissionReceipt,
8
9
  } from '@raidiant/notifai-protocol'
9
10
  import { ApiCallError, NetworkError } from './client.js'
10
11
  import type { FlagOverrides, loadConfig } from './config.js'
11
- import { MIN_REPLY_WINDOW_SECONDS, readSessionState } from './hooks.js'
12
+ import {
13
+ MIN_REPLY_WINDOW_SECONDS,
14
+ inspectQuestionState,
15
+ readSessionState,
16
+ type QuestionStateView,
17
+ } from './hooks.js'
12
18
  import { enableProject, projectBinding } from './project-enablement.js'
13
19
  import {
14
20
  buildDraft,
@@ -702,63 +708,163 @@ function printNoReply(deps: CommandDeps, requestId: string, expiresAt?: string |
702
708
 
703
709
  export async function statusCommand(
704
710
  deps: CommandDeps,
705
- requestId: string,
711
+ id: string,
706
712
  flags: { json?: boolean },
707
713
  ): Promise<number> {
714
+ const local = id.startsWith('q_') ? inspectQuestionState(id, deps.env) : null
715
+ if (local?.found === true) {
716
+ return questionStatusCommand(deps, local.session_id, local.question, flags)
717
+ }
718
+ if (local?.found === false) {
719
+ deps.io.err(
720
+ local.ambiguous
721
+ ? `Question ${id} appears in more than one local Agent Session; refusing to guess.`
722
+ : `No local registration state exists for ${id}. Inspect or close the original identity instead of registering a replacement.`,
723
+ )
724
+ return EXIT.failed
725
+ }
726
+
708
727
  const config = loadLoggedConfig(deps, { cwd: deps.cwd, env: deps.env })
709
728
  const authed = authedClient(deps, config)
710
729
  if (!authed) return EXIT.auth
711
730
  try {
712
- const snapshot = await authed.client.evidence(requestId)
731
+ const snapshot = await authed.client.evidence(id)
713
732
  recordObservedDeliveryProof(deps, snapshot, setupProofProject(deps, config.project.value))
714
733
  if (flags.json) {
715
734
  deps.io.out(JSON.stringify(snapshot, null, 2))
716
735
  return EXIT.ok
717
736
  }
718
- deps.io.out(`request ${snapshot.request_id} — ${snapshot.overall}`)
719
- let anyReplyReceived = false
720
- for (const d of snapshot.deliveries) {
721
- deps.io.out(` ${d.device_name}:`)
722
- deps.io.out(` Delivery: ${d.state} after ${d.attempts} attempt(s)`)
737
+ printEvidenceSnapshot(deps, snapshot)
738
+ return EXIT.ok
739
+ } catch (err) {
740
+ return reportError(deps, err)
741
+ }
742
+ }
743
+
744
+ async function questionStatusCommand(
745
+ deps: CommandDeps,
746
+ sessionId: string,
747
+ local: QuestionStateView,
748
+ flags: { json?: boolean },
749
+ ): Promise<number> {
750
+ if (local.request_id === null) {
751
+ const output = questionStatusOutput(local, null)
752
+ if (flags.json) deps.io.out(JSON.stringify(output, null, 2))
753
+ else printLocalQuestionStatus(deps, local)
754
+ return EXIT.ok
755
+ }
756
+
757
+ const config = loadLoggedConfig(deps, { cwd: deps.cwd, env: deps.env, sessionId })
758
+ const authed = authedClient(deps, config)
759
+ if (!authed) return EXIT.auth
760
+ try {
761
+ const snapshot = await authed.client.evidence(local.request_id)
762
+ recordObservedDeliveryProof(deps, snapshot, setupProofProject(deps, config.project.value))
763
+ const answered = snapshot.deliveries.some((delivery) =>
764
+ delivery.events.some((event) => event.stage === 'reply_received'),
765
+ )
766
+ const question = answered && local.state === 'live'
767
+ ? { ...local, state: 'answered' as const }
768
+ : local
769
+ if (flags.json) {
770
+ deps.io.out(JSON.stringify(questionStatusOutput(question, snapshot), null, 2))
771
+ return EXIT.ok
772
+ }
773
+ deps.io.out(`question ${question.question_id} — ${question.state} as ${question.request_id}`)
774
+ printEvidenceSnapshot(deps, snapshot)
775
+ deps.io.out(
776
+ ` Recovery: inspect or close ${question.question_id}; do not register a replacement for the same question.`,
777
+ )
778
+ return EXIT.ok
779
+ } catch (err) {
780
+ return reportError(deps, err, { question_id: local.question_id, request_id: local.request_id })
781
+ }
782
+ }
783
+
784
+ function questionStatusOutput(local: QuestionStateView, evidence: EvidenceSnapshot | null) {
785
+ const receipt = evidence === null
786
+ ? 'not_available'
787
+ : evidence.deliveries.some((delivery) => delivery.companion_receipt.state === 'observed')
788
+ ? 'observed'
789
+ : 'unknown'
790
+ return {
791
+ question_id: local.question_id,
792
+ state: local.state,
793
+ submitted: local.submitted,
794
+ request_id: local.request_id,
795
+ frozen_request_id: local.frozen_request_id,
796
+ provider_acceptance: evidence?.overall ?? 'not_available',
797
+ companion_receipt: receipt,
798
+ evidence,
799
+ recovery: {
800
+ inspect: `notifai status ${local.question_id}`,
801
+ close: `notifai close ${local.question_id}`,
802
+ register_replacement: false,
803
+ },
804
+ }
805
+ }
806
+
807
+ function printLocalQuestionStatus(deps: CommandDeps, local: QuestionStateView): void {
808
+ deps.io.out(`question ${local.question_id} — ${local.state}`)
809
+ if (local.state === 'local') {
810
+ deps.io.out(' Submission: not submitted — this is only a local registration until question settlement runs')
811
+ deps.io.out(' Provider Acceptance: not available — no Notification Request exists yet')
812
+ } else if (local.state === 'frozen') {
813
+ deps.io.out(
814
+ ` Submission: frozen under reserved identity ${local.frozen_request_id}; service acceptance is not proven locally`,
815
+ )
816
+ deps.io.out(' Provider Acceptance: not available until the frozen identity is promoted locally')
817
+ } else if (local.state === 'withdrawn') {
818
+ deps.io.out(' Submission: not submitted — the local registration was withdrawn')
819
+ deps.io.out(' Provider Acceptance: not available — no Notification Request was created')
820
+ } else {
821
+ deps.io.out(' Submission: retired before local promotion; service acceptance is not proven locally')
822
+ deps.io.out(' Provider Acceptance: not available')
823
+ }
824
+ deps.io.out(
825
+ ` Recovery: inspect or close ${local.question_id}; do not register a replacement for the same question.`,
826
+ )
827
+ }
828
+
829
+ function printEvidenceSnapshot(deps: CommandDeps, snapshot: EvidenceSnapshot): void {
830
+ deps.io.out(`request ${snapshot.request_id} — ${snapshot.overall}`)
831
+ let anyReplyReceived = false
832
+ for (const d of snapshot.deliveries) {
833
+ deps.io.out(` ${d.device_name}:`)
834
+ deps.io.out(` Delivery: ${d.state} after ${d.attempts} attempt(s)`)
835
+ deps.io.out(
836
+ ` Provider Acceptance: ${d.state === 'provider_accepted' ? 'accepted' : 'not recorded'}`,
837
+ )
838
+ if (d.companion_receipt.state === 'observed') {
839
+ const latency = d.companion_receipt.latency_ms
723
840
  deps.io.out(
724
- ` Provider Acceptance: ${d.state === 'provider_accepted' ? 'accepted' : 'not recorded'}`,
841
+ ` Companion Receipt (the app's delivery confirmation): observed at ${d.companion_receipt.observed_at}` +
842
+ (latency === null ? '' : ` (${formatElapsed(latency)} after Provider Acceptance)`),
725
843
  )
726
- if (d.companion_receipt.state === 'observed') {
727
- const latency = d.companion_receipt.latency_ms
728
- deps.io.out(
729
- ` Companion Receipt (the app's delivery confirmation): observed at ${d.companion_receipt.observed_at}` +
730
- (latency === null ? '' : ` (${formatElapsed(latency)} after Provider Acceptance)`),
731
- )
732
- } else {
733
- deps.io.out(
734
- " Companion Receipt (the app's delivery confirmation): unknown — not observed; this is not a failure or proof of non-receipt",
735
- )
736
- }
737
- // Notifai never learns whether the OS painted a banner; saying so stops
738
- // a reply-wait fault from being misread as "the phone never showed it".
844
+ } else {
739
845
  deps.io.out(
740
- ' OS presentation: not observed by Notifai — Provider Acceptance and Companion Receipt do not prove a banner was shown',
846
+ " Companion Receipt (the app's delivery confirmation): unknown — not observed; this is not a failure or proof of non-receipt",
741
847
  )
742
- const replyEvent = d.events.find((e) => e.stage === 'reply_received')
743
- if (replyEvent) {
744
- anyReplyReceived = true
745
- deps.io.out(` Reply received: yes (first at ${replyEvent.occurred_at})`)
746
- } else {
747
- deps.io.out(' Reply received: not yet recorded on this delivery')
748
- }
749
- for (const e of d.events) {
750
- deps.io.out(` ${e.occurred_at} ${e.stage}${e.reason ? ` (${e.reason})` : ''}`)
751
- }
752
848
  }
753
849
  deps.io.out(
754
- anyReplyReceived
755
- ? ` Reply wait: answers are on the server — collect with \`notifai replies ${snapshot.request_id}\` (a local wait fault does not erase them)`
756
- : ` Reply wait: no answer stored yet — a blocking wait failure is independent of Delivery above; retry with \`notifai replies ${snapshot.request_id}\``,
850
+ ' OS presentation: not observed by Notifai — Provider Acceptance and Companion Receipt do not prove a banner was shown',
757
851
  )
758
- return EXIT.ok
759
- } catch (err) {
760
- return reportError(deps, err)
852
+ const replyEvent = d.events.find((e) => e.stage === 'reply_received')
853
+ if (replyEvent) {
854
+ anyReplyReceived = true
855
+ deps.io.out(` Reply received: yes (first at ${replyEvent.occurred_at})`)
856
+ } else {
857
+ deps.io.out(' Reply received: not yet recorded on this delivery')
858
+ }
859
+ for (const e of d.events) {
860
+ deps.io.out(` ${e.occurred_at} ${e.stage}${e.reason ? ` (${e.reason})` : ''}`)
861
+ }
761
862
  }
863
+ deps.io.out(
864
+ anyReplyReceived
865
+ ? ` Reply wait: answers are on the server — collect with \`notifai replies ${snapshot.request_id}\` (a local wait fault does not erase them)`
866
+ : ` Reply wait: no answer stored yet — a blocking wait failure is independent of Delivery above; retry with \`notifai replies ${snapshot.request_id}\``,
867
+ )
762
868
  }
763
869
 
764
870
  function formatElapsed(milliseconds: number): string {
@@ -0,0 +1,180 @@
1
+ import { spawnSync } from 'node:child_process'
2
+ import { existsSync, realpathSync } from 'node:fs'
3
+ import path from 'node:path'
4
+ import {
5
+ inspectCliInstallations,
6
+ type CliInstallationInspection,
7
+ } from './cli-bin.js'
8
+ import { EXIT, type CommandDeps } from './commands-core.js'
9
+ import {
10
+ hookAdapterPath,
11
+ installHookAdapter,
12
+ inspectHookAdapter,
13
+ isNpxAdapterTarget,
14
+ } from './hook-adapter.js'
15
+ import { packageVersion } from './release.js'
16
+ import { compareCliSemVer } from './cli-release.js'
17
+
18
+ export interface CliUpdateFlags {
19
+ json?: boolean
20
+ }
21
+
22
+ function runningArtifact(deps: CommandDeps): string | undefined {
23
+ const target = deps.hookInstallTarget
24
+ if (target !== undefined && !isNpxAdapterTarget(target)) return target.scriptPath
25
+ return process.argv[1]
26
+ }
27
+
28
+ function npmRun(
29
+ deps: CommandDeps,
30
+ args: readonly string[],
31
+ ): ReturnType<typeof spawnSync> {
32
+ const npmCli = deps.env['npm_execpath']
33
+ if (typeof npmCli === 'string' && npmCli !== '') {
34
+ return spawnSync(process.execPath, [npmCli, ...args], {
35
+ encoding: 'utf8',
36
+ env: deps.env,
37
+ windowsHide: true,
38
+ })
39
+ }
40
+ return spawnSync((deps.hookPlatform ?? process.platform) === 'win32' ? 'npm.cmd' : 'npm', args, {
41
+ encoding: 'utf8',
42
+ env: deps.env,
43
+ windowsHide: true,
44
+ })
45
+ }
46
+
47
+ function inspection(deps: CommandDeps): CliInstallationInspection {
48
+ const artifact = runningArtifact(deps)
49
+ return inspectCliInstallations(
50
+ deps.env,
51
+ deps.hookPlatform ?? process.platform,
52
+ {
53
+ ...(artifact === undefined ? {} : { runningArtifactPath: artifact }),
54
+ currentVersion: packageVersion(),
55
+ },
56
+ )
57
+ }
58
+
59
+ function sameLocalPath(left: string, right: string): boolean {
60
+ const canonical = (value: string) => {
61
+ try {
62
+ return realpathSync(value)
63
+ } catch {
64
+ return path.resolve(value)
65
+ }
66
+ }
67
+ return canonical(left) === canonical(right)
68
+ }
69
+
70
+ function prefixIsAddressable(deps: CommandDeps, prefix: string): boolean {
71
+ const platform = deps.hookPlatform ?? process.platform
72
+ const raw = platform === 'win32' ? (deps.env['Path'] ?? deps.env['PATH'] ?? '') : (deps.env['PATH'] ?? '')
73
+ const delimiter = platform === 'win32' ? ';' : ':'
74
+ const commandDirectory = platform === 'win32' ? prefix : path.join(prefix, 'bin')
75
+ return raw.split(delimiter).some((directory) => directory !== '' && sameLocalPath(directory, commandDirectory))
76
+ }
77
+
78
+ function failed(
79
+ deps: CommandDeps,
80
+ flags: CliUpdateFlags,
81
+ code: string,
82
+ before: CliInstallationInspection,
83
+ packageManagerPrefix: string | null,
84
+ ): number {
85
+ if (flags.json === true) {
86
+ deps.io.out(JSON.stringify({ ok: false, code, package_manager_prefix: packageManagerPrefix, before }, null, 2))
87
+ } else {
88
+ deps.io.err('Notifai could not safely update the command that wins PATH. Run `notifai doctor --json` for the local installation details.')
89
+ }
90
+ return EXIT.failed
91
+ }
92
+
93
+ /**
94
+ * Update the npm-global installation the shell actually resolves, then point
95
+ * the stable hook adapter at that same artifact. The npm executable may own a
96
+ * different global prefix; --prefix makes that ambient choice irrelevant.
97
+ */
98
+ export function cliUpdateCommand(deps: CommandDeps, flags: CliUpdateFlags): number {
99
+ const before = inspection(deps)
100
+ const prefixResult = npmRun(deps, ['prefix', '--global'])
101
+ const packageManagerPrefix =
102
+ prefixResult.status === 0 && typeof prefixResult.stdout === 'string' && prefixResult.stdout.trim() !== ''
103
+ ? prefixResult.stdout.trim()
104
+ : null
105
+ const pathCandidate = before.effective ?? before.entries[0] ?? null
106
+ const targetPrefix = pathCandidate === null
107
+ ? packageManagerPrefix
108
+ : pathCandidate.install_prefix
109
+ if (targetPrefix === null) {
110
+ return failed(deps, flags, 'update_destination_unknown', before, packageManagerPrefix)
111
+ }
112
+ if (pathCandidate === null && !prefixIsAddressable(deps, targetPrefix)) {
113
+ return failed(deps, flags, 'package_manager_prefix_not_on_path', before, packageManagerPrefix)
114
+ }
115
+
116
+ const install = npmRun(deps, [
117
+ 'install',
118
+ '--global',
119
+ '--prefix',
120
+ targetPrefix,
121
+ '@raidiant/notifai',
122
+ ])
123
+ if (install.status !== 0) {
124
+ return failed(deps, flags, 'package_install_failed', before, packageManagerPrefix)
125
+ }
126
+
127
+ const after = inspection(deps)
128
+ const effective = after.effective
129
+ if (
130
+ effective === null ||
131
+ effective.artifact_path === null ||
132
+ effective.version === null ||
133
+ (before.effective !== null && effective.command_path !== before.effective.command_path)
134
+ ) {
135
+ return failed(deps, flags, 'effective_command_not_repaired', before, packageManagerPrefix)
136
+ }
137
+ const minimumCurrent = before.current.version
138
+ const currentComparison = minimumCurrent === null
139
+ ? null
140
+ : compareCliSemVer(effective.version, minimumCurrent)
141
+ if (
142
+ currentComparison !== null &&
143
+ currentComparison < 0
144
+ ) {
145
+ return failed(deps, flags, 'effective_command_still_older', before, packageManagerPrefix)
146
+ }
147
+
148
+ let adapterRetargeted = false
149
+ const adapter = hookAdapterPath(deps.hookAdapterHome)
150
+ if (existsSync(adapter)) {
151
+ try {
152
+ adapterRetargeted = installHookAdapter(
153
+ { execPath: process.execPath, scriptPath: effective.artifact_path },
154
+ deps.hookAdapterHome,
155
+ deps.hookPlatform ?? process.platform,
156
+ ).changed
157
+ } catch {
158
+ return failed(deps, flags, 'hook_adapter_retarget_failed', before, packageManagerPrefix)
159
+ }
160
+ }
161
+
162
+ if (flags.json === true) {
163
+ const adapterInspection = existsSync(adapter)
164
+ ? inspectHookAdapter(deps.hookAdapterHome, deps.hookPlatform ?? process.platform)
165
+ : null
166
+ deps.io.out(JSON.stringify({
167
+ ok: true,
168
+ package_manager_prefix: packageManagerPrefix,
169
+ update_prefix: targetPrefix,
170
+ before,
171
+ after,
172
+ hook_adapter: adapterInspection === null
173
+ ? null
174
+ : { path: adapterInspection.path, target: adapterInspection.target, retargeted: adapterRetargeted },
175
+ }, null, 2))
176
+ } else {
177
+ deps.io.out('Notifai is updated. Re-run `notifai init` to continue setup.')
178
+ }
179
+ return EXIT.ok
180
+ }