@raidiant/notifai 0.1.7 → 0.2.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/README.md +10 -3
- package/dist/atomic-file.d.ts +12 -0
- package/dist/atomic-file.js +55 -0
- package/dist/atomic-file.js.map +1 -0
- package/dist/client.d.ts +2 -1
- package/dist/client.js +1 -0
- package/dist/client.js.map +1 -1
- package/dist/commands.d.ts +46 -15
- package/dist/commands.js +760 -248
- package/dist/commands.js.map +1 -1
- package/dist/hooks.d.ts +27 -12
- package/dist/hooks.js +519 -155
- package/dist/hooks.js.map +1 -1
- package/dist/install-hooks.d.ts +8 -4
- package/dist/install-hooks.js +45 -64
- package/dist/install-hooks.js.map +1 -1
- package/dist/main.js +80 -36
- package/dist/main.js.map +1 -1
- package/dist/native-skills.d.ts +31 -0
- package/dist/native-skills.js +117 -0
- package/dist/native-skills.js.map +1 -0
- package/dist/opencode-plugin.d.ts +1 -0
- package/dist/opencode-plugin.js +37 -4
- package/dist/opencode-plugin.js.map +1 -1
- package/dist/send.d.ts +19 -29
- package/dist/send.js +62 -57
- package/dist/send.js.map +1 -1
- package/package.json +5 -5
- package/src/atomic-file.ts +73 -0
- package/src/client.ts +3 -0
- package/src/commands.test.ts +1036 -72
- package/src/commands.ts +923 -261
- package/src/config.test.ts +68 -6
- package/src/hooks.test.ts +500 -9
- package/src/hooks.ts +624 -173
- package/src/install-hooks.test.ts +44 -16
- package/src/install-hooks.ts +50 -68
- package/src/main.ts +95 -41
- package/src/native-skills.ts +177 -0
- package/src/opencode-plugin.ts +38 -5
- package/src/send.ts +82 -60
- package/src/skill.test.ts +43 -0
package/dist/send.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CLI_SOUNDS, INTERRUPTION_LEVELS, NOTIFICATION_SCHEMA_VERSION, NOTIFICATION_KINDS, PLATFORMS, } from '@raidiant/notifai-protocol';
|
|
1
|
+
import { CLI_SOUNDS, COLLAPSE_KEY_MAX_BYTES, INTERRUPTION_LEVELS, NOTIFICATION_SCHEMA_VERSION, QUESTION_TEXT_MAX_LENGTH, NOTIFICATION_KINDS, PLATFORMS, } from '@raidiant/notifai-protocol';
|
|
2
2
|
/** Merge flags over resolved config into a Notification Request draft. */
|
|
3
3
|
export function buildDraft(config, flags) {
|
|
4
4
|
if (!flags.title || !flags.body)
|
|
@@ -9,14 +9,20 @@ export function buildDraft(config, flags) {
|
|
|
9
9
|
}
|
|
10
10
|
const deviceIds = flags.all ? null : (flags.device?.length ? flags.device : config.devices.value);
|
|
11
11
|
const targets = deviceIds && deviceIds.length > 0 ? { mode: 'selected', device_ids: deviceIds } : { mode: 'all' };
|
|
12
|
-
const
|
|
12
|
+
const effectiveKind = flags.reply ? 'question' : (flags.kind ?? 'update');
|
|
13
|
+
const profile = KIND_PROFILES[effectiveKind];
|
|
14
|
+
const configuredSound = config.sound.source === 'default' ? undefined : config.sound.value;
|
|
15
|
+
const sound = flags.sound ?? configuredSound ?? profile?.sound;
|
|
13
16
|
if (sound !== null && sound !== undefined && !CLI_SOUNDS.includes(sound)) {
|
|
14
17
|
return {
|
|
15
18
|
ok: false,
|
|
16
19
|
error: `Unknown sound "${sound}" — supported: ${CLI_SOUNDS.map((value) => `"${value}"`).join(', ')}.`,
|
|
17
20
|
};
|
|
18
21
|
}
|
|
19
|
-
const
|
|
22
|
+
const configuredLevel = config.interruption_level.source === 'default'
|
|
23
|
+
? undefined
|
|
24
|
+
: config.interruption_level.value;
|
|
25
|
+
const level = flags.level ?? configuredLevel ?? profile?.level;
|
|
20
26
|
if (level !== null &&
|
|
21
27
|
level !== undefined &&
|
|
22
28
|
!INTERRUPTION_LEVELS.includes(level)) {
|
|
@@ -28,17 +34,11 @@ export function buildDraft(config, flags) {
|
|
|
28
34
|
else if (sound !== null && sound !== undefined) {
|
|
29
35
|
options.sound = sound;
|
|
30
36
|
}
|
|
31
|
-
if (flags.badge !== undefined)
|
|
32
|
-
options.badge = flags.badge;
|
|
33
37
|
if (flags.threadId !== undefined)
|
|
34
38
|
options.thread_id = flags.threadId;
|
|
35
39
|
if (level !== null && level !== undefined) {
|
|
36
40
|
options.interruption_level = level;
|
|
37
41
|
}
|
|
38
|
-
if (flags.relevance !== undefined)
|
|
39
|
-
options.relevance_score = flags.relevance;
|
|
40
|
-
if (flags.targetContentId !== undefined)
|
|
41
|
-
options.target_content_id = flags.targetContentId;
|
|
42
42
|
if (flags.data?.length) {
|
|
43
43
|
const data = {};
|
|
44
44
|
for (const pair of flags.data) {
|
|
@@ -58,6 +58,14 @@ export function buildDraft(config, flags) {
|
|
|
58
58
|
}
|
|
59
59
|
const ttl = flags.ttl ?? config.ttl_seconds.value;
|
|
60
60
|
const collapse = flags.collapseKey ?? config.collapse_key.value;
|
|
61
|
+
if (collapse !== null &&
|
|
62
|
+
collapse !== undefined &&
|
|
63
|
+
Buffer.byteLength(collapse, 'utf8') > COLLAPSE_KEY_MAX_BYTES) {
|
|
64
|
+
return {
|
|
65
|
+
ok: false,
|
|
66
|
+
error: `--collapse-key must be at most ${COLLAPSE_KEY_MAX_BYTES} UTF-8 bytes.`,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
61
69
|
const project = flags.project ?? config.project.value;
|
|
62
70
|
// Harnesses can export NOTIFAI_SESSION once instead of passing the flag
|
|
63
71
|
// on every send.
|
|
@@ -71,6 +79,21 @@ export function buildDraft(config, flags) {
|
|
|
71
79
|
error: CHOICE_USAGE,
|
|
72
80
|
};
|
|
73
81
|
}
|
|
82
|
+
if (flags.replyMulti && choices === null && flags.questions === undefined) {
|
|
83
|
+
return { ok: false, error: '--reply-multi needs answers to select between; add --reply-choice.' };
|
|
84
|
+
}
|
|
85
|
+
// The banner body doubles as the question text on a single-question send, so
|
|
86
|
+
// it inherits the question's readable-in-full bound; context beyond that
|
|
87
|
+
// belongs in --detail, which never rides the banner.
|
|
88
|
+
if (flags.reply &&
|
|
89
|
+
flags.questions === undefined &&
|
|
90
|
+
flags.body.length > QUESTION_TEXT_MAX_LENGTH) {
|
|
91
|
+
return {
|
|
92
|
+
ok: false,
|
|
93
|
+
error: `A question must be readable where it is answered: keep it within ${QUESTION_TEXT_MAX_LENGTH} ` +
|
|
94
|
+
'characters and put the longer context in --detail.',
|
|
95
|
+
};
|
|
96
|
+
}
|
|
74
97
|
const draft = {
|
|
75
98
|
schema_version: NOTIFICATION_SCHEMA_VERSION,
|
|
76
99
|
...(flags.event !== undefined ? { event: flags.event } : {}),
|
|
@@ -94,19 +117,33 @@ export function buildDraft(config, flags) {
|
|
|
94
117
|
...(flags.reply
|
|
95
118
|
? {
|
|
96
119
|
reply: {
|
|
97
|
-
expires_in_seconds: flags.replyWindow ??
|
|
98
|
-
|
|
120
|
+
expires_in_seconds: flags.replyWindow ?? 3600,
|
|
121
|
+
questions: flags.questions ?? [
|
|
122
|
+
{
|
|
123
|
+
id: 'q1',
|
|
124
|
+
text: flags.body,
|
|
125
|
+
...(choices !== null ? { choices } : {}),
|
|
126
|
+
...(flags.replyMulti ? { multi: true } : {}),
|
|
127
|
+
},
|
|
128
|
+
],
|
|
99
129
|
},
|
|
100
130
|
}
|
|
101
131
|
: {}),
|
|
102
132
|
...(Object.keys(options).length > 0
|
|
103
|
-
? platform ===
|
|
104
|
-
? { platform: { ios: options } }
|
|
105
|
-
:
|
|
133
|
+
? flags.platform === undefined
|
|
134
|
+
? { platform: { ios: options, macos: options } }
|
|
135
|
+
: platform === 'ios'
|
|
136
|
+
? { platform: { ios: options } }
|
|
137
|
+
: { platform: { macos: options } }
|
|
106
138
|
: {}),
|
|
107
139
|
};
|
|
108
140
|
return { ok: true, draft, platform };
|
|
109
141
|
}
|
|
142
|
+
const KIND_PROFILES = {
|
|
143
|
+
update: { sound: 'none', level: 'passive' },
|
|
144
|
+
done: { sound: 'done', level: 'passive' },
|
|
145
|
+
question: { sound: 'attention', level: 'active' },
|
|
146
|
+
};
|
|
110
147
|
function resolvePlatform(value) {
|
|
111
148
|
if (value === undefined)
|
|
112
149
|
return 'ios';
|
|
@@ -137,23 +174,18 @@ export function receiptExitCode(receipt) {
|
|
|
137
174
|
return receipt.overall === 'provider_rejected_all' ? 1 : 0;
|
|
138
175
|
}
|
|
139
176
|
/**
|
|
140
|
-
*
|
|
177
|
+
* One flag occurrence, one answer: `--choice Staging --choice Production` ->
|
|
178
|
+
* `[{id:'staging',label:'Staging'}, ...]`.
|
|
141
179
|
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
* agent meant two.
|
|
180
|
+
* Commas are ordinary characters. The old single-value comma split produced
|
|
181
|
+
* buttons the agent never intended ("Nothing, I'll review" became two answers)
|
|
182
|
+
* and nothing downstream could detect it, so the delimiter form is gone
|
|
183
|
+
* rather than guarded: a label is a label.
|
|
147
184
|
*/
|
|
148
185
|
export function parseChoices(value) {
|
|
149
|
-
if (value === undefined)
|
|
150
|
-
return null;
|
|
151
|
-
const raw = Array.isArray(value) ? value : [value];
|
|
152
|
-
if (raw.length === 0)
|
|
186
|
+
if (value === undefined || value.length === 0)
|
|
153
187
|
return null;
|
|
154
|
-
const labels = (
|
|
155
|
-
.map((part) => part.trim())
|
|
156
|
-
.filter((part) => part.length > 0);
|
|
188
|
+
const labels = value.map((part) => part.trim()).filter((part) => part.length > 0);
|
|
157
189
|
if (labels.length < 2 || labels.length > 6)
|
|
158
190
|
return 'invalid';
|
|
159
191
|
const choices = labels.map((label) => ({ id: slugify(label), label }));
|
|
@@ -164,36 +196,9 @@ export function parseChoices(value) {
|
|
|
164
196
|
return choices;
|
|
165
197
|
}
|
|
166
198
|
/** Message shared by every surface that accepts choice labels. */
|
|
167
|
-
export const CHOICE_USAGE = '
|
|
168
|
-
'
|
|
169
|
-
|
|
170
|
-
* Catches the split that looked like it worked.
|
|
171
|
-
*
|
|
172
|
-
* `--choice "macOS parity,Long-form detail,Nothing, I'll review"` produced four
|
|
173
|
-
* buttons rather than three, because the third label contained a comma. Nothing
|
|
174
|
-
* downstream can detect it — the labels are all well-formed — and the CLI
|
|
175
|
-
* cheerfully echoed the wrong list back, so the user saw buttons the agent
|
|
176
|
-
* never intended.
|
|
177
|
-
*
|
|
178
|
-
* The tell is a comma followed by a space. A delimiter list is written
|
|
179
|
-
* "Yes,No"; prose is written "Nothing, I'll review". That is a heuristic and it
|
|
180
|
-
* will occasionally be wrong, which is exactly why this warns and names the
|
|
181
|
-
* escape hatch rather than rejecting: the split may well have been intended.
|
|
182
|
-
*/
|
|
183
|
-
export function ambiguousChoiceSplit(value) {
|
|
184
|
-
if (value === undefined || Array.isArray(value))
|
|
185
|
-
return null;
|
|
186
|
-
if (!/,\s/.test(value))
|
|
187
|
-
return null;
|
|
188
|
-
const parts = value
|
|
189
|
-
.split(',')
|
|
190
|
-
.map((part) => part.trim())
|
|
191
|
-
.filter((part) => part.length > 0);
|
|
192
|
-
return (`Heads up: "${value}" was split on commas into ${parts.length} answers ` +
|
|
193
|
-
`(${parts.join(' / ')}). If one of those labels was meant to contain a comma, ` +
|
|
194
|
-
'pass --choice once per answer instead.');
|
|
195
|
-
}
|
|
196
|
-
function slugify(label) {
|
|
199
|
+
export const CHOICE_USAGE = 'Offer 2-6 answers, one --choice flag per answer, unique once slugified — ' +
|
|
200
|
+
'e.g. --choice "Yes, ship it" --choice "Not yet".';
|
|
201
|
+
export function slugify(label) {
|
|
197
202
|
return label
|
|
198
203
|
.toLowerCase()
|
|
199
204
|
.replace(/[^a-z0-9]+/g, '-')
|
package/dist/send.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"send.js","sourceRoot":"","sources":["../src/send.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,mBAAmB,EACnB,2BAA2B,
|
|
1
|
+
{"version":3,"file":"send.js","sourceRoot":"","sources":["../src/send.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@raidiant/notifai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Send native phone notifications from agents and local programs",
|
|
6
6
|
"bin": {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@clack/prompts": "^1.7.0",
|
|
21
|
-
"@raidiant/notifai-protocol": "0.
|
|
21
|
+
"@raidiant/notifai-protocol": "0.2.0",
|
|
22
22
|
"commander": "^14.0.0",
|
|
23
23
|
"smol-toml": "^1.4.0"
|
|
24
24
|
},
|
|
@@ -31,16 +31,16 @@
|
|
|
31
31
|
},
|
|
32
32
|
"license": "Apache-2.0",
|
|
33
33
|
"author": "Rafael Vidaurre",
|
|
34
|
-
"homepage": "https://github.com/
|
|
34
|
+
"homepage": "https://github.com/Raidiant-io/notifai#readme",
|
|
35
35
|
"bugs": {
|
|
36
|
-
"url": "https://github.com/
|
|
36
|
+
"url": "https://github.com/Raidiant-io/notifai/issues"
|
|
37
37
|
},
|
|
38
38
|
"publishConfig": {
|
|
39
39
|
"access": "public"
|
|
40
40
|
},
|
|
41
41
|
"repository": {
|
|
42
42
|
"type": "git",
|
|
43
|
-
"url": "git+https://github.com/
|
|
43
|
+
"url": "git+https://github.com/Raidiant-io/notifai.git",
|
|
44
44
|
"directory": "apps/cli"
|
|
45
45
|
},
|
|
46
46
|
"keywords": [
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto'
|
|
2
|
+
import {
|
|
3
|
+
closeSync,
|
|
4
|
+
fsyncSync,
|
|
5
|
+
lstatSync,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
openSync,
|
|
8
|
+
renameSync,
|
|
9
|
+
unlinkSync,
|
|
10
|
+
writeFileSync,
|
|
11
|
+
} from 'node:fs'
|
|
12
|
+
import path from 'node:path'
|
|
13
|
+
|
|
14
|
+
export interface AtomicWriteOptions {
|
|
15
|
+
/** Mode for a newly created file. Existing regular files keep their mode. */
|
|
16
|
+
mode?: number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Replace one regular file without exposing a truncated intermediate state.
|
|
21
|
+
*
|
|
22
|
+
* The temporary file is a unique sibling, so rename is atomic on every
|
|
23
|
+
* supported filesystem. The contents are flushed before the rename, and a
|
|
24
|
+
* symlink target is refused instead of silently writing through it.
|
|
25
|
+
*/
|
|
26
|
+
export function atomicWriteFileSync(
|
|
27
|
+
file: string,
|
|
28
|
+
contents: string,
|
|
29
|
+
options: AtomicWriteOptions = {},
|
|
30
|
+
): void {
|
|
31
|
+
mkdirSync(path.dirname(file), { recursive: true })
|
|
32
|
+
const mode = targetMode(file, options.mode ?? 0o600)
|
|
33
|
+
const temp = path.join(
|
|
34
|
+
path.dirname(file),
|
|
35
|
+
`.${path.basename(file)}.notifai-${process.pid}-${randomBytes(6).toString('hex')}.tmp`,
|
|
36
|
+
)
|
|
37
|
+
let handle: number | undefined
|
|
38
|
+
try {
|
|
39
|
+
handle = openSync(temp, 'wx', mode)
|
|
40
|
+
writeFileSync(handle, contents)
|
|
41
|
+
fsyncSync(handle)
|
|
42
|
+
closeSync(handle)
|
|
43
|
+
handle = undefined
|
|
44
|
+
renameSync(temp, file)
|
|
45
|
+
} catch (err) {
|
|
46
|
+
if (handle !== undefined) closeSync(handle)
|
|
47
|
+
try {
|
|
48
|
+
unlinkSync(temp)
|
|
49
|
+
} catch {
|
|
50
|
+
// Preserve the original error; the temp may not have been created.
|
|
51
|
+
}
|
|
52
|
+
throw err
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Mode to preserve, or the mode for a new file. Refuses a non-regular target. */
|
|
57
|
+
function targetMode(file: string, fallback: number): number {
|
|
58
|
+
let stat
|
|
59
|
+
try {
|
|
60
|
+
stat = lstatSync(file)
|
|
61
|
+
} catch (err) {
|
|
62
|
+
if ((err as NodeJS.ErrnoException).code === 'ENOENT') return fallback
|
|
63
|
+
throw err
|
|
64
|
+
}
|
|
65
|
+
if (stat.isSymbolicLink()) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
`${file} is a symlink; refusing to write through it. Replace it with a regular file, ` +
|
|
68
|
+
'or use a different state directory.',
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
if (!stat.isFile()) throw new Error(`${file} is not a regular file; refusing to replace it.`)
|
|
72
|
+
return stat.mode & 0o777
|
|
73
|
+
}
|
package/src/client.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
ApiErrorBody,
|
|
3
|
+
AccountAccessResponse,
|
|
3
4
|
BeginPairingResponse,
|
|
4
5
|
CapabilityDocument,
|
|
5
6
|
CreateMediaUploadRequestT,
|
|
@@ -47,6 +48,7 @@ export interface ApiClient {
|
|
|
47
48
|
poll_verifier_hash: string
|
|
48
49
|
}): Promise<BeginPairingResponse>
|
|
49
50
|
pollPairing(pairingId: string, pollVerifier: string): Promise<PollPairingResponse>
|
|
51
|
+
accessStatus(): Promise<AccountAccessResponse>
|
|
50
52
|
listDevices(): Promise<ListDevicesResponse>
|
|
51
53
|
capabilities(platform?: Platform): Promise<CapabilityDocument>
|
|
52
54
|
submit(body: SubmitNotificationRequestT, waitSeconds: number): Promise<SubmissionReceipt>
|
|
@@ -138,6 +140,7 @@ export function createClient(
|
|
|
138
140
|
beginPairing: (body) => call('POST', '/api/v1/pairings', body),
|
|
139
141
|
pollPairing: (pairingId, pollVerifier) =>
|
|
140
142
|
call('POST', `/api/v1/pairings/${pairingId}/poll`, { poll_verifier: pollVerifier }),
|
|
143
|
+
accessStatus: () => call('GET', '/api/v1/account/access'),
|
|
141
144
|
listDevices: () => call('GET', '/api/v1/devices'),
|
|
142
145
|
capabilities: (platform = 'ios') => call('GET', `/api/v1/capabilities/${platform}`),
|
|
143
146
|
submit: (body, waitSeconds) =>
|