@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.
- package/CHANGELOG.md +31 -0
- package/README.md +4 -1
- package/dist/cli-bin.d.ts +21 -1
- package/dist/cli-bin.js +141 -10
- package/dist/cli-bin.js.map +1 -1
- package/dist/cli-update-contract.d.ts +7 -0
- package/dist/cli-update-contract.js +10 -0
- package/dist/cli-update-contract.js.map +1 -0
- package/dist/client.d.ts +6 -1
- package/dist/client.js +1 -0
- package/dist/client.js.map +1 -1
- package/dist/codex-wake.d.ts +2 -0
- package/dist/codex-wake.js +2 -1
- package/dist/codex-wake.js.map +1 -1
- package/dist/commands-auth.d.ts +15 -1
- package/dist/commands-auth.js +32 -14
- package/dist/commands-auth.js.map +1 -1
- package/dist/commands-core.d.ts +10 -1
- package/dist/commands-core.js +13 -4
- package/dist/commands-core.js.map +1 -1
- package/dist/commands-devices.d.ts +10 -1
- package/dist/commands-devices.js +17 -13
- package/dist/commands-devices.js.map +1 -1
- package/dist/commands-doctor.js +158 -71
- package/dist/commands-doctor.js.map +1 -1
- package/dist/commands-harness-context.js +8 -0
- package/dist/commands-harness-context.js.map +1 -1
- package/dist/commands-hooks.js +169 -15
- package/dist/commands-hooks.js.map +1 -1
- package/dist/commands-init.d.ts +16 -0
- package/dist/commands-init.js +254 -91
- package/dist/commands-init.js.map +1 -1
- package/dist/commands-send.d.ts +1 -1
- package/dist/commands-send.js +121 -35
- package/dist/commands-send.js.map +1 -1
- package/dist/commands-update.d.ts +10 -0
- package/dist/commands-update.js +141 -0
- package/dist/commands-update.js.map +1 -0
- package/dist/commands.d.ts +2 -1
- package/dist/commands.js +2 -1
- package/dist/commands.js.map +1 -1
- package/dist/harnesses.d.ts +2 -2
- package/dist/harnesses.js +22 -2
- package/dist/harnesses.js.map +1 -1
- package/dist/hook-adapter.d.ts +2 -0
- package/dist/hook-adapter.js +12 -1
- package/dist/hook-adapter.js.map +1 -1
- package/dist/hooks.d.ts +43 -2
- package/dist/hooks.js +163 -21
- package/dist/hooks.js.map +1 -1
- package/dist/install-hooks.js +15 -9
- package/dist/install-hooks.js.map +1 -1
- package/dist/interactive.js +20 -23
- package/dist/interactive.js.map +1 -1
- package/dist/main-run.d.ts +1 -0
- package/dist/main-run.js +42 -0
- package/dist/main-run.js.map +1 -0
- package/dist/main.js +10 -31
- package/dist/main.js.map +1 -1
- package/dist/node-floor.d.ts +18 -0
- package/dist/node-floor.js +35 -0
- package/dist/node-floor.js.map +1 -0
- package/dist/openclaw-plugin.d.ts +49 -0
- package/dist/openclaw-plugin.js +350 -0
- package/dist/openclaw-plugin.js.map +1 -0
- package/dist/program.d.ts +2 -1
- package/dist/program.js +31 -13
- package/dist/program.js.map +1 -1
- package/dist/question-settlement-process.d.ts +15 -0
- package/dist/question-settlement-process.js +35 -0
- package/dist/question-settlement-process.js.map +1 -0
- package/dist/readiness.d.ts +25 -1
- package/dist/readiness.js +32 -2
- package/dist/readiness.js.map +1 -1
- package/dist/session-activation.js +1 -1
- package/dist/session-activation.js.map +1 -1
- package/dist/setup-destinations.d.ts +23 -0
- package/dist/setup-destinations.js +44 -0
- package/dist/setup-destinations.js.map +1 -0
- package/dist/ui/help.d.ts +4 -5
- package/dist/ui/help.js +5 -6
- package/dist/ui/help.js.map +1 -1
- package/package.json +2 -2
- package/src/cli-bin.test.ts +145 -7
- package/src/cli-bin.ts +176 -10
- package/src/cli-update-contract.ts +9 -0
- package/src/cli-update.test.ts +125 -0
- package/src/client.ts +7 -0
- package/src/codex-wake.ts +5 -1
- package/src/commands-auth.ts +47 -13
- package/src/commands-core.ts +21 -4
- package/src/commands-devices.ts +23 -16
- package/src/commands-doctor.ts +197 -75
- package/src/commands-harness-context.ts +8 -0
- package/src/commands-hooks.ts +210 -18
- package/src/commands-init.ts +275 -92
- package/src/commands-send.ts +145 -39
- package/src/commands-update.ts +180 -0
- package/src/commands.test.ts +701 -53
- package/src/commands.ts +2 -0
- package/src/fixtures/openclaw-2026.7.1-2-agent-local.json +57 -0
- package/src/harness-integration.test.ts +82 -0
- package/src/harnesses.test.ts +9 -1
- package/src/harnesses.ts +24 -2
- package/src/hook-adapter.ts +14 -0
- package/src/hooks.test.ts +275 -0
- package/src/hooks.ts +270 -18
- package/src/install-hooks.test.ts +208 -0
- package/src/install-hooks.ts +19 -9
- package/src/interactive.test.ts +31 -0
- package/src/interactive.ts +20 -26
- package/src/main-run.ts +45 -0
- package/src/main.ts +9 -33
- package/src/node-floor.test.ts +27 -0
- package/src/node-floor.ts +37 -0
- package/src/openclaw-plugin.ts +409 -0
- package/src/program.test.ts +35 -0
- package/src/program.ts +37 -12
- package/src/question-settlement-process.ts +47 -0
- package/src/readiness.ts +35 -2
- package/src/session-activation.ts +1 -1
- package/src/setup-destinations.test.ts +41 -0
- package/src/setup-destinations.ts +50 -0
- package/src/skill.test.ts +13 -2
- package/src/ui/help.test.ts +16 -0
- package/src/ui/help.ts +6 -7
- package/src/commands-devices.test.ts +0 -15
package/src/commands-send.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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(
|
|
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
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
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
|
-
`
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
759
|
-
|
|
760
|
-
|
|
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
|
+
}
|