subconscious-cli 6.2.1 → 6.3.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.
@@ -1,259 +0,0 @@
1
- #!/usr/bin/env bash
2
- # ── Subconscious API Gateway — Cursor hooks setup ─────────────────────────────
3
- # Install Cursor hooks that announce each agent handoff to your gateway so
4
- # /v1/chat/completions requests are grouped into Conversations.
5
- #
6
- # Quick start:
7
- # ./install.sh --gateway-url https://your-gateway.example --api-key sk-gw-...
8
- # ./install.sh status # show current hook config
9
- # ./install.sh uninstall # remove hooks
10
- #
11
- # Cursor model/URL are set in Cursor settings (OpenAI API Key Override), not
12
- # here. This script installs the conversation-correlation hooks (user-wide
13
- # under ~/.cursor). Restart Cursor after install so it reloads hooks.json.
14
- #
15
- # ── What this does under the hood ────────────────────────────────────────────
16
- # Equivalent manual setup (three pieces):
17
- #
18
- # 1. Cursor settings → enable "OpenAI API Key Override":
19
- # Base URL: https://your-gateway.example/v1
20
- # API Key: sk-gw-...
21
- #
22
- # 2. Write ~/.cursor/hooks.json pointing at the hook script:
23
- # {
24
- # "version": 1,
25
- # "hooks": {
26
- # "beforeSubmitPrompt": [{ "command": "~/.cursor/hooks/subconscious-hook.sh", "timeout": 2 }],
27
- # "preCompact": [{ "command": "~/.cursor/hooks/subconscious-hook.sh", "timeout": 2 }]
28
- # }
29
- # }
30
- #
31
- # 3. Write ~/.cursor/subconscious-hooks.env (mode 600):
32
- # export SUBCONSCIOUS_GATEWAY_URL=https://your-gateway.example
33
- # export SUBCONSCIOUS_API_KEY=sk-gw-...
34
- #
35
- # The hook script (hook.sh) POSTs conversation_ensure to /v1/agent-hooks with the raw
36
- # prompt text once per submission. The gateway fingerprints the prompt itself,
37
- # binds the first LLM request of that prompt, then chains every later turn of the
38
- # conversation onto it -- including subagents. Unlike Claude Code / Pi /
39
- # OpenCode, Cursor has no native session headers and hooks cannot inject headers
40
- # into model HTTP, so this announcement is required for correlation.
41
- # ─────────────────────────────────────────────────────────────────────────────
42
-
43
- set -euo pipefail
44
-
45
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
46
- HOOK_SRC="${SCRIPT_DIR}/hook.sh"
47
-
48
- GATEWAY_URL="${GATEWAY_URL:-}"
49
- API_KEY="${CURSOR_API_KEY:-${API_KEY:-}}"
50
- MODEL="${MODEL:-}"
51
- COMMAND="install"
52
-
53
- usage() {
54
- cat <<'EOF'
55
- Usage:
56
- subc cursor install [--gateway-url URL] [--api-key KEY]
57
- subc cursor uninstall
58
- subc cursor status
59
-
60
- Installs Cursor hooks (user-wide under ~/.cursor) that POST to /v1/agent-hooks:
61
- conversation_ensure on each prompt submission so the gateway can group
62
- Conversations for Cursor traffic, and conversation_compaction on preCompact so
63
- context accounting restarts at the right turn. Existing hook entries are kept.
64
-
65
- Requires: jq, curl. Restart Cursor after install.
66
- EOF
67
- }
68
-
69
- while [[ $# -gt 0 ]]; do
70
- case "$1" in
71
- install|uninstall|status)
72
- COMMAND="$1"
73
- shift
74
- ;;
75
- --gateway-url)
76
- GATEWAY_URL="${2:-}"
77
- shift 2
78
- ;;
79
- --api-key)
80
- API_KEY="${2:-}"
81
- shift 2
82
- ;;
83
- -h|--help)
84
- usage
85
- exit 0
86
- ;;
87
- *)
88
- echo "unknown argument: $1" >&2
89
- usage >&2
90
- exit 1
91
- ;;
92
- esac
93
- done
94
-
95
- CURSOR_DIR="${HOME}/.cursor"
96
- HOOKS_JSON="${CURSOR_DIR}/hooks.json"
97
- HOOK_DST="${CURSOR_DIR}/hooks/subconscious-hook.sh"
98
- ENV_FILE="${CURSOR_DIR}/subconscious-hooks.env"
99
- MARKER="subconscious-hook.sh"
100
-
101
- require_cmds() {
102
- local missing=0
103
- for c in jq curl; do
104
- if ! command -v "$c" >/dev/null 2>&1; then
105
- echo "missing required command: $c" >&2
106
- missing=1
107
- fi
108
- done
109
- if [[ "$missing" -ne 0 ]]; then
110
- exit 1
111
- fi
112
- }
113
-
114
- write_env() {
115
- umask 077
116
- cat >"$ENV_FILE" <<EOF
117
- # Generated by subc — do not commit secrets.
118
- export SUBCONSCIOUS_GATEWAY_URL='${GATEWAY_URL}'
119
- export SUBCONSCIOUS_API_KEY='${API_KEY}'
120
- EOF
121
- chmod 600 "$ENV_FILE"
122
- }
123
-
124
- install_hook_script() {
125
- mkdir -p "$(dirname "$HOOK_DST")"
126
- cp "$HOOK_SRC" "$HOOK_DST"
127
- chmod +x "$HOOK_DST"
128
- }
129
-
130
- # merge_hook_entries <hooks_json> <marker> <command> <event> [<event>...]
131
- #
132
- # Additive, idempotent merge of a single hook command into one or more events
133
- # of a hooks.json file. Preserves ALL other entries verbatim, including
134
- # third-party hooks, prompt hooks (no `command` field), and unknown events.
135
- #
136
- # Invariants:
137
- # - Only entries whose `command` contains <marker> are replaced; others untouched.
138
- # - Running twice yields the same result as once (idempotent, order-stable).
139
- # - Entries without a `command` field are never matched/removed.
140
- # - Missing/empty file is initialized to {"version":1,"hooks":{}}.
141
- merge_hook_entries() {
142
- local hooks_json="$1" marker="$2" command="$3"
143
- shift 3
144
- local events
145
- events="$(printf '%s\n' "$@" | jq -R . | jq -s .)"
146
-
147
- if [[ ! -f "$hooks_json" ]]; then
148
- printf '%s\n' '{"version":1,"hooks":{}}' >"$hooks_json"
149
- fi
150
-
151
- local tmp
152
- tmp="$(mktemp)"
153
- jq --arg marker "$marker" --arg cmd "$command" --argjson events "$events" '
154
- .version = (.version // 1) |
155
- .hooks = (.hooks // {}) |
156
- # Strip prior entries with our marker from every event, keep everything else.
157
- .hooks |= with_entries(
158
- .value = ((.value // []) | map(select((.command // "") | tostring | contains($marker) | not)))
159
- ) |
160
- # Append our entry to each requested event (preserving any other entries).
161
- .hooks = (.hooks | reduce ($events[]) as $e (.;
162
- .[$e] = ((.[$e] // []) + [{"command": $cmd, "timeout": 2}])
163
- ))
164
- ' "$hooks_json" >"$tmp"
165
- mv "$tmp" "$hooks_json"
166
- }
167
-
168
- # remove_hook_entries <hooks_json> <marker>
169
- #
170
- # Remove every entry whose `command` contains <marker> from all events.
171
- # Preserves all other entries (third-party, prompt hooks, unknown events).
172
- remove_hook_entries() {
173
- local hooks_json="$1" marker="$2"
174
- [[ -f "$hooks_json" ]] || return 0
175
- local tmp
176
- tmp="$(mktemp)"
177
- jq --arg marker "$marker" '
178
- .hooks //= {} |
179
- .hooks |= with_entries(
180
- .value = ((.value // []) | map(select((.command // "") | tostring | contains($marker) | not)))
181
- )
182
- ' "$hooks_json" >"$tmp"
183
- mv "$tmp" "$hooks_json"
184
- }
185
-
186
- merge_hooks_json() {
187
- local command
188
- command="$(jq -rn --arg path "$HOOK_DST" '$path | @sh')"
189
- # The merge handles both first install and refresh; jq escapes JSON while
190
- # @sh keeps spaces and shell characters inside the executable path.
191
- merge_hook_entries "$HOOKS_JSON" "$MARKER" "$command" beforeSubmitPrompt preCompact
192
- }
193
-
194
- uninstall_hooks() {
195
- remove_hook_entries "$HOOKS_JSON" "$MARKER"
196
- rm -f "$HOOK_DST" "$ENV_FILE"
197
- }
198
-
199
- status() {
200
- echo "cursor dir: $CURSOR_DIR"
201
- echo "hooks.json: $HOOKS_JSON"
202
- if [[ -f "$HOOKS_JSON" ]] && jq -e --arg m "$MARKER" '
203
- [.hooks[]?[]? | select((.command // "") | tostring | contains($m))] | length > 0
204
- ' "$HOOKS_JSON" >/dev/null 2>&1; then
205
- echo "hooks: installed"
206
- else
207
- echo "hooks: not installed"
208
- fi
209
- if [[ -f "$ENV_FILE" ]]; then
210
- echo "env: $ENV_FILE (present)"
211
- # shellcheck disable=SC1090
212
- source "$ENV_FILE"
213
- echo "gateway: ${SUBCONSCIOUS_GATEWAY_URL:-unset}"
214
- if [[ -n "${SUBCONSCIOUS_API_KEY:-}" ]]; then
215
- echo "api key: set (${#SUBCONSCIOUS_API_KEY} chars)"
216
- else
217
- echo "api key: unset"
218
- fi
219
- else
220
- echo "env: missing"
221
- fi
222
- if [[ -x "$HOOK_DST" ]]; then
223
- echo "hook script: $HOOK_DST (executable)"
224
- else
225
- echo "hook script: missing"
226
- fi
227
- }
228
-
229
- case "$COMMAND" in
230
- install)
231
- require_cmds
232
- if [[ -z "$GATEWAY_URL" || -z "$API_KEY" ]]; then
233
- echo "--gateway-url and --api-key are required for install" >&2
234
- exit 1
235
- fi
236
- mkdir -p "$CURSOR_DIR"
237
- install_hook_script
238
- write_env
239
- merge_hooks_json
240
- echo "Installed Subconscious Cursor hooks into $CURSOR_DIR"
241
- echo ""
242
- echo "Finish in Cursor Settings"
243
- echo " Enable OpenAI API Key Override, then use:"
244
- echo " Base URL: ${GATEWAY_URL%/}/v1"
245
- echo " Add the available custom models:"
246
- while IFS= read -r model_id; do
247
- echo " ${model_id}"
248
- done <<< "$SUBC_MODEL_IDS"
249
- echo " Select ${MODEL} for this profile."
250
- echo " Paste your Subconscious API key there, then fully restart Cursor."
251
- ;;
252
- uninstall)
253
- uninstall_hooks
254
- echo "Removed Subconscious Cursor hooks from $CURSOR_DIR"
255
- ;;
256
- status)
257
- status
258
- ;;
259
- esac