omnish 1.6.6 → 2.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 +32 -0
- package/README.md +26 -18
- package/config.example.json +1 -0
- package/dist/downloads/omnish-claude/install.sh +174 -0
- package/dist/downloads/omnish-claude/uninstall.sh +114 -0
- package/dist/downloads/omnish-claude.tar.gz +0 -0
- package/dist/downloads/omnish-cursor/install.sh +162 -0
- package/dist/downloads/omnish-cursor/uninstall.sh +107 -0
- package/dist/downloads/omnish-cursor.tar.gz +0 -0
- package/dist/index.js +424 -392
- package/dist/omnish-claude/README.md +80 -0
- package/dist/omnish-claude/hooks/omnish-notify.py +213 -0
- package/dist/omnish-claude/hooks/omnish-notify.sh +13 -0
- package/dist/omnish-claude/install.sh +174 -0
- package/dist/omnish-claude/omnish-notify.json.example +8 -0
- package/dist/omnish-claude/scripts/doctor.sh +99 -0
- package/dist/omnish-claude/skill/SKILL.md +39 -0
- package/dist/omnish-claude/skill/files-and-sharing.md +94 -0
- package/dist/omnish-claude/skill/notifications.md +113 -0
- package/dist/omnish-claude/skill/setup-paths.md +81 -0
- package/dist/omnish-claude/uninstall.sh +114 -0
- package/dist/omnish-cursor/README.md +176 -0
- package/dist/omnish-cursor/hooks/__pycache__/omnish-notify.cpython-313.pyc +0 -0
- package/dist/omnish-cursor/hooks/omnish-notify.py +563 -0
- package/dist/omnish-cursor/hooks/omnish-notify.sh +13 -0
- package/dist/omnish-cursor/hooks/omnish-session-start.sh +48 -0
- package/dist/omnish-cursor/install.sh +162 -0
- package/dist/omnish-cursor/omnish-notify.json.example +13 -0
- package/dist/omnish-cursor/rules/omnish-notify.mdc +45 -0
- package/dist/omnish-cursor/scripts/doctor.sh +126 -0
- package/dist/omnish-cursor/skill/SKILL.md +129 -0
- package/dist/omnish-cursor/skill/files-and-sharing.md +94 -0
- package/dist/omnish-cursor/skill/notifications.md +155 -0
- package/dist/omnish-cursor/skill/setup-paths.md +81 -0
- package/dist/omnish-cursor/uninstall.sh +107 -0
- package/dist/ui/assets/{index-aUJGrxrr.js → index-BwG51a2I.js} +10 -10
- package/dist/ui/index.html +16 -1
- package/package.json +12 -4
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Omnish Claude Code integration
|
|
2
|
+
|
|
3
|
+
Install Omnish in Claude Code for file sharing, mobile notifications, and Done briefs when the agent finishes useful work.
|
|
4
|
+
|
|
5
|
+
Works with any Omnish setup (standalone WhatsApp/Telegram or platform attached mode).
|
|
6
|
+
|
|
7
|
+
## Quick install
|
|
8
|
+
|
|
9
|
+
**Offline (bundled with omnish — no omnish.dev):**
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
omnish agents install claude
|
|
13
|
+
omnish agents install all
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
From allowlisted chat (when `agentInstallFromChat` is enabled):
|
|
17
|
+
|
|
18
|
+
```text
|
|
19
|
+
/agents install claude
|
|
20
|
+
/agents install all
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Online (curl):**
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
curl -fsSL https://omnish.dev/downloads/omnish-claude/install.sh | bash
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
From a local omnish clone:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
bash contrib/omnish-claude/install.sh
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Partial install:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
./install.sh --hooks-only # Stop hook + notify config
|
|
39
|
+
./install.sh --skill-only # Claude skill + doctor script only
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Requires: **Claude Code**, **python3**, **omnish** on `PATH` for hooks install.
|
|
43
|
+
|
|
44
|
+
## What it installs
|
|
45
|
+
|
|
46
|
+
| Path | Purpose |
|
|
47
|
+
| ------------------------------------------- | ---------------------------------- |
|
|
48
|
+
| `~/.claude/skills/omnish/SKILL.md` | Setup/usage skill for Claude |
|
|
49
|
+
| `~/.claude/skills/omnish/scripts/doctor.sh` | Health check script |
|
|
50
|
+
| `~/.claude/hooks/omnish-notify.py` | Stop-hook logic |
|
|
51
|
+
| `~/.claude/hooks/omnish-notify.sh` | Stop hook wrapper |
|
|
52
|
+
| `~/.claude/omnish-notify.json` | Notify config (created if missing) |
|
|
53
|
+
| `~/.claude/settings.json` | Merges `Stop` hook entry |
|
|
54
|
+
|
|
55
|
+
Restart Claude Code after install.
|
|
56
|
+
|
|
57
|
+
## Verify
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
omnish agents doctor claude
|
|
61
|
+
# or: bash ~/.claude/skills/omnish/scripts/doctor.sh
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Notify config
|
|
65
|
+
|
|
66
|
+
Same schema as Cursor — see [contrib/omnish-cursor/README.md](../omnish-cursor/README.md).
|
|
67
|
+
|
|
68
|
+
## Uninstall
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
omnish agents uninstall claude
|
|
72
|
+
# or: bash contrib/omnish-claude/uninstall.sh
|
|
73
|
+
# or: curl -fsSL https://omnish.dev/downloads/omnish-claude/uninstall.sh | bash
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Add `--with-config` to remove notify json.
|
|
77
|
+
|
|
78
|
+
## License
|
|
79
|
+
|
|
80
|
+
Same as omnish (MIT).
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Claude Code Stop hook: send a curated Omnish brief when agent work is worth notifying."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import re
|
|
9
|
+
import subprocess
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
CONFIG_PATH = Path.home() / ".claude" / "omnish-notify.json"
|
|
15
|
+
LOG_PATH = Path.home() / ".claude" / "hooks" / "omnish-notify.log"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def log(message: str) -> None:
|
|
19
|
+
try:
|
|
20
|
+
LOG_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
21
|
+
with LOG_PATH.open("a", encoding="utf-8") as handle:
|
|
22
|
+
handle.write(message.rstrip() + "\n")
|
|
23
|
+
except OSError:
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def load_config() -> dict[str, Any]:
|
|
28
|
+
defaults: dict[str, Any] = {
|
|
29
|
+
"enabled": True,
|
|
30
|
+
"peer": os.environ.get("OMNISH_CLAUDE_NOTIFY_PEER", "*"),
|
|
31
|
+
"min_assistant_chars": 120,
|
|
32
|
+
"notify_on_error": True,
|
|
33
|
+
"skip_aborted": True,
|
|
34
|
+
"include_full_chat_id": True,
|
|
35
|
+
}
|
|
36
|
+
if CONFIG_PATH.exists():
|
|
37
|
+
try:
|
|
38
|
+
defaults.update(json.loads(CONFIG_PATH.read_text(encoding="utf-8")))
|
|
39
|
+
except (OSError, json.JSONDecodeError) as exc:
|
|
40
|
+
log(f"config read failed: {exc}")
|
|
41
|
+
return defaults
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def summarize_text(text: str, limit: int = 320) -> str:
|
|
45
|
+
compact = re.sub(r"\s+", " ", text).strip()
|
|
46
|
+
if len(compact) <= limit:
|
|
47
|
+
return compact
|
|
48
|
+
return compact[: limit - 1].rstrip() + "…"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def count_tool_uses(transcript_path: str | None) -> tuple[int, int]:
|
|
52
|
+
if not transcript_path:
|
|
53
|
+
return 0, 0
|
|
54
|
+
path = Path(transcript_path)
|
|
55
|
+
if not path.is_file():
|
|
56
|
+
return 0, 0
|
|
57
|
+
tool_count = 0
|
|
58
|
+
action_count = 0
|
|
59
|
+
action_names = {"Bash", "Write", "Edit", "MultiEdit", "NotebookEdit", "Task"}
|
|
60
|
+
try:
|
|
61
|
+
for line in path.read_text(encoding="utf-8", errors="replace").splitlines():
|
|
62
|
+
line = line.strip()
|
|
63
|
+
if not line:
|
|
64
|
+
continue
|
|
65
|
+
try:
|
|
66
|
+
entry = json.loads(line)
|
|
67
|
+
except json.JSONDecodeError:
|
|
68
|
+
continue
|
|
69
|
+
if entry.get("type") != "assistant":
|
|
70
|
+
continue
|
|
71
|
+
message = entry.get("message") or {}
|
|
72
|
+
content = message.get("content")
|
|
73
|
+
if not isinstance(content, list):
|
|
74
|
+
continue
|
|
75
|
+
for item in content:
|
|
76
|
+
if not isinstance(item, dict):
|
|
77
|
+
continue
|
|
78
|
+
if item.get("type") in ("tool_use", "toolUse"):
|
|
79
|
+
tool_count += 1
|
|
80
|
+
name = item.get("name") or item.get("tool_name")
|
|
81
|
+
if name in action_names:
|
|
82
|
+
action_count += 1
|
|
83
|
+
except OSError as exc:
|
|
84
|
+
log(f"transcript read failed: {exc}")
|
|
85
|
+
return tool_count, action_count
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def should_notify(payload: dict[str, Any], cfg: dict[str, Any]) -> bool:
|
|
89
|
+
if payload.get("stop_hook_active"):
|
|
90
|
+
return False
|
|
91
|
+
|
|
92
|
+
event = str(payload.get("hook_event_name") or "Stop")
|
|
93
|
+
if event == "StopFailure" and cfg.get("notify_on_error"):
|
|
94
|
+
return True
|
|
95
|
+
|
|
96
|
+
last_msg = str(payload.get("last_assistant_message") or "")
|
|
97
|
+
assistant_chars = len(last_msg)
|
|
98
|
+
tool_count, action_count = count_tool_uses(payload.get("transcript_path"))
|
|
99
|
+
|
|
100
|
+
if action_count > 0:
|
|
101
|
+
return True
|
|
102
|
+
if tool_count >= 2:
|
|
103
|
+
return True
|
|
104
|
+
|
|
105
|
+
min_chars = int(cfg.get("min_assistant_chars") or 120)
|
|
106
|
+
if assistant_chars >= min_chars:
|
|
107
|
+
return True
|
|
108
|
+
|
|
109
|
+
return False
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def build_message(payload: dict[str, Any], cfg: dict[str, Any]) -> tuple[str, str] | None:
|
|
113
|
+
event = str(payload.get("hook_event_name") or "Stop")
|
|
114
|
+
session_id = str(payload.get("session_id") or payload.get("sessionId") or "").strip()
|
|
115
|
+
cwd = str(payload.get("cwd") or payload.get("working_directory") or "").strip()
|
|
116
|
+
last_msg = summarize_text(str(payload.get("last_assistant_message") or ""), 320)
|
|
117
|
+
|
|
118
|
+
if event == "StopFailure":
|
|
119
|
+
title = "Claude Error"
|
|
120
|
+
else:
|
|
121
|
+
title = "Claude Done"
|
|
122
|
+
|
|
123
|
+
parts: list[str] = []
|
|
124
|
+
if cfg.get("include_full_chat_id", True) and session_id:
|
|
125
|
+
parts.append(f"session {session_id}")
|
|
126
|
+
elif session_id:
|
|
127
|
+
parts.append(f"session {session_id[:8]}")
|
|
128
|
+
|
|
129
|
+
if cwd:
|
|
130
|
+
parts.append(f"cwd {Path(cwd).name}")
|
|
131
|
+
|
|
132
|
+
if last_msg:
|
|
133
|
+
parts.append(f"result: {last_msg}")
|
|
134
|
+
elif event == "StopFailure":
|
|
135
|
+
parts.append(f"result: {summarize_text(str(payload.get('error') or 'API error'), 160)}")
|
|
136
|
+
else:
|
|
137
|
+
parts.append("result: agent finished")
|
|
138
|
+
|
|
139
|
+
brief = " | ".join(parts)
|
|
140
|
+
if not brief.strip():
|
|
141
|
+
return None
|
|
142
|
+
return title, brief
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def send_notification(title: str, brief: str, peer: str) -> None:
|
|
146
|
+
destination = (peer or "*").strip() or "*"
|
|
147
|
+
env = os.environ.copy()
|
|
148
|
+
if destination != "*":
|
|
149
|
+
env["OMNISH_PEER_KEY"] = destination
|
|
150
|
+
inner = f"/sendto {destination} -t {title} -- {brief}"
|
|
151
|
+
subprocess.run(
|
|
152
|
+
["omnish", "i", "-c", inner],
|
|
153
|
+
check=False,
|
|
154
|
+
env=env,
|
|
155
|
+
stdout=subprocess.DEVNULL,
|
|
156
|
+
stderr=subprocess.DEVNULL,
|
|
157
|
+
timeout=30,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def which(name: str) -> str | None:
|
|
162
|
+
for directory in os.environ.get("PATH", "").split(os.pathsep):
|
|
163
|
+
candidate = Path(directory) / name
|
|
164
|
+
if candidate.is_file() and os.access(candidate, os.X_OK):
|
|
165
|
+
return str(candidate)
|
|
166
|
+
return None
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def main() -> int:
|
|
170
|
+
try:
|
|
171
|
+
payload = json.load(sys.stdin)
|
|
172
|
+
except json.JSONDecodeError as exc:
|
|
173
|
+
log(f"invalid hook payload: {exc}")
|
|
174
|
+
print("{}")
|
|
175
|
+
return 0
|
|
176
|
+
|
|
177
|
+
cfg = load_config()
|
|
178
|
+
if not cfg.get("enabled", True):
|
|
179
|
+
print("{}")
|
|
180
|
+
return 0
|
|
181
|
+
|
|
182
|
+
if not should_notify(payload, cfg):
|
|
183
|
+
log(f"skip notify event={payload.get('hook_event_name')} session={payload.get('session_id')}")
|
|
184
|
+
print("{}")
|
|
185
|
+
return 0
|
|
186
|
+
|
|
187
|
+
message = build_message(payload, cfg)
|
|
188
|
+
if not message:
|
|
189
|
+
print("{}")
|
|
190
|
+
return 0
|
|
191
|
+
|
|
192
|
+
title, brief = message
|
|
193
|
+
peer = str(cfg.get("peer") or "*").strip() or "*"
|
|
194
|
+
|
|
195
|
+
if not which("omnish"):
|
|
196
|
+
log("omnish not found on PATH")
|
|
197
|
+
print("{}")
|
|
198
|
+
return 0
|
|
199
|
+
|
|
200
|
+
try:
|
|
201
|
+
send_notification(title, brief, peer)
|
|
202
|
+
log(f"sent session={payload.get('session_id')} dest={peer!r} title={title!r}")
|
|
203
|
+
except subprocess.TimeoutExpired:
|
|
204
|
+
log(f"notify timeout session={payload.get('session_id')}")
|
|
205
|
+
except OSError as exc:
|
|
206
|
+
log(f"notify failed: {exc}")
|
|
207
|
+
|
|
208
|
+
print("{}")
|
|
209
|
+
return 0
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
if __name__ == "__main__":
|
|
213
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Claude Code Stop hook: send curated Omnish brief when work is worth notifying.
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
|
+
PYTHON="${OMNISH_NOTIFY_PYTHON:-python3}"
|
|
7
|
+
|
|
8
|
+
if ! command -v "$PYTHON" >/dev/null 2>&1; then
|
|
9
|
+
echo '{}'
|
|
10
|
+
exit 0
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
exec "$PYTHON" "$SCRIPT_DIR/omnish-notify.py"
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Install Omnish Claude Code bundle: Stop hook + skill into ~/.claude
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
OMNISH_CLAUDE_BUNDLE_URL="${OMNISH_CLAUDE_BUNDLE_URL:-https://omnish.dev/downloads/omnish-claude.tar.gz}"
|
|
6
|
+
_TMP_BUNDLE_DIR=""
|
|
7
|
+
|
|
8
|
+
need() {
|
|
9
|
+
command -v "$1" >/dev/null 2>&1 || {
|
|
10
|
+
echo "error: required command not found: $1" >&2
|
|
11
|
+
exit 1
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
cleanup() {
|
|
16
|
+
if [[ -n "$_TMP_BUNDLE_DIR" && -d "$_TMP_BUNDLE_DIR" ]]; then
|
|
17
|
+
rm -rf "$_TMP_BUNDLE_DIR"
|
|
18
|
+
fi
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
_SCRIPT="${BASH_SOURCE[0]:-}"
|
|
22
|
+
if [[ -n "$_SCRIPT" && -f "$_SCRIPT" ]]; then
|
|
23
|
+
ROOT="$(cd "$(dirname "$_SCRIPT")" && pwd)"
|
|
24
|
+
else
|
|
25
|
+
need curl
|
|
26
|
+
need tar
|
|
27
|
+
_TMP_BUNDLE_DIR="$(mktemp -d "${TMPDIR:-/tmp}/omnish-claude.XXXXXX")"
|
|
28
|
+
trap cleanup EXIT
|
|
29
|
+
echo "Downloading Omnish Claude Code bundle..." >&2
|
|
30
|
+
curl -fsSL "$OMNISH_CLAUDE_BUNDLE_URL" | tar -xzf - -C "$_TMP_BUNDLE_DIR"
|
|
31
|
+
if [[ ! -d "$_TMP_BUNDLE_DIR/omnish-claude" ]]; then
|
|
32
|
+
echo "error: bundle missing omnish-claude/ top-level directory" >&2
|
|
33
|
+
exit 1
|
|
34
|
+
fi
|
|
35
|
+
ROOT="$_TMP_BUNDLE_DIR/omnish-claude"
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
CLAUDE_DIR="${CLAUDE_DIR:-$HOME/.claude}"
|
|
39
|
+
HOOKS_DIR="$CLAUDE_DIR/hooks"
|
|
40
|
+
SKILL_DIR="$CLAUDE_DIR/skills/omnish"
|
|
41
|
+
SETTINGS_JSON="$CLAUDE_DIR/settings.json"
|
|
42
|
+
HOOK_CMD="$HOOKS_DIR/omnish-notify.sh"
|
|
43
|
+
|
|
44
|
+
INSTALL_HOOKS=1
|
|
45
|
+
INSTALL_SKILL=1
|
|
46
|
+
|
|
47
|
+
usage() {
|
|
48
|
+
cat <<EOF
|
|
49
|
+
Usage: $(basename "$0") [options]
|
|
50
|
+
|
|
51
|
+
Install Omnish Claude Code integration (Stop hook, skill, notify config).
|
|
52
|
+
|
|
53
|
+
Options:
|
|
54
|
+
--hooks-only Install only Stop hook + notify config
|
|
55
|
+
--skill-only Install only the omnish Claude skill (+ doctor script)
|
|
56
|
+
-h, --help Show this help
|
|
57
|
+
EOF
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
while [[ $# -gt 0 ]]; do
|
|
61
|
+
case "$1" in
|
|
62
|
+
--hooks-only)
|
|
63
|
+
INSTALL_HOOKS=1
|
|
64
|
+
INSTALL_SKILL=0
|
|
65
|
+
shift
|
|
66
|
+
;;
|
|
67
|
+
--skill-only)
|
|
68
|
+
INSTALL_HOOKS=0
|
|
69
|
+
INSTALL_SKILL=1
|
|
70
|
+
shift
|
|
71
|
+
;;
|
|
72
|
+
-h | --help)
|
|
73
|
+
usage
|
|
74
|
+
exit 0
|
|
75
|
+
;;
|
|
76
|
+
*)
|
|
77
|
+
echo "error: unknown option: $1" >&2
|
|
78
|
+
usage >&2
|
|
79
|
+
exit 1
|
|
80
|
+
;;
|
|
81
|
+
esac
|
|
82
|
+
done
|
|
83
|
+
|
|
84
|
+
if [[ "$INSTALL_HOOKS" -eq 1 ]]; then
|
|
85
|
+
need python3
|
|
86
|
+
need omnish
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
if [[ "$INSTALL_HOOKS" -eq 1 ]]; then
|
|
90
|
+
mkdir -p "$HOOKS_DIR"
|
|
91
|
+
|
|
92
|
+
install -m 755 "$ROOT/hooks/omnish-notify.sh" "$HOOKS_DIR/omnish-notify.sh"
|
|
93
|
+
install -m 644 "$ROOT/hooks/omnish-notify.py" "$HOOKS_DIR/omnish-notify.py"
|
|
94
|
+
|
|
95
|
+
if [[ ! -f "$CLAUDE_DIR/omnish-notify.json" ]]; then
|
|
96
|
+
install -m 644 "$ROOT/omnish-notify.json.example" "$CLAUDE_DIR/omnish-notify.json"
|
|
97
|
+
echo "created $CLAUDE_DIR/omnish-notify.json"
|
|
98
|
+
else
|
|
99
|
+
echo "kept existing $CLAUDE_DIR/omnish-notify.json"
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
python3 - "$SETTINGS_JSON" "$HOOK_CMD" <<'PY'
|
|
103
|
+
import json
|
|
104
|
+
import sys
|
|
105
|
+
from pathlib import Path
|
|
106
|
+
|
|
107
|
+
settings_path = Path(sys.argv[1])
|
|
108
|
+
hook_cmd = sys.argv[2]
|
|
109
|
+
entry = {
|
|
110
|
+
"hooks": [
|
|
111
|
+
{
|
|
112
|
+
"type": "command",
|
|
113
|
+
"command": hook_cmd,
|
|
114
|
+
}
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if settings_path.exists():
|
|
119
|
+
try:
|
|
120
|
+
data = json.loads(settings_path.read_text(encoding="utf-8"))
|
|
121
|
+
except json.JSONDecodeError:
|
|
122
|
+
data = {}
|
|
123
|
+
else:
|
|
124
|
+
data = {}
|
|
125
|
+
|
|
126
|
+
hooks = data.setdefault("hooks", {})
|
|
127
|
+
existing = hooks.get("Stop") or []
|
|
128
|
+
if not isinstance(existing, list):
|
|
129
|
+
existing = []
|
|
130
|
+
|
|
131
|
+
found = False
|
|
132
|
+
for group in existing:
|
|
133
|
+
if not isinstance(group, dict):
|
|
134
|
+
continue
|
|
135
|
+
inner = group.get("hooks") or []
|
|
136
|
+
if any(isinstance(h, dict) and h.get("command") == hook_cmd for h in inner):
|
|
137
|
+
found = True
|
|
138
|
+
break
|
|
139
|
+
|
|
140
|
+
if not found:
|
|
141
|
+
existing.append(entry)
|
|
142
|
+
hooks["Stop"] = existing
|
|
143
|
+
settings_path.parent.mkdir(parents=True, exist_ok=True)
|
|
144
|
+
settings_path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
|
|
145
|
+
print(f"updated {settings_path} (added Stop hook)")
|
|
146
|
+
else:
|
|
147
|
+
print(f"Stop hook already registered in {settings_path}")
|
|
148
|
+
PY
|
|
149
|
+
fi
|
|
150
|
+
|
|
151
|
+
if [[ "$INSTALL_SKILL" -eq 1 ]]; then
|
|
152
|
+
mkdir -p "$SKILL_DIR/scripts"
|
|
153
|
+
install -m 644 "$ROOT/skill/SKILL.md" "$SKILL_DIR/SKILL.md"
|
|
154
|
+
install -m 644 "$ROOT/skill/setup-paths.md" "$SKILL_DIR/setup-paths.md"
|
|
155
|
+
install -m 644 "$ROOT/skill/files-and-sharing.md" "$SKILL_DIR/files-and-sharing.md"
|
|
156
|
+
install -m 644 "$ROOT/skill/notifications.md" "$SKILL_DIR/notifications.md"
|
|
157
|
+
install -m 755 "$ROOT/scripts/doctor.sh" "$SKILL_DIR/scripts/doctor.sh"
|
|
158
|
+
echo "installed skill to $SKILL_DIR"
|
|
159
|
+
fi
|
|
160
|
+
|
|
161
|
+
cat <<EOF
|
|
162
|
+
|
|
163
|
+
Installed Omnish Claude Code bundle.
|
|
164
|
+
|
|
165
|
+
Next steps:
|
|
166
|
+
1. Ensure omnish gateway is running: omnish run
|
|
167
|
+
2. Platform attached: omnish config show platform
|
|
168
|
+
Local mode: omnish link && omnish allow +E164 (or tg:id)
|
|
169
|
+
3. Restart Claude Code (hooks reload from settings.json)
|
|
170
|
+
4. Run: bash $SKILL_DIR/scripts/doctor.sh
|
|
171
|
+
|
|
172
|
+
Config: $CLAUDE_DIR/omnish-notify.json
|
|
173
|
+
Default peer: * (all allowlisted contacts)
|
|
174
|
+
EOF
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Read-only Omnish + Claude Code integration health check.
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
CLAUDE_DIR="${CLAUDE_DIR:-$HOME/.claude}"
|
|
6
|
+
WARN=0
|
|
7
|
+
|
|
8
|
+
ok() { printf ' OK %s\n' "$*"; }
|
|
9
|
+
warn() { printf ' WARN %s\n' "$*"; WARN=1; }
|
|
10
|
+
fail() { printf ' FAIL %s\n' "$*"; }
|
|
11
|
+
|
|
12
|
+
section() { printf '\n== %s ==\n' "$*"; }
|
|
13
|
+
|
|
14
|
+
section "Omnish CLI"
|
|
15
|
+
if command -v omnish >/dev/null 2>&1; then
|
|
16
|
+
ok "omnish on PATH: $(command -v omnish)"
|
|
17
|
+
omnish --version 2>/dev/null | sed 's/^/ /' || true
|
|
18
|
+
else
|
|
19
|
+
fail "omnish not found — run: npm install -g omnish"
|
|
20
|
+
exit 1
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
section "Gateway status"
|
|
24
|
+
if omnish status 2>&1 | sed 's/^/ /'; then
|
|
25
|
+
if omnish status 2>&1 | grep -q 'gateway process: running'; then
|
|
26
|
+
ok "gateway running"
|
|
27
|
+
else
|
|
28
|
+
warn "gateway not running — run: omnish run (or omnish start)"
|
|
29
|
+
fi
|
|
30
|
+
else
|
|
31
|
+
warn "omnish status failed"
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
section "Claude notify config"
|
|
35
|
+
CFG="$CLAUDE_DIR/omnish-notify.json"
|
|
36
|
+
if [[ -f "$CFG" ]]; then
|
|
37
|
+
ok "found $CFG"
|
|
38
|
+
python3 - "$CFG" <<'PY'
|
|
39
|
+
import json, sys
|
|
40
|
+
cfg = json.load(open(sys.argv[1], encoding="utf-8"))
|
|
41
|
+
enabled = cfg.get("enabled", True)
|
|
42
|
+
peer = cfg.get("peer", "*")
|
|
43
|
+
print(f" enabled={enabled} peer={peer!r}")
|
|
44
|
+
PY
|
|
45
|
+
else
|
|
46
|
+
warn "missing $CFG — run: omnish agents install claude"
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
section "Claude hooks"
|
|
50
|
+
HOOK_CMD="$CLAUDE_DIR/hooks/omnish-notify.sh"
|
|
51
|
+
for f in omnish-notify.sh omnish-notify.py; do
|
|
52
|
+
if [[ -f "$CLAUDE_DIR/hooks/$f" ]]; then
|
|
53
|
+
ok "hook file: hooks/$f"
|
|
54
|
+
else
|
|
55
|
+
warn "missing hooks/$f"
|
|
56
|
+
fi
|
|
57
|
+
done
|
|
58
|
+
|
|
59
|
+
SETTINGS="$CLAUDE_DIR/settings.json"
|
|
60
|
+
if [[ -f "$SETTINGS" ]]; then
|
|
61
|
+
python3 - "$SETTINGS" "$HOOK_CMD" <<'PY'
|
|
62
|
+
import json, sys
|
|
63
|
+
from pathlib import Path
|
|
64
|
+
data = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
|
|
65
|
+
hook_cmd = sys.argv[2]
|
|
66
|
+
groups = (data.get("hooks") or {}).get("Stop") or []
|
|
67
|
+
found = False
|
|
68
|
+
for group in groups:
|
|
69
|
+
if not isinstance(group, dict):
|
|
70
|
+
continue
|
|
71
|
+
for h in group.get("hooks") or []:
|
|
72
|
+
if isinstance(h, dict) and h.get("command") == hook_cmd:
|
|
73
|
+
found = True
|
|
74
|
+
print(f" Stop hook: {'registered' if found else 'MISSING ' + hook_cmd}")
|
|
75
|
+
PY
|
|
76
|
+
else
|
|
77
|
+
warn "missing $SETTINGS"
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
section "Claude skill"
|
|
81
|
+
SKILL="$CLAUDE_DIR/skills/omnish/SKILL.md"
|
|
82
|
+
if [[ -f "$SKILL" ]]; then
|
|
83
|
+
ok "skill installed: skills/omnish/SKILL.md"
|
|
84
|
+
else
|
|
85
|
+
warn "skill not installed — run: omnish agents install claude"
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
section "Suggested next steps"
|
|
89
|
+
if [[ $WARN -eq 0 ]]; then
|
|
90
|
+
echo " All checks passed. Test notify:"
|
|
91
|
+
echo " omnish i -c '/sendto * -t Omnish Test -- claude doctor ok'"
|
|
92
|
+
else
|
|
93
|
+
echo " Fix WARN items above, then:"
|
|
94
|
+
echo " omnish run"
|
|
95
|
+
echo " omnish agents install claude"
|
|
96
|
+
echo " omnish i -c '/sendto * -t Omnish Test -- setup ok'"
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
exit 0
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: omnish
|
|
3
|
+
description: Install, configure, and use Omnish (WhatsApp/Telegram/platform shell gateway)—chat commands, file sharing (/send, /sendto, /receive), and mobile notifications. Use when setting up omnish, omnish run, platform token, file transfer, agent notify, or Claude Code omnish hooks.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Omnish setup and usage (Claude Code)
|
|
7
|
+
|
|
8
|
+
Help the user install, configure, and use **omnish** — a messaging-to-shell gateway (WhatsApp, Telegram, or platform attached mode). Chat is the primary UX; emphasize **file sharing** and **notifications** as high-value extras.
|
|
9
|
+
|
|
10
|
+
## First step: triage
|
|
11
|
+
|
|
12
|
+
Run the bundled doctor (after install) or from the repo:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
bash ~/.claude/skills/omnish/scripts/doctor.sh
|
|
16
|
+
# or: bash contrib/omnish-claude/scripts/doctor.sh
|
|
17
|
+
# or: omnish agents doctor claude
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Interpret output: omnish on PATH, gateway running, allowlist/platform token, Claude Stop hook + skill installed. Never echo secrets from config.
|
|
21
|
+
|
|
22
|
+
## Install without omnish.dev
|
|
23
|
+
|
|
24
|
+
From chat (when enabled): `/agents install claude`
|
|
25
|
+
|
|
26
|
+
From terminal:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
omnish agents install claude
|
|
30
|
+
# or: bash contrib/omnish-claude/install.sh
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
See [setup-paths.md](setup-paths.md), [files-and-sharing.md](files-and-sharing.md), and [notifications.md](notifications.md) for the full reference (shared with Cursor bundle).
|
|
34
|
+
|
|
35
|
+
## Claude automatic notifications
|
|
36
|
+
|
|
37
|
+
Stop hook sends curated briefs when agent work is substantive. Config: `~/.claude/omnish-notify.json` (same keys as Cursor).
|
|
38
|
+
|
|
39
|
+
Manual notify from scripts or `/run` / `/apps`: see [notifications.md](notifications.md).
|