@raidiant/notifai 4.0.0 → 6.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.
Files changed (140) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/README.md +7 -2
  3. package/dist/client.d.ts +8 -3
  4. package/dist/client.js +24 -3
  5. package/dist/client.js.map +1 -1
  6. package/dist/commands-auth.d.ts +14 -0
  7. package/dist/commands-auth.js +154 -0
  8. package/dist/commands-auth.js.map +1 -0
  9. package/dist/commands-config.d.ts +31 -0
  10. package/dist/commands-config.js +246 -0
  11. package/dist/commands-config.js.map +1 -0
  12. package/dist/commands-core.d.ts +102 -0
  13. package/dist/commands-core.js +135 -0
  14. package/dist/commands-core.js.map +1 -0
  15. package/dist/commands-devices.d.ts +31 -0
  16. package/dist/commands-devices.js +124 -0
  17. package/dist/commands-devices.js.map +1 -0
  18. package/dist/commands-doctor.d.ts +24 -0
  19. package/dist/commands-doctor.js +1011 -0
  20. package/dist/commands-doctor.js.map +1 -0
  21. package/dist/commands-harness-context.d.ts +61 -0
  22. package/dist/commands-harness-context.js +93 -0
  23. package/dist/commands-harness-context.js.map +1 -0
  24. package/dist/commands-hook-shape.d.ts +17 -0
  25. package/dist/commands-hook-shape.js +37 -0
  26. package/dist/commands-hook-shape.js.map +1 -0
  27. package/dist/commands-hooks.d.ts +115 -0
  28. package/dist/commands-hooks.js +1225 -0
  29. package/dist/commands-hooks.js.map +1 -0
  30. package/dist/commands-init.d.ts +35 -0
  31. package/dist/commands-init.js +580 -0
  32. package/dist/commands-init.js.map +1 -0
  33. package/dist/commands-io.d.ts +2 -0
  34. package/dist/commands-io.js +110 -0
  35. package/dist/commands-io.js.map +1 -0
  36. package/dist/commands-logs.d.ts +30 -0
  37. package/dist/commands-logs.js +201 -0
  38. package/dist/commands-logs.js.map +1 -0
  39. package/dist/commands-send-support.d.ts +44 -0
  40. package/dist/commands-send-support.js +180 -0
  41. package/dist/commands-send-support.js.map +1 -0
  42. package/dist/commands-send.d.ts +33 -0
  43. package/dist/commands-send.js +519 -0
  44. package/dist/commands-send.js.map +1 -0
  45. package/dist/commands-setup-proof.d.ts +15 -0
  46. package/dist/commands-setup-proof.js +53 -0
  47. package/dist/commands-setup-proof.js.map +1 -0
  48. package/dist/commands-skill.d.ts +10 -0
  49. package/dist/commands-skill.js +71 -0
  50. package/dist/commands-skill.js.map +1 -0
  51. package/dist/commands.d.ts +12 -378
  52. package/dist/commands.js +12 -4420
  53. package/dist/commands.js.map +1 -1
  54. package/dist/config-schema.js +15 -6
  55. package/dist/config-schema.js.map +1 -1
  56. package/dist/config.d.ts +4 -2
  57. package/dist/config.js +13 -0
  58. package/dist/config.js.map +1 -1
  59. package/dist/credentials.js +13 -3
  60. package/dist/credentials.js.map +1 -1
  61. package/dist/file-lock.d.ts +6 -1
  62. package/dist/file-lock.js +55 -9
  63. package/dist/file-lock.js.map +1 -1
  64. package/dist/harnesses.d.ts +1 -1
  65. package/dist/harnesses.js.map +1 -1
  66. package/dist/hooks.d.ts +74 -7
  67. package/dist/hooks.js +522 -166
  68. package/dist/hooks.js.map +1 -1
  69. package/dist/install-hooks.d.ts +91 -14
  70. package/dist/install-hooks.js +226 -34
  71. package/dist/install-hooks.js.map +1 -1
  72. package/dist/interactive.d.ts +1 -1
  73. package/dist/interactive.js +5 -5
  74. package/dist/interactive.js.map +1 -1
  75. package/dist/logging.d.ts +8 -0
  76. package/dist/logging.js +9 -0
  77. package/dist/logging.js.map +1 -1
  78. package/dist/main.js +45 -18
  79. package/dist/main.js.map +1 -1
  80. package/dist/opencode-plugin.d.ts +8 -0
  81. package/dist/opencode-plugin.js +24 -162
  82. package/dist/opencode-plugin.js.map +1 -1
  83. package/dist/readiness.d.ts +3 -1
  84. package/dist/readiness.js.map +1 -1
  85. package/dist/send.d.ts +6 -0
  86. package/dist/send.js +25 -14
  87. package/dist/send.js.map +1 -1
  88. package/dist/ui/help.d.ts +0 -1
  89. package/dist/ui/help.js +6 -3
  90. package/dist/ui/help.js.map +1 -1
  91. package/dist/ui/theme.d.ts +0 -15
  92. package/dist/ui/theme.js +2 -18
  93. package/dist/ui/theme.js.map +1 -1
  94. package/package.json +2 -2
  95. package/src/client.test.ts +90 -1
  96. package/src/client.ts +35 -2
  97. package/src/commands-auth.ts +174 -0
  98. package/src/commands-config.ts +307 -0
  99. package/src/commands-core.ts +240 -0
  100. package/src/commands-devices.ts +150 -0
  101. package/src/commands-doctor.ts +1209 -0
  102. package/src/commands-harness-context.ts +126 -0
  103. package/src/commands-hook-shape.ts +53 -0
  104. package/src/commands-hooks.ts +1586 -0
  105. package/src/commands-init.ts +721 -0
  106. package/src/commands-io.ts +108 -0
  107. package/src/commands-logs.ts +243 -0
  108. package/src/commands-send-support.ts +224 -0
  109. package/src/commands-send.ts +623 -0
  110. package/src/commands-setup-proof.ts +71 -0
  111. package/src/commands-skill.ts +84 -0
  112. package/src/commands.test.ts +677 -89
  113. package/src/commands.ts +58 -5472
  114. package/src/config-schema.test.ts +1 -1
  115. package/src/config-schema.ts +17 -6
  116. package/src/config.test.ts +50 -9
  117. package/src/config.ts +19 -1
  118. package/src/credentials.test.ts +25 -1
  119. package/src/credentials.ts +13 -3
  120. package/src/file-lock.test.ts +41 -0
  121. package/src/file-lock.ts +59 -9
  122. package/src/harnesses.test.ts +37 -29
  123. package/src/harnesses.ts +1 -4
  124. package/src/hooks.test.ts +703 -26
  125. package/src/hooks.ts +590 -182
  126. package/src/install-hooks.test.ts +232 -12
  127. package/src/install-hooks.ts +241 -37
  128. package/src/interactive.test.ts +19 -1
  129. package/src/interactive.ts +5 -5
  130. package/src/logging-process-worker.test.ts +8 -2
  131. package/src/logging.test.ts +48 -20
  132. package/src/logging.ts +12 -0
  133. package/src/main.ts +58 -24
  134. package/src/opencode-plugin.ts +24 -164
  135. package/src/question-caps.test.ts +49 -0
  136. package/src/readiness.ts +3 -1
  137. package/src/send.ts +32 -16
  138. package/src/skill.test.ts +199 -146
  139. package/src/ui/help.ts +8 -3
  140. package/src/ui/theme.ts +2 -16
