subconscious-cli 6.2.0 → 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.
- package/README.md +10 -18
- package/bin/agent-launch.js +37 -4
- package/bin/agents.js +18 -0
- package/bin/auth.js +1 -1
- package/bin/claude-config-drift.js +58 -0
- package/bin/claude-config-plugins.js +103 -0
- package/bin/claude-config.js +624 -0
- package/bin/cli.js +35 -4
- package/bin/command-output.js +75 -0
- package/bin/native/subc-tui-darwin-amd64 +0 -0
- package/bin/native/subc-tui-darwin-arm64 +0 -0
- package/bin/native/subc-tui-linux-amd64 +0 -0
- package/bin/native/subc-tui-linux-arm64 +0 -0
- package/bin/native/subc-tui-windows-amd64.exe +0 -0
- package/bin/native/subc-tui-windows-arm64.exe +0 -0
- package/bin/profiles.js +4 -0
- package/bin/runbook/README.md +2 -3
- package/bin/runbook/claude-code/agent.json +25 -1
- package/bin/runbook/shared.json +1 -2
- package/bin/sessions.js +24 -27
- package/bin/tui.js +18 -8
- package/bin/windows/hook.cjs +4 -10
- package/bin/windows/launch.js +22 -9
- package/bin/windows/process.js +1 -18
- package/bin/windows/setup.js +2 -8
- package/package.json +3 -4
- package/bin/runbook/cursor/agent.json +0 -138
- package/bin/runbook/cursor/hook.sh +0 -145
- package/bin/runbook/cursor/hooks.json +0 -17
- package/bin/runbook/cursor/install.sh +0 -259
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"id": "cursor",
|
|
3
|
-
"command": "cursor",
|
|
4
|
-
"aliases": [],
|
|
5
|
-
"name": "Cursor",
|
|
6
|
-
"description": "Install Cursor conversation and compaction hooks in the Cursor desktop app and IDE, then finish model setup in Cursor Settings. The Cursor CLI is not supported.",
|
|
7
|
-
"protocol": "openai",
|
|
8
|
-
"homepage": "https://cursor.com",
|
|
9
|
-
"install": {
|
|
10
|
-
"commands": null,
|
|
11
|
-
"url": "https://cursor.com/download",
|
|
12
|
-
"method": "user",
|
|
13
|
-
"package": null,
|
|
14
|
-
"version": null,
|
|
15
|
-
"minimum_version": null,
|
|
16
|
-
"verified": true,
|
|
17
|
-
"note": "IDE installed by the user. The model endpoint is set by hand in Cursor Settings (OpenAI API Key Override)."
|
|
18
|
-
},
|
|
19
|
-
"runbook": {
|
|
20
|
-
"mode": "setup",
|
|
21
|
-
"setup_script": "install.sh",
|
|
22
|
-
"setup_actions": ["install", "status", "uninstall"]
|
|
23
|
-
},
|
|
24
|
-
"help": {
|
|
25
|
-
"usage": "subc [-p NAME] cursor [help|install|status|uninstall]",
|
|
26
|
-
"behavior": "Manages Cursor correlation hooks in the Cursor desktop app and IDE; model endpoint setup is completed in Cursor Settings. The Cursor CLI is not supported.",
|
|
27
|
-
"options": [
|
|
28
|
-
["help", "Show this help"],
|
|
29
|
-
["install", "Install or update the Cursor hooks (default action)"],
|
|
30
|
-
["status", "Inspect the installed hook configuration"],
|
|
31
|
-
["uninstall", "Remove only the Subconscious Cursor hooks"]
|
|
32
|
-
]
|
|
33
|
-
},
|
|
34
|
-
"prerequisites": [
|
|
35
|
-
{ "command": "jq", "needed_for": "install", "required": true },
|
|
36
|
-
{ "command": "curl", "needed_for": "install", "required": true }
|
|
37
|
-
],
|
|
38
|
-
"launch": null,
|
|
39
|
-
"inputs": [
|
|
40
|
-
{
|
|
41
|
-
"name": "CURSOR_API_KEY",
|
|
42
|
-
"label": "Cursor hook API key",
|
|
43
|
-
"description": "Cursor hook credential override; blank uses API_KEY.",
|
|
44
|
-
"type": "secret"
|
|
45
|
-
}
|
|
46
|
-
],
|
|
47
|
-
"env": [],
|
|
48
|
-
"config": [
|
|
49
|
-
{
|
|
50
|
-
"kind": "file",
|
|
51
|
-
"path": "~/.cursor/hooks.json",
|
|
52
|
-
"lifetime": "persistent",
|
|
53
|
-
"description": "beforeSubmitPrompt and preCompact entries are merged in; other entries are kept."
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
"kind": "file",
|
|
57
|
-
"path": "~/.cursor/hooks/subconscious-hook.sh",
|
|
58
|
-
"lifetime": "persistent",
|
|
59
|
-
"description": "Hook script."
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
"kind": "file",
|
|
63
|
-
"path": "~/.cursor/subconscious-hooks.env",
|
|
64
|
-
"lifetime": "persistent",
|
|
65
|
-
"description": "SUBCONSCIOUS_GATEWAY_URL and SUBCONSCIOUS_API_KEY for the hook (mode 600)."
|
|
66
|
-
}
|
|
67
|
-
],
|
|
68
|
-
"capabilities": {
|
|
69
|
-
"compaction": {
|
|
70
|
-
"default": "unknown",
|
|
71
|
-
"on": {
|
|
72
|
-
"set_by_subc": false,
|
|
73
|
-
"kind": null,
|
|
74
|
-
"name": null,
|
|
75
|
-
"value": null,
|
|
76
|
-
"verified": false,
|
|
77
|
-
"note": "subc does not configure Cursor compaction."
|
|
78
|
-
},
|
|
79
|
-
"off": {
|
|
80
|
-
"set_by_subc": false,
|
|
81
|
-
"kind": null,
|
|
82
|
-
"name": null,
|
|
83
|
-
"value": null,
|
|
84
|
-
"verified": false,
|
|
85
|
-
"note": "subc does not configure Cursor compaction."
|
|
86
|
-
},
|
|
87
|
-
"threshold": {
|
|
88
|
-
"set_by_subc": false,
|
|
89
|
-
"kind": null,
|
|
90
|
-
"name": null,
|
|
91
|
-
"value": null,
|
|
92
|
-
"verified": false,
|
|
93
|
-
"note": "subc does not configure Cursor compaction."
|
|
94
|
-
}
|
|
95
|
-
},
|
|
96
|
-
"mcp": {
|
|
97
|
-
"configured_by_subc": false,
|
|
98
|
-
"transports": null,
|
|
99
|
-
"verified": false,
|
|
100
|
-
"note": "subc configures no MCP servers."
|
|
101
|
-
},
|
|
102
|
-
"headers": [
|
|
103
|
-
{
|
|
104
|
-
"name": "x-subconscious-client",
|
|
105
|
-
"value": "cursor",
|
|
106
|
-
"on": "hook POSTs to {baseUrl}/v1/agent-hooks",
|
|
107
|
-
"set_by_subc": true,
|
|
108
|
-
"verified": true,
|
|
109
|
-
"note": "Cursor cannot add headers to model requests."
|
|
110
|
-
},
|
|
111
|
-
{
|
|
112
|
-
"name": "Authorization",
|
|
113
|
-
"value": "Bearer {apiKey}",
|
|
114
|
-
"on": "hook POSTs to {baseUrl}/v1/agent-hooks",
|
|
115
|
-
"set_by_subc": true,
|
|
116
|
-
"verified": true
|
|
117
|
-
}
|
|
118
|
-
],
|
|
119
|
-
"hooks": [
|
|
120
|
-
{
|
|
121
|
-
"event": "beforeSubmitPrompt",
|
|
122
|
-
"posts": "conversation_ensure",
|
|
123
|
-
"endpoint": "{baseUrl}/v1/agent-hooks",
|
|
124
|
-
"config": "~/.cursor/hooks.json",
|
|
125
|
-
"installed_by": ["install"],
|
|
126
|
-
"verified": true
|
|
127
|
-
},
|
|
128
|
-
{
|
|
129
|
-
"event": "preCompact",
|
|
130
|
-
"posts": "conversation_compaction phase=point",
|
|
131
|
-
"endpoint": "{baseUrl}/v1/agent-hooks",
|
|
132
|
-
"config": "~/.cursor/hooks.json",
|
|
133
|
-
"installed_by": ["install"],
|
|
134
|
-
"verified": true
|
|
135
|
-
}
|
|
136
|
-
]
|
|
137
|
-
}
|
|
138
|
-
}
|
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Fail-open Cursor hook: announce prompts and compactions to the gateway. Never
|
|
3
|
-
# blocks the agent.
|
|
4
|
-
# Stdin: Cursor hook JSON. Stdout: permissive JSON for Cursor.
|
|
5
|
-
#
|
|
6
|
-
# Docs: https://cursor.com/docs/hooks
|
|
7
|
-
#
|
|
8
|
-
# Two events, one call each:
|
|
9
|
-
# beforeSubmitPrompt -> conversation_ensure { conversation_id, prompt }
|
|
10
|
-
# preCompact -> conversation_compaction { conversation_id, phase: point }
|
|
11
|
-
#
|
|
12
|
-
# The gateway fingerprints the raw prompt itself and chains every later turn of
|
|
13
|
-
# the conversation onto the first one, so this hook needs no hashing and no local
|
|
14
|
-
# state. Cursor cannot inject headers into model HTTP, which is why this
|
|
15
|
-
# announcement exists at all.
|
|
16
|
-
#
|
|
17
|
-
# preCompact is observational and Cursor has no postCompact, so compaction is
|
|
18
|
-
# reported as a zero-width "point": the gateway places the context boundary on the
|
|
19
|
-
# first following turn whose retained context actually shrinks. Cursor compacts
|
|
20
|
-
# mid-turn and keeps working without a new prompt, so a window closed at the next
|
|
21
|
-
# beforeSubmitPrompt would wrongly swallow real turns.
|
|
22
|
-
#
|
|
23
|
-
# Deliberately NOT registered: afterAgentThought / afterAgentResponse /
|
|
24
|
-
# preToolUse / stop / subagentStart / subagentStop. Subagents are correlated
|
|
25
|
-
# gateway-side from the parent's tool-call prompt. Tool-execution hooks are also
|
|
26
|
-
# the ones Cursor is known to emit with an empty conversation_id after a mid-turn
|
|
27
|
-
# compaction; conversation-lifecycle hooks like the two above are unaffected.
|
|
28
|
-
|
|
29
|
-
set -u
|
|
30
|
-
|
|
31
|
-
CONFIG="${SUBCONSCIOUS_HOOKS_ENV:-${HOME}/.cursor/subconscious-hooks.env}"
|
|
32
|
-
if [[ -f "$CONFIG" ]]; then
|
|
33
|
-
# shellcheck disable=SC1090
|
|
34
|
-
source "$CONFIG"
|
|
35
|
-
fi
|
|
36
|
-
|
|
37
|
-
GATEWAY_URL="${SUBCONSCIOUS_GATEWAY_URL:-}"
|
|
38
|
-
API_KEY="${SUBCONSCIOUS_API_KEY:-}"
|
|
39
|
-
|
|
40
|
-
# Cursor reads `user_message` on preCompact and a permission object on
|
|
41
|
-
# beforeSubmitPrompt, so the reply shape depends on the event.
|
|
42
|
-
HOOK_EVENT=""
|
|
43
|
-
|
|
44
|
-
fail_open() {
|
|
45
|
-
if [[ "$HOOK_EVENT" == "preCompact" ]]; then
|
|
46
|
-
printf '%s\n' '{}'
|
|
47
|
-
else
|
|
48
|
-
printf '%s\n' '{"continue":true,"permission":"allow"}'
|
|
49
|
-
fi
|
|
50
|
-
exit 0
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if [[ -z "$GATEWAY_URL" || -z "$API_KEY" ]]; then
|
|
54
|
-
echo "subconscious hook: missing SUBCONSCIOUS_GATEWAY_URL or SUBCONSCIOUS_API_KEY" >&2
|
|
55
|
-
fail_open
|
|
56
|
-
fi
|
|
57
|
-
|
|
58
|
-
for tool in jq curl; do
|
|
59
|
-
if ! command -v "$tool" >/dev/null 2>&1; then
|
|
60
|
-
echo "subconscious hook: ${tool} is required" >&2
|
|
61
|
-
fail_open
|
|
62
|
-
fi
|
|
63
|
-
done
|
|
64
|
-
|
|
65
|
-
INPUT="$(cat || true)"
|
|
66
|
-
if [[ -z "$INPUT" ]]; then
|
|
67
|
-
fail_open
|
|
68
|
-
fi
|
|
69
|
-
|
|
70
|
-
HOOK_EVENT="$(printf '%s' "$INPUT" | jq -r '.hook_event_name // empty' 2>/dev/null || true)"
|
|
71
|
-
case "$HOOK_EVENT" in
|
|
72
|
-
beforeSubmitPrompt | preCompact) ;;
|
|
73
|
-
*) fail_open ;;
|
|
74
|
-
esac
|
|
75
|
-
|
|
76
|
-
CONVERSATION_ID="$(printf '%s' "$INPUT" | jq -r '.conversation_id // .session_id // empty' 2>/dev/null || true)"
|
|
77
|
-
if [[ -z "$CONVERSATION_ID" ]]; then
|
|
78
|
-
fail_open
|
|
79
|
-
fi
|
|
80
|
-
|
|
81
|
-
if [[ "$HOOK_EVENT" == "preCompact" ]]; then
|
|
82
|
-
# generation_id plus is_first_compaction keeps successive compactions distinct
|
|
83
|
-
# while making a redelivery of the same one idempotent server-side.
|
|
84
|
-
PAYLOAD="$(printf '%s' "$INPUT" | jq -c \
|
|
85
|
-
--arg conversation_id "$CONVERSATION_ID" \
|
|
86
|
-
--arg hook_event_name "$HOOK_EVENT" \
|
|
87
|
-
'{
|
|
88
|
-
event: "conversation_compaction",
|
|
89
|
-
conversation_id: $conversation_id,
|
|
90
|
-
phase: "point",
|
|
91
|
-
hook_event_name: $hook_event_name,
|
|
92
|
-
dedupe_key: ($conversation_id + ":" + ((.generation_id // "gen") | tostring)
|
|
93
|
-
+ ":" + ((.is_first_compaction // false) | tostring)),
|
|
94
|
-
metadata: {
|
|
95
|
-
trigger: .trigger,
|
|
96
|
-
context_tokens: .context_tokens,
|
|
97
|
-
context_window_size: .context_window_size,
|
|
98
|
-
context_usage_percent: .context_usage_percent,
|
|
99
|
-
message_count: .message_count,
|
|
100
|
-
messages_to_compact: .messages_to_compact,
|
|
101
|
-
is_first_compaction: .is_first_compaction
|
|
102
|
-
} | with_entries(select(.value != null))
|
|
103
|
-
}' 2>/dev/null || true)"
|
|
104
|
-
else
|
|
105
|
-
PROMPT="$(printf '%s' "$INPUT" | jq -r '.prompt // empty' 2>/dev/null || true)"
|
|
106
|
-
WORKSPACE="$(printf '%s' "$INPUT" | jq -r '(.workspace_roots // [])[0] // empty' 2>/dev/null || true)"
|
|
107
|
-
if [[ -n "$WORKSPACE" ]]; then
|
|
108
|
-
WORKSPACE="$(basename "$WORKSPACE")"
|
|
109
|
-
fi
|
|
110
|
-
|
|
111
|
-
# Nothing to anchor on without a prompt.
|
|
112
|
-
if [[ -z "$PROMPT" ]]; then
|
|
113
|
-
fail_open
|
|
114
|
-
fi
|
|
115
|
-
|
|
116
|
-
PAYLOAD="$(jq -n \
|
|
117
|
-
--arg event "conversation_ensure" \
|
|
118
|
-
--arg conversation_id "$CONVERSATION_ID" \
|
|
119
|
-
--arg prompt "$PROMPT" \
|
|
120
|
-
--arg workspace "$WORKSPACE" \
|
|
121
|
-
--arg hook_event_name "$HOOK_EVENT" \
|
|
122
|
-
'{
|
|
123
|
-
event: $event,
|
|
124
|
-
conversation_id: $conversation_id,
|
|
125
|
-
prompt: $prompt,
|
|
126
|
-
workspace: (if $workspace == "" then null else $workspace end),
|
|
127
|
-
hook_event_name: $hook_event_name
|
|
128
|
-
} | with_entries(select(.value != null))'
|
|
129
|
-
)"
|
|
130
|
-
fi
|
|
131
|
-
|
|
132
|
-
if [[ -z "$PAYLOAD" ]]; then
|
|
133
|
-
fail_open
|
|
134
|
-
fi
|
|
135
|
-
|
|
136
|
-
# Response body is intentionally ignored: the gateway resolves the Cursor
|
|
137
|
-
# conversation id to its own UUID on every call, so there is no mapping to cache.
|
|
138
|
-
curl -sS -m 2 \
|
|
139
|
-
-H "Authorization: Bearer ${API_KEY}" \
|
|
140
|
-
-H "Content-Type: application/json" \
|
|
141
|
-
-H "x-subconscious-client: cursor" \
|
|
142
|
-
-d "$PAYLOAD" \
|
|
143
|
-
"${GATEWAY_URL%/}/v1/agent-hooks" >/dev/null 2>&1 || true
|
|
144
|
-
|
|
145
|
-
fail_open
|
|
@@ -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
|