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
package/CHANGELOG.md CHANGED
@@ -7,6 +7,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.0.0] - 2026-06-18
11
+
12
+ ### Breaking
13
+
14
+ - **Platform routing:** unprefixed chat commands route to the **default device only**; use `>label cmd` for other hosts. Dashboard peer bindings no longer affect inbound dispatch ([docs/adr/0013-platform-device-prefix-routing.md](docs/adr/0013-platform-device-prefix-routing.md)).
15
+ - **Cluster CLI:** `omnish cluster here` removed — use chat `/c here` or `omnish cluster use <sender> <label>`.
16
+ - **Cluster chat:** `/c step-down` removed; per-sender binding via `/c unuse`.
17
+ - **`clusterRole`:** informational only; no longer gates traffic.
18
+ - **Telemetry default-on:** gateways send anonymous heartbeats unless `OMNISH_TELEMETRY=0` or `telemetryEnabled: false` ([docs/adr/0017-default-on-instance-telemetry.md](docs/adr/0017-default-on-instance-telemetry.md)).
19
+
20
+ ### Added
21
+
22
+ - **Connectors:** Signal integration; OAuth link flows for Slack, Discord, and Twitch; Slack DM gateway assets.
23
+ - **Platform:** user auth (signup/login, email verification, password reset, SMTP templates); default device management; `/devices` routing commands; admin telemetry APIs and AdminView improvements.
24
+ - **Integrations:** Omnish Cursor and Claude Code install/notify paths; enhanced `omnish-notify` config.
25
+ - **Gateway:** lifecycle broadcast to allowlisted peers on startup/shutdown (with WhatsApp JID fix).
26
+ - **Cluster:** WebSocket keep-alive; attached auto-binding on inbound; enhanced cluster state handling.
27
+ - **CLI/UI:** `/apps start` flags; platform dashboard mobile nav; analytics hooks; `.nvmrc`.
28
+
29
+ ### Changed
30
+
31
+ - Connector management and user allowlist handling.
32
+ - Dependencies for expanded channel support.
33
+ - Postinstall / native module checks.
34
+
35
+ ### Fixed
36
+
37
+ - Signal QR display issue.
38
+ - AdminView fetch error handling.
39
+
40
+ [2.0.0]: https://github.com/labKnowledge/omnish/compare/v1.6.6...v2.0.0
41
+
10
42
  ## [1.6.6] - 2026-05-30
11
43
 
12
44
  ### Fixed