@@ -0,0 +1,126 @@
1
+ import { readLiveProjectSessionPointers } from './hooks.js'
2
+ import { type Harness } from './install-hooks.js'
3
+
4
+ /**
5
+ * Exact evidence that this shell command is running inside one supported
6
+ * harness. Configuration-directory variables are deliberately absent: they
7
+ * describe where a tool stores files, not which tool owns the current shell.
8
+ * OpenCode's generated plugin supplies the Notifai-owned marker because its
9
+ * plugin API exposes session identity but the ordinary environment does not.
10
+ */
11
+ export interface ActiveHarnessSession {
12
+ harness: Harness
13
+ label: string
14
+ sessionId?: string
15
+ }
16
+
17
+ /**
18
+ * Every harness marker present in this environment, in declared order.
19
+ *
20
+ * Order here is a last resort, not an answer. A harness exports its markers
21
+ * into every process it starts, so a nested harness inherits its parent's
22
+ * markers alongside its own and the environment alone cannot say which of them
23
+ * owns this shell; `resolveActiveHarness` settles that with live evidence.
24
+ */
25
+ function harnessEnvCandidates(env: NodeJS.ProcessEnv): ActiveHarnessSession[] {
26
+ const candidates: ActiveHarnessSession[] = []
27
+ if (env['NOTIFAI_ACTIVE_HARNESS'] === 'opencode') {
28
+ const sessionId = env['NOTIFAI_ACTIVE_SESSION_ID']
29
+ candidates.push({
30
+ harness: 'opencode',
31
+ label: 'OpenCode',
32
+ ...(sessionId === undefined || sessionId === '' ? {} : { sessionId }),
33
+ })
34
+ }
35
+ if (env['CLAUDECODE'] === '1') {
36
+ const sessionId = env['CLAUDE_CODE_SESSION_ID']
37
+ candidates.push({
38
+ harness: 'claude-code',
39
+ label: 'Claude Code',
40
+ ...(sessionId === undefined || sessionId === '' ? {} : { sessionId }),
41
+ })
42
+ }
43
+ const codexSession = env['CODEX_THREAD_ID']
44
+ if (codexSession !== undefined && codexSession !== '') {
45
+ candidates.push({ harness: 'codex', label: 'Codex', sessionId: codexSession })
46
+ }
47
+ if ((env['CURSOR_AGENT'] ?? '') !== '') candidates.push({ harness: 'cursor', label: 'Cursor' })
48
+ return candidates
49
+ }
50
+
51
+ interface ActiveHarnessResolution {
52
+ active: ActiveHarnessSession | null
53
+ /**
54
+ * Markers of harnesses that could equally own this shell, present only when
55
+ * nothing here has fired yet and declared order had to pick. Whatever is
56
+ * reported then has to hold for every one of them.
57
+ */
58
+ contested: ActiveHarnessSession[]
59
+ }
60
+
61
+ /**
62
+ * Which harness session owns this shell, when several claim to.
63
+ *
64
+ * Nesting is ordinary: an orchestrator running inside Claude Code starts a
65
+ * Codex session, and that Codex process inherits `CLAUDECODE` and
66
+ * `CLAUDE_CODE_SESSION_ID` on top of its own `CODEX_THREAD_ID`. The mirror is
67
+ * just as ordinary, so no fixed precedence between two markers can be right —
68
+ * whichever one it favours is wrong in the opposite nesting, and the cost is
69
+ * silent: `ask` looks up a session that is not this one, and every remedy the
70
+ * agent is told to try addresses a harness that is not running here.
71
+ *
72
+ * The general rule is to prefer the most specific *live* signal over inherited
73
+ * environment. An inherited marker is a claim about some ancestor process; a
74
+ * session id that names an entry in this directory's live pointer index is
75
+ * evidence that that exact session fired a hook here and its state still
76
+ * exists. Live evidence therefore wins over declared order, and the most
77
+ * recently active pointer wins between two live candidates: the harness whose
78
+ * turn is running is the one that fired last, while its parent sits blocked on
79
+ * the child it started. Declared order decides only when nothing here has
80
+ * fired yet, and it says so — every route fails closed there anyway.
81
+ */
82
+ export function resolveActiveHarness(
83
+ env: NodeJS.ProcessEnv,
84
+ cwd: string,
85
+ now: number,
86
+ ): ActiveHarnessResolution {
87
+ const candidates = harnessEnvCandidates(env)
88
+ const first = candidates[0]
89
+ if (first === undefined) return { active: null, contested: [] }
90
+ if (candidates.length === 1) return { active: first, contested: [] }
91
+ for (const pointer of readLiveProjectSessionPointers(cwd, env, now)) {
92
+ const owner = candidates.find(
93
+ (candidate) =>
94
+ candidate.harness === pointer.harness && candidate.sessionId === pointer.sessionId,
95
+ )
96
+ if (owner !== undefined) return { active: owner, contested: [] }
97
+ }
98
+ return { active: first, contested: candidates }
99
+ }
100
+
101
+ export function activeHarnessSession(
102
+ env: NodeJS.ProcessEnv,
103
+ cwd: string,
104
+ now: number,
105
+ ): ActiveHarnessSession | null {
106
+ return resolveActiveHarness(env, cwd, now).active
107
+ }
108
+
109
+ /**
110
+ * The pid of the Claude Code session this hook belongs to.
111
+ *
112
+ * Claude exports `CLAUDE_PID` to its hooks, and that is the authoritative
113
+ * answer. `process.ppid` agrees with it today — the hook command runs through
114
+ * a shell, but the shell `exec`s, so this process's parent *is* the session
115
+ * (probed against a live 2.1.228 session) — and it remains the fallback for a
116
+ * harness build that stops exporting the variable.
117
+ *
118
+ * Preferring the explicit value costs a line and removes a silent failure: if
119
+ * this ever resolved to a shell instead, no session descriptor would match, the
120
+ * route could not prove own-child ownership, and every answer would degrade to
121
+ * hold-for-next-turn with nothing reported as wrong.
122
+ */
123
+ export function claudeSessionPid(env: NodeJS.ProcessEnv): number {
124
+ const declared = Number(env['CLAUDE_PID'])
125
+ return Number.isInteger(declared) && declared > 0 ? declared : process.ppid
126
+ }
@@ -0,0 +1,53 @@
1
+ import {
2
+ BLOCKING_STOP_TIMEOUT_SECONDS,
3
+ CLAUDE_ASYNC_STOP_TIMEOUT_SECONDS,
4
+ handlerEvent,
5
+ stopHandlerIsDetached,
6
+ type Installation,
7
+ } from './install-hooks.js'
8
+
9
+ /**
10
+ * Whether an installed Stop handler declares the shape its harness needs.
11
+ *
12
+ * The three answers differ, and getting one wrong fails silently in a
13
+ * different way each time:
14
+ *
15
+ * - Claude Code's handler must be `async: true`, or the waiter holds the
16
+ * user's turn for its whole wait instead of returning at once. It must
17
+ * also declare a `timeout`, because the harness default is 600 s and the
18
+ * kill is silent — the backgrounded waiter vanishes and the answer the
19
+ * user already gave is never delivered.
20
+ * - Codex owns its Stop timeout. Declaring one changes the definition it
21
+ * hashes into `trusted_hash`, and an untrusted handler is simply not run.
22
+ * - Everything else blocks its turn and needs a ceiling above the wait.
23
+ */
24
+ export function stopShapeProblems(
25
+ installation: Installation,
26
+ platform: NodeJS.Platform = process.platform,
27
+ ): string[] {
28
+ if (installation.harness === 'codex') return []
29
+ const problems: string[] = []
30
+ for (const handler of installation.handlers.filter(
31
+ (entry) => handlerEvent(entry.command) === 'stop',
32
+ )) {
33
+ if (stopHandlerIsDetached(installation.harness, platform)) {
34
+ if (handler.async !== true) {
35
+ problems.push(
36
+ `${installation.file} declares a blocking Stop handler; the Claude Code wake route needs \`async: true\` so the turn ends while the waiter runs`,
37
+ )
38
+ }
39
+ if (handler.timeout === undefined || handler.timeout < CLAUDE_ASYNC_STOP_TIMEOUT_SECONDS) {
40
+ problems.push(
41
+ `${installation.file} gives Stop ${handler.timeout ?? 'no'} declared seconds; Claude Code then kills the backgrounded waiter at its 600s default without reporting anything, so it needs an explicit ${CLAUDE_ASYNC_STOP_TIMEOUT_SECONDS}s`,
42
+ )
43
+ }
44
+ continue
45
+ }
46
+ if (handler.timeout === undefined || handler.timeout < BLOCKING_STOP_TIMEOUT_SECONDS) {
47
+ problems.push(
48
+ `${installation.file} gives Stop ${handler.timeout ?? 'no'}s, but the blocking answer owner requires ${BLOCKING_STOP_TIMEOUT_SECONDS}s`,
49
+ )
50
+ }
51
+ }
52
+ return problems
53
+ }