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.
Files changed (38) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +26 -18
  3. package/config.example.json +1 -0
  4. package/dist/downloads/omnish-claude/install.sh +174 -0
  5. package/dist/downloads/omnish-claude/uninstall.sh +114 -0
  6. package/dist/downloads/omnish-claude.tar.gz +0 -0
  7. package/dist/downloads/omnish-cursor/install.sh +162 -0
  8. package/dist/downloads/omnish-cursor/uninstall.sh +107 -0
  9. package/dist/downloads/omnish-cursor.tar.gz +0 -0
  10. package/dist/index.js +424 -392
  11. package/dist/omnish-claude/README.md +80 -0
  12. package/dist/omnish-claude/hooks/omnish-notify.py +213 -0
  13. package/dist/omnish-claude/hooks/omnish-notify.sh +13 -0
  14. package/dist/omnish-claude/install.sh +174 -0
  15. package/dist/omnish-claude/omnish-notify.json.example +8 -0
  16. package/dist/omnish-claude/scripts/doctor.sh +99 -0
  17. package/dist/omnish-claude/skill/SKILL.md +39 -0
  18. package/dist/omnish-claude/skill/files-and-sharing.md +94 -0
  19. package/dist/omnish-claude/skill/notifications.md +113 -0
  20. package/dist/omnish-claude/skill/setup-paths.md +81 -0
  21. package/dist/omnish-claude/uninstall.sh +114 -0
  22. package/dist/omnish-cursor/README.md +176 -0
  23. package/dist/omnish-cursor/hooks/__pycache__/omnish-notify.cpython-313.pyc +0 -0
  24. package/dist/omnish-cursor/hooks/omnish-notify.py +563 -0
  25. package/dist/omnish-cursor/hooks/omnish-notify.sh +13 -0
  26. package/dist/omnish-cursor/hooks/omnish-session-start.sh +48 -0
  27. package/dist/omnish-cursor/install.sh +162 -0
  28. package/dist/omnish-cursor/omnish-notify.json.example +13 -0
  29. package/dist/omnish-cursor/rules/omnish-notify.mdc +45 -0
  30. package/dist/omnish-cursor/scripts/doctor.sh +126 -0
  31. package/dist/omnish-cursor/skill/SKILL.md +129 -0
  32. package/dist/omnish-cursor/skill/files-and-sharing.md +94 -0
  33. package/dist/omnish-cursor/skill/notifications.md +155 -0
  34. package/dist/omnish-cursor/skill/setup-paths.md +81 -0
  35. package/dist/omnish-cursor/uninstall.sh +107 -0
  36. package/dist/ui/assets/{index-aUJGrxrr.js → index-BwG51a2I.js} +10 -10
  37. package/dist/ui/index.html +16 -1
  38. 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,8 @@
1
+ {
2
+ "enabled": true,
3
+ "peer": "*",
4
+ "min_assistant_chars": 120,
5
+ "notify_on_error": true,
6
+ "skip_aborted": true,
7
+ "include_full_chat_id": true
8
+ }
@@ -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).