@raidiant/notifai 1.0.1 → 2.0.0
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 +50 -0
- package/README.md +17 -7
- package/dist/client.d.ts +5 -1
- package/dist/client.js +2 -0
- package/dist/client.js.map +1 -1
- package/dist/commands.d.ts +10 -1
- package/dist/commands.js +151 -54
- package/dist/commands.js.map +1 -1
- package/dist/config.d.ts +2 -2
- package/dist/config.js +5 -9
- package/dist/config.js.map +1 -1
- package/dist/credentials.d.ts +54 -3
- package/dist/credentials.js +237 -18
- package/dist/credentials.js.map +1 -1
- package/dist/file-lock.js +15 -6
- package/dist/file-lock.js.map +1 -1
- package/dist/hook-adapter.d.ts +6 -2
- package/dist/hook-adapter.js +110 -29
- package/dist/hook-adapter.js.map +1 -1
- package/dist/hooks.d.ts +15 -1
- package/dist/hooks.js +183 -12
- package/dist/hooks.js.map +1 -1
- package/dist/install-hooks.d.ts +50 -21
- package/dist/install-hooks.js +141 -57
- package/dist/install-hooks.js.map +1 -1
- package/dist/logging.d.ts +1 -1
- package/dist/logging.js +4 -0
- package/dist/logging.js.map +1 -1
- package/dist/main.js +14 -3
- package/dist/main.js.map +1 -1
- package/dist/native-skills.js +5 -10
- package/dist/native-skills.js.map +1 -1
- package/dist/opencode-plugin.d.ts +7 -2
- package/dist/opencode-plugin.js +142 -4
- package/dist/opencode-plugin.js.map +1 -1
- package/dist/platform.d.ts +73 -0
- package/dist/platform.js +143 -0
- package/dist/platform.js.map +1 -0
- package/dist/ui/config-view.js +5 -1
- package/dist/ui/config-view.js.map +1 -1
- package/package.json +11 -10
- package/src/client.test.ts +53 -0
- package/src/client.ts +24 -0
- package/src/commands.test.ts +351 -30
- package/src/commands.ts +207 -49
- package/src/config.ts +11 -9
- package/src/credentials.test.ts +291 -0
- package/src/credentials.ts +274 -17
- package/src/file-lock.ts +34 -9
- package/src/hook-adapter.test.ts +136 -0
- package/src/hook-adapter.ts +120 -28
- package/src/hooks.test.ts +175 -1
- package/src/hooks.ts +241 -12
- package/src/install-hooks.test.ts +174 -33
- package/src/install-hooks.ts +186 -54
- package/src/logging.ts +4 -0
- package/src/main.ts +17 -2
- package/src/native-skills.ts +5 -10
- package/src/opencode-plugin.ts +148 -3
- package/src/platform.test.ts +152 -0
- package/src/platform.ts +173 -0
- package/src/skill.test.ts +32 -1
- package/src/ui/config-view.ts +5 -5
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
} from 'node:fs'
|
|
11
11
|
import os from 'node:os'
|
|
12
12
|
import path from 'node:path'
|
|
13
|
+
import { parse as parseToml, stringify as stringifyToml } from 'smol-toml'
|
|
13
14
|
import { describe, expect, it } from 'vitest'
|
|
14
15
|
import { parseChoices } from './send.js'
|
|
15
16
|
import {
|
|
@@ -19,7 +20,9 @@ import {
|
|
|
19
20
|
opencodePluginSource,
|
|
20
21
|
opencodePluginTarget,
|
|
21
22
|
} from './opencode-plugin.js'
|
|
23
|
+
import { hookAdapterPath, installHookAdapter } from './hook-adapter.js'
|
|
22
24
|
import {
|
|
25
|
+
BLOCKING_STOP_TIMEOUT_SECONDS,
|
|
23
26
|
HARNESSES,
|
|
24
27
|
applyPlan,
|
|
25
28
|
detectHarness,
|
|
@@ -33,7 +36,9 @@ import {
|
|
|
33
36
|
codexTrustKey,
|
|
34
37
|
codexTrustProblems,
|
|
35
38
|
handlerEvent,
|
|
39
|
+
harnessAccountHome,
|
|
36
40
|
hookCommand,
|
|
41
|
+
quoteWindowsArg,
|
|
37
42
|
loadSettings,
|
|
38
43
|
mergeHooks,
|
|
39
44
|
removeHooks,
|
|
@@ -126,7 +131,7 @@ describe('hook config', () => {
|
|
|
126
131
|
})
|
|
127
132
|
})
|
|
128
133
|
|
|
129
|
-
it('gives Claude Code an asynchronous Stop with an explicit waiter budget', () => {
|
|
134
|
+
it('gives Claude Code an asynchronous Stop with an explicit waiter budget on POSIX', () => {
|
|
130
135
|
const claude = buildHookConfig({ adapterPath: ADAPTER, harness: 'claude-code' })
|
|
131
136
|
|
|
132
137
|
// `async: true` is what frees the turn: the handler returns at once and the
|
|
@@ -143,6 +148,22 @@ describe('hook config', () => {
|
|
|
143
148
|
expect(claude['SessionEnd']?.[0]?.hooks[0]?.async).toBeUndefined()
|
|
144
149
|
})
|
|
145
150
|
|
|
151
|
+
it('gives Claude Code a blocking Stop continuation on Windows', () => {
|
|
152
|
+
const options = {
|
|
153
|
+
adapterPath: 'C:\\Users\\Ada\\.notifai\\bin\\hook-adapter',
|
|
154
|
+
harness: 'claude-code' as const,
|
|
155
|
+
platform: 'win32' as const,
|
|
156
|
+
nodePath: 'C:\\Program Files\\nodejs\\node.exe',
|
|
157
|
+
}
|
|
158
|
+
const claude = buildHookConfig(options)
|
|
159
|
+
|
|
160
|
+
expect(claude['Stop']?.[0]?.hooks[0]).toEqual({
|
|
161
|
+
type: 'command',
|
|
162
|
+
command: hookCommand(options.adapterPath, 'stop', 'claude-code', options),
|
|
163
|
+
timeout: BLOCKING_STOP_TIMEOUT_SECONDS,
|
|
164
|
+
})
|
|
165
|
+
})
|
|
166
|
+
|
|
146
167
|
it('leaves exactly one handler per event when an old blocking shape is reinstalled over', () => {
|
|
147
168
|
const old = mergeHooks({}, buildHookConfig({ adapterPath: ADAPTER }), SCRIPT)
|
|
148
169
|
const migrated = mergeHooks(
|
|
@@ -226,18 +247,37 @@ describe('settings locations', () => {
|
|
|
226
247
|
)
|
|
227
248
|
})
|
|
228
249
|
|
|
229
|
-
it('
|
|
230
|
-
expect(
|
|
250
|
+
it('keeps ambient global settings inside the test account', () => {
|
|
251
|
+
expect(os.homedir()).toBe(process.env['HOME'])
|
|
252
|
+
expect(os.homedir()).toBe(process.env['NOTIFAI_TEST_HOME'])
|
|
253
|
+
expect(settingsFile('claude-code', true, '/repo', {})).toBe(
|
|
254
|
+
path.join(process.env['HOME']!, '.claude', 'settings.json'),
|
|
255
|
+
)
|
|
231
256
|
expect(settingsFile('codex', true, '/repo', {})).toBe(
|
|
232
|
-
path.join(
|
|
257
|
+
path.join(process.env['HOME']!, '.codex', 'hooks.json'),
|
|
233
258
|
)
|
|
234
259
|
})
|
|
235
260
|
|
|
236
|
-
it('
|
|
237
|
-
|
|
261
|
+
it('uses the dedicated Codex hooks file for a new layer', () => {
|
|
262
|
+
expect(settingsFile('codex', false, '/repo', {})).toBe('/repo/.codex/hooks.json')
|
|
263
|
+
expect(settingsFile('codex', true, '/repo', {})).toBe(
|
|
264
|
+
path.join(os.homedir(), '.codex', 'hooks.json'),
|
|
265
|
+
)
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
it('keeps a Notifai-only hooks.json instead of rewriting config.toml', () => {
|
|
269
|
+
const repo = mkdtempSync(path.join(os.tmpdir(), 'notifai-codex-json-'))
|
|
238
270
|
mkdirSync(path.join(repo, '.codex'), { recursive: true })
|
|
239
271
|
applyPlan(path.join(repo, '.codex', 'hooks.json'), { hooks: ours() })
|
|
240
272
|
|
|
273
|
+
expect(settingsFile('codex', false, repo, {})).toBe(path.join(repo, '.codex', 'hooks.json'))
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
it('keeps a Notifai-only inline representation so existing trust remains valid', () => {
|
|
277
|
+
const repo = mkdtempSync(path.join(os.tmpdir(), 'notifai-codex-ours-toml-'))
|
|
278
|
+
mkdirSync(path.join(repo, '.codex'), { recursive: true })
|
|
279
|
+
applyPlan(path.join(repo, '.codex', 'config.toml'), { hooks: ours() })
|
|
280
|
+
|
|
241
281
|
expect(settingsFile('codex', false, repo, {})).toBe(path.join(repo, '.codex', 'config.toml'))
|
|
242
282
|
})
|
|
243
283
|
|
|
@@ -282,17 +322,18 @@ describe('settings locations', () => {
|
|
|
282
322
|
|
|
283
323
|
it('does not treat the Codex trust store as an inline hook representation', () => {
|
|
284
324
|
const home = mkdtempSync(path.join(os.tmpdir(), 'notifai-codex-trust-only-'))
|
|
325
|
+
mkdirSync(path.join(home, '.codex'), { recursive: true })
|
|
285
326
|
writeFileSync(
|
|
286
|
-
path.join(home, 'config.toml'),
|
|
287
|
-
`[hooks.state."${home}/hooks.json:stop:0:0"]\ntrusted_hash = "sha256:abc"\n`,
|
|
327
|
+
path.join(home, '.codex', 'config.toml'),
|
|
328
|
+
`[hooks.state."${home}/.codex/hooks.json:stop:0:0"]\ntrusted_hash = "sha256:abc"\n`,
|
|
288
329
|
)
|
|
289
330
|
|
|
290
|
-
expect(settingsFile('codex', true, '/repo', { CODEX_HOME: home })).toBe(
|
|
291
|
-
path.join(home, '
|
|
331
|
+
expect(settingsFile('codex', true, '/repo', { HOME: home, CODEX_HOME: path.join(home, '.codex') })).toBe(
|
|
332
|
+
path.join(home, '.codex', 'hooks.json'),
|
|
292
333
|
)
|
|
293
334
|
})
|
|
294
335
|
|
|
295
|
-
it('
|
|
336
|
+
it('preserves foreign inline hooks when both representations exist', () => {
|
|
296
337
|
const repo = mkdtempSync(path.join(os.tmpdir(), 'notifai-codex-both-'))
|
|
297
338
|
const layer = path.join(repo, '.codex')
|
|
298
339
|
mkdirSync(layer, { recursive: true })
|
|
@@ -330,7 +371,7 @@ describe('a Codex install run inside a git worktree', () => {
|
|
|
330
371
|
it('targets the main repository, which is the file Codex actually reads', () => {
|
|
331
372
|
const { main, worktree } = repoWithWorktree()
|
|
332
373
|
|
|
333
|
-
expect(settingsFile('codex', false, worktree, {})).toBe(path.join(main, '.codex', '
|
|
374
|
+
expect(settingsFile('codex', false, worktree, {})).toBe(path.join(main, '.codex', 'hooks.json'))
|
|
334
375
|
expect(codexProjectRoot(worktree)).toBe(main)
|
|
335
376
|
})
|
|
336
377
|
|
|
@@ -395,19 +436,13 @@ describe('a Codex install run inside a git worktree', () => {
|
|
|
395
436
|
)
|
|
396
437
|
})
|
|
397
438
|
|
|
398
|
-
it('follows
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
expect(settingsFile('codex', true, '/repo', { CODEX_HOME: '/managed/codex' })).toBe(
|
|
402
|
-
'/managed/codex/config.toml',
|
|
439
|
+
it('follows the active harness home for global hook install', () => {
|
|
440
|
+
expect(settingsFile('codex', true, '/repo', { HOME: '/user', CODEX_HOME: '/managed/codex' })).toBe(
|
|
441
|
+
'/managed/codex/hooks.json',
|
|
403
442
|
)
|
|
404
443
|
expect(settingsFile('claude-code', true, '/repo', { CLAUDE_CONFIG_DIR: '/managed/claude' })).toBe(
|
|
405
444
|
'/managed/claude/settings.json',
|
|
406
445
|
)
|
|
407
|
-
// An empty value is not a relocation.
|
|
408
|
-
expect(settingsFile('codex', true, '/repo', { CODEX_HOME: '' })).toBe(
|
|
409
|
-
path.join(os.homedir(), '.codex', 'config.toml'),
|
|
410
|
-
)
|
|
411
446
|
})
|
|
412
447
|
})
|
|
413
448
|
|
|
@@ -560,7 +595,7 @@ describe('finding what is installed', () => {
|
|
|
560
595
|
].join('\n'),
|
|
561
596
|
)
|
|
562
597
|
|
|
563
|
-
const found = findInstallations(cwd, {
|
|
598
|
+
const found = findInstallations(cwd, { HOME: path.join(cwd, 'isolated-home') })
|
|
564
599
|
const codex = found.find((installation) => installation.harness === 'codex' && !installation.global)
|
|
565
600
|
expect(codex?.file).toBe(file)
|
|
566
601
|
expect(codex?.handlers.map((handler) => handler.event)).toEqual(['Stop'])
|
|
@@ -580,7 +615,7 @@ describe('finding what is installed', () => {
|
|
|
580
615
|
),
|
|
581
616
|
)
|
|
582
617
|
|
|
583
|
-
const problems = codexRepresentationProblems(cwd, {
|
|
618
|
+
const problems = codexRepresentationProblems(cwd, { HOME: path.join(cwd, 'isolated-home') })
|
|
584
619
|
expect(problems).toHaveLength(1)
|
|
585
620
|
expect(problems[0]).toContain('loading hooks from both')
|
|
586
621
|
expect(problems[0]).toContain(path.join(layer, 'hooks.json'))
|
|
@@ -594,7 +629,7 @@ describe('finding what is installed', () => {
|
|
|
594
629
|
mkdirSync(path.join(cwd, '.claude'), { recursive: true })
|
|
595
630
|
applyPlan(path.join(cwd, '.claude', 'settings.local.json'), { hooks: ours() })
|
|
596
631
|
|
|
597
|
-
const found = findInstallations(cwd, {
|
|
632
|
+
const found = findInstallations(cwd, { HOME: path.join(cwd, 'isolated-home') })
|
|
598
633
|
const claude = found.find((i) => i.harness === 'claude-code' && !i.global)
|
|
599
634
|
|
|
600
635
|
expect(claude).toBeDefined()
|
|
@@ -615,39 +650,41 @@ describe('finding what is installed', () => {
|
|
|
615
650
|
|
|
616
651
|
it('detects when Codex still trusts an older definition of an installed hook', () => {
|
|
617
652
|
const cwd = mkdtempSync(path.join(os.tmpdir(), 'notifai-codex-trust-'))
|
|
618
|
-
const
|
|
653
|
+
const home = path.join(cwd, 'home')
|
|
654
|
+
const env = { HOME: home }
|
|
619
655
|
const hookFile = path.join(cwd, '.codex', 'hooks.json')
|
|
620
656
|
mkdirSync(path.dirname(hookFile), { recursive: true })
|
|
621
657
|
applyPlan(hookFile, { hooks: ours() })
|
|
622
|
-
const installations = findInstallations(cwd,
|
|
658
|
+
const installations = findInstallations(cwd, env)
|
|
623
659
|
const codex = installations.find((installation) => installation.harness === 'codex')
|
|
624
660
|
const stop = codex?.handlers.find((handler) => handler.event === 'Stop')
|
|
625
661
|
expect(stop).toBeDefined()
|
|
626
662
|
|
|
627
|
-
|
|
663
|
+
const trustFile = path.join(home, '.codex', 'config.toml')
|
|
664
|
+
mkdirSync(path.dirname(trustFile), { recursive: true })
|
|
628
665
|
writeFileSync(
|
|
629
|
-
|
|
666
|
+
trustFile,
|
|
630
667
|
`[hooks.state.${JSON.stringify(codexTrustKey(codex!, stop!))}]\ntrusted_hash = "sha256:obsolete"\n`,
|
|
631
668
|
)
|
|
632
669
|
|
|
633
|
-
const stale = codexTrustProblems(installations,
|
|
670
|
+
const stale = codexTrustProblems(installations, env)
|
|
634
671
|
expect(stale).toHaveLength(3)
|
|
635
672
|
expect(stale).toEqual(expect.arrayContaining([expect.stringMatching(/Stop.*changed.*\/hooks/i)]))
|
|
636
673
|
|
|
637
674
|
writeFileSync(
|
|
638
|
-
|
|
675
|
+
trustFile,
|
|
639
676
|
`[hooks.state.${JSON.stringify(codexTrustKey(codex!, stop!))}]\ntrusted_hash = "${codexHookIdentityHash(stop!)}"\n`,
|
|
640
677
|
)
|
|
641
|
-
expect(codexTrustProblems(installations,
|
|
678
|
+
expect(codexTrustProblems(installations, env)).toEqual([
|
|
642
679
|
expect.stringMatching(/UserPromptSubmit.*not trusted.*\/hooks/i),
|
|
643
680
|
expect.stringMatching(/SessionEnd.*not trusted.*\/hooks/i),
|
|
644
681
|
])
|
|
645
682
|
|
|
646
683
|
writeFileSync(
|
|
647
|
-
|
|
684
|
+
trustFile,
|
|
648
685
|
`[hooks.state.${JSON.stringify(codexTrustKey(codex!, stop!))}]\ntrusted_hash = "${codexHookIdentityHash(stop!)}"\nenabled = false\n`,
|
|
649
686
|
)
|
|
650
|
-
expect(codexTrustProblems(installations,
|
|
687
|
+
expect(codexTrustProblems(installations, env)).toEqual(
|
|
651
688
|
expect.arrayContaining([expect.stringMatching(/Stop.*disabled.*\/hooks/i)]),
|
|
652
689
|
)
|
|
653
690
|
})
|
|
@@ -843,6 +880,14 @@ describe('detectHarness', () => {
|
|
|
843
880
|
])
|
|
844
881
|
})
|
|
845
882
|
|
|
883
|
+
it('detects Codex from the active custom CODEX_HOME', () => {
|
|
884
|
+
const home = mkdtempSync(path.join(os.tmpdir(), 'notifai-detect-home-'))
|
|
885
|
+
const codexHome = path.join(home, 'managed', 'codex')
|
|
886
|
+
mkdirSync(codexHome, { recursive: true })
|
|
887
|
+
|
|
888
|
+
expect(detectedHarnesses(project(), { HOME: home, CODEX_HOME: codexHome })).toEqual(['codex'])
|
|
889
|
+
})
|
|
890
|
+
|
|
846
891
|
it('prefers project evidence over anything installed on the machine', () => {
|
|
847
892
|
// The regression. Machine-global markers used to be OR-ed in per harness,
|
|
848
893
|
// so a developer with several agent tools installed got a candidate for
|
|
@@ -865,3 +910,99 @@ describe('detectHarness', () => {
|
|
|
865
910
|
expect(answer === null || HARNESSES.includes(answer)).toBe(true)
|
|
866
911
|
})
|
|
867
912
|
})
|
|
913
|
+
|
|
914
|
+
describe('Windows hook commands and discovery', () => {
|
|
915
|
+
const winNode = 'C:\\Program Files\\nodejs\\node.exe'
|
|
916
|
+
const winAdapter = 'C:\\Users\\Ada Lovelace\\.notifai\\bin\\hook-adapter'
|
|
917
|
+
const winOpts = { platform: 'win32' as const, nodePath: winNode }
|
|
918
|
+
|
|
919
|
+
it('keeps POSIX command bytes unchanged', () => {
|
|
920
|
+
expect(hookCommand(ADAPTER, 'stop', undefined, { platform: 'posix' })).toBe(
|
|
921
|
+
`'${ADAPTER}' hook stop --owner notifai`,
|
|
922
|
+
)
|
|
923
|
+
expect(hookCommand(ADAPTER, 'stop', 'codex', { platform: 'posix' })).toBe(
|
|
924
|
+
`'${ADAPTER}' hook stop --owner notifai --harness codex`,
|
|
925
|
+
)
|
|
926
|
+
})
|
|
927
|
+
|
|
928
|
+
it('emits a Windows-safe command with no POSIX single quotes', () => {
|
|
929
|
+
const command = hookCommand(winAdapter, 'stop', 'codex', winOpts)
|
|
930
|
+
expect(command).toBe(
|
|
931
|
+
`"${winNode}" "${winAdapter}" hook stop --owner notifai --harness codex`,
|
|
932
|
+
)
|
|
933
|
+
expect(command).not.toContain("'")
|
|
934
|
+
expect(quoteWindowsArg('C:\\ends\\with\\')).toBe('"C:\\ends\\with\\\\"')
|
|
935
|
+
expect(quoteWindowsArg('say "hi"')).toBe('"say \\"hi\\""')
|
|
936
|
+
})
|
|
937
|
+
|
|
938
|
+
it('round-trips the Windows command through JSON and TOML documents', () => {
|
|
939
|
+
const command = hookCommand(winAdapter, 'stop', 'cursor', winOpts)
|
|
940
|
+
expect(JSON.parse(JSON.stringify({ command }))).toEqual({ command })
|
|
941
|
+
expect(parseToml(stringifyToml({ command }))).toEqual({ command })
|
|
942
|
+
})
|
|
943
|
+
|
|
944
|
+
it('discovers a Windows-quoted installation as the stable adapter', () => {
|
|
945
|
+
const cwd = mkdtempSync(path.join(os.tmpdir(), 'notifai-win-discover-'))
|
|
946
|
+
const homeDir = path.join(cwd, 'home')
|
|
947
|
+
const script = path.join(cwd, 'cli.js')
|
|
948
|
+
writeFileSync(script, '')
|
|
949
|
+
installHookAdapter({ execPath: process.execPath, scriptPath: script }, homeDir, 'win32')
|
|
950
|
+
const adapter = hookAdapterPath(homeDir)
|
|
951
|
+
mkdirSync(path.join(cwd, '.claude'), { recursive: true })
|
|
952
|
+
applyPlan(path.join(cwd, '.claude', 'settings.local.json'), {
|
|
953
|
+
hooks: buildHookConfig({
|
|
954
|
+
adapterPath: adapter,
|
|
955
|
+
harness: 'claude-code',
|
|
956
|
+
platform: 'win32',
|
|
957
|
+
nodePath: process.execPath,
|
|
958
|
+
}),
|
|
959
|
+
})
|
|
960
|
+
|
|
961
|
+
const found = findInstallations(
|
|
962
|
+
cwd,
|
|
963
|
+
{ HOME: homeDir, USERPROFILE: 'C:\\Users\\Ada' },
|
|
964
|
+
homeDir,
|
|
965
|
+
'win32',
|
|
966
|
+
)
|
|
967
|
+
expect(found).toHaveLength(1)
|
|
968
|
+
expect(found[0]?.harness).toBe('claude-code')
|
|
969
|
+
expect(found[0]?.problems ?? []).toEqual([])
|
|
970
|
+
expect(found[0]?.handlers[0]?.command.startsWith(`${quoteWindowsArg(process.execPath)} `)).toBe(
|
|
971
|
+
true,
|
|
972
|
+
)
|
|
973
|
+
})
|
|
974
|
+
|
|
975
|
+
it('prefers a Windows-absolute USERPROFILE over an MSYS HOME', () => {
|
|
976
|
+
const env = { HOME: '/c/Users/msys', USERPROFILE: 'C:\\Users\\Ada' }
|
|
977
|
+
expect(harnessAccountHome(env, 'win32')).toBe('C:\\Users\\Ada')
|
|
978
|
+
expect(settingsFile('cursor', true, '/repo', env, 'win32')).toBe(
|
|
979
|
+
path.join('C:\\Users\\Ada', '.cursor', 'hooks.json'),
|
|
980
|
+
)
|
|
981
|
+
expect(settingsFile('claude-code', true, '/repo', env, 'win32')).toBe(
|
|
982
|
+
path.join('C:\\Users\\Ada', '.claude', 'settings.json'),
|
|
983
|
+
)
|
|
984
|
+
expect(opencodePluginPath(true, '/repo', env, 'win32')).toBe(
|
|
985
|
+
path.join('C:\\Users\\Ada', '.config', 'opencode', 'plugins', 'notifai.js'),
|
|
986
|
+
)
|
|
987
|
+
})
|
|
988
|
+
|
|
989
|
+
it('makes OpenCode spawn the adapter through Node on Windows with shell:false', () => {
|
|
990
|
+
const source = opencodePluginSource({
|
|
991
|
+
adapterPath: winAdapter,
|
|
992
|
+
timeoutSeconds: 240,
|
|
993
|
+
platform: 'win32',
|
|
994
|
+
nodePath: winNode,
|
|
995
|
+
})
|
|
996
|
+
expect(source).toContain(`const NODE = ${JSON.stringify(winNode)}`)
|
|
997
|
+
expect(source).toContain('spawn(NODE, [ADAPTER, "hook", event, "--owner", "notifai", "--harness", "opencode"]')
|
|
998
|
+
expect(source).toContain('shell: false')
|
|
999
|
+
expect(source).toContain('windowsHide: true')
|
|
1000
|
+
expect(source).not.toContain('spawn(ADAPTER,')
|
|
1001
|
+
expect(opencodePluginTarget(source)).toEqual({
|
|
1002
|
+
adapter: winAdapter,
|
|
1003
|
+
current: true,
|
|
1004
|
+
timeoutSeconds: 240,
|
|
1005
|
+
nodePath: winNode,
|
|
1006
|
+
})
|
|
1007
|
+
})
|
|
1008
|
+
})
|