@raidiant/notifai 1.0.0 → 1.1.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.
Files changed (49) hide show
  1. package/CHANGELOG.md +26 -1
  2. package/README.md +17 -7
  3. package/dist/commands.d.ts +2 -0
  4. package/dist/commands.js +43 -38
  5. package/dist/commands.js.map +1 -1
  6. package/dist/config.d.ts +2 -2
  7. package/dist/config.js +5 -9
  8. package/dist/config.js.map +1 -1
  9. package/dist/credentials.d.ts +54 -3
  10. package/dist/credentials.js +237 -18
  11. package/dist/credentials.js.map +1 -1
  12. package/dist/file-lock.js +15 -6
  13. package/dist/file-lock.js.map +1 -1
  14. package/dist/hook-adapter.d.ts +6 -2
  15. package/dist/hook-adapter.js +110 -29
  16. package/dist/hook-adapter.js.map +1 -1
  17. package/dist/hooks.js +2 -1
  18. package/dist/hooks.js.map +1 -1
  19. package/dist/install-hooks.d.ts +54 -22
  20. package/dist/install-hooks.js +125 -56
  21. package/dist/install-hooks.js.map +1 -1
  22. package/dist/native-skills.js +5 -10
  23. package/dist/native-skills.js.map +1 -1
  24. package/dist/opencode-plugin.d.ts +7 -2
  25. package/dist/opencode-plugin.js +142 -4
  26. package/dist/opencode-plugin.js.map +1 -1
  27. package/dist/platform.d.ts +73 -0
  28. package/dist/platform.js +143 -0
  29. package/dist/platform.js.map +1 -0
  30. package/dist/ui/config-view.js +5 -1
  31. package/dist/ui/config-view.js.map +1 -1
  32. package/package.json +3 -3
  33. package/src/commands.test.ts +144 -34
  34. package/src/commands.ts +65 -35
  35. package/src/config.ts +11 -9
  36. package/src/credentials.test.ts +291 -0
  37. package/src/credentials.ts +274 -17
  38. package/src/file-lock.ts +34 -9
  39. package/src/hook-adapter.test.ts +136 -0
  40. package/src/hook-adapter.ts +120 -28
  41. package/src/hooks.test.ts +27 -1
  42. package/src/hooks.ts +2 -1
  43. package/src/install-hooks.test.ts +188 -27
  44. package/src/install-hooks.ts +177 -52
  45. package/src/native-skills.ts +5 -10
  46. package/src/opencode-plugin.ts +148 -3
  47. package/src/platform.test.ts +152 -0
  48. package/src/platform.ts +173 -0
  49. package/src/ui/config-view.ts +5 -5
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"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"}
@@ -47,7 +47,11 @@ export function renderConfigList(config, options = {}) {
47
47
  }
48
48
  }
49
49
  lines.push('');
50
- lines.push(style.dim(`${glyph.arrow} ${style.code('notifai config explain <key>')} for what one of these means and how to change it.`));
50
+ const command = 'notifai config explain <key>';
51
+ const help = `${glyph.arrow} ${command} for what one of these means and how to change it.`;
52
+ for (const line of wrap(help, columns)) {
53
+ lines.push(style.dim(line.replace(command, style.code(command))));
54
+ }
51
55
  return lines;
52
56
  }
