@masstensor/anotify 0.0.0-stage → 0.5.9
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/LICENSE +21 -0
- package/README.md +14 -2
- package/dist/src/adapters.js +97 -0
- package/dist/src/adapters.js.map +1 -0
- package/dist/src/claude-question-wait.js +67 -0
- package/dist/src/claude-question-wait.js.map +1 -0
- package/dist/src/claude-question.js +22 -0
- package/dist/src/claude-question.js.map +1 -0
- package/dist/src/claude.js +23 -0
- package/dist/src/claude.js.map +1 -0
- package/dist/src/cli.js +294 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/codex-readiness.js +37 -0
- package/dist/src/codex-readiness.js.map +1 -0
- package/dist/src/config.js +21 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/crypto.js +26 -0
- package/dist/src/crypto.js.map +1 -0
- package/dist/src/daemon.js +356 -0
- package/dist/src/daemon.js.map +1 -0
- package/dist/src/demand.js +89 -0
- package/dist/src/demand.js.map +1 -0
- package/dist/src/device-name.js +12 -0
- package/dist/src/device-name.js.map +1 -0
- package/dist/src/http.js +38 -0
- package/dist/src/http.js.map +1 -0
- package/dist/src/install.js +254 -0
- package/dist/src/install.js.map +1 -0
- package/dist/src/integration.js +184 -0
- package/dist/src/integration.js.map +1 -0
- package/dist/src/mcp.js +48 -0
- package/dist/src/mcp.js.map +1 -0
- package/dist/src/native-owner.js +44 -0
- package/dist/src/native-owner.js.map +1 -0
- package/dist/src/npm-cli.js +24 -0
- package/dist/src/npm-cli.js.map +1 -0
- package/dist/src/owners.js +57 -0
- package/dist/src/owners.js.map +1 -0
- package/dist/src/pairing-cli.js +125 -0
- package/dist/src/pairing-cli.js.map +1 -0
- package/dist/src/pairing.js +57 -0
- package/dist/src/pairing.js.map +1 -0
- package/dist/src/preferences.js +18 -0
- package/dist/src/preferences.js.map +1 -0
- package/dist/src/protocol.js +40 -0
- package/dist/src/protocol.js.map +1 -0
- package/dist/src/question-crypto.js +29 -0
- package/dist/src/question-crypto.js.map +1 -0
- package/dist/src/readiness.js +58 -0
- package/dist/src/readiness.js.map +1 -0
- package/dist/src/service.js +57 -0
- package/dist/src/service.js.map +1 -0
- package/dist/src/sessions.js +217 -0
- package/dist/src/sessions.js.map +1 -0
- package/dist/src/setup-cli.js +59 -0
- package/dist/src/setup-cli.js.map +1 -0
- package/dist/src/terminal.js +68 -0
- package/dist/src/terminal.js.map +1 -0
- package/dist/src/vault.js +49 -0
- package/dist/src/vault.js.map +1 -0
- package/dist/src/wizard.js +43 -0
- package/dist/src/wizard.js.map +1 -0
- package/npm-shrinkwrap.json +1919 -0
- package/package.json +26 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Agent Notify contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Agent Notify
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Encrypted notifications from Claude Code and Codex to your iPhone. Install the iPhone app and Node.js 22.18 or later first.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npx --package=@masstensor/anotify anotify install
|
|
7
|
+
~/.local/bin/anotify pair
|
|
8
|
+
claude
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Run `~/.local/bin/anotify` to manage notifications and connections. Use `anotify deliveries` to inspect recent delivery states. Run `anotify uninstall` to remove the integration while preserving encrypted pairing and history on your Mac. Agents must load and trust the installed hooks. In standalone Claude Code and Codex CLI sessions, /clear keeps notifications on by default and carries an explicit mute forward. Agent Notify verifies the same native process and its start time; it never guesses from the project directory. If the predecessor cannot be verified (including shared desktop/server processes), the new session stays muted for safety.
|
|
12
|
+
|
|
13
|
+
Permission alerts are off by default. Use `anotify permission-alerts on` to opt in. On iPhone, choose Show message previews to see the agent and project; decryption stays on your phone.
|
|
14
|
+
|
|
15
|
+
The iPhone app is currently available to internal testers. Learn more at https://anotify.fenginwind.com/quick-start.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { basename } from 'node:path';
|
|
3
|
+
import { hash } from './protocol.js';
|
|
4
|
+
const textId = z.string().min(1).max(200);
|
|
5
|
+
const common = z.object({ hook_event_name: textId, session_id: textId, agent_id: z.string().optional() }).passthrough();
|
|
6
|
+
export const bindingKey = (host, call) => (host === 'claude-code' ? 'claude' : host) + ':' + call;
|
|
7
|
+
/** Extract only host-provided identity. The model's arguments never choose a session. */
|
|
8
|
+
export function nativeBinding(host, meta) {
|
|
9
|
+
if (host === 'claude-code')
|
|
10
|
+
return { capability: bindingKey(host, textId.parse(meta?.['claudecode/toolUseId'])) };
|
|
11
|
+
if (host !== 'codex')
|
|
12
|
+
throw new Error('Gemini requires a BeforeTool capability');
|
|
13
|
+
const raw = z.object({ threadId: textId, sessionId: textId, callId: textId, 'x-codex-turn-metadata': z.object({ turn_id: textId, session_id: textId, thread_id: textId, thread_source: z.literal('user') }) }).parse(meta);
|
|
14
|
+
const turn = raw['x-codex-turn-metadata'];
|
|
15
|
+
if (raw.threadId !== raw.sessionId || raw.threadId !== turn.thread_id || raw.sessionId !== turn.session_id)
|
|
16
|
+
throw new Error('Conflicting Codex session metadata');
|
|
17
|
+
return { capability: bindingKey(host, raw.callId), context: { host, sessionId: raw.threadId, turnId: turn.turn_id } };
|
|
18
|
+
}
|
|
19
|
+
export async function hostHook(host, instance, input, sessions, post) {
|
|
20
|
+
const raw = common.parse(input);
|
|
21
|
+
if (raw.agent_id)
|
|
22
|
+
return {};
|
|
23
|
+
const session = raw.session_id;
|
|
24
|
+
if (raw.hook_event_name === 'SessionStart') {
|
|
25
|
+
sessions.start(instance, host, session, z.string().parse(raw.source));
|
|
26
|
+
return {};
|
|
27
|
+
}
|
|
28
|
+
if (raw.hook_event_name === 'SessionEnd') {
|
|
29
|
+
sessions.end(instance, host, session, z.string().parse(raw.reason));
|
|
30
|
+
return {};
|
|
31
|
+
}
|
|
32
|
+
sessions.assertSession(instance, host, session);
|
|
33
|
+
const project = typeof raw.cwd === 'string' && raw.cwd ? { project: basename(raw.cwd).slice(0, 200) } : {};
|
|
34
|
+
if (host === 'claude-code' && raw.hook_event_name === 'PreToolUse' && raw.tool_name === 'AskUserQuestion') {
|
|
35
|
+
const input = z.object({ questions: z.array(z.object({ question: z.string().min(1).max(2000), options: z.array(z.object({ label: z.string().max(200) })).max(4).optional() })).min(1).max(4) }).parse(raw.tool_input);
|
|
36
|
+
const turnId = textId.parse(raw.prompt_id), callId = textId.parse(raw.tool_use_id);
|
|
37
|
+
const markdown = input.questions.map(q => q.question + (q.options?.length ? '\n' + q.options.map(o => '• ' + o.label).join('\n') : '')).join('\n\n') + '\n\nAction needed: answer in Claude Code on your Mac. This notification does not answer or approve anything.';
|
|
38
|
+
await post('/event', { context: { host, sessionId: session, turnId }, source: 'hook', callId, content: { kind: 'action_required', title: 'Your answer is needed', markdown, ...project } });
|
|
39
|
+
return {};
|
|
40
|
+
}
|
|
41
|
+
const notification = raw.hook_event_name === 'Notification' && ((host === 'claude-code' && raw.notification_type === 'permission_prompt') || (host === 'gemini-cli' && raw.notification_type === 'ToolPermission'));
|
|
42
|
+
const approval = host === 'codex' && raw.hook_event_name === 'PermissionRequest';
|
|
43
|
+
if (raw.hook_event_name === 'Notification' && !notification)
|
|
44
|
+
return {};
|
|
45
|
+
if (notification || approval) {
|
|
46
|
+
const turnId = host === 'gemini-cli' ? sessions.currentTurn(instance, session, z.string().parse(raw.timestamp)) : textId.parse(host === 'claude-code' ? raw.prompt_id : raw.turn_id);
|
|
47
|
+
const message = approval ? `Codex 请求审批:${textId.parse(raw.tool_name)}` : z.string().min(1).max(4000).parse(raw.message);
|
|
48
|
+
// Some hosts do not expose an approval ID. Equal native request details in
|
|
49
|
+
// one turn deliberately coalesce; never infer the session from the text.
|
|
50
|
+
const callId = hash(JSON.stringify([raw.hook_event_name, raw.notification_type, raw.tool_name, raw.tool_input, raw.message, raw.details]));
|
|
51
|
+
await post('/event', { context: { host, sessionId: session, turnId }, source: 'hook', callId, content: { kind: 'attention', title: approval ? 'Agent 正在请求权限' : 'Agent 等待权限确认', markdown: `${message}\n\n请回到电脑端检查并批准或拒绝。手机不会自动批准操作。\n\n请求可能已被处理,请以电脑端当前状态为准。`, ...project } });
|
|
52
|
+
return {};
|
|
53
|
+
}
|
|
54
|
+
if (host === 'gemini-cli') {
|
|
55
|
+
const timestamp = z.string().parse(raw.timestamp);
|
|
56
|
+
if (raw.hook_event_name === 'BeforeAgent') {
|
|
57
|
+
sessions.begin(instance, session, timestamp, z.string().parse(raw.prompt));
|
|
58
|
+
return {};
|
|
59
|
+
}
|
|
60
|
+
if (raw.hook_event_name === 'BeforeTool') {
|
|
61
|
+
const tool = z.object({ tool_name: z.string(), tool_input: z.record(z.string(), z.unknown()), mcp_context: z.object({ server_name: z.literal('agent_notify'), tool_name: z.enum(['notify', 'set_notification_policy']) }) }).safeParse(raw);
|
|
62
|
+
if (!tool.success) {
|
|
63
|
+
if (String(raw.tool_name).startsWith('mcp_agent_notify_'))
|
|
64
|
+
throw new Error('Gemini notification tool lacks verified MCP context');
|
|
65
|
+
return {};
|
|
66
|
+
}
|
|
67
|
+
const turnId = sessions.currentTurn(instance, session, timestamp);
|
|
68
|
+
const { _notifyCapability: _ignored, ...argumentsWithoutCapability } = tool.data.tool_input;
|
|
69
|
+
const signature = hash(JSON.stringify([tool.data.mcp_context.tool_name, argumentsWithoutCapability]));
|
|
70
|
+
const capability = await sessions.toolCapability(instance, session, timestamp, signature, async () => (await post('/bind', { host, sessionId: session, turnId })).capability);
|
|
71
|
+
return { hookSpecificOutput: { hookEventName: 'BeforeTool', tool_input: { ...tool.data.tool_input, _notifyCapability: capability } } };
|
|
72
|
+
}
|
|
73
|
+
if (raw.hook_event_name === 'AfterAgent') {
|
|
74
|
+
const stop = z.object({ prompt_response: z.string().min(1), stop_hook_active: z.boolean() }).parse(raw);
|
|
75
|
+
const turnId = sessions.finish(instance, session, timestamp, stop.stop_hook_active);
|
|
76
|
+
if (!sessions.latestEnding(instance, session, timestamp, turnId))
|
|
77
|
+
return {};
|
|
78
|
+
await post('/event', { context: { host, sessionId: session, turnId }, source: 'hook', content: { kind: 'turn_end', title: '本轮处理结束', markdown: stop.prompt_response, ...project } });
|
|
79
|
+
}
|
|
80
|
+
return {};
|
|
81
|
+
}
|
|
82
|
+
if (!['PreToolUse', 'Stop'].includes(raw.hook_event_name))
|
|
83
|
+
return {};
|
|
84
|
+
const turnId = textId.parse(host === 'claude-code' ? raw.prompt_id : raw.turn_id);
|
|
85
|
+
const context = { host, sessionId: session, turnId };
|
|
86
|
+
if (raw.hook_event_name === 'PreToolUse') {
|
|
87
|
+
const tool = z.object({ tool_use_id: textId, tool_name: z.string() }).parse(raw);
|
|
88
|
+
if (/^mcp__agent_notify__(notify|set_notification_policy)$/.test(tool.tool_name))
|
|
89
|
+
await post('/bind', { ...context, call: tool.tool_use_id });
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
const stop = z.object({ last_assistant_message: z.string().min(1) }).parse(raw);
|
|
93
|
+
await post('/event', { context, source: 'hook', content: { kind: 'turn_end', title: '本轮处理结束', markdown: stop.last_assistant_message, ...project } });
|
|
94
|
+
}
|
|
95
|
+
return {};
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=adapters.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapters.js","sourceRoot":"","sources":["../../src/adapters.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { setTimeout as sleep } from 'node:timers/promises';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { encryptedAnswerSchema, openAnswer } from './question-crypto.js';
|
|
4
|
+
import { claudeAnswerOutput } from './claude-question.js';
|
|
5
|
+
import { request } from './http.js';
|
|
6
|
+
const localSchema = z.object({ status: z.string(), header: z.object({ v: z.literal(1), deviceId: z.string().uuid(), senderId: z.string().uuid(), messageId: z.string().uuid(), revision: z.number(), expiresAt: z.number() }) });
|
|
7
|
+
const relaySchema = z.object({ state: z.string(), answer: encryptedAnswerSchema.optional() });
|
|
8
|
+
export async function waitForClaudeAnswer(input, messageId, post, config, signal, active) {
|
|
9
|
+
let opened = false, completed = false;
|
|
10
|
+
const url = config.relay + '/v1/questions/' + messageId;
|
|
11
|
+
try {
|
|
12
|
+
const maximum = Date.now() + 240000;
|
|
13
|
+
let header;
|
|
14
|
+
while (Date.now() < maximum && !signal?.aborted) {
|
|
15
|
+
if (active && !await active())
|
|
16
|
+
throw Error('Question session is no longer active');
|
|
17
|
+
const local = localSchema.parse(await post('/question/status', { messageId }));
|
|
18
|
+
if (local.header.deviceId !== config.deviceId || local.header.senderId !== config.senderId || local.header.messageId !== messageId)
|
|
19
|
+
throw Error('Question destination changed');
|
|
20
|
+
if (local.header.expiresAt <= Date.now() || ['expired', 'superseded'].includes(local.status))
|
|
21
|
+
throw Error('Question expired');
|
|
22
|
+
if (local.status === 'relay_accepted') {
|
|
23
|
+
header = local.header;
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
await sleep(1000, undefined, { signal });
|
|
27
|
+
}
|
|
28
|
+
if (!header)
|
|
29
|
+
throw Error('Question was not delivered to relay');
|
|
30
|
+
await request(config.relay + '/v1/questions', config.senderToken, 'POST', { questionId: messageId });
|
|
31
|
+
opened = true;
|
|
32
|
+
const expected = { v: 1, deviceId: header.deviceId, senderId: header.senderId, questionId: messageId, expiresAt: header.expiresAt };
|
|
33
|
+
while (Date.now() < Math.min(maximum, header.expiresAt) && !signal?.aborted) {
|
|
34
|
+
if (active && !await active())
|
|
35
|
+
throw Error('Question session is no longer active');
|
|
36
|
+
let result;
|
|
37
|
+
try {
|
|
38
|
+
result = relaySchema.parse(await request(url, config.senderToken));
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
await sleep(2000, undefined, { signal });
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (result.state === 'answered' && result.answer) {
|
|
45
|
+
const answers = await openAnswer(result.answer, expected, config.keys, config.recipientPublicKey);
|
|
46
|
+
const output = claudeAnswerOutput(input, answers);
|
|
47
|
+
if (signal?.aborted || (active && !await active()))
|
|
48
|
+
throw Error('Question session is no longer active');
|
|
49
|
+
await request(url + '/complete', config.senderToken, 'POST', {});
|
|
50
|
+
completed = true;
|
|
51
|
+
return output;
|
|
52
|
+
}
|
|
53
|
+
if (result.state !== 'open')
|
|
54
|
+
throw Error('Question is no longer open');
|
|
55
|
+
await sleep(2000, undefined, { signal });
|
|
56
|
+
}
|
|
57
|
+
throw Error('Question timed out');
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
if (opened && !completed)
|
|
61
|
+
try {
|
|
62
|
+
await request(url + '/cancel', config.senderToken, 'POST', {});
|
|
63
|
+
}
|
|
64
|
+
catch { }
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=claude-question-wait.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-question-wait.js","sourceRoot":"","sources":["../../src/claude-question-wait.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { questionSchema } from './protocol.js';
|
|
3
|
+
const nativeQuestion = z.object({ question: z.string().min(1).max(2000), header: z.string().max(200), options: z.array(z.object({ label: z.string().min(1).max(200), description: z.string().max(1000) }).passthrough()).min(2).max(4), multiSelect: z.boolean().optional() }).passthrough();
|
|
4
|
+
export const claudeInputSchema = z.object({ questions: z.array(nativeQuestion).min(1).max(4) }).passthrough().refine(value => new Set(value.questions.map(q => q.question)).size === value.questions.length, 'Duplicate question text');
|
|
5
|
+
export function normalizeClaudeQuestions(input) {
|
|
6
|
+
const parsed = claudeInputSchema.parse(input);
|
|
7
|
+
return parsed.questions.map((q, i) => questionSchema.parse({ id: 'q' + i, question: q.question, header: q.header, options: q.options.map(o => ({ label: o.label, description: o.description })), multiSelect: q.multiSelect ?? false }));
|
|
8
|
+
}
|
|
9
|
+
export function claudeAnswerOutput(input, answers) {
|
|
10
|
+
const parsed = claudeInputSchema.parse(input);
|
|
11
|
+
if (answers.length !== parsed.questions.length || new Set(answers.map(a => a.id)).size !== answers.length)
|
|
12
|
+
throw Error('Answer set does not match questions');
|
|
13
|
+
const output = {};
|
|
14
|
+
for (const [i, q] of parsed.questions.entries()) {
|
|
15
|
+
const answer = answers.find(a => a.id === 'q' + i);
|
|
16
|
+
if (!answer || answer.values.length < 1 || (!q.multiSelect && answer.values.length !== 1) || answer.values.some(v => !v.trim() || v.length > 4000))
|
|
17
|
+
throw Error('Invalid question answer');
|
|
18
|
+
output[q.question] = answer.values.join(', ');
|
|
19
|
+
}
|
|
20
|
+
return { hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'allow', updatedInput: { ...parsed, answers: output } } };
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=claude-question.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-question.js","sourceRoot":"","sources":["../../src/claude-question.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
const common = z.object({ hook_event_name: z.string(), session_id: z.string().min(1), prompt_id: z.string().min(1).optional(), agent_id: z.string().optional() }).passthrough();
|
|
3
|
+
export async function claudeHook(input, post) {
|
|
4
|
+
const raw = common.parse(input);
|
|
5
|
+
if (raw.agent_id)
|
|
6
|
+
return;
|
|
7
|
+
if (!['PreToolUse', 'Stop'].includes(raw.hook_event_name))
|
|
8
|
+
return;
|
|
9
|
+
if (!raw.prompt_id)
|
|
10
|
+
throw Error('Claude hook lacks prompt_id; refusing to guess the turn');
|
|
11
|
+
const context = { host: 'claude-code', sessionId: raw.session_id, turnId: raw.prompt_id };
|
|
12
|
+
if (raw.hook_event_name === 'PreToolUse') {
|
|
13
|
+
const tool = z.object({ tool_use_id: z.string().min(1), tool_name: z.string() }).parse(raw);
|
|
14
|
+
if (!/^mcp__agent_notify__/.test(tool.tool_name))
|
|
15
|
+
return;
|
|
16
|
+
await post('/claude-bind', { context, toolUseId: tool.tool_use_id });
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
const stop = z.object({ last_assistant_message: z.string().min(1) }).parse(raw);
|
|
20
|
+
await post('/event', { context, source: 'hook', content: { kind: 'turn_end', title: '本轮处理结束', markdown: stop.last_assistant_message } });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=claude.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude.js","sourceRoot":"","sources":["../../src/claude.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,MAAM,MAAM,GAAC,CAAC,CAAC,MAAM,CAAC,EAAC,eAAe,EAAC,CAAC,CAAC,MAAM,EAAE,EAAC,UAAU,EAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAC,SAAS,EAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,EAAC,QAAQ,EAAC,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,EAAC,CAAC,CAAC,WAAW,EAAE,CAAC;AACrK,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,KAAa,EAAC,IAAiD;IAC/F,MAAM,GAAG,GAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC9B,IAAG,GAAG,CAAC,QAAQ;QAAC,OAAO;IACvB,IAAG,CAAC,CAAC,YAAY,EAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,eAAe,CAAC;QAAC,OAAO;IAC/D,IAAG,CAAC,GAAG,CAAC,SAAS;QAAC,MAAM,KAAK,CAAC,yDAAyD,CAAC,CAAC;IACzF,MAAM,OAAO,GAAC,EAAC,IAAI,EAAC,aAAa,EAAC,SAAS,EAAC,GAAG,CAAC,UAAU,EAAC,MAAM,EAAC,GAAG,CAAC,SAAS,EAAC,CAAC;IACjF,IAAG,GAAG,CAAC,eAAe,KAAG,YAAY,EAAC,CAAC;QACtC,MAAM,IAAI,GAAC,CAAC,CAAC,MAAM,CAAC,EAAC,WAAW,EAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAC,SAAS,EAAC,CAAC,CAAC,MAAM,EAAE,EAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACrF,IAAG,CAAC,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC;YAAC,OAAO;QACvD,MAAM,IAAI,CAAC,cAAc,EAAC,EAAC,OAAO,EAAC,SAAS,EAAC,IAAI,CAAC,WAAW,EAAC,CAAC,CAAC;IACjE,CAAC;SAAI,CAAC;QACL,MAAM,IAAI,GAAC,CAAC,CAAC,MAAM,CAAC,EAAC,sBAAsB,EAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC3E,MAAM,IAAI,CAAC,QAAQ,EAAC,EAAC,OAAO,EAAC,MAAM,EAAC,MAAM,EAAC,OAAO,EAAC,EAAC,IAAI,EAAC,UAAU,EAAC,KAAK,EAAC,QAAQ,EAAC,QAAQ,EAAC,IAAI,CAAC,sBAAsB,EAAC,EAAC,CAAC,CAAC;IAC5H,CAAC;AACF,CAAC"}
|
package/dist/src/cli.js
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { basename, join, resolve } from 'node:path';
|
|
4
|
+
import { spawn } from 'node:child_process';
|
|
5
|
+
import { Vault } from './vault.js';
|
|
6
|
+
import { Daemon } from './daemon.js';
|
|
7
|
+
import { token, hostSchema } from './protocol.js';
|
|
8
|
+
import { request, closeServer } from './http.js';
|
|
9
|
+
import { serveMcp } from './mcp.js';
|
|
10
|
+
import { hostConfig } from './config.js';
|
|
11
|
+
import { uninstall, installationStatus } from './install.js';
|
|
12
|
+
import { serviceLease } from './service.js';
|
|
13
|
+
import { demandPost, ensureService, stopService } from './demand.js';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
import { processOwner } from './owners.js';
|
|
16
|
+
import { nativeHookOwner } from './native-owner.js';
|
|
17
|
+
import { setupCLI } from './setup-cli.js';
|
|
18
|
+
import { HELP, terminal, menu, formatStatus } from './terminal.js';
|
|
19
|
+
import { loadPreferences } from './preferences.js';
|
|
20
|
+
import { inspectAgents } from './readiness.js';
|
|
21
|
+
import { layout } from './install.js';
|
|
22
|
+
import { pairPhone } from './pairing-cli.js';
|
|
23
|
+
const hostName = (value) => hostSchema.parse(value === 'claude' ? 'claude-code' : value === 'gemini' ? 'gemini-cli' : value);
|
|
24
|
+
async function main(input = process.argv.slice(2), io) {
|
|
25
|
+
let [command, ...args] = input;
|
|
26
|
+
if (['--help', '-h', 'help'].includes(command ?? '')) {
|
|
27
|
+
console.log(HELP);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (!command || command === 'menu') {
|
|
31
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
32
|
+
console.log(HELP);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const screen = terminal();
|
|
36
|
+
try {
|
|
37
|
+
await menu(screen, args => main(args, screen));
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
screen.close();
|
|
41
|
+
}
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (process.env.AGENT_NOTIFY_MANAGED === '1' && ['claude', 'codex', 'gemini'].includes(command ?? '')) {
|
|
45
|
+
args = [command, ...args];
|
|
46
|
+
command = 'run';
|
|
47
|
+
}
|
|
48
|
+
if (command === 'install' || command === 'setup') {
|
|
49
|
+
await setupCLI(args, io);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (command === 'uninstall') {
|
|
53
|
+
console.log(JSON.stringify(await uninstall(), null, 2));
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (command === 'status') {
|
|
57
|
+
const status = await installationStatus();
|
|
58
|
+
console.log(args.includes('--json') ? JSON.stringify(status, null, 2) : formatStatus(status));
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const dir = resolve(process.env.AGENT_NOTIFY_HOME || join(homedir(), '.agent-notify'));
|
|
62
|
+
if (command === 'config') {
|
|
63
|
+
console.log(JSON.stringify(hostConfig(hostName(args[0])), null, 2));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (command === 'relay') {
|
|
67
|
+
const { Relay } = await import('./relay.js');
|
|
68
|
+
const { ApplePush } = await import('./apns.js');
|
|
69
|
+
const relay = new Relay(join(dir, 'relay.sqlite'), ApplePush.fromEnvironment());
|
|
70
|
+
const s = await relay.start(Number(process.env.PORT ?? 8788), process.env.BIND ?? '127.0.0.1');
|
|
71
|
+
console.log('Relay listening:', s.url);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const vault = new Vault(dir, { developmentFileKey: !process.env.AGENT_NOTIFY_MANAGED && !command?.startsWith('installed-') && process.env.AGENT_NOTIFY_DEV_FILE_KEY === '1' });
|
|
75
|
+
const ownCli = fileURLToPath(import.meta.url);
|
|
76
|
+
if (command === 'doctor') {
|
|
77
|
+
const installation = await installationStatus(), settings = loadPreferences(dir), sender = vault.load('sender');
|
|
78
|
+
let relayReachable = false;
|
|
79
|
+
if (sender)
|
|
80
|
+
try {
|
|
81
|
+
relayReachable = (await fetch(sender.relay + '/health', { signal: AbortSignal.timeout(10000) })).ok;
|
|
82
|
+
}
|
|
83
|
+
catch { }
|
|
84
|
+
const agents = await inspectAgents(homedir(), layout().current, settings.hosts, process.cwd());
|
|
85
|
+
const report = { installation, paired: !!sender, relayReachable, agents, delivery: 'not_verified', hint: !sender ? 'Run anotify pair.' : !relayReachable ? 'Check your network and relay health.' : 'Run anotify test to check delivery. Model authentication is not tested by doctor.' };
|
|
86
|
+
console.log(args.includes('--json') ? JSON.stringify(report, null, 2) : ['Agent Notify diagnostics', formatStatus(installation), ...agents.map(agent => agent.host + ': ' + agent.state + ' — ' + agent.hint), 'Relay reachable: ' + relayReachable, report.hint].join('\n'));
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (command === 'stop') {
|
|
90
|
+
await stopService(vault);
|
|
91
|
+
console.log('On-demand service stopped; pairing and queued messages are preserved.');
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (command === 'restart') {
|
|
95
|
+
await stopService(vault);
|
|
96
|
+
await ensureService(vault, ownCli);
|
|
97
|
+
console.log('On-demand service ready; it will exit when idle.');
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (command === 'daemon') {
|
|
101
|
+
const idleSeconds = args[0] === '--idle-seconds' ? Number(args[1]) : 60;
|
|
102
|
+
if (!Number.isFinite(idleSeconds) || idleSeconds < 0.1)
|
|
103
|
+
throw Error('Invalid idle timeout');
|
|
104
|
+
const lease = serviceLease(dir);
|
|
105
|
+
const daemon = new Daemon(vault);
|
|
106
|
+
const auth = token();
|
|
107
|
+
let stopping = false;
|
|
108
|
+
let server;
|
|
109
|
+
const stop = async () => { if (stopping)
|
|
110
|
+
return; stopping = true; server.stop(); await closeServer(server.server); await daemon.idle(); daemon.db.close(); lease.close(); process.exit(0); };
|
|
111
|
+
try {
|
|
112
|
+
server = await daemon.start(auth, Number(process.env.PORT ?? 0), { idleMs: idleSeconds * 1000, stop: () => void stop() });
|
|
113
|
+
vault.save('connection', { url: server.url, auth });
|
|
114
|
+
}
|
|
115
|
+
catch (e) {
|
|
116
|
+
daemon.db.close();
|
|
117
|
+
lease.close();
|
|
118
|
+
throw e;
|
|
119
|
+
}
|
|
120
|
+
process.on('SIGTERM', () => void stop());
|
|
121
|
+
process.on('SIGINT', () => void stop());
|
|
122
|
+
console.log('Local notification service ready:', server.url);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (command === 'pair') {
|
|
126
|
+
if (args.length && !(args.length === 2 && args[0] === '--relay') && !(args.length === 1 && args[0] === '--cancel'))
|
|
127
|
+
throw Error('Use anotify pair [--relay https://your-relay]. Old invitation URLs are no longer accepted.');
|
|
128
|
+
if (io) {
|
|
129
|
+
await pairPhone(vault, io, args[1], args[0] === '--cancel');
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY)
|
|
133
|
+
throw Error('Run anotify pair in an interactive terminal to compare verification codes.');
|
|
134
|
+
const screen = terminal();
|
|
135
|
+
try {
|
|
136
|
+
await pairPhone(vault, screen, args[1], args[0] === '--cancel');
|
|
137
|
+
}
|
|
138
|
+
finally {
|
|
139
|
+
screen.close();
|
|
140
|
+
}
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const lazy = process.env.AGENT_NOTIFY_MANAGED === '1' || command?.startsWith('installed-') || ['test', 'deliveries', 'notifications', 'pause', 'resume', 'sessions', 'session'].includes(command ?? '');
|
|
144
|
+
if (command === 'run' && !loadPreferences(dir).hosts.includes(hostName(args[0])))
|
|
145
|
+
throw Error('This agent is not selected in setup. Run anotify setup to connect it.');
|
|
146
|
+
const connection = vault.load('connection');
|
|
147
|
+
if (!connection && !lazy)
|
|
148
|
+
throw Error('Start the daemon first');
|
|
149
|
+
const post = (path, body) => {
|
|
150
|
+
if ((path === '/event' || path === '/mcp') && body && typeof body === 'object' && 'content' in body) {
|
|
151
|
+
const event = body;
|
|
152
|
+
body = { ...event, content: { ...event.content, project: event.content.project ?? basename(process.cwd()).slice(0, 200) } };
|
|
153
|
+
}
|
|
154
|
+
if (lazy)
|
|
155
|
+
return demandPost(vault, ownCli, path, body);
|
|
156
|
+
const current = vault.load('connection');
|
|
157
|
+
if (!current)
|
|
158
|
+
throw Error('Local service connection is missing');
|
|
159
|
+
return request(current.url + path, current.auth, 'POST', body);
|
|
160
|
+
};
|
|
161
|
+
if (command === 'test') {
|
|
162
|
+
const result = await post('/test', {});
|
|
163
|
+
console.log(args.includes('--json') ? JSON.stringify(result, null, 2) : 'Encrypted test queued. Check your iPhone; queuing does not confirm phone display.');
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (command === 'deliveries') {
|
|
167
|
+
const result = await post('/deliveries', {});
|
|
168
|
+
if (args.includes('--json')) {
|
|
169
|
+
console.log(JSON.stringify(result, null, 2));
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const labels = { queued: 'Waiting on this Mac', relay_accepted: 'Received by relay', apns_accepted: 'Accepted by Apple', retry: 'Relay retrying', push_unconfigured: 'Push not configured', invalid_token: 'Phone token invalid', expired: 'Expired', superseded: 'Superseded', unavailable: 'Relay status unavailable', revision_mismatch: 'Relay has a different revision', previous_pairing: 'From an earlier pairing' };
|
|
173
|
+
console.log(result.deliveries.length ? result.deliveries.map(d => `${d.id.slice(0, 8)} ${d.source ?? 'unknown'} / ${d.kind ?? 'message'} ${labels[d.relayStatus ?? d.localStatus] ?? 'Status unavailable'}`).join('\n') : 'No recent deliveries.');
|
|
174
|
+
console.log('Queued messages are being retried while the helper is awake. Apple acceptance does not confirm that the iPhone displayed the notification.');
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (command === 'permission-alerts') {
|
|
178
|
+
if (!['on', 'off'].includes(args[0] ?? ''))
|
|
179
|
+
throw Error('Use anotify permission-alerts on|off');
|
|
180
|
+
await post('/preferences/change', { permissionAlerts: args[0] === 'on' });
|
|
181
|
+
console.log('Permission alerts ' + args[0] + '.');
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (command === 'notifications' || command === 'pause' || command === 'resume') {
|
|
185
|
+
if (command === 'notifications' && !['on', 'off'].includes(args[0] ?? ''))
|
|
186
|
+
throw Error('Use anotify notifications on|off. Session overrides are preserved.');
|
|
187
|
+
const patch = command === 'notifications' ? { defaultMode: args[0] === 'on' ? 'all' : 'off' } : { paused: command === 'pause' };
|
|
188
|
+
const result = await post('/preferences/change', patch);
|
|
189
|
+
if (args.includes('--json')) {
|
|
190
|
+
console.log(JSON.stringify(result, null, 2));
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
console.log(command === 'notifications' ? 'Default saved. Existing explicit session overrides are kept.' : command === 'pause' ? 'Automatic notifications paused. Explicitly requested messages remain available.' : 'Automatic notifications resumed with the previous session settings.');
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
if (command === 'sessions' || command === 'session') {
|
|
197
|
+
const result = await post('/clients/list', {});
|
|
198
|
+
const rows = result.clients;
|
|
199
|
+
if (command === 'sessions' && !args.includes('--select')) {
|
|
200
|
+
if (args.includes('--json'))
|
|
201
|
+
console.log(JSON.stringify(result, null, 2));
|
|
202
|
+
else
|
|
203
|
+
console.log(rows.length ? rows.map(row => row.id.slice(0, 8) + ' ' + row.host + ' ' + (row.mode ?? 'starting') + ' ' + (row.sessionId ?? 'no session yet')).join('\n') : 'No registered sessions. Start your agent normally after trusting its hooks.');
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
let id = args[0], mode = args[1];
|
|
207
|
+
if (command === 'sessions') {
|
|
208
|
+
if (!io)
|
|
209
|
+
throw Error('Open anotify to select a session, or use anotify session <id> on|off|once.');
|
|
210
|
+
if (!rows.length) {
|
|
211
|
+
io.write('No registered sessions.');
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
rows.forEach((row, index) => io.write((index + 1) + ') ' + row.host + ' · ' + (row.sessionId?.slice(0, 12) ?? 'starting') + ' · ' + (row.mode ?? 'starting')));
|
|
215
|
+
const selected = Number(await io.ask('Session number (0 to cancel): '));
|
|
216
|
+
if (!selected)
|
|
217
|
+
return;
|
|
218
|
+
if (!Number.isInteger(selected) || !rows[selected - 1])
|
|
219
|
+
throw Error('Invalid session number');
|
|
220
|
+
id = rows[selected - 1].id;
|
|
221
|
+
mode = (await io.ask('Mode: on, off, once (next completion, including a running task): ')).trim();
|
|
222
|
+
}
|
|
223
|
+
if (!id || !['on', 'off', 'once'].includes(mode ?? ''))
|
|
224
|
+
throw Error('Use anotify session <id> on|off|once');
|
|
225
|
+
const found = rows.filter(row => row.id.startsWith(id));
|
|
226
|
+
if (found.length !== 1)
|
|
227
|
+
throw Error('Session ID is missing or ambiguous. Run anotify sessions.');
|
|
228
|
+
const selected = found[0];
|
|
229
|
+
if (!selected.sessionId)
|
|
230
|
+
throw Error('This agent has not started its native session yet. Try again shortly.');
|
|
231
|
+
const saved = await post('/clients/policy', { instanceId: selected.id, expectedSessionId: selected.sessionId, mode: mode === 'on' ? 'all' : mode === 'off' ? 'off' : 'once' });
|
|
232
|
+
console.log(args.includes('--json') ? JSON.stringify(saved, null, 2) : 'Saved: ' + selected.host + ' · ' + selected.sessionId.slice(0, 12) + ' → ' + mode);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
if (command === 'run') {
|
|
236
|
+
const host = hostName(args[0]);
|
|
237
|
+
const binary = host === 'claude-code' ? 'claude' : host === 'gemini-cli' ? 'gemini' : 'codex';
|
|
238
|
+
if (host === 'codex' && args.slice(1).includes('--phone-questions'))
|
|
239
|
+
throw Error('Codex phone answers are no longer supported. Start Codex with anotify codex; its notifications still work.');
|
|
240
|
+
const { instanceId } = await post('/clients/open', { host, owner: processOwner(process.pid) });
|
|
241
|
+
try {
|
|
242
|
+
const forwarded = args.slice(1);
|
|
243
|
+
const childEnv = { ...process.env, AGENT_NOTIFY_INSTANCE: instanceId, AGENT_NOTIFY_HOME: dir, AGENT_NOTIFY_DEV_FILE_KEY: process.env.AGENT_NOTIFY_DEV_FILE_KEY ?? '0' };
|
|
244
|
+
if (process.env.AGENT_NOTIFY_MANAGED === '1' && host === 'codex')
|
|
245
|
+
forwarded.push('-c', 'features.hooks=true');
|
|
246
|
+
const child = spawn(binary, forwarded, { stdio: 'inherit', env: childEnv });
|
|
247
|
+
const onInterrupt = () => child.kill('SIGINT'), onTerminate = () => child.kill('SIGTERM');
|
|
248
|
+
process.on('SIGINT', onInterrupt);
|
|
249
|
+
process.on('SIGTERM', onTerminate);
|
|
250
|
+
const done = new Promise((resolve, reject) => { child.once('error', reject); child.once('close', (code, signal) => resolve(code ?? (signal === 'SIGINT' ? 130 : 143))); });
|
|
251
|
+
try {
|
|
252
|
+
const owner = child.pid ? processOwner(child.pid) : undefined;
|
|
253
|
+
if (owner)
|
|
254
|
+
await post('/clients/owner', { instanceId, owner }).catch(() => { });
|
|
255
|
+
process.exitCode = await done;
|
|
256
|
+
}
|
|
257
|
+
finally {
|
|
258
|
+
process.off('SIGINT', onInterrupt);
|
|
259
|
+
process.off('SIGTERM', onTerminate);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
finally {
|
|
263
|
+
await post('/clients/close', { instanceId }).catch(() => { });
|
|
264
|
+
}
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (command === 'mcp' || command === 'installed-mcp') {
|
|
268
|
+
const instanceId = process.env.AGENT_NOTIFY_INSTANCE === '${AGENT_NOTIFY_INSTANCE}' ? undefined : process.env.AGENT_NOTIFY_INSTANCE;
|
|
269
|
+
await serveMcp(post, hostName(args[0] ?? 'claude'), instanceId);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
// Developer boundary: adapters must supply verified host context, never infer it from cwd or the prompt.
|
|
273
|
+
if (command === 'hook' || command === 'installed-hook') {
|
|
274
|
+
const instanceId = process.env.AGENT_NOTIFY_INSTANCE;
|
|
275
|
+
let raw = '';
|
|
276
|
+
for await (const chunk of process.stdin)
|
|
277
|
+
raw += chunk;
|
|
278
|
+
const input = JSON.parse(raw);
|
|
279
|
+
const host = hostName(args[0]), nativeOwner = instanceId ? undefined : nativeHookOwner(host);
|
|
280
|
+
const result = await post('/hook', { host, ...(instanceId ? { instanceId } : {}), ...(nativeOwner ? { nativeOwner } : {}), input });
|
|
281
|
+
console.log(JSON.stringify(result));
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (['event', 'bind', 'policy'].includes(command ?? '')) {
|
|
285
|
+
let raw = '';
|
|
286
|
+
for await (const chunk of process.stdin)
|
|
287
|
+
raw += chunk;
|
|
288
|
+
console.log(JSON.stringify(await post('/' + command, JSON.parse(raw))));
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
throw Error('Unknown command. Run anotify --help.');
|
|
292
|
+
}
|
|
293
|
+
main().catch(e => { console.error(e instanceof Error ? e.message : 'Command failed'); process.exitCode = 1; });
|
|
294
|
+
//# sourceMappingURL=cli.js.map
|