package/README.md CHANGED
@@ -79,6 +79,12 @@ Full steps for systemd, launchd, and Task Scheduler — **[docs/guides/backgroun
79
79
 
80
80
  Run `omnish status` to see the resolved data directory and auth path.
81
81
 
82
+ ## Telemetry (default-on, opt-out)
83
+
84
+ While **`omnish run`** is active, omnish sends anonymous instance health metadata to the platform telemetry endpoint (default: `https://tunnel.omnish.dev`): install id (`~/.omnish/node-id`), omnish version, OS/arch, Node version, gateway mode, and enabled channel count. No message content, allowlists, or file paths are included.
85
+
86
+ **Opt out:** `OMNISH_TELEMETRY=0` or `omnish config set telemetryEnabled false`. Override the endpoint with `telemetryUrl` in config.
87
+
82
88
  While **`omnish run`** is active, a localhost control channel may write **`gateway-control.json`** (and a random high port on `127.0.0.1`) so **`omnish i`** can request outbound file sends.
83
89
 
84
90
  Per-chat shell cwd is in `sessions.json`; **user shortcuts** are stored in `shortcuts.json` (same data directory).
@@ -157,24 +163,24 @@ Output is **debounced** (`appsFlushMs`, default 300 ms), **throttled** (`appsMin
157
163
 
158
164
  ### `/apps` cheat sheet
159
165
 
160
- | Command | Action |
161
- | ------------------------------------------- | ----------------------------------------------------------------------------- |
162
- | `/apps start <name> <cmd…>` | Spawn app session in session cwd; **auto-attaches** `<name>`. |
163
- | `/apps attach <name>` | Focus plain DMs on that running session. |
164
- | `/apps detach` | Clear focus. |
165
- | `/apps list` | Sessions for this chat; `*` marks focus. Shows `attached: …` or `(no focus)`. |
166
- | `/apps info <name>` / `/apps get <name>` | Cmd, cwd, env key count, terminal size, ring bytes, log path/size, mute/raw. |
167
- | `/apps send <name> <text>` | Write `text` + newline (no attach). |
168
- | `>name text` | Same as `/apps send`. |
169
- | `/apps key <name> KEY[,KEY…]` | Special keys: `Enter`, `Tab`, `Esc`, arrows, `^C`, `ctrl+d`, `\x1b`, … |
170
- | `/apps tail <name> [lines]` | Last _lines_ of log (default `appsLogTailLines`). |
171
- | `/apps since <name>` | New log bytes since your last `/apps since` for that name. |
172
- | `/apps mute <name>` / `/apps unmute <name>` | Pause / resume streaming to the chat (log still grows). |
173
- | `/apps raw <name> on\|off` | Keep ANSI in streamed messages when `on`. |
174
- | `/apps resize <name> <cols> <rows>` | Resize terminal. |
175
- | `/apps stop <name>` | SIGTERM; SIGKILL after 5 s if still alive. |
176
- | `/apps kill <name>` | SIGKILL immediately. |
177
- | `/apps rm <name>` | Only after the process has **exited**; removes metadata + log file. |
166
+ | Command | Action |
167
+ | --------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
168
+ | `/apps start <name> [--mute\|-mute\|--detach\|-d] <cmd…>` | Spawn in session cwd; **auto-attaches** unless `--detach`; `--mute` skips chat streaming. |
169
+ | `/apps attach <name>` | Focus plain DMs on that running session. |
170
+ | `/apps detach` | Clear focus. |
171
+ | `/apps list` | Sessions for this chat; `*` marks focus. Shows `attached: …` or `(no focus)`. |
172
+ | `/apps info <name>` / `/apps get <name>` | Cmd, cwd, env key count, terminal size, ring bytes, log path/size, mute/raw. |
173
+ | `/apps send <name> <text>` | Write `text` + newline (no attach). |
174
+ | `>name text` | Same as `/apps send`. |
175
+ | `/apps key <name> KEY[,KEY…]` | Special keys: `Enter`, `Tab`, `Esc`, arrows, `^C`, `ctrl+d`, `\x1b`, … |
176
+ | `/apps tail <name> [lines]` | Last _lines_ of log (default `appsLogTailLines`). |
177
+ | `/apps since <name>` | New log bytes since your last `/apps since` for that name. |
178
+ | `/apps mute <name>` / `/apps unmute <name>` | Pause / resume streaming to the chat (log still grows). |
179
+ | `/apps raw <name> on\|off` | Keep ANSI in streamed messages when `on`. |
180
+ | `/apps resize <name> <cols> <rows>` | Resize terminal. |
181
+ | `/apps stop <name>` | SIGTERM; SIGKILL after 5 s if still alive. |
182
+ | `/apps kill <name>` | SIGKILL immediately. |
183
+ | `/apps rm <name>` | Only after the process has **exited**; removes metadata + log file. |
178
184
 
179
185
  To send a literal `!`, `/`, or `>` to the attached program, use **`/apps send <name> …`** or **`>name …`**.
180
186
 
@@ -266,6 +272,8 @@ Index and curated paths: **[docs/README.md](docs/README.md)**.
266
272
  | Documentation search (`/s`, `omnish search`) | [docs/features/docs-search-from-chat.md](docs/features/docs-search-from-chat.md) |
267
273
  | System agents + `/run` | [docs/guides/system-agents-and-run.md](docs/guides/system-agents-and-run.md) |
268
274
  | MCP IDE spike (experimental) | [contrib/mcp-spike/README.md](contrib/mcp-spike/README.md) |
275
+ | Cursor integration (hooks + skill) | [contrib/omnish-cursor/README.md](contrib/omnish-cursor/README.md) |
276
+ | Claude Code integration (hooks + skill) | [contrib/omnish-claude/README.md](contrib/omnish-claude/README.md) |
269
277
  | Telegram | [docs/telegram-integration-notes.md](docs/telegram-integration-notes.md) |
270
278
  | Send/receive files (`/send` selectors, `/sendto` destinations) | [docs/files-send-receive.md](docs/files-send-receive.md) |
271
279
  | Troubleshooting | [docs/advanced/troubleshooting.md](docs/advanced/troubleshooting.md) |
@@ -38,6 +38,7 @@
38
38
  "clusterRole": "secondary",
39
39
  "clusterSenderBindings": {},
40
40
  "serviceInstallFromChat": false,
41
+ "agentInstallFromChat": false,
41
42
  "updateCheckEnabled": false,
42
43
  "updateCheckIntervalMs": 86400000,
43
44
  "updateCheckPackageName": "omnish",
@@ -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,114 @@
1
+ #!/usr/bin/env bash
2
+ # Remove Omnish Claude Code bundle entries from ~/.claude (keeps unrelated hooks).
3
+ set -euo pipefail
4
+
5
+ CLAUDE_DIR="${CLAUDE_DIR:-$HOME/.claude}"
6
+ SETTINGS_JSON="$CLAUDE_DIR/settings.json"
7
+ HOOK_CMD="$CLAUDE_DIR/hooks/omnish-notify.sh"
8
+
9
+ REMOVE_HOOKS=1
10
+ REMOVE_SKILL=1
11
+ REMOVE_CONFIG=0
12
+
13
+ usage() {
14
+ cat <<EOF
15
+ Usage: $(basename "$0") [options]
16
+
17
+ Remove Omnish Claude Code integration installed by install.sh.
18
+
19
+ Options:
20
+ --hooks-only Remove Stop hook + hook scripts only
21
+ --skill-only Remove ~/.claude/skills/omnish only
22
+ --with-config Also delete ~/.claude/omnish-notify.json
23
+ -h, --help Show this help
24
+ EOF
25
+ }
26
+
27
+ while [[ $# -gt 0 ]]; do
28
+ case "$1" in
29
+ --hooks-only)
30
+ REMOVE_HOOKS=1
31
+ REMOVE_SKILL=0
32
+ shift
33
+ ;;
34
+ --skill-only)
35
+ REMOVE_HOOKS=0
36
+ REMOVE_SKILL=1
37
+ shift
38
+ ;;
39
+ --with-config)
40
+ REMOVE_CONFIG=1
41
+ shift
42
+ ;;
43
+ -h | --help)
44
+ usage
45
+ exit 0
46
+ ;;
47
+ *)
48
+ echo "error: unknown option: $1" >&2
49
+ usage >&2
50
+ exit 1
51
+ ;;
52
+ esac
53
+ done
54
+
55
+ if [[ "$REMOVE_HOOKS" -eq 1 && -f "$SETTINGS_JSON" ]]; then
56
+ python3 - "$SETTINGS_JSON" "$HOOK_CMD" <<'PY'
57
+ import json
58
+ import sys
59
+ from pathlib import Path
60
+
61
+ settings_path = Path(sys.argv[1])
62
+ hook_cmd = sys.argv[2]
63
+ if not settings_path.is_file():
64
+ raise SystemExit(0)
65
+ data = json.loads(settings_path.read_text(encoding="utf-8"))
66
+ hooks = data.get("hooks") or {}
67
+ existing = hooks.get("Stop") or []
68
+ if not isinstance(existing, list):
69
+ raise SystemExit(0)
70
+ filtered = []
71
+ changed = False
72
+ for group in existing:
73
+ if not isinstance(group, dict):
74
+ filtered.append(group)
75
+ continue
76
+ inner = group.get("hooks") or []
77
+ new_inner = [
78
+ h for h in inner
79
+ if not (isinstance(h, dict) and h.get("command") == hook_cmd)
80
+ ]
81
+ if len(new_inner) != len(inner):
82
+ changed = True
83
+ if new_inner:
84
+ group = dict(group)
85
+ group["hooks"] = new_inner
86
+ filtered.append(group)
87
+ if changed:
88
+ if filtered:
89
+ hooks["Stop"] = filtered
90
+ else:
91
+ hooks.pop("Stop", None)
92
+ settings_path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
93
+ print(f"updated {settings_path} (removed omnish Stop hook)")
94
+ else:
95
+ print(f"no omnish Stop hook in {settings_path}")
96
+ PY
97
+ fi
98
+
99
+ if [[ "$REMOVE_HOOKS" -eq 1 ]]; then
100
+ rm -f "$CLAUDE_DIR/hooks/omnish-notify.sh" "$CLAUDE_DIR/hooks/omnish-notify.py"
101
+ echo "removed hook scripts"
102
+ fi
103
+
104
+ if [[ "$REMOVE_SKILL" -eq 1 ]]; then
105
+ rm -rf "$CLAUDE_DIR/skills/omnish"
106
+ echo "removed $CLAUDE_DIR/skills/omnish"
107
+ fi
108
+
109
+ if [[ "$REMOVE_CONFIG" -eq 1 ]]; then
110
+ rm -f "$CLAUDE_DIR/omnish-notify.json"
111
+ echo "removed $CLAUDE_DIR/omnish-notify.json"
112
+ fi
113
+
114
+ echo "Done. Restart Claude Code if hooks were removed."
@@ -0,0 +1,162 @@
1
+ #!/usr/bin/env bash
2
+ # Install Omnish Cursor bundle: hooks + skill + rule into ~/.cursor
3
+ set -euo pipefail
4
+
5
+ OMNISH_CURSOR_BUNDLE_URL="${OMNISH_CURSOR_BUNDLE_URL:-https://omnish.dev/downloads/omnish-cursor.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-cursor.XXXXXX")"
28
+ trap cleanup EXIT
29
+ echo "Downloading Omnish Cursor bundle..." >&2
30
+ curl -fsSL "$OMNISH_CURSOR_BUNDLE_URL" | tar -xzf - -C "$_TMP_BUNDLE_DIR"
31
+ if [[ ! -d "$_TMP_BUNDLE_DIR/omnish-cursor" ]]; then
32
+ echo "error: bundle missing omnish-cursor/ top-level directory" >&2
33
+ exit 1
34
+ fi
35
+ ROOT="$_TMP_BUNDLE_DIR/omnish-cursor"
36
+ fi
37
+
38
+ CURSOR_DIR="${CURSOR_DIR:-$HOME/.cursor}"
39
+ HOOKS_DIR="$CURSOR_DIR/hooks"
40
+ RULES_DIR="$CURSOR_DIR/rules"
41
+ SKILL_DIR="$CURSOR_DIR/skills/omnish"
42
+
43
+ INSTALL_HOOKS=1
44
+ INSTALL_SKILL=1
45
+
46
+ usage() {
47
+ cat <<EOF
48
+ Usage: $(basename "$0") [options]
49
+
50
+ Install Omnish Cursor integration (hooks, skill, rule, config).
51
+
52
+ Options:
53
+ --hooks-only Install only Cursor hooks + rule + notify config
54
+ --skill-only Install only the omnish Cursor skill (+ doctor script)
55
+ -h, --help Show this help
56
+ EOF
57
+ }
58
+
59
+ while [[ $# -gt 0 ]]; do
60
+ case "$1" in
61
+ --hooks-only)
62
+ INSTALL_HOOKS=1
63
+ INSTALL_SKILL=0
64
+ shift
65
+ ;;
66
+ --skill-only)
67
+ INSTALL_HOOKS=0
68
+ INSTALL_SKILL=1
69
+ shift
70
+ ;;
71
+ -h | --help)
72
+ usage
73
+ exit 0
74
+ ;;
75
+ *)
76
+ echo "error: unknown option: $1" >&2
77
+ usage >&2
78
+ exit 1
79
+ ;;
80
+ esac
81
+ done
82
+
83
+ if [[ "$INSTALL_HOOKS" -eq 1 ]]; then
84
+ need python3
85
+ need omnish
86
+ fi
87
+
88
+ if [[ "$INSTALL_HOOKS" -eq 1 ]]; then
89
+ mkdir -p "$HOOKS_DIR" "$RULES_DIR"
90
+
91
+ install -m 755 "$ROOT/hooks/omnish-notify.sh" "$HOOKS_DIR/omnish-notify.sh"
92
+ install -m 755 "$ROOT/hooks/omnish-session-start.sh" "$HOOKS_DIR/omnish-session-start.sh"
93
+ install -m 644 "$ROOT/hooks/omnish-notify.py" "$HOOKS_DIR/omnish-notify.py"
94
+ install -m 644 "$ROOT/rules/omnish-notify.mdc" "$RULES_DIR/omnish-notify.mdc"
95
+
96
+ if [[ ! -f "$CURSOR_DIR/omnish-notify.json" ]]; then
97
+ install -m 644 "$ROOT/omnish-notify.json.example" "$CURSOR_DIR/omnish-notify.json"
98
+ echo "created $CURSOR_DIR/omnish-notify.json"
99
+ else
100
+ echo "kept existing $CURSOR_DIR/omnish-notify.json"
101
+ fi
102
+
103
+ python3 - "$CURSOR_DIR/hooks.json" <<'PY'
104
+ import json
105
+ import sys
106
+ from pathlib import Path
107
+
108
+ hooks_path = Path(sys.argv[1])
109
+ desired = {
110
+ "sessionStart": [{"command": "./hooks/omnish-session-start.sh"}],
111
+ "stop": [{"command": "./hooks/omnish-notify.sh"}],
112
+ }
113
+
114
+ if hooks_path.exists():
115
+ try:
116
+ data = json.loads(hooks_path.read_text(encoding="utf-8"))
117
+ except json.JSONDecodeError:
118
+ data = {"version": 1, "hooks": {}}
119
+ else:
120
+ data = {"version": 1, "hooks": {}}
121
+
122
+ data.setdefault("version", 1)
123
+ hooks = data.setdefault("hooks", {})
124
+
125
+ for event, entries in desired.items():
126
+ existing = hooks.get(event) or []
127
+ commands = {entry.get("command") for entry in existing if isinstance(entry, dict)}
128
+ merged = list(existing)
129
+ for entry in entries:
130
+ if entry["command"] not in commands:
131
+ merged.append(entry)
132
+ hooks[event] = merged
133
+
134
+ hooks_path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
135
+ print(f"updated {hooks_path}")
136
+ PY
137
+ fi
138
+
139
+ if [[ "$INSTALL_SKILL" -eq 1 ]]; then
140
+ mkdir -p "$SKILL_DIR/scripts"
141
+ install -m 644 "$ROOT/skill/SKILL.md" "$SKILL_DIR/SKILL.md"
142
+ install -m 644 "$ROOT/skill/setup-paths.md" "$SKILL_DIR/setup-paths.md"
143
+ install -m 644 "$ROOT/skill/files-and-sharing.md" "$SKILL_DIR/files-and-sharing.md"
144
+ install -m 644 "$ROOT/skill/notifications.md" "$SKILL_DIR/notifications.md"
145
+ install -m 755 "$ROOT/scripts/doctor.sh" "$SKILL_DIR/scripts/doctor.sh"
146
+ echo "installed skill to $SKILL_DIR"
147
+ fi
148
+
149
+ cat <<EOF
150
+
151
+ Installed Omnish Cursor bundle.
152
+
153
+ Next steps:
154
+ 1. Ensure omnish gateway is running: omnish run
155
+ 2. Platform attached: omnish config show platform
156
+ Local mode: omnish link && omnish allow +E164 (or tg:id)
157
+ 3. Restart Cursor (or reload Hooks in Settings)
158
+ 4. Run: bash $SKILL_DIR/scripts/doctor.sh
159
+
160
+ Config: $CURSOR_DIR/omnish-notify.json
161
+ Default peer: * (all allowlisted contacts)
162
+ EOF
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env bash
2
+ # Remove Omnish Cursor bundle entries from ~/.cursor (keeps unrelated hooks).
3
+ set -euo pipefail
4
+
5
+ CURSOR_DIR="${CURSOR_DIR:-$HOME/.cursor}"
6
+ HOOKS_JSON="$CURSOR_DIR/hooks.json"
7
+
8
+ REMOVE_HOOKS=1
9
+ REMOVE_SKILL=1
10
+ REMOVE_CONFIG=0
11
+
12
+ usage() {
13
+ cat <<EOF
14
+ Usage: $(basename "$0") [options]
15
+
16
+ Remove Omnish Cursor integration installed by install.sh.
17
+
18
+ Options:
19
+ --hooks-only Remove hooks + rule + hooks.json entries only
20
+ --skill-only Remove ~/.cursor/skills/omnish only
21
+ --with-config Also delete ~/.cursor/omnish-notify.json
22
+ -h, --help Show this help
23
+ EOF
24
+ }
25
+
26
+ while [[ $# -gt 0 ]]; do
27
+ case "$1" in
28
+ --hooks-only)
29
+ REMOVE_HOOKS=1
30
+ REMOVE_SKILL=0
31
+ shift
32
+ ;;
33
+ --skill-only)
34
+ REMOVE_HOOKS=0
35
+ REMOVE_SKILL=1
36
+ shift
37
+ ;;
38
+ --with-config)
39
+ REMOVE_CONFIG=1
40
+ shift
41
+ ;;
42
+ -h | --help)
43
+ usage
44
+ exit 0
45
+ ;;
46
+ *)
47
+ echo "error: unknown option: $1" >&2
48
+ usage >&2
49
+ exit 1
50
+ ;;
51
+ esac
52
+ done
53
+
54
+ OMNISH_COMMANDS=(
55
+ "./hooks/omnish-session-start.sh"
56
+ "./hooks/omnish-notify.sh"
57
+ )
58
+
59
+ if [[ "$REMOVE_HOOKS" -eq 1 && -f "$HOOKS_JSON" ]]; then
60
+ python3 - "$HOOKS_JSON" "${OMNISH_COMMANDS[@]}" <<'PY'
61
+ import json
62
+ import sys
63
+ from pathlib import Path
64
+
65
+ hooks_path = Path(sys.argv[1])
66
+ remove_cmds = set(sys.argv[2:])
67
+ data = json.loads(hooks_path.read_text(encoding="utf-8"))
68
+ hooks = data.get("hooks") or {}
69
+ changed = False
70
+ for event, entries in list(hooks.items()):
71
+ if not isinstance(entries, list):
72
+ continue
73
+ filtered = [
74
+ e for e in entries
75
+ if not (isinstance(e, dict) and e.get("command") in remove_cmds)
76
+ ]
77
+ if len(filtered) != len(entries):
78
+ changed = True
79
+ hooks[event] = filtered
80
+ if changed:
81
+ hooks_path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
82
+ print(f"updated {hooks_path} (removed omnish hook entries)")
83
+ else
84
+ print(f"no omnish hook entries in {hooks_path}")
85
+ PY
86
+ fi
87
+
88
+ if [[ "$REMOVE_HOOKS" -eq 1 ]]; then
89
+ rm -f \
90
+ "$CURSOR_DIR/hooks/omnish-notify.sh" \
91
+ "$CURSOR_DIR/hooks/omnish-notify.py" \
92
+ "$CURSOR_DIR/hooks/omnish-session-start.sh" \
93
+ "$CURSOR_DIR/rules/omnish-notify.mdc"
94
+ echo "removed hook scripts and omnish-notify rule"
95
+ fi
96
+
97
+ if [[ "$REMOVE_SKILL" -eq 1 ]]; then
98
+ rm -rf "$CURSOR_DIR/skills/omnish"
99
+ echo "removed $CURSOR_DIR/skills/omnish"
100
+ fi
101
+
102
+ if [[ "$REMOVE_CONFIG" -eq 1 ]]; then
103
+ rm -f "$CURSOR_DIR/omnish-notify.json"
104
+ echo "removed $CURSOR_DIR/omnish-notify.json"
105
+ fi
106
+
107
+ echo "Done. Restart Cursor if hooks were removed."