53
57
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"config-view.js","sourceRoot":"","sources":["../../src/ui/config-view.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config-view.js","sourceRoot":"","sources":["../../src/ui/config-view.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raidiant/notifai",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "type": "module",
5
5
  "description": "Send native phone notifications from agents and local programs",
6
6
  "bin": {
@@ -14,7 +14,7 @@
14
14
  ],
15
15
  "dependencies": {
16
16
  "@clack/prompts": "^1.7.0",
17
- "@raidiant/notifai-protocol": "0.3.0",
17
+ "@raidiant/notifai-protocol": "0.4.0",
18
18
  "commander": "^14.0.0",
19
19
  "picocolors": "^1.1.1",
20
20
  "smol-toml": "^1.4.0"
@@ -54,6 +54,6 @@
54
54
  "build": "node ../../scripts/clean-dist.mjs && tsc -p tsconfig.json",
55
55
  "pretest": "pnpm run build",
56
56
  "typecheck": "tsc -p tsconfig.json --noEmit",
57
- "test": "vitest run"
57
+ "test": "node ../../scripts/run-cli-tests.mjs"
58
58
  }
59
59
  }
@@ -1,4 +1,13 @@
1
- import { existsSync, mkdirSync, mkdtempSync, readFileSync, symlinkSync, writeFileSync } from 'node:fs'
1
+ import {
2
+ existsSync,
3
+ mkdirSync,
4
+ mkdtempSync,
5
+ readFileSync,
6
+ readdirSync,
7
+ rmSync,
8
+ symlinkSync,
9
+ writeFileSync,
10
+ } from 'node:fs'
2
11
  import os from 'node:os'
3
12
  import path from 'node:path'
4
13
  import { fileURLToPath } from 'node:url'
@@ -10,7 +19,7 @@ import type {
10
19
  SubmissionReceipt,
11
20
  SubmitNotificationRequestT,
12
21
  } from '@raidiant/notifai-protocol'
13
- import { describe, expect, it } from 'vitest'
22
+ import { afterEach, describe, expect, it } from 'vitest'
14
23
  import { ApiCallError, NetworkError, type ApiClient } from './client.js'
15
24
  import type { ClaudeWakeAdapters } from './claude-wake.js'
16
25
  import {
@@ -51,7 +60,7 @@ import {
51
60
  } from './install-hooks.js'
52
61
  import { readSessionState, writeProjectSession, writeSessionState } from './hooks.js'
53
62
  import { nativeSkills as realNativeSkills, type NativeSkill, type NativeSkills, type SkillScope } from './native-skills.js'
54
- import { CONFIG_KEYS, loadConfig, personalProjectConfigPath } from './config.js'
63
+ import { CONFIG_KEYS, loadConfig, personalProjectConfigPath, stateDir } from './config.js'
55
64
  import { createLogger, logsDiskUsage, readLogRecords } from './logging.js'
56
65
  import { hookAdapterPath, inspectHookAdapter } from './hook-adapter.js'
57
66
  import type { Tone } from './ui/theme.js'
@@ -161,6 +170,29 @@ class InteractiveIo extends CapturedIo {
161
170
  }
162
171
 
163
172
  function trustInstalledCodexHooks(cwd: string, env: NodeJS.ProcessEnv): void {
173
+ const home = env['HOME']
174
+ if (home === undefined || home === '') {
175
+ throw new Error(
176
+ 'trustInstalledCodexHooks requires env.HOME; refusing to use the real home directory',
177
+ )
178
+ }
179
+ const resolvedHome = path.resolve(home)
180
+ const tmpRoot = path.resolve(os.tmpdir())
181
+ const underTmp = resolvedHome === tmpRoot || resolvedHome.startsWith(`${tmpRoot}${path.sep}`)
182
+ if (!underTmp) {
183
+ throw new Error(
184
+ `trustInstalledCodexHooks will only write under ${tmpRoot}, not ${resolvedHome}`,
185
+ )
186
+ }
187
+ const codexHome = env['CODEX_HOME'] !== undefined && env['CODEX_HOME'] !== ''
188
+ ? path.resolve(env['CODEX_HOME'])
189
+ : path.join(resolvedHome, '.codex')
190
+ if (codexHome !== tmpRoot && !codexHome.startsWith(`${tmpRoot}${path.sep}`)) {
191
+ throw new Error(
192
+ `trustInstalledCodexHooks will only write under ${tmpRoot}, not ${codexHome}`,
193
+ )
194
+ }
195
+ const file = path.join(codexHome, 'config.toml')
164
196
  const installations = findInstallations(cwd, env).filter(
165
197
  (installation) => installation.harness === 'codex',
166
198
  )
@@ -170,9 +202,11 @@ function trustInstalledCodexHooks(cwd: string, env: NodeJS.ProcessEnv): void {
170
202
  return `[hooks.state.${JSON.stringify(key)}]\ntrusted_hash = ${JSON.stringify(codexHookIdentityHash(handler))}\n`
171
203
  }),
172
204
  )
173
- const codexHome = env['CODEX_HOME'] as string
174
- mkdirSync(codexHome, { recursive: true })
175
- writeFileSync(path.join(codexHome, 'config.toml'), sections.join('\n'))
205
+ mkdirSync(path.dirname(file), { recursive: true })
206
+ // Trust lives in the same config.toml as inline [hooks]. Overwriting the
207
+ // file would delete the global handlers this helper is trying to trust.
208
+ const existing = existsSync(file) ? readFileSync(file, 'utf8').trimEnd() : ''
209
+ writeFileSync(file, [existing, ...sections].filter((block) => block.length > 0).join('\n') + '\n')
176
210
  }
177
211
 
178
212
  function makeDeps(io: CapturedIo, client: ApiClient): CommandDeps {
@@ -215,6 +249,7 @@ function isolatedEnv(cwd: string): NodeJS.ProcessEnv {
215
249
  HOME: home,
216
250
  CLAUDE_CONFIG_DIR: path.join(home, '.claude'),
217
251
  CODEX_HOME: path.join(home, '.codex'),
252
+ OPENCODE_CONFIG_DIR: path.join(home, '.config', 'opencode'),
218
253
  XDG_CONFIG_HOME: path.join(cwd, 'config'),
219
254
  XDG_STATE_HOME: path.join(cwd, 'state'),
220
255
  }
@@ -1178,7 +1213,7 @@ describe('Cursor hook commands', () => {
1178
1213
  it('installs native Cursor hooks with bounded chained answer continuations', () => {
1179
1214
  const cwd = mkdtempSync(path.join(os.tmpdir(), 'notifai-cursor-install-'))
1180
1215
  const io = new CapturedIo()
1181
- const deps = { ...makeDeps(io, {} as ApiClient), cwd }
1216
+ const deps = { ...makeDeps(io, {} as ApiClient), cwd, env: isolatedEnv(cwd) }
1182
1217
 
1183
1218
  expect(
1184
1219
  hooksInstallCommand(deps, { harness: 'cursor', execPath, scriptPath }),
@@ -1221,6 +1256,7 @@ describe('Cursor hook commands', () => {
1221
1256
  XDG_STATE_HOME: path.join(cwd, 'state'),
1222
1257
  CODEX_HOME: path.join(cwd, 'codex'),
1223
1258
  CLAUDE_CONFIG_DIR: path.join(cwd, 'claude'),
1259
+ OPENCODE_CONFIG_DIR: path.join(cwd, 'opencode'),
1224
1260
  },
1225
1261
  store: { load: () => null, save: () => {}, clear: () => {}, describe: () => 'empty store' },
1226
1262
  }
@@ -1240,7 +1276,7 @@ describe('Cursor hook commands', () => {
1240
1276
  it('uninstalls only Notifai Cursor hooks and preserves foreign hooks', () => {
1241
1277
  const cwd = mkdtempSync(path.join(os.tmpdir(), 'notifai-cursor-uninstall-'))
1242
1278
  const io = new CapturedIo()
1243
- const deps = { ...makeDeps(io, {} as ApiClient), cwd }
1279
+ const deps = { ...makeDeps(io, {} as ApiClient), cwd, env: isolatedEnv(cwd) }
1244
1280
  expect(
1245
1281
  hooksInstallCommand(deps, { harness: 'cursor', execPath, scriptPath }),
1246
1282
  ).toBe(EXIT.ok)
@@ -1299,7 +1335,7 @@ describe('Codex hook representation', () => {
1299
1335
  expect(io.outLines.join('\n')).toMatch(/Stop and the existing one will both fire/i)
1300
1336
  })
1301
1337
 
1302
- it('collapses a leftover hooks.json onto inline [hooks] and says both Stops will run', async () => {
1338
+ it('preserves a foreign inline Stop while collapsing our duplicate definition', async () => {
1303
1339
  const cwd = mkdtempSync(path.join(os.tmpdir(), 'notifai-codex-collapse-'))
1304
1340
  const toml = writeInlineStop(cwd, 'gdh-stop')
1305
1341
  const json = path.join(cwd, '.codex', 'hooks.json')
@@ -1353,7 +1389,7 @@ describe('harness activation guidance', () => {
1353
1389
  it('does not require a Claude Code restart for project hook files', () => {
1354
1390
  const cwd = mkdtempSync(path.join(os.tmpdir(), 'notifai-claude-activation-'))
1355
1391
  const io = new CapturedIo()
1356
- const deps = { ...makeDeps(io, {} as ApiClient), cwd }
1392
+ const deps = { ...makeDeps(io, {} as ApiClient), cwd, env: isolatedEnv(cwd) }
1357
1393
 
1358
1394
  expect(
1359
1395
  hooksInstallCommand(deps, { harness: 'claude-code', execPath, scriptPath }),
@@ -1368,7 +1404,7 @@ describe('harness activation guidance', () => {
1368
1404
  it('does not invent a Codex hook trust gate', () => {
1369
1405
  const cwd = mkdtempSync(path.join(os.tmpdir(), 'notifai-codex-activation-'))
1370
1406
  const io = new CapturedIo()
1371
- const deps = { ...makeDeps(io, {} as ApiClient), cwd }
1407
+ const deps = { ...makeDeps(io, {} as ApiClient), cwd, env: isolatedEnv(cwd) }
1372
1408
 
1373
1409
  expect(hooksInstallCommand(deps, { harness: 'codex', execPath, scriptPath })).toBe(
1374
1410
  EXIT.ok,
@@ -1382,7 +1418,7 @@ describe('harness activation guidance', () => {
1382
1418
  it('names the installed harness in the close, never a different one', () => {
1383
1419
  const cwd = mkdtempSync(path.join(os.tmpdir(), 'notifai-cursor-close-'))
1384
1420
  const io = new CapturedIo()
1385
- const deps = { ...makeDeps(io, {} as ApiClient), cwd }
1421
+ const deps = { ...makeDeps(io, {} as ApiClient), cwd, env: isolatedEnv(cwd) }
1386
1422
 
1387
1423
  expect(hooksInstallCommand(deps, { harness: 'cursor', execPath, scriptPath })).toBe(EXIT.ok)
1388
1424
 
@@ -1481,7 +1517,11 @@ describe('harness activation guidance', () => {
1481
1517
  const deps = {
1482
1518
  ...makeDeps(io, {} as ApiClient),
1483
1519
  cwd,
1484
- env: { XDG_STATE_HOME: path.join(cwd, 'state') },
1520
+ env: {
1521
+ HOME: path.join(cwd, 'home'),
1522
+ XDG_STATE_HOME: path.join(cwd, 'state'),
1523
+ OPENCODE_CONFIG_DIR: path.join(cwd, 'opencode-home'),
1524
+ },
1485
1525
  }
1486
1526
 
1487
1527
  expect(hooksInstallCommand(deps, { harness: 'opencode', execPath, scriptPath })).toBe(
@@ -1557,10 +1597,11 @@ describe('stable hook installation', () => {
1557
1597
  ).toBe(EXIT.ok)
1558
1598
  const global = readFileSync(path.join(env.CLAUDE_CONFIG_DIR, 'settings.json'), 'utf8')
1559
1599
  expect(global).toBe(firstDefinition)
1600
+ expect(readFileSync(local, 'utf8')).not.toContain('--owner notifai')
1560
1601
 
1561
1602
  io.outLines = []
1562
1603
  await doctorCommand(deps, {})
1563
- expect(io.outLines.join('\n')).toMatch(/hooks \(duplicates\).*project or global routing/is)
1604
+ expect(io.outLines.join('\n')).not.toMatch(/hooks \(duplicates\)/)
1564
1605
 
1565
1606
  expect(hooksUninstallCommand(deps, { harness: 'claude-code' })).toBe(EXIT.ok)
1566
1607
  expect(existsSync(hookAdapterPath(deps.hookAdapterHome))).toBe(true)
@@ -1570,6 +1611,27 @@ describe('stable hook installation', () => {
1570
1611
  ),
1571
1612
  ).toHaveLength(1)
1572
1613
  })
1614
+
1615
+ it('does not add a project copy when global hooks already cover the machine', () => {
1616
+ const cwd = mkdtempSync(path.join(os.tmpdir(), 'notifai-hooks-global-enough-'))
1617
+ const io = new CapturedIo()
1618
+ const env = isolatedEnv(cwd)
1619
+ const deps = { ...makeDeps(io, {} as ApiClient), cwd, env }
1620
+ const execPath = process.execPath
1621
+ const scriptPath = fileURLToPath(import.meta.url)
1622
+
1623
+ expect(hooksInstallCommand(deps, { harness: 'codex', global: true, execPath, scriptPath })).toBe(
1624
+ EXIT.ok,
1625
+ )
1626
+ io.outLines = []
1627
+ expect(hooksInstallCommand(deps, { harness: 'codex', execPath, scriptPath })).toBe(EXIT.ok)
1628
+ expect(existsSync(path.join(cwd, '.codex', 'config.toml'))).toBe(false)
1629
+ expect(io.outLines.join('\n')).toMatch(/already cover this machine/)
1630
+ expect(io.outLines.join('\n')).toMatch(/uninstall --harness codex --global/)
1631
+ expect(
1632
+ findInstallations(cwd, env, deps.hookAdapterHome).filter((item) => item.harness === 'codex'),
1633
+ ).toEqual([expect.objectContaining({ global: true })])
1634
+ })
1573
1635
  })
1574
1636
 
1575
1637
  describe('projectSlugFrom', () => {
@@ -2094,6 +2156,7 @@ describe('init', () => {
2094
2156
  platform: 'ios' as const,
2095
2157
  permission_status: 'authorized',
2096
2158
  registration_healthy: true,
2159
+ reply_protocol_version: 1,
2097
2160
  last_seen_at: '2026-08-05T18:00:00.000Z',
2098
2161
  }
2099
2162
 
@@ -2467,6 +2530,7 @@ describe('init', () => {
2467
2530
  expect(wired).toEqual(['claude-code', 'codex'])
2468
2531
  expect(existsSync(path.join(cwd, '.claude', 'settings.local.json'))).toBe(true)
2469
2532
  expect(existsSync(path.join(cwd, '.codex', 'hooks.json'))).toBe(true)
2533
+ expect(existsSync(path.join(cwd, '.codex', 'config.toml'))).toBe(false)
2470
2534
  expect(io.outLines.join('\n')).toContain('Installed claude-code hooks')
2471
2535
  expect(io.outLines.join('\n')).toContain('Installed codex hooks')
2472
2536
  })
@@ -2632,10 +2696,12 @@ describe('init', () => {
2632
2696
  ...makeDeps(io, client),
2633
2697
  cwd,
2634
2698
  env: {
2699
+ HOME: path.join(cwd, 'home'),
2635
2700
  XDG_CONFIG_HOME: path.join(cwd, 'config'),
2636
2701
  XDG_STATE_HOME: path.join(cwd, 'state'),
2637
2702
  CODEX_HOME: path.join(cwd, 'codex'),
2638
2703
  CLAUDE_CONFIG_DIR: path.join(cwd, 'claude'),
2704
+ OPENCODE_CONFIG_DIR: path.join(cwd, 'opencode'),
2639
2705
  },
2640
2706
  }
2641
2707
 
@@ -2725,6 +2791,9 @@ describe('init', () => {
2725
2791
  expect(io.notes.some((n) => n.message.includes('I will wait up to 10 minutes'))).toBe(true)
2726
2792
  expect(io.spinnerEvents).toContain('stop:iPhone is ready to receive')
2727
2793
  expect(io.spinnerEvents).toContain('stop:Receipt observed from iPhone')
2794
+ expect(io.outLines.join('\n')).toContain(
2795
+ "Companion Receipt (the app's delivery confirmation) observed from iPhone.",
2796
+ )
2728
2797
  expect(io.outLines.join('\n')).toContain('All set.')
2729
2798
  expect(submitCalls).toBe(1)
2730
2799
  expect(submittedDraft?.draft.event).toBe('setup_verified')
@@ -2867,6 +2936,8 @@ describe('init', () => {
2867
2936
  expect(io.outLines.join('\n')).toContain('Next: Delivery proof')
2868
2937
  expect(io.outLines.join('\n')).toContain('Provider accepted the notification')
2869
2938
  expect(io.outLines.join('\n')).toContain('Proof may still arrive')
2939
+ const proofDir = path.join(stateDir(deps.env), 'setup-proofs')
2940
+ expect(readdirSync(proofDir).some((name) => name.endsWith('.json'))).toBe(true)
2870
2941
 
2871
2942
  io.outLines = []
2872
2943
  io.errLines = []
@@ -3276,6 +3347,27 @@ describe('an outage is not an answer', () => {
3276
3347
  describe('asking before the hooks have ever run', () => {
3277
3348
  const execPath = process.execPath
3278
3349
  const scriptPath = fileURLToPath(import.meta.url)
3350
+ const scratch: string[] = []
3351
+
3352
+ function scratchDir(prefix: string): string {
3353
+ const dir = mkdtempSync(path.join(os.tmpdir(), prefix))
3354
+ scratch.push(dir)
3355
+ return dir
3356
+ }
3357
+
3358
+ afterEach(() => {
3359
+ while (scratch.length > 0) {
3360
+ rmSync(scratch.pop()!, { recursive: true, force: true })
3361
+ }
3362
+ })
3363
+
3364
+ it('refuses to write Codex trust outside the isolated test account', () => {
3365
+ expect(() => trustInstalledCodexHooks(os.tmpdir(), {})).toThrow(/requires env.HOME/)
3366
+ expect(() => trustInstalledCodexHooks(os.tmpdir(), { HOME: path.dirname(os.tmpdir()) })).toThrow(
3367
+ /will only write under/,
3368
+ )
3369
+ expect(os.homedir()).toBe(process.env['NOTIFAI_TEST_HOME'])
3370
+ })
3279
3371
 
3280
3372
  it('names the active Codex harness instead of unrelated installed adapters', () => {
3281
3373
  const cwd = mkdtempSync(path.join(os.tmpdir(), 'notifai-active-codex-missing-'))
@@ -3283,6 +3375,7 @@ describe('asking before the hooks have ever run', () => {
3283
3375
  const env = {
3284
3376
  XDG_CONFIG_HOME: path.join(cwd, 'config'),
3285
3377
  XDG_STATE_HOME: path.join(cwd, 'state'),
3378
+ HOME: path.join(cwd, 'home'),
3286
3379
  CODEX_HOME: path.join(cwd, 'codex-home'),
3287
3380
  CODEX_THREAD_ID: 'codex-current-thread',
3288
3381
  CLAUDE_CONFIG_DIR: path.join(cwd, 'claude-home'),
@@ -3307,6 +3400,7 @@ describe('asking before the hooks have ever run', () => {
3307
3400
  const env = {
3308
3401
  XDG_CONFIG_HOME: path.join(cwd, 'config'),
3309
3402
  XDG_STATE_HOME: path.join(cwd, 'state'),
3403
+ HOME: path.join(cwd, 'home'),
3310
3404
  CODEX_HOME: path.join(cwd, 'codex-home'),
3311
3405
  CODEX_THREAD_ID: 'codex-current-thread',
3312
3406
  }
@@ -3325,11 +3419,12 @@ describe('asking before the hooks have ever run', () => {
3325
3419
  })
3326
3420
 
3327
3421
  it('registers only when the active Codex thread owns the project pointer', () => {
3328
- const cwd = mkdtempSync(path.join(os.tmpdir(), 'notifai-active-codex-matching-'))
3422
+ const cwd = scratchDir('notifai-active-codex-matching-')
3329
3423
  const io = new CapturedIo()
3330
3424
  const env = {
3331
3425
  XDG_CONFIG_HOME: path.join(cwd, 'config'),
3332
3426
  XDG_STATE_HOME: path.join(cwd, 'state'),
3427
+ HOME: path.join(cwd, 'home'),
3333
3428
  CODEX_HOME: path.join(cwd, 'codex-home'),
3334
3429
  CODEX_THREAD_ID: 'codex-current-thread',
3335
3430
  }
@@ -3469,11 +3564,12 @@ describe('asking before the hooks have ever run', () => {
3469
3564
  })
3470
3565
 
3471
3566
  it('makes one work-resumption commitment per offered answer part of the asking turn', () => {
3472
- const cwd = mkdtempSync(path.join(os.tmpdir(), 'notifai-ask-commitment-'))
3567
+ const cwd = scratchDir('notifai-ask-commitment-')
3473
3568
  const io = new CapturedIo()
3474
3569
  const env = {
3475
3570
  XDG_CONFIG_HOME: path.join(cwd, 'config'),
3476
3571
  XDG_STATE_HOME: path.join(cwd, 'state'),
3572
+ HOME: path.join(cwd, 'home'),
3477
3573
  CODEX_HOME: path.join(cwd, 'codex-home'),
3478
3574
  CODEX_THREAD_ID: 'codex-commitment-thread',
3479
3575
  }
@@ -3504,11 +3600,12 @@ describe('asking before the hooks have ever run', () => {
3504
3600
  })
3505
3601
 
3506
3602
  it('requires a concrete fallback for free-text answers before the turn ends', () => {
3507
- const cwd = mkdtempSync(path.join(os.tmpdir(), 'notifai-ask-free-text-'))
3603
+ const cwd = scratchDir('notifai-ask-free-text-')
3508
3604
  const io = new CapturedIo()
3509
3605
  const env = {
3510
3606
  XDG_CONFIG_HOME: path.join(cwd, 'config'),
3511
3607
  XDG_STATE_HOME: path.join(cwd, 'state'),
3608
+ HOME: path.join(cwd, 'home'),
3512
3609
  CODEX_HOME: path.join(cwd, 'codex-home'),
3513
3610
  CODEX_THREAD_ID: 'codex-free-text-thread',
3514
3611
  }
@@ -3527,11 +3624,12 @@ describe('asking before the hooks have ever run', () => {
3527
3624
  })
3528
3625
 
3529
3626
  it('refuses a Codex question when the installed Stop definition is no longer trusted', async () => {
3530
- const cwd = mkdtempSync(path.join(os.tmpdir(), 'notifai-codex-stale-trust-'))
3627
+ const cwd = scratchDir('notifai-codex-stale-trust-')
3531
3628
  const io = new CapturedIo()
3532
3629
  const env = {
3533
3630
  XDG_CONFIG_HOME: path.join(cwd, 'config'),
3534
3631
  XDG_STATE_HOME: path.join(cwd, 'state'),
3632
+ HOME: path.join(cwd, 'home'),
3535
3633
  CODEX_HOME: path.join(cwd, 'codex-home'),
3536
3634
  CODEX_THREAD_ID: 'codex-current-thread',
3537
3635
  }
@@ -3542,7 +3640,7 @@ describe('asking before the hooks have ever run', () => {
3542
3640
  .find((installation) => installation.harness === 'codex')
3543
3641
  ?.handlers.find((handler) => handler.event === 'Stop')
3544
3642
  expect(stop).toBeDefined()
3545
- const configFile = path.join(env.CODEX_HOME, 'config.toml')
3643
+ const configFile = path.join(env.CODEX_HOME!, 'config.toml')
3546
3644
  writeFileSync(
3547
3645
  configFile,
3548
3646
  readFileSync(configFile, 'utf8').replace(codexHookIdentityHash(stop!), 'sha256:obsolete'),
@@ -3566,6 +3664,7 @@ describe('asking before the hooks have ever run', () => {
3566
3664
  const env = {
3567
3665
  XDG_CONFIG_HOME: path.join(cwd, 'config'),
3568
3666
  XDG_STATE_HOME: path.join(cwd, 'state'),
3667
+ HOME: path.join(cwd, 'home'),
3569
3668
  CODEX_HOME: path.join(cwd, 'codex-home'),
3570
3669
  CODEX_THREAD_ID: 'codex-current-thread',
3571
3670
  CLAUDE_CONFIG_DIR: path.join(cwd, 'claude-home'),
@@ -3674,7 +3773,7 @@ describe('asking before the hooks have ever run', () => {
3674
3773
  })
3675
3774
 
3676
3775
  it('rejects duplicate active Codex definitions before registration', () => {
3677
- const cwd = mkdtempSync(path.join(os.tmpdir(), 'notifai-active-codex-duplicate-'))
3776
+ const cwd = scratchDir('notifai-active-codex-duplicate-')
3678
3777
  const io = new CapturedIo()
3679
3778
  const env = {
3680
3779
  HOME: path.join(cwd, 'home'),
@@ -3685,9 +3784,13 @@ describe('asking before the hooks have ever run', () => {
3685
3784
  }
3686
3785
  const deps = { ...makeDeps(io, {} as ApiClient), cwd, env, now: () => 42 }
3687
3786
  expect(hooksInstallCommand(deps, { harness: 'codex', execPath, scriptPath })).toBe(EXIT.ok)
3688
- expect(
3689
- hooksInstallCommand(deps, { harness: 'codex', global: true, execPath, scriptPath }),
3690
- ).toBe(EXIT.ok)
3787
+ mkdirSync(env.CODEX_HOME!, { recursive: true })
3788
+ applyPlan(path.join(env.CODEX_HOME!, 'config.toml'), {
3789
+ hooks: buildHookConfig({
3790
+ adapterPath: hookAdapterPath(deps.hookAdapterHome),
3791
+ harness: 'codex',
3792
+ }),
3793
+ })
3691
3794
  trustInstalledCodexHooks(cwd, env)
3692
3795
  writeSessionState('codex-current-thread', env, { last_prompt_at: 42, last_stop_at: 41 })
3693
3796
  writeProjectSession(cwd, env, 'codex-current-thread', 42, 'codex')
@@ -3791,6 +3894,7 @@ describe('asking before the hooks have ever run', () => {
3791
3894
  const env = {
3792
3895
  XDG_CONFIG_HOME: path.join(cwd, 'config'),
3793
3896
  XDG_STATE_HOME: path.join(cwd, 'state'),
3897
+ HOME: path.join(cwd, 'home'),
3794
3898
  CODEX_HOME: path.join(cwd, 'codex-home'),
3795
3899
  CLAUDE_CONFIG_DIR: path.join(cwd, 'claude-home'),
3796
3900
  CLAUDECODE: '1',
@@ -3813,6 +3917,7 @@ describe('asking before the hooks have ever run', () => {
3813
3917
  XDG_CONFIG_HOME: path.join(cwd, 'config'),
3814
3918
  XDG_STATE_HOME: path.join(cwd, 'state'),
3815
3919
  CLAUDE_CONFIG_DIR: path.join(cwd, 'claude-home'),
3920
+ HOME: path.join(cwd, 'home'),
3816
3921
  CODEX_HOME: path.join(cwd, 'codex-home'),
3817
3922
  CLAUDECODE: '1',
3818
3923
  CLAUDE_CODE_SESSION_ID: 'claude-current',
@@ -3995,7 +4100,12 @@ describe('asking before the hooks have ever run', () => {
3995
4100
  ...isolatedEnv(cwd),
3996
4101
  CODEX_THREAD_ID: '9f1c2b3a-4d5e-6f70-8192-a3b4c5d6e7f8',
3997
4102
  }
3998
- const deps = { ...makeDeps(io, {} as ApiClient), cwd, env }
4103
+ const deps = {
4104
+ ...makeDeps(io, {} as ApiClient),
4105
+ cwd,
4106
+ env,
4107
+ hookPlatform: 'darwin' as NodeJS.Platform,
4108
+ }
3999
4109
  expect(hooksInstallCommand(deps, { harness: 'codex', execPath, scriptPath })).toBe(EXIT.ok)
4000
4110
 
4001
4111
  io.outLines = []
@@ -4006,14 +4116,8 @@ describe('asking before the hooks have ever run', () => {
4006
4116
  io.outLines = []
4007
4117
  await doctorCommand(deps, {})
4008
4118
  const out = io.outLines.join('\n')
4009
- if (process.platform === 'darwin') {
4010
- expect(out).toMatch(/ok\s+hooks \(wake route\)/)
4011
- expect(out).toContain('can prove a stopped thread unowned before resuming it')
4012
- } else {
4013
- // Linux has no non-blocking `flock` through Node's `open(2)` flags, so
4014
- // the gate stays shut and doctor says so rather than implying a wake.
4015
- expect(out).toContain('nothing can be resumed')
4016
- }
4119
+ expect(out).toMatch(/ok\s+hooks \(wake route\)/)
4120
+ expect(out).toContain('can prove a stopped thread unowned before resuming it')
4017
4121
  })
4018
4122
 
4019
4123
  it('documents the failing exit contract in doctor JSON', async () => {
@@ -4050,7 +4154,7 @@ describe('asking before the hooks have ever run', () => {
4050
4154
  const deps = {
4051
4155
  ...makeDeps(io, {} as ApiClient),
4052
4156
  cwd,
4053
- env: { XDG_STATE_HOME: path.join(cwd, 'state') },
4157
+ env: isolatedEnv(cwd),
4054
4158
  }
4055
4159
 
4056
4160
  expect(hooksInstallCommand(deps, { harness: 'cursor', execPath, scriptPath })).toBe(EXIT.ok)
@@ -4087,7 +4191,13 @@ describe('asking before the hooks have ever run', () => {
4087
4191
  const deps = {
4088
4192
  ...makeDeps(io, {} as ApiClient),
4089
4193
  cwd,
4090
- env: { XDG_STATE_HOME: cwd, CODEX_HOME: path.join(cwd, 'none'), CLAUDE_CONFIG_DIR: path.join(cwd, 'none') },
4194
+ env: {
4195
+ HOME: path.join(cwd, 'home'),
4196
+ XDG_STATE_HOME: cwd,
4197
+ CODEX_HOME: path.join(cwd, 'none'),
4198
+ CLAUDE_CONFIG_DIR: path.join(cwd, 'none'),
4199
+ OPENCODE_CONFIG_DIR: path.join(cwd, 'none'),
4200
+ },
4091
4201
  }
4092
4202
 
4093
4203
  expect(askCommand(deps, 'Ship it?', {})).toBe(EXIT.usage)