@raidiant/notifai 5.0.0 → 7.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 +51 -0
- package/README.md +2 -2
- package/dist/atomic-file.js +13 -0
- package/dist/atomic-file.js.map +1 -1
- package/dist/client.d.ts +8 -3
- package/dist/client.js +24 -3
- package/dist/client.js.map +1 -1
- package/dist/commands-auth.d.ts +14 -0
- package/dist/commands-auth.js +154 -0
- package/dist/commands-auth.js.map +1 -0
- package/dist/commands-config.d.ts +31 -0
- package/dist/commands-config.js +246 -0
- package/dist/commands-config.js.map +1 -0
- package/dist/commands-core.d.ts +102 -0
- package/dist/commands-core.js +135 -0
- package/dist/commands-core.js.map +1 -0
- package/dist/commands-devices.d.ts +31 -0
- package/dist/commands-devices.js +124 -0
- package/dist/commands-devices.js.map +1 -0
- package/dist/commands-doctor.d.ts +24 -0
- package/dist/commands-doctor.js +1011 -0
- package/dist/commands-doctor.js.map +1 -0
- package/dist/commands-harness-context.d.ts +65 -0
- package/dist/commands-harness-context.js +97 -0
- package/dist/commands-harness-context.js.map +1 -0
- package/dist/commands-hook-shape.d.ts +17 -0
- package/dist/commands-hook-shape.js +37 -0
- package/dist/commands-hook-shape.js.map +1 -0
- package/dist/commands-hooks.d.ts +115 -0
- package/dist/commands-hooks.js +1225 -0
- package/dist/commands-hooks.js.map +1 -0
- package/dist/commands-init.d.ts +35 -0
- package/dist/commands-init.js +580 -0
- package/dist/commands-init.js.map +1 -0
- package/dist/commands-io.d.ts +2 -0
- package/dist/commands-io.js +110 -0
- package/dist/commands-io.js.map +1 -0
- package/dist/commands-logs.d.ts +30 -0
- package/dist/commands-logs.js +201 -0
- package/dist/commands-logs.js.map +1 -0
- package/dist/commands-send-support.d.ts +44 -0
- package/dist/commands-send-support.js +183 -0
- package/dist/commands-send-support.js.map +1 -0
- package/dist/commands-send.d.ts +33 -0
- package/dist/commands-send.js +519 -0
- package/dist/commands-send.js.map +1 -0
- package/dist/commands-setup-proof.d.ts +15 -0
- package/dist/commands-setup-proof.js +53 -0
- package/dist/commands-setup-proof.js.map +1 -0
- package/dist/commands-skill.d.ts +10 -0
- package/dist/commands-skill.js +71 -0
- package/dist/commands-skill.js.map +1 -0
- package/dist/commands.d.ts +12 -387
- package/dist/commands.js +12 -4536
- package/dist/commands.js.map +1 -1
- package/dist/config-schema.js +1 -1
- package/dist/config-schema.js.map +1 -1
- package/dist/config.d.ts +4 -4
- package/dist/config.js +6 -4
- package/dist/config.js.map +1 -1
- package/dist/credentials.js +13 -3
- package/dist/credentials.js.map +1 -1
- package/dist/generated-session-label.d.ts +2 -0
- package/dist/generated-session-label.js +152 -0
- package/dist/generated-session-label.js.map +1 -0
- package/dist/hooks.d.ts +2 -2
- package/dist/hooks.js +127 -99
- package/dist/hooks.js.map +1 -1
- package/dist/interactive.js +4 -4
- package/dist/interactive.js.map +1 -1
- package/dist/invocation-context.d.ts +3 -0
- package/dist/invocation-context.js +23 -6
- package/dist/invocation-context.js.map +1 -1
- package/dist/logging.js +1 -0
- package/dist/logging.js.map +1 -1
- package/dist/main.js +3 -3
- package/dist/main.js.map +1 -1
- package/dist/opencode-plugin.js +18 -3
- package/dist/opencode-plugin.js.map +1 -1
- package/dist/readiness.d.ts +3 -1
- package/dist/readiness.js.map +1 -1
- package/dist/session-labels.d.ts +30 -0
- package/dist/session-labels.js +278 -0
- package/dist/session-labels.js.map +1 -0
- package/package.json +2 -2
- package/src/atomic-file.ts +12 -0
- package/src/client.test.ts +90 -1
- package/src/client.ts +35 -2
- package/src/commands-auth.ts +174 -0
- package/src/commands-config.ts +307 -0
- package/src/commands-core.ts +240 -0
- package/src/commands-devices.ts +150 -0
- package/src/commands-doctor.ts +1209 -0
- package/src/commands-harness-context.ts +134 -0
- package/src/commands-hook-shape.ts +53 -0
- package/src/commands-hooks.ts +1586 -0
- package/src/commands-init.ts +721 -0
- package/src/commands-io.ts +108 -0
- package/src/commands-logs.ts +243 -0
- package/src/commands-send-support.ts +227 -0
- package/src/commands-send.ts +623 -0
- package/src/commands-setup-proof.ts +71 -0
- package/src/commands-skill.ts +84 -0
- package/src/commands.test.ts +567 -42
- package/src/commands.ts +58 -5614
- package/src/config-schema.ts +1 -1
- package/src/config.test.ts +9 -0
- package/src/config.ts +10 -5
- package/src/credentials.test.ts +25 -1
- package/src/credentials.ts +13 -3
- package/src/generated-session-label.ts +155 -0
- package/src/hooks.test.ts +355 -16
- package/src/hooks.ts +145 -97
- package/src/install-hooks.test.ts +84 -1
- package/src/interactive.test.ts +19 -1
- package/src/interactive.ts +4 -4
- package/src/invocation-context.test.ts +115 -16
- package/src/invocation-context.ts +32 -11
- package/src/logging.test.ts +15 -2
- package/src/logging.ts +1 -0
- package/src/main.ts +9 -3
- package/src/opencode-plugin.ts +18 -3
- package/src/readiness.ts +3 -1
- package/src/session-labels.test.ts +303 -0
- package/src/session-labels.ts +360 -0
- package/src/skill.test.ts +13 -0
package/src/config-schema.ts
CHANGED
|
@@ -83,7 +83,7 @@ const INFO: Record<ConfigKey, Omit<ConfigKeyInfo, 'key'>> = {
|
|
|
83
83
|
label: 'Send questions to my devices',
|
|
84
84
|
group: 'questions',
|
|
85
85
|
kind: 'boolean',
|
|
86
|
-
summary: 'Whether a registered question may reach your
|
|
86
|
+
summary: 'Whether a registered question may reach your iPhone at all',
|
|
87
87
|
detail:
|
|
88
88
|
'The master switch for question routing. When this is off, a question an agent registers with `notifai ask` stays in the terminal and never leaves this machine, whatever else is configured. Turn it off to stop being reached for a while without uninstalling the harness hooks.',
|
|
89
89
|
example: 'true',
|
package/src/config.test.ts
CHANGED
|
@@ -177,6 +177,15 @@ describe('config precedence', () => {
|
|
|
177
177
|
expect(config.wait_seconds.value).toBe(2)
|
|
178
178
|
expect(config.wait_seconds.source).toBe('flag')
|
|
179
179
|
})
|
|
180
|
+
|
|
181
|
+
it('uses the prior default when a newer CLI wrote an unknown enum value', () => {
|
|
182
|
+
const { env, cwd } = setup({
|
|
183
|
+
globalToml: 'sound = "future-sound"\ninterruption_level = "future-level"\n',
|
|
184
|
+
})
|
|
185
|
+
const config = loadConfig({ cwd, env })
|
|
186
|
+
expect(config.sound).toEqual({ value: null, source: 'default' })
|
|
187
|
+
expect(config.interruption_level).toEqual({ value: null, source: 'default' })
|
|
188
|
+
})
|
|
180
189
|
})
|
|
181
190
|
|
|
182
191
|
describe('draft building', () => {
|
package/src/config.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
CLI_SOUNDS,
|
|
3
|
+
INTERRUPTION_LEVELS,
|
|
4
|
+
REPLY_MAX_WINDOW_SECONDS,
|
|
5
|
+
} from '@raidiant/notifai-protocol'
|
|
2
6
|
import { createHash } from 'node:crypto'
|
|
3
7
|
import { existsSync, readFileSync, realpathSync } from 'node:fs'
|
|
4
8
|
import path from 'node:path'
|
|
5
9
|
import { parse as parseToml } from 'smol-toml'
|
|
6
|
-
import type { CLI_SOUNDS, INTERRUPTION_LEVELS } from '@raidiant/notifai-protocol'
|
|
7
10
|
import { configHome, stateHome } from './platform.js'
|
|
8
11
|
|
|
9
12
|
/**
|
|
@@ -133,6 +136,8 @@ export const NUMERIC_CONFIG_KEYS: readonly ConfigKey[] = [
|
|
|
133
136
|
* is the same discipline the numeric bounds follow.
|
|
134
137
|
*/
|
|
135
138
|
export const ENUM_CONFIG_VALUES: Partial<Record<ConfigKey, readonly string[]>> = {
|
|
139
|
+
sound: CLI_SOUNDS,
|
|
140
|
+
interruption_level: INTERRUPTION_LEVELS,
|
|
136
141
|
log_level: LOG_LEVELS,
|
|
137
142
|
}
|
|
138
143
|
|
|
@@ -183,9 +188,9 @@ export function globalConfigPath(env: NodeJS.ProcessEnv = process.env): string {
|
|
|
183
188
|
}
|
|
184
189
|
|
|
185
190
|
/**
|
|
186
|
-
* Machine-local mutable state: presence markers, pending questions,
|
|
187
|
-
* session config overrides. Separate from the config dir
|
|
188
|
-
*
|
|
191
|
+
* Machine-local mutable state: presence markers, pending questions, frozen
|
|
192
|
+
* session names, and session config overrides. Separate from the config dir
|
|
193
|
+
* because none of it should ever be synced or committed.
|
|
189
194
|
*/
|
|
190
195
|
export function stateDir(
|
|
191
196
|
env: NodeJS.ProcessEnv = process.env,
|
package/src/credentials.test.ts
CHANGED
|
@@ -168,7 +168,10 @@ describe('FileStore', () => {
|
|
|
168
168
|
if (process.platform !== 'win32') {
|
|
169
169
|
expect(statSync(file).mode & 0o777).toBe(0o600)
|
|
170
170
|
}
|
|
171
|
-
expect(JSON.parse(readFileSync(file, 'utf8'))).toEqual(
|
|
171
|
+
expect(JSON.parse(readFileSync(file, 'utf8'))).toEqual({
|
|
172
|
+
format: 'notifai.machine-credential.v1',
|
|
173
|
+
...SAMPLE,
|
|
174
|
+
})
|
|
172
175
|
expect(store.load()).toEqual(SAMPLE)
|
|
173
176
|
store.save(OTHER)
|
|
174
177
|
expect(store.load()).toEqual(OTHER)
|
|
@@ -190,6 +193,27 @@ describe('FileStore', () => {
|
|
|
190
193
|
writeRaw(file, `${JSON.stringify({ machineId: 'mach_fixture' })}\n`)
|
|
191
194
|
expect(new FileStore(env).load()).toBeNull()
|
|
192
195
|
})
|
|
196
|
+
|
|
197
|
+
it('reads the unversioned v1 shape while making every new save explicit', () => {
|
|
198
|
+
const { env } = sandbox('file-unversioned-v1')
|
|
199
|
+
const file = path.join(env.XDG_CONFIG_HOME!, 'notifai', 'credentials.json')
|
|
200
|
+
writeRaw(file, `${JSON.stringify(SAMPLE)}\n`)
|
|
201
|
+
const store = new FileStore(env)
|
|
202
|
+
expect(store.load()).toEqual(SAMPLE)
|
|
203
|
+
store.save(SAMPLE)
|
|
204
|
+
expect(JSON.parse(readFileSync(file, 'utf8'))).toMatchObject({
|
|
205
|
+
format: 'notifai.machine-credential.v1',
|
|
206
|
+
})
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
it('does not interpret or erase a credential from a future format epoch', () => {
|
|
210
|
+
const { env } = sandbox('file-future-format')
|
|
211
|
+
const file = path.join(env.XDG_CONFIG_HOME!, 'notifai', 'credentials.json')
|
|
212
|
+
const future = `${JSON.stringify({ format: 'notifai.machine-credential.v2', ...SAMPLE })}\n`
|
|
213
|
+
writeRaw(file, future)
|
|
214
|
+
expect(new FileStore(env).load()).toBeNull()
|
|
215
|
+
expect(readFileSync(file, 'utf8')).toBe(future)
|
|
216
|
+
})
|
|
193
217
|
})
|
|
194
218
|
|
|
195
219
|
describe('WindowsDpapiStore', () => {
|
package/src/credentials.ts
CHANGED
|
@@ -44,6 +44,7 @@ export interface CredentialStoreOptions {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
const SERVICE = 'io.notifai.cli'
|
|
47
|
+
const CREDENTIAL_FORMAT = 'notifai.machine-credential.v1'
|
|
47
48
|
const DPAPI_FORMAT = 'notifai.dpapi.current-user.v1'
|
|
48
49
|
const DPAPI_ENTROPY = 'io.notifai.cli'
|
|
49
50
|
const DPAPI_TIMEOUT_MS = 15_000
|
|
@@ -136,7 +137,7 @@ export class KeychainStore implements CredentialStore {
|
|
|
136
137
|
'-a',
|
|
137
138
|
'machine',
|
|
138
139
|
'-w',
|
|
139
|
-
JSON.stringify(credential),
|
|
140
|
+
JSON.stringify(serializeCredential(credential)),
|
|
140
141
|
],
|
|
141
142
|
{ stdio: 'ignore' },
|
|
142
143
|
)
|
|
@@ -189,7 +190,7 @@ export class FileStore implements CredentialStore {
|
|
|
189
190
|
}
|
|
190
191
|
|
|
191
192
|
save(credential: MachineCredential): void {
|
|
192
|
-
atomicWriteFileSync(this.filePath(), `${JSON.stringify(credential, null, 2)}\n`, {
|
|
193
|
+
atomicWriteFileSync(this.filePath(), `${JSON.stringify(serializeCredential(credential), null, 2)}\n`, {
|
|
193
194
|
mode: 0o600,
|
|
194
195
|
preserveMode: false,
|
|
195
196
|
requireCurrentUserOwner: true,
|
|
@@ -251,7 +252,7 @@ export class WindowsDpapiStore implements CredentialStore {
|
|
|
251
252
|
if (file === null) {
|
|
252
253
|
throw new Error('Windows credential store path is not configured (LOCALAPPDATA is missing)')
|
|
253
254
|
}
|
|
254
|
-
const blob = this.protect(Buffer.from(JSON.stringify(credential), 'utf8'))
|
|
255
|
+
const blob = this.protect(Buffer.from(JSON.stringify(serializeCredential(credential)), 'utf8'))
|
|
255
256
|
atomicWriteFileSync(file, `${JSON.stringify({ format: DPAPI_FORMAT, data: blob })}\n`, {
|
|
256
257
|
mode: 0o600,
|
|
257
258
|
preserveMode: false,
|
|
@@ -302,6 +303,10 @@ export class WindowsDpapiStore implements CredentialStore {
|
|
|
302
303
|
}
|
|
303
304
|
}
|
|
304
305
|
|
|
306
|
+
function serializeCredential(credential: MachineCredential): Record<string, string> {
|
|
307
|
+
return { format: CREDENTIAL_FORMAT, ...credential }
|
|
308
|
+
}
|
|
309
|
+
|
|
305
310
|
function parseCredentialJson(raw: string): MachineCredential | null {
|
|
306
311
|
try {
|
|
307
312
|
return parseCredential(JSON.parse(raw) as unknown)
|
|
@@ -313,6 +318,11 @@ function parseCredentialJson(raw: string): MachineCredential | null {
|
|
|
313
318
|
function parseCredential(raw: unknown): MachineCredential | null {
|
|
314
319
|
if (raw === null || typeof raw !== 'object') return null
|
|
315
320
|
const value = raw as Record<string, unknown>
|
|
321
|
+
const format = value['format']
|
|
322
|
+
// Unversioned credentials are the pre-epoch v1 representation. A future
|
|
323
|
+
// format is left untouched on disk/keychain and treated as unavailable;
|
|
324
|
+
// interpreting it as v1 could silently change authentication meaning.
|
|
325
|
+
if (format !== undefined && format !== CREDENTIAL_FORMAT) return null
|
|
316
326
|
const machineId = value['machineId']
|
|
317
327
|
const secret = value['secret']
|
|
318
328
|
const baseUrl = value['baseUrl']
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
const FNV1A_64_OFFSET = 0xcbf29ce484222325n
|
|
2
|
+
const FNV1A_64_PRIME = 0x100000001b3n
|
|
3
|
+
const WORD_COUNT = 64n
|
|
4
|
+
|
|
5
|
+
/** Frozen word lists keep generated fallbacks stable for a given opaque id. */
|
|
6
|
+
const ADJECTIVES = [
|
|
7
|
+
'Amber',
|
|
8
|
+
'Arctic',
|
|
9
|
+
'Ash',
|
|
10
|
+
'Azure',
|
|
11
|
+
'Blue',
|
|
12
|
+
'Bold',
|
|
13
|
+
'Bright',
|
|
14
|
+
'Bronze',
|
|
15
|
+
'Calm',
|
|
16
|
+
'Cedar',
|
|
17
|
+
'Clear',
|
|
18
|
+
'Copper',
|
|
19
|
+
'Coral',
|
|
20
|
+
'Crisp',
|
|
21
|
+
'Dawn',
|
|
22
|
+
'Deep',
|
|
23
|
+
'Ember',
|
|
24
|
+
'Fern',
|
|
25
|
+
'Frost',
|
|
26
|
+
'Gentle',
|
|
27
|
+
'Golden',
|
|
28
|
+
'Green',
|
|
29
|
+
'Harbor',
|
|
30
|
+
'Hazel',
|
|
31
|
+
'Indigo',
|
|
32
|
+
'Ivory',
|
|
33
|
+
'Jade',
|
|
34
|
+
'Keen',
|
|
35
|
+
'Lively',
|
|
36
|
+
'Lunar',
|
|
37
|
+
'Maple',
|
|
38
|
+
'Meadow',
|
|
39
|
+
'Mellow',
|
|
40
|
+
'Misty',
|
|
41
|
+
'Noble',
|
|
42
|
+
'Ocean',
|
|
43
|
+
'Olive',
|
|
44
|
+
'Onyx',
|
|
45
|
+
'Quiet',
|
|
46
|
+
'Rapid',
|
|
47
|
+
'Red',
|
|
48
|
+
'River',
|
|
49
|
+
'Royal',
|
|
50
|
+
'Sage',
|
|
51
|
+
'Silver',
|
|
52
|
+
'Solar',
|
|
53
|
+
'Steady',
|
|
54
|
+
'Stone',
|
|
55
|
+
'Sunny',
|
|
56
|
+
'Swift',
|
|
57
|
+
'Teal',
|
|
58
|
+
'Timber',
|
|
59
|
+
'True',
|
|
60
|
+
'Velvet',
|
|
61
|
+
'Verdant',
|
|
62
|
+
'Violet',
|
|
63
|
+
'Warm',
|
|
64
|
+
'Wild',
|
|
65
|
+
'Winter',
|
|
66
|
+
'Wise',
|
|
67
|
+
'Witty',
|
|
68
|
+
'Yellow',
|
|
69
|
+
'Young',
|
|
70
|
+
'Zealous',
|
|
71
|
+
] as const
|
|
72
|
+
|
|
73
|
+
const NOUNS = [
|
|
74
|
+
'Alpaca',
|
|
75
|
+
'Antelope',
|
|
76
|
+
'Badger',
|
|
77
|
+
'Bear',
|
|
78
|
+
'Beaver',
|
|
79
|
+
'Bison',
|
|
80
|
+
'Bobcat',
|
|
81
|
+
'Caribou',
|
|
82
|
+
'Cougar',
|
|
83
|
+
'Coyote',
|
|
84
|
+
'Crane',
|
|
85
|
+
'Deer',
|
|
86
|
+
'Dolphin',
|
|
87
|
+
'Dragonfly',
|
|
88
|
+
'Eagle',
|
|
89
|
+
'Elk',
|
|
90
|
+
'Falcon',
|
|
91
|
+
'Finch',
|
|
92
|
+
'Firefly',
|
|
93
|
+
'Fox',
|
|
94
|
+
'Gazelle',
|
|
95
|
+
'Gecko',
|
|
96
|
+
'Hare',
|
|
97
|
+
'Heron',
|
|
98
|
+
'Ibex',
|
|
99
|
+
'Jay',
|
|
100
|
+
'Kestrel',
|
|
101
|
+
'Kingfisher',
|
|
102
|
+
'Koala',
|
|
103
|
+
'Lark',
|
|
104
|
+
'Leopard',
|
|
105
|
+
'Lynx',
|
|
106
|
+
'Magpie',
|
|
107
|
+
'Mantis',
|
|
108
|
+
'Marten',
|
|
109
|
+
'Moose',
|
|
110
|
+
'Osprey',
|
|
111
|
+
'Otter',
|
|
112
|
+
'Owl',
|
|
113
|
+
'Panda',
|
|
114
|
+
'Panther',
|
|
115
|
+
'Pelican',
|
|
116
|
+
'Penguin',
|
|
117
|
+
'Puma',
|
|
118
|
+
'Quail',
|
|
119
|
+
'Rabbit',
|
|
120
|
+
'Raven',
|
|
121
|
+
'Robin',
|
|
122
|
+
'Sable',
|
|
123
|
+
'Salmon',
|
|
124
|
+
'Seal',
|
|
125
|
+
'Shark',
|
|
126
|
+
'Sparrow',
|
|
127
|
+
'Stag',
|
|
128
|
+
'Swan',
|
|
129
|
+
'Tiger',
|
|
130
|
+
'Tortoise',
|
|
131
|
+
'Trout',
|
|
132
|
+
'Turtle',
|
|
133
|
+
'Viper',
|
|
134
|
+
'Whale',
|
|
135
|
+
'Wolf',
|
|
136
|
+
'Wren',
|
|
137
|
+
'Yak',
|
|
138
|
+
] as const
|
|
139
|
+
|
|
140
|
+
function fnv1a64(value: string): bigint {
|
|
141
|
+
let hash = FNV1A_64_OFFSET
|
|
142
|
+
for (const byte of new TextEncoder().encode(value)) {
|
|
143
|
+
hash ^= BigInt(byte)
|
|
144
|
+
hash = BigInt.asUintN(64, hash * FNV1A_64_PRIME)
|
|
145
|
+
}
|
|
146
|
+
return hash
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Deterministic fallback for a session whose work has no semantic title. */
|
|
150
|
+
export function generatedSessionLabel(sessionId: string): string {
|
|
151
|
+
const hash = fnv1a64(sessionId)
|
|
152
|
+
const adjective = ADJECTIVES[Number(hash % WORD_COUNT)]
|
|
153
|
+
const noun = NOUNS[Number((hash >> 6n) % WORD_COUNT)]
|
|
154
|
+
return `${adjective} ${noun}`
|
|
155
|
+
}
|