@raidiant/notifai 10.1.1 → 10.1.3
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 +35 -0
- package/dist/commands-core.d.ts +4 -1
- package/dist/commands-core.js.map +1 -1
- package/dist/commands-doctor.js +2 -7
- package/dist/commands-doctor.js.map +1 -1
- package/dist/commands-hooks.d.ts +12 -0
- package/dist/commands-hooks.js +15 -1
- package/dist/commands-hooks.js.map +1 -1
- package/dist/commands-init.js +9 -6
- package/dist/commands-init.js.map +1 -1
- package/dist/commands-send-support.js +10 -11
- package/dist/commands-send-support.js.map +1 -1
- package/dist/commands-skill.js +72 -54
- package/dist/commands-skill.js.map +1 -1
- package/dist/harness-session-title.d.ts +22 -0
- package/dist/harness-session-title.js +74 -0
- package/dist/harness-session-title.js.map +1 -0
- package/dist/invocation-context.d.ts +1 -0
- package/dist/invocation-context.js +15 -1
- package/dist/invocation-context.js.map +1 -1
- package/dist/native-skills.d.ts +13 -3
- package/dist/native-skills.js +51 -12
- package/dist/native-skills.js.map +1 -1
- package/dist/node-floor.d.ts +2 -1
- package/dist/node-floor.js +9 -5
- package/dist/node-floor.js.map +1 -1
- package/dist/orca-session-title.d.ts +5 -4
- package/dist/orca-session-title.js +32 -19
- package/dist/orca-session-title.js.map +1 -1
- package/dist/platform.d.ts +11 -17
- package/dist/platform.js +20 -23
- package/dist/platform.js.map +1 -1
- package/dist/release.d.ts +5 -5
- package/dist/release.js +5 -5
- package/dist/session-labels.d.ts +2 -0
- package/dist/session-labels.js +20 -9
- package/dist/session-labels.js.map +1 -1
- package/dist/setup-destinations.js +1 -1
- package/dist/setup-destinations.js.map +1 -1
- package/dist/skill-integrity.d.ts +49 -0
- package/dist/skill-integrity.js +154 -0
- package/dist/skill-integrity.js.map +1 -0
- package/dist/skill-source/manifest.json +16 -0
- package/dist/skill-source/notifai/SKILL.md +390 -0
- package/dist/skill-source/notifai/references/harness-setup.md +220 -0
- package/package.json +11 -14
- package/src/atomic-file.ts +0 -198
- package/src/claude-wake.test.ts +0 -321
- package/src/claude-wake.ts +0 -499
- package/src/cli-bin.test.ts +0 -176
- package/src/cli-bin.ts +0 -233
- package/src/cli-release.test.ts +0 -46
- package/src/cli-release.ts +0 -78
- package/src/cli-update-contract.ts +0 -9
- package/src/cli-update.test.ts +0 -125
- package/src/client.test.ts +0 -390
- package/src/client.ts +0 -352
- package/src/codex-wake.test.ts +0 -254
- package/src/codex-wake.ts +0 -301
- package/src/command-session.ts +0 -38
- package/src/commands-agent-sessions.ts +0 -72
- package/src/commands-auth.ts +0 -238
- package/src/commands-config.ts +0 -336
- package/src/commands-core.ts +0 -312
- package/src/commands-devices.ts +0 -180
- package/src/commands-doctor.ts +0 -1501
- package/src/commands-guidance.ts +0 -181
- package/src/commands-harness-context.ts +0 -207
- package/src/commands-hook-shape.ts +0 -69
- package/src/commands-hooks.ts +0 -2318
- package/src/commands-init.ts +0 -1055
- package/src/commands-io.ts +0 -108
- package/src/commands-logs.ts +0 -243
- package/src/commands-project.ts +0 -47
- package/src/commands-send-support.test.ts +0 -156
- package/src/commands-send-support.ts +0 -311
- package/src/commands-send.ts +0 -875
- package/src/commands-setup-proof.test.ts +0 -72
- package/src/commands-setup-proof.ts +0 -124
- package/src/commands-skill.test.ts +0 -44
- package/src/commands-skill.ts +0 -204
- package/src/commands-sounds.test.ts +0 -106
- package/src/commands-sounds.ts +0 -49
- package/src/commands-update.ts +0 -180
- package/src/commands.test.ts +0 -8297
- package/src/commands.ts +0 -77
- package/src/config-schema.test.ts +0 -153
- package/src/config-schema.ts +0 -349
- package/src/config.test.ts +0 -581
- package/src/config.ts +0 -475
- package/src/credentials.test.ts +0 -403
- package/src/credentials.ts +0 -403
- package/src/file-lock-process-worker.test.ts +0 -64
- package/src/file-lock.test.ts +0 -320
- package/src/file-lock.ts +0 -393
- package/src/fixtures/hermes-0.20.6-classic-cli-local.json +0 -50
- package/src/fixtures/openclaw-2026.7.1-2-agent-local.json +0 -57
- package/src/generated-session-label.ts +0 -155
- package/src/guidance-content.ts +0 -278
- package/src/guidance-render.ts +0 -56
- package/src/guidance.test.ts +0 -327
- package/src/guidance.ts +0 -233
- package/src/harness-integration.test.ts +0 -294
- package/src/harnesses.test.ts +0 -149
- package/src/harnesses.ts +0 -164
- package/src/hook-adapter.test.ts +0 -496
- package/src/hook-adapter.ts +0 -424
- package/src/hook-input.test.ts +0 -39
- package/src/hook-input.ts +0 -72
- package/src/hooks.test.ts +0 -5824
- package/src/hooks.ts +0 -4011
- package/src/install-hooks.test.ts +0 -1614
- package/src/install-hooks.ts +0 -1501
- package/src/interactive.test.ts +0 -387
- package/src/interactive.ts +0 -858
- package/src/invocation-context.test.ts +0 -352
- package/src/invocation-context.ts +0 -197
- package/src/logging-process-worker.test.ts +0 -45
- package/src/logging.test.ts +0 -544
- package/src/logging.ts +0 -698
- package/src/main-run.ts +0 -45
- package/src/main.ts +0 -12
- package/src/native-skills.test.ts +0 -115
- package/src/native-skills.ts +0 -162
- package/src/node-floor.test.ts +0 -27
- package/src/node-floor.ts +0 -37
- package/src/onboarding-docs.test.ts +0 -25
- package/src/openclaw-plugin.ts +0 -409
- package/src/opencode-plugin.ts +0 -298
- package/src/orca-session-title.test.ts +0 -54
- package/src/orca-session-title.ts +0 -103
- package/src/pack-feedback-slice.test.ts +0 -212
- package/src/pack-feedback-slice.ts +0 -187
- package/src/platform.test.ts +0 -152
- package/src/platform.ts +0 -173
- package/src/program.test.ts +0 -317
- package/src/program.ts +0 -842
- package/src/project-enablement.test.ts +0 -55
- package/src/project-enablement.ts +0 -99
- package/src/question-caps.test.ts +0 -49
- package/src/question-settlement-process.ts +0 -47
- package/src/question-timing.ts +0 -56
- package/src/readiness.test.ts +0 -25
- package/src/readiness.ts +0 -212
- package/src/release.ts +0 -51
- package/src/send-attempts.test.ts +0 -143
- package/src/send-attempts.ts +0 -188
- package/src/send.test.ts +0 -28
- package/src/send.ts +0 -436
- package/src/session-activation.ts +0 -61
- package/src/session-end-log-lock-worker.test.ts +0 -35
- package/src/session-labels.test.ts +0 -490
- package/src/session-labels.ts +0 -474
- package/src/setup-destinations.test.ts +0 -41
- package/src/setup-destinations.ts +0 -50
- package/src/skill.test.ts +0 -426
- package/src/sound-ref.test.ts +0 -31
- package/src/sound-ref.ts +0 -34
- package/src/trust-policy.test.ts +0 -701
- package/src/ui/banner.ts +0 -95
- package/src/ui/config-view.test.ts +0 -90
- package/src/ui/config-view.ts +0 -138
- package/src/ui/help.test.ts +0 -16
- package/src/ui/help.ts +0 -128
- package/src/ui/theme.test.ts +0 -92
- package/src/ui/theme.ts +0 -265
- package/src/url-policy.ts +0 -399
- package/tsconfig.json +0 -10
package/src/cli-bin.ts
DELETED
|
@@ -1,233 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
accessSync,
|
|
3
|
-
constants,
|
|
4
|
-
existsSync,
|
|
5
|
-
lstatSync,
|
|
6
|
-
readFileSync,
|
|
7
|
-
realpathSync,
|
|
8
|
-
} from 'node:fs'
|
|
9
|
-
import path from 'node:path'
|
|
10
|
-
import type { ReadinessState } from './readiness.js'
|
|
11
|
-
import { packageVersion } from './release.js'
|
|
12
|
-
import { cliUpdateRecoveryCommand } from './cli-update-contract.js'
|
|
13
|
-
|
|
14
|
-
const POSIX_NAMES = ['notifai']
|
|
15
|
-
const WINDOWS_NAMES = ['notifai.cmd', 'notifai.exe', 'notifai']
|
|
16
|
-
|
|
17
|
-
export interface CliBinReadinessOptions {
|
|
18
|
-
runningArtifactPath?: string
|
|
19
|
-
currentVersion?: string | null
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface CliPathEntry {
|
|
23
|
-
command_path: string
|
|
24
|
-
executable: boolean
|
|
25
|
-
artifact_path: string | null
|
|
26
|
-
version: string | null
|
|
27
|
-
install_prefix: string | null
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface CliInstallationInspection {
|
|
31
|
-
current: { artifact_path: string; version: string | null }
|
|
32
|
-
effective: CliPathEntry | null
|
|
33
|
-
entries: CliPathEntry[]
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function pathNotifaiEntries(
|
|
37
|
-
env: NodeJS.ProcessEnv,
|
|
38
|
-
platform: NodeJS.Platform = process.platform,
|
|
39
|
-
): string[] {
|
|
40
|
-
const raw = platform === 'win32' ? (env['Path'] ?? env['PATH'] ?? '') : (env['PATH'] ?? '')
|
|
41
|
-
const delimiter = platform === 'win32' ? ';' : ':'
|
|
42
|
-
const names = platform === 'win32' ? WINDOWS_NAMES : POSIX_NAMES
|
|
43
|
-
const found: string[] = []
|
|
44
|
-
for (const directory of raw.split(delimiter)) {
|
|
45
|
-
if (directory === '') continue
|
|
46
|
-
for (const name of names) {
|
|
47
|
-
const candidate = path.join(directory, name)
|
|
48
|
-
if (!existsSync(candidate)) continue
|
|
49
|
-
if (!found.includes(candidate)) found.push(candidate)
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return found
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export function isExecutablePath(file: string, platform: NodeJS.Platform = process.platform): boolean {
|
|
56
|
-
if (!existsSync(file)) return false
|
|
57
|
-
if (platform === 'win32') return true
|
|
58
|
-
try {
|
|
59
|
-
const target = lstatSync(file).isSymbolicLink() ? realpathSync(file) : file
|
|
60
|
-
accessSync(target, constants.X_OK)
|
|
61
|
-
return true
|
|
62
|
-
} catch {
|
|
63
|
-
return false
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function canonicalPath(file: string): string {
|
|
68
|
-
try {
|
|
69
|
-
return realpathSync(file)
|
|
70
|
-
} catch {
|
|
71
|
-
return path.resolve(file)
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function windowsShimArtifact(file: string): string | null {
|
|
76
|
-
if (path.extname(file).toLowerCase() !== '.cmd') return null
|
|
77
|
-
try {
|
|
78
|
-
const source = readFileSync(file, 'utf8')
|
|
79
|
-
const match = /(?:%dp0%|%~dp0)?([^"\r\n]*node_modules[\\/]@raidiant[\\/]notifai[\\/]dist[\\/]main\.js)/i.exec(source)
|
|
80
|
-
if (match?.[0] === undefined) return null
|
|
81
|
-
const expanded = match[0]
|
|
82
|
-
.replace(/^%~?dp0%/i, `${path.dirname(file)}${path.sep}`)
|
|
83
|
-
.replaceAll('\\', path.sep)
|
|
84
|
-
return canonicalPath(expanded)
|
|
85
|
-
} catch {
|
|
86
|
-
return null
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function artifactForCommand(file: string, platform: NodeJS.Platform): string | null {
|
|
91
|
-
const shim = platform === 'win32' ? windowsShimArtifact(file) : null
|
|
92
|
-
if (shim !== null) return shim
|
|
93
|
-
try {
|
|
94
|
-
return canonicalPath(file)
|
|
95
|
-
} catch {
|
|
96
|
-
return null
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function artifactVersion(artifact: string | null): string | null {
|
|
101
|
-
if (artifact === null) return null
|
|
102
|
-
try {
|
|
103
|
-
const parsed: unknown = JSON.parse(
|
|
104
|
-
readFileSync(path.join(path.dirname(artifact), '..', 'package.json'), 'utf8'),
|
|
105
|
-
)
|
|
106
|
-
if (typeof parsed !== 'object' || parsed === null) return null
|
|
107
|
-
const version = (parsed as { version?: unknown }).version
|
|
108
|
-
return typeof version === 'string' && version !== '' ? version : null
|
|
109
|
-
} catch {
|
|
110
|
-
return null
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function installPrefix(artifact: string | null): string | null {
|
|
115
|
-
if (artifact !== null) {
|
|
116
|
-
const marker = /[\\/]lib[\\/]node_modules[\\/]@raidiant[\\/]notifai[\\/]/i.exec(artifact)
|
|
117
|
-
?? /[\\/]node_modules[\\/]@raidiant[\\/]notifai[\\/]/i.exec(artifact)
|
|
118
|
-
if (marker?.index !== undefined) return artifact.slice(0, marker.index)
|
|
119
|
-
}
|
|
120
|
-
return null
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
export function inspectCliInstallations(
|
|
124
|
-
env: NodeJS.ProcessEnv,
|
|
125
|
-
platform: NodeJS.Platform = process.platform,
|
|
126
|
-
options: CliBinReadinessOptions = {},
|
|
127
|
-
): CliInstallationInspection {
|
|
128
|
-
const runningArtifact = canonicalPath(options.runningArtifactPath ?? process.argv[1] ?? 'notifai')
|
|
129
|
-
const entries = pathNotifaiEntries(env, platform).map((command): CliPathEntry => {
|
|
130
|
-
const artifact = artifactForCommand(command, platform)
|
|
131
|
-
return {
|
|
132
|
-
command_path: command,
|
|
133
|
-
executable: isExecutablePath(command, platform),
|
|
134
|
-
artifact_path: artifact,
|
|
135
|
-
version: artifactVersion(artifact),
|
|
136
|
-
install_prefix: installPrefix(artifact),
|
|
137
|
-
}
|
|
138
|
-
})
|
|
139
|
-
return {
|
|
140
|
-
current: {
|
|
141
|
-
artifact_path: runningArtifact,
|
|
142
|
-
version: options.currentVersion === undefined ? packageVersion() : options.currentVersion,
|
|
143
|
-
},
|
|
144
|
-
effective: entries.find((entry) => entry.executable) ?? null,
|
|
145
|
-
entries,
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
export function cliBinReadiness(
|
|
150
|
-
env: NodeJS.ProcessEnv,
|
|
151
|
-
platform: NodeJS.Platform = process.platform,
|
|
152
|
-
options: CliBinReadinessOptions = {},
|
|
153
|
-
): ReadinessState {
|
|
154
|
-
const inspection = inspectCliInstallations(env, platform, options)
|
|
155
|
-
const { current, effective, entries } = inspection
|
|
156
|
-
const updateCommand = cliUpdateRecoveryCommand()
|
|
157
|
-
if (effective === null && entries.length > 0) {
|
|
158
|
-
return {
|
|
159
|
-
id: 'cli-bin',
|
|
160
|
-
title: 'notifai command',
|
|
161
|
-
status: 'gap',
|
|
162
|
-
detail: 'the notifai command is on PATH but cannot run',
|
|
163
|
-
technical: inspection,
|
|
164
|
-
remedy: {
|
|
165
|
-
by: 'user-here',
|
|
166
|
-
summary: 'repair the global notifai command so it can run',
|
|
167
|
-
command: updateCommand,
|
|
168
|
-
},
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
// A global install that landed outside PATH used to report `ready` with
|
|
172
|
-
// "this process can run" — which is true of the running process and false of
|
|
173
|
-
// every instruction it goes on to print. Hooks embed absolute paths, so
|
|
174
|
-
// nothing visibly breaks until the reader types `notifai` themselves.
|
|
175
|
-
//
|
|
176
|
-
// Not a blocker: this process is already running, so the whole setup —
|
|
177
|
-
// pairing, the app, the delivery proof — still completes. It is the later
|
|
178
|
-
// `notifai …` lines that will not be found, and saying so is the fix.
|
|
179
|
-
if (effective === null) {
|
|
180
|
-
return {
|
|
181
|
-
id: 'cli-bin',
|
|
182
|
-
title: 'notifai command',
|
|
183
|
-
status: 'optional-gap',
|
|
184
|
-
detail:
|
|
185
|
-
'no `notifai` on PATH — this process runs, but a typed `notifai …` command will not be found',
|
|
186
|
-
remedy: {
|
|
187
|
-
by: 'user-here',
|
|
188
|
-
summary: 'install notifai globally so the command is on your PATH',
|
|
189
|
-
command: updateCommand,
|
|
190
|
-
},
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const effectiveIsCurrent =
|
|
195
|
-
effective.artifact_path === current.artifact_path ||
|
|
196
|
-
(effective.version !== null && current.version !== null && effective.version === current.version)
|
|
197
|
-
if (!effectiveIsCurrent) {
|
|
198
|
-
return {
|
|
199
|
-
id: 'cli-bin',
|
|
200
|
-
title: 'notifai command',
|
|
201
|
-
status: 'gap',
|
|
202
|
-
detail: 'the notifai command resolves to a different installation than this current CLI',
|
|
203
|
-
technical: inspection,
|
|
204
|
-
remedy: {
|
|
205
|
-
by: 'user-here',
|
|
206
|
-
summary: 'update the notifai command that wins PATH',
|
|
207
|
-
command: updateCommand,
|
|
208
|
-
},
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const duplicateNeedsCleanup = entries.some(
|
|
213
|
-
(entry) =>
|
|
214
|
-
entry !== effective &&
|
|
215
|
-
(!entry.executable || entry.artifact_path !== effective.artifact_path),
|
|
216
|
-
)
|
|
217
|
-
if (duplicateNeedsCleanup) {
|
|
218
|
-
return {
|
|
219
|
-
id: 'cli-bin',
|
|
220
|
-
title: 'notifai command',
|
|
221
|
-
status: 'optional-gap',
|
|
222
|
-
detail: 'the notifai command is ready; another installation remains for cleanup',
|
|
223
|
-
technical: inspection,
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
return {
|
|
227
|
-
id: 'cli-bin',
|
|
228
|
-
title: 'notifai command',
|
|
229
|
-
status: 'ready',
|
|
230
|
-
detail: 'the notifai command is ready',
|
|
231
|
-
technical: inspection,
|
|
232
|
-
}
|
|
233
|
-
}
|
package/src/cli-release.test.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { afterEach, describe, expect, it } from 'vitest'
|
|
2
|
-
import {
|
|
3
|
-
compareCliSemVer,
|
|
4
|
-
latestPublishedCliVersion,
|
|
5
|
-
newerPublishedCli,
|
|
6
|
-
resetLatestPublishedCliVersionForTest,
|
|
7
|
-
shouldConsultCliRegistry,
|
|
8
|
-
} from './cli-release.js'
|
|
9
|
-
|
|
10
|
-
afterEach(() => {
|
|
11
|
-
resetLatestPublishedCliVersionForTest()
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
describe('CLI registry recommendation', () => {
|
|
15
|
-
it('is suppressed for JSON, non-interactive, and CI callers', () => {
|
|
16
|
-
expect(shouldConsultCliRegistry({ interactive: true })).toBe(true)
|
|
17
|
-
expect(shouldConsultCliRegistry({ interactive: true, json: true })).toBe(false)
|
|
18
|
-
expect(shouldConsultCliRegistry({ interactive: false })).toBe(false)
|
|
19
|
-
expect(shouldConsultCliRegistry({ interactive: true, env: { CI: 'true' } })).toBe(false)
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
it('never throws when the registry is unreachable', async () => {
|
|
23
|
-
const latest = await latestPublishedCliVersion(async () => {
|
|
24
|
-
throw new Error('network down')
|
|
25
|
-
})
|
|
26
|
-
expect(latest).toBeNull()
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
it('caches a successful dist-tag read', async () => {
|
|
30
|
-
let calls = 0
|
|
31
|
-
const fetchImpl: typeof fetch = async () => {
|
|
32
|
-
calls += 1
|
|
33
|
-
return new Response(JSON.stringify({ latest: '9.1.0' }), { status: 200 })
|
|
34
|
-
}
|
|
35
|
-
expect(await latestPublishedCliVersion(fetchImpl)).toBe('9.1.0')
|
|
36
|
-
expect(await latestPublishedCliVersion(fetchImpl)).toBe('9.1.0')
|
|
37
|
-
expect(calls).toBe(1)
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
it('only recommends a strictly newer published version', () => {
|
|
41
|
-
expect(newerPublishedCli('8.0.0', '8.0.1')).toBe('8.0.1')
|
|
42
|
-
expect(newerPublishedCli('8.0.0', '8.0.0')).toBeNull()
|
|
43
|
-
expect(newerPublishedCli('8.0.1', '8.0.0')).toBeNull()
|
|
44
|
-
expect(compareCliSemVer('8.0.0', '7.0.2')).toBeGreaterThan(0)
|
|
45
|
-
})
|
|
46
|
-
})
|
package/src/cli-release.ts
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { packageVersion } from './release.js'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* The newest published CLI, read from the registry that publishes it.
|
|
5
|
-
*
|
|
6
|
-
* Which version is newest is a registry fact. Doctor consults it as a
|
|
7
|
-
* best-effort, cached, short-timeout hint: never fatal, and never on a
|
|
8
|
-
* non-interactive or CI invocation — those callers already get structured
|
|
9
|
-
* support state from the server when they are signed in.
|
|
10
|
-
*/
|
|
11
|
-
const DIST_TAGS_URL = 'https://registry.npmjs.org/-/package/@raidiant/notifai/dist-tags'
|
|
12
|
-
const REQUEST_TIMEOUT_MS = 2_000
|
|
13
|
-
const CACHE_TTL_MS = 60 * 60 * 1000
|
|
14
|
-
|
|
15
|
-
let cached: { value: string | null; at: number } | null = null
|
|
16
|
-
|
|
17
|
-
export function compareCliSemVer(left: string, right: string): number | null {
|
|
18
|
-
const parse = (value: string): [number, number, number] | null => {
|
|
19
|
-
const match = /^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/.exec(value)
|
|
20
|
-
if (!match) return null
|
|
21
|
-
return [Number(match[1]), Number(match[2]), Number(match[3])]
|
|
22
|
-
}
|
|
23
|
-
const a = parse(left)
|
|
24
|
-
const b = parse(right)
|
|
25
|
-
if (a === null || b === null) return null
|
|
26
|
-
for (let index = 0; index < 3; index += 1) {
|
|
27
|
-
if (a[index] !== b[index]) return a[index]! - b[index]!
|
|
28
|
-
}
|
|
29
|
-
return 0
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function shouldConsultCliRegistry(input: {
|
|
33
|
-
interactive?: boolean
|
|
34
|
-
json?: boolean
|
|
35
|
-
env?: NodeJS.ProcessEnv
|
|
36
|
-
}): boolean {
|
|
37
|
-
if (input.json === true) return false
|
|
38
|
-
if (input.interactive !== true) return false
|
|
39
|
-
const ci = input.env?.['CI']
|
|
40
|
-
return ci !== '1' && ci !== 'true'
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export async function latestPublishedCliVersion(
|
|
44
|
-
fetchImpl: typeof fetch = fetch,
|
|
45
|
-
): Promise<string | null> {
|
|
46
|
-
if (cached !== null && Date.now() - cached.at < CACHE_TTL_MS) return cached.value
|
|
47
|
-
try {
|
|
48
|
-
const response = await fetchImpl(DIST_TAGS_URL, {
|
|
49
|
-
headers: { accept: 'application/json' },
|
|
50
|
-
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
51
|
-
})
|
|
52
|
-
if (!response.ok) return cached?.value ?? null
|
|
53
|
-
const body: unknown = await response.json()
|
|
54
|
-
if (typeof body !== 'object' || body === null) return cached?.value ?? null
|
|
55
|
-
const latest = (body as { latest?: unknown }).latest
|
|
56
|
-
if (typeof latest !== 'string' || compareCliSemVer(latest, '0.0.0') === null) {
|
|
57
|
-
return cached?.value ?? null
|
|
58
|
-
}
|
|
59
|
-
cached = { value: latest, at: Date.now() }
|
|
60
|
-
return latest
|
|
61
|
-
} catch {
|
|
62
|
-
return cached?.value ?? null
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function newerPublishedCli(local: string | null, latest: string | null): string | null {
|
|
67
|
-
if (local === null || latest === null) return null
|
|
68
|
-
const comparison = compareCliSemVer(latest, local)
|
|
69
|
-
return comparison !== null && comparison > 0 ? latest : null
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export function thisCliVersion(): string | null {
|
|
73
|
-
return packageVersion()
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export function resetLatestPublishedCliVersionForTest(): void {
|
|
77
|
-
cached = null
|
|
78
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Resolve the current published CLI independently of every installed
|
|
3
|
-
* `notifai` command. The fetched build owns the locally derived prefix repair,
|
|
4
|
-
* so this action neither invokes the stale PATH winner nor exposes local
|
|
5
|
-
* installation paths.
|
|
6
|
-
*/
|
|
7
|
-
export function cliUpdateRecoveryCommand(): string {
|
|
8
|
-
return 'npx --yes @raidiant/notifai@latest update'
|
|
9
|
-
}
|
package/src/cli-update.test.ts
DELETED
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import { spawnSync } from 'node:child_process'
|
|
2
|
-
import {
|
|
3
|
-
chmodSync,
|
|
4
|
-
mkdirSync,
|
|
5
|
-
mkdtempSync,
|
|
6
|
-
readFileSync,
|
|
7
|
-
realpathSync,
|
|
8
|
-
symlinkSync,
|
|
9
|
-
writeFileSync,
|
|
10
|
-
} from 'node:fs'
|
|
11
|
-
import os from 'node:os'
|
|
12
|
-
import path from 'node:path'
|
|
13
|
-
import { describe, expect, it } from 'vitest'
|
|
14
|
-
import type { CommandDeps, CommandIo } from './commands-core.js'
|
|
15
|
-
import { cliUpdateCommand } from './commands-update.js'
|
|
16
|
-
import { installHookAdapter, inspectHookAdapter } from './hook-adapter.js'
|
|
17
|
-
import { packageVersion } from './release.js'
|
|
18
|
-
|
|
19
|
-
class CapturedIo implements CommandIo {
|
|
20
|
-
outLines: string[] = []
|
|
21
|
-
errLines: string[] = []
|
|
22
|
-
out(line: string) { this.outLines.push(line) }
|
|
23
|
-
err(line: string) { this.errLines.push(line) }
|
|
24
|
-
async confirm() { return false }
|
|
25
|
-
openUrl() {}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function npmInstall(root: string, name: string, version: string) {
|
|
29
|
-
const prefix = path.join(root, name)
|
|
30
|
-
const packageRoot = path.join(prefix, 'lib', 'node_modules', '@raidiant', 'notifai')
|
|
31
|
-
const artifact = path.join(packageRoot, 'dist', 'main.js')
|
|
32
|
-
const command = path.join(prefix, 'bin', 'notifai')
|
|
33
|
-
mkdirSync(path.dirname(artifact), { recursive: true })
|
|
34
|
-
mkdirSync(path.dirname(command), { recursive: true })
|
|
35
|
-
writeFileSync(path.join(packageRoot, 'package.json'), JSON.stringify({ version }))
|
|
36
|
-
writeFileSync(artifact, `#!${process.execPath}\nprocess.stdout.write(${JSON.stringify(version)} + "\\n")\n`)
|
|
37
|
-
chmodSync(artifact, 0o755)
|
|
38
|
-
symlinkSync(path.relative(path.dirname(command), artifact), command)
|
|
39
|
-
return { prefix, packageRoot, artifact, command }
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
describe('CLI update recovery', () => {
|
|
43
|
-
it('updates the PATH winner prefix and retargets the shared hook adapter in one action', () => {
|
|
44
|
-
const root = mkdtempSync(path.join(os.tmpdir(), 'notifai-update-loop-'))
|
|
45
|
-
const currentVersion = packageVersion()
|
|
46
|
-
if (currentVersion === null) throw new Error('test build has no package version')
|
|
47
|
-
const stale = npmInstall(root, 'stale-prefix', '3.0.1')
|
|
48
|
-
const current = npmInstall(root, 'current-prefix', currentVersion)
|
|
49
|
-
const managerPrefix = path.join(root, 'manager-prefix')
|
|
50
|
-
const managerBin = path.join(managerPrefix, 'bin')
|
|
51
|
-
const npm = path.join(managerBin, 'npm')
|
|
52
|
-
const calls = path.join(root, 'npm-calls.jsonl')
|
|
53
|
-
mkdirSync(managerBin, { recursive: true })
|
|
54
|
-
writeFileSync(
|
|
55
|
-
npm,
|
|
56
|
-
`#!${process.execPath}
|
|
57
|
-
const fs = require('node:fs');
|
|
58
|
-
const path = require('node:path');
|
|
59
|
-
const args = process.argv.slice(2);
|
|
60
|
-
fs.appendFileSync(${JSON.stringify(calls)}, JSON.stringify(args) + '\\n');
|
|
61
|
-
if (args[0] === 'prefix') {
|
|
62
|
-
process.stdout.write(${JSON.stringify(managerPrefix)} + '\\n');
|
|
63
|
-
process.exit(0);
|
|
64
|
-
}
|
|
65
|
-
const prefix = args[args.indexOf('--prefix') + 1];
|
|
66
|
-
const packageRoot = path.join(prefix, 'lib', 'node_modules', '@raidiant', 'notifai');
|
|
67
|
-
const artifact = path.join(packageRoot, 'dist', 'main.js');
|
|
68
|
-
fs.mkdirSync(path.dirname(artifact), { recursive: true });
|
|
69
|
-
fs.writeFileSync(path.join(packageRoot, 'package.json'), JSON.stringify({ version: ${JSON.stringify(currentVersion)} }));
|
|
70
|
-
fs.writeFileSync(artifact, '#!${process.execPath}\\nprocess.stdout.write(${JSON.stringify(`${currentVersion}\\n`)})\\n', { mode: 0o755 });
|
|
71
|
-
`,
|
|
72
|
-
)
|
|
73
|
-
chmodSync(npm, 0o755)
|
|
74
|
-
|
|
75
|
-
const home = path.join(root, 'home')
|
|
76
|
-
installHookAdapter({ execPath: process.execPath, scriptPath: stale.artifact }, home)
|
|
77
|
-
const io = new CapturedIo()
|
|
78
|
-
const deps: CommandDeps = {
|
|
79
|
-
io,
|
|
80
|
-
store: { load: () => null, save: () => {}, clear: () => {}, describe: () => 'test' },
|
|
81
|
-
env: {
|
|
82
|
-
PATH: [managerBin, path.dirname(stale.command), path.dirname(current.command)].join(':'),
|
|
83
|
-
},
|
|
84
|
-
cwd: root,
|
|
85
|
-
hookAdapterHome: home,
|
|
86
|
-
hookInstallTarget: { execPath: process.execPath, scriptPath: current.artifact },
|
|
87
|
-
hookPlatform: 'darwin',
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
expect(cliUpdateCommand(deps, {})).toBe(0)
|
|
91
|
-
expect(JSON.parse(readFileSync(path.join(stale.packageRoot, 'package.json'), 'utf8'))).toMatchObject({
|
|
92
|
-
version: currentVersion,
|
|
93
|
-
})
|
|
94
|
-
expect(
|
|
95
|
-
readFileSync(calls, 'utf8').trim().split('\n').map((line) => JSON.parse(line)),
|
|
96
|
-
).toEqual([
|
|
97
|
-
['prefix', '--global'],
|
|
98
|
-
['install', '--global', '--prefix', realpathSync(stale.prefix), '@raidiant/notifai'],
|
|
99
|
-
])
|
|
100
|
-
expect(spawnSync(stale.command, ['--version'], { encoding: 'utf8' }).stdout.trim()).toBe(currentVersion)
|
|
101
|
-
expect(inspectHookAdapter(home).target).toMatchObject({ scriptPath: realpathSync(stale.artifact) })
|
|
102
|
-
expect(io.outLines).toEqual(['Notifai is updated. Re-run `notifai init` to continue setup.'])
|
|
103
|
-
expect(io.outLines.join('\n')).not.toContain(root)
|
|
104
|
-
expect(io.errLines).toEqual([])
|
|
105
|
-
|
|
106
|
-
io.outLines = []
|
|
107
|
-
expect(cliUpdateCommand(deps, { json: true })).toBe(0)
|
|
108
|
-
const result = JSON.parse(io.outLines[0] ?? '{}') as Record<string, unknown>
|
|
109
|
-
expect(result).toMatchObject({
|
|
110
|
-
ok: true,
|
|
111
|
-
package_manager_prefix: managerPrefix,
|
|
112
|
-
update_prefix: realpathSync(stale.prefix),
|
|
113
|
-
after: {
|
|
114
|
-
effective: {
|
|
115
|
-
command_path: stale.command,
|
|
116
|
-
artifact_path: realpathSync(stale.artifact),
|
|
117
|
-
version: currentVersion,
|
|
118
|
-
},
|
|
119
|
-
},
|
|
120
|
-
hook_adapter: {
|
|
121
|
-
target: { scriptPath: realpathSync(stale.artifact) },
|
|
122
|
-
},
|
|
123
|
-
})
|
|
124
|
-
})
|
|
125
|
-
})
|