subconscious-cli 0.2.0 → 0.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 +237 -54
- package/bin/agents.js +634 -114
- package/bin/auth.js +94 -20
- package/bin/branding.js +39 -0
- package/bin/cli.js +187 -17
- package/bin/colors.js +22 -10
- package/bin/profiles.js +852 -0
- package/bin/registry.generated.json +268 -0
- package/bin/runbook/README.md +25 -0
- package/bin/runbook/claude-code/install.sh +316 -0
- package/bin/runbook/claude-code/run.sh +164 -0
- package/bin/runbook/codex/hook.sh +112 -0
- package/bin/runbook/codex/hooks.json +29 -0
- package/bin/runbook/codex/install.sh +524 -0
- package/bin/runbook/codex/run.sh +264 -0
- package/bin/runbook/copilot/hook.sh +173 -0
- package/bin/runbook/copilot/hooks.json +19 -0
- package/bin/runbook/copilot/install.sh +469 -0
- package/bin/runbook/cursor/hook.sh +145 -0
- package/bin/runbook/cursor/hooks.json +17 -0
- package/bin/runbook/cursor/install.sh +259 -0
- package/bin/runbook/opencode/install.sh +298 -0
- package/bin/runbook/opencode/run.sh +107 -0
- package/bin/runbook/opencode/subconscious-compaction.ts +100 -0
- package/bin/runbook/pi/install.sh +282 -0
- package/bin/runbook/pi/run.sh +25 -0
- package/bin/runbook/pi/subconscious-compaction.ts +151 -0
- package/package.json +10 -5
|
@@ -0,0 +1,259 @@
|
|
|
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
|
|
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
|
+
HOOKS_TEMPLATE="${SCRIPT_DIR}/hooks.json"
|
|
48
|
+
|
|
49
|
+
# Load shared env from coding-agents/.env (gitignored) or env.example.
|
|
50
|
+
SHARED_ENV="${MBTA_ENV_FILE:-${SCRIPT_DIR}/../.env}"
|
|
51
|
+
[[ -f "$SHARED_ENV" ]] || SHARED_ENV="${SCRIPT_DIR}/../env.example"
|
|
52
|
+
if [[ -f "$SHARED_ENV" ]]; then set -a; source "$SHARED_ENV"; set +a; fi
|
|
53
|
+
|
|
54
|
+
GATEWAY_URL="${GATEWAY_URL:-}"
|
|
55
|
+
API_KEY="${CURSOR_API_KEY:-${API_KEY:-}}"
|
|
56
|
+
COMMAND="install"
|
|
57
|
+
|
|
58
|
+
usage() {
|
|
59
|
+
cat <<'EOF'
|
|
60
|
+
Usage:
|
|
61
|
+
install.sh [install] --gateway-url URL --api-key KEY
|
|
62
|
+
install.sh uninstall
|
|
63
|
+
install.sh status
|
|
64
|
+
|
|
65
|
+
`install` is the default subcommand and may be omitted.
|
|
66
|
+
|
|
67
|
+
Installs Cursor hooks (user-wide under ~/.cursor) that POST to /v1/agent-hooks:
|
|
68
|
+
conversation_ensure on each prompt submission so the gateway can group
|
|
69
|
+
Conversations for Cursor traffic, and conversation_compaction on preCompact so
|
|
70
|
+
context accounting restarts at the right turn.
|
|
71
|
+
|
|
72
|
+
Requires: jq, curl. Restart Cursor after install.
|
|
73
|
+
EOF
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
while [[ $# -gt 0 ]]; do
|
|
77
|
+
case "$1" in
|
|
78
|
+
install|uninstall|status)
|
|
79
|
+
COMMAND="$1"
|
|
80
|
+
shift
|
|
81
|
+
;;
|
|
82
|
+
--gateway-url)
|
|
83
|
+
GATEWAY_URL="${2:-}"
|
|
84
|
+
shift 2
|
|
85
|
+
;;
|
|
86
|
+
--api-key)
|
|
87
|
+
API_KEY="${2:-}"
|
|
88
|
+
shift 2
|
|
89
|
+
;;
|
|
90
|
+
-h|--help)
|
|
91
|
+
usage
|
|
92
|
+
exit 0
|
|
93
|
+
;;
|
|
94
|
+
*)
|
|
95
|
+
echo "unknown argument: $1" >&2
|
|
96
|
+
usage >&2
|
|
97
|
+
exit 1
|
|
98
|
+
;;
|
|
99
|
+
esac
|
|
100
|
+
done
|
|
101
|
+
|
|
102
|
+
CURSOR_DIR="${HOME}/.cursor"
|
|
103
|
+
HOOKS_JSON="${CURSOR_DIR}/hooks.json"
|
|
104
|
+
HOOK_DST="${CURSOR_DIR}/hooks/subconscious-hook.sh"
|
|
105
|
+
ENV_FILE="${CURSOR_DIR}/subconscious-hooks.env"
|
|
106
|
+
MARKER="subconscious-hook.sh"
|
|
107
|
+
|
|
108
|
+
require_cmds() {
|
|
109
|
+
local missing=0
|
|
110
|
+
for c in jq curl; do
|
|
111
|
+
if ! command -v "$c" >/dev/null 2>&1; then
|
|
112
|
+
echo "missing required command: $c" >&2
|
|
113
|
+
missing=1
|
|
114
|
+
fi
|
|
115
|
+
done
|
|
116
|
+
if [[ "$missing" -ne 0 ]]; then
|
|
117
|
+
exit 1
|
|
118
|
+
fi
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
write_env() {
|
|
122
|
+
umask 077
|
|
123
|
+
cat >"$ENV_FILE" <<EOF
|
|
124
|
+
# Generated by ol-runbook/coding-agents/cursor/install.sh — do not commit secrets.
|
|
125
|
+
export SUBCONSCIOUS_GATEWAY_URL='${GATEWAY_URL}'
|
|
126
|
+
export SUBCONSCIOUS_API_KEY='${API_KEY}'
|
|
127
|
+
EOF
|
|
128
|
+
chmod 600 "$ENV_FILE"
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
install_hook_script() {
|
|
132
|
+
mkdir -p "$(dirname "$HOOK_DST")"
|
|
133
|
+
cp "$HOOK_SRC" "$HOOK_DST"
|
|
134
|
+
chmod +x "$HOOK_DST"
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
# merge_hook_entries <hooks_json> <marker> <command> <event> [<event>...]
|
|
138
|
+
#
|
|
139
|
+
# Additive, idempotent merge of a single hook command into one or more events
|
|
140
|
+
# of a hooks.json file. Preserves ALL other entries verbatim, including
|
|
141
|
+
# third-party hooks, prompt hooks (no `command` field), and unknown events.
|
|
142
|
+
#
|
|
143
|
+
# Invariants:
|
|
144
|
+
# - Only entries whose `command` contains <marker> are replaced; others untouched.
|
|
145
|
+
# - Running twice yields the same result as once (idempotent, order-stable).
|
|
146
|
+
# - Entries without a `command` field are never matched/removed.
|
|
147
|
+
# - Missing/empty file is initialized to {"version":1,"hooks":{}}.
|
|
148
|
+
merge_hook_entries() {
|
|
149
|
+
local hooks_json="$1" marker="$2" command="$3"
|
|
150
|
+
shift 3
|
|
151
|
+
local events
|
|
152
|
+
events="$(printf '%s\n' "$@" | jq -R . | jq -s .)"
|
|
153
|
+
|
|
154
|
+
if [[ ! -f "$hooks_json" ]]; then
|
|
155
|
+
printf '%s\n' '{"version":1,"hooks":{}}' >"$hooks_json"
|
|
156
|
+
fi
|
|
157
|
+
|
|
158
|
+
local tmp
|
|
159
|
+
tmp="$(mktemp)"
|
|
160
|
+
jq --arg marker "$marker" --arg cmd "$command" --argjson events "$events" '
|
|
161
|
+
.version = (.version // 1) |
|
|
162
|
+
.hooks = (.hooks // {}) |
|
|
163
|
+
# Strip prior entries with our marker from every event, keep everything else.
|
|
164
|
+
.hooks |= with_entries(
|
|
165
|
+
.value = ((.value // []) | map(select((.command // "") | tostring | contains($marker) | not)))
|
|
166
|
+
) |
|
|
167
|
+
# Append our entry to each requested event (preserving any other entries).
|
|
168
|
+
.hooks = (.hooks | reduce ($events[]) as $e (.;
|
|
169
|
+
.[$e] = ((.[$e] // []) + [{"command": $cmd, "timeout": 2}])
|
|
170
|
+
))
|
|
171
|
+
' "$hooks_json" >"$tmp"
|
|
172
|
+
mv "$tmp" "$hooks_json"
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
# remove_hook_entries <hooks_json> <marker>
|
|
176
|
+
#
|
|
177
|
+
# Remove every entry whose `command` contains <marker> from all events.
|
|
178
|
+
# Preserves all other entries (third-party, prompt hooks, unknown events).
|
|
179
|
+
remove_hook_entries() {
|
|
180
|
+
local hooks_json="$1" marker="$2"
|
|
181
|
+
[[ -f "$hooks_json" ]] || return 0
|
|
182
|
+
local tmp
|
|
183
|
+
tmp="$(mktemp)"
|
|
184
|
+
jq --arg marker "$marker" '
|
|
185
|
+
.hooks //= {} |
|
|
186
|
+
.hooks |= with_entries(
|
|
187
|
+
.value = ((.value // []) | map(select((.command // "") | tostring | contains($marker) | not)))
|
|
188
|
+
)
|
|
189
|
+
' "$hooks_json" >"$tmp"
|
|
190
|
+
mv "$tmp" "$hooks_json"
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
merge_hooks_json() {
|
|
194
|
+
if [[ ! -f "$HOOKS_JSON" ]]; then
|
|
195
|
+
sed "s|HOOK_SH_PATH|${HOOK_DST}|g" "$HOOKS_TEMPLATE" >"$HOOKS_JSON"
|
|
196
|
+
return
|
|
197
|
+
fi
|
|
198
|
+
# Replace our marker entries, then register the two lifecycle events we use.
|
|
199
|
+
remove_hook_entries "$HOOKS_JSON" "$MARKER"
|
|
200
|
+
merge_hook_entries "$HOOKS_JSON" "$MARKER" "$HOOK_DST" beforeSubmitPrompt preCompact
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
uninstall_hooks() {
|
|
204
|
+
remove_hook_entries "$HOOKS_JSON" "$MARKER"
|
|
205
|
+
rm -f "$HOOK_DST" "$ENV_FILE"
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
status() {
|
|
209
|
+
echo "cursor dir: $CURSOR_DIR"
|
|
210
|
+
echo "hooks.json: $HOOKS_JSON"
|
|
211
|
+
if [[ -f "$HOOKS_JSON" ]] && jq -e --arg m "$MARKER" '
|
|
212
|
+
[.hooks[]?[]? | select((.command // "") | tostring | contains($m))] | length > 0
|
|
213
|
+
' "$HOOKS_JSON" >/dev/null 2>&1; then
|
|
214
|
+
echo "hooks: installed"
|
|
215
|
+
else
|
|
216
|
+
echo "hooks: not installed"
|
|
217
|
+
fi
|
|
218
|
+
if [[ -f "$ENV_FILE" ]]; then
|
|
219
|
+
echo "env: $ENV_FILE (present)"
|
|
220
|
+
# shellcheck disable=SC1090
|
|
221
|
+
source "$ENV_FILE"
|
|
222
|
+
echo "gateway: ${SUBCONSCIOUS_GATEWAY_URL:-unset}"
|
|
223
|
+
if [[ -n "${SUBCONSCIOUS_API_KEY:-}" ]]; then
|
|
224
|
+
echo "api key: set (${#SUBCONSCIOUS_API_KEY} chars)"
|
|
225
|
+
else
|
|
226
|
+
echo "api key: unset"
|
|
227
|
+
fi
|
|
228
|
+
else
|
|
229
|
+
echo "env: missing"
|
|
230
|
+
fi
|
|
231
|
+
if [[ -x "$HOOK_DST" ]]; then
|
|
232
|
+
echo "hook script: $HOOK_DST (executable)"
|
|
233
|
+
else
|
|
234
|
+
echo "hook script: missing"
|
|
235
|
+
fi
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
case "$COMMAND" in
|
|
239
|
+
install)
|
|
240
|
+
require_cmds
|
|
241
|
+
if [[ -z "$GATEWAY_URL" || -z "$API_KEY" ]]; then
|
|
242
|
+
echo "--gateway-url and --api-key are required for install" >&2
|
|
243
|
+
exit 1
|
|
244
|
+
fi
|
|
245
|
+
mkdir -p "$CURSOR_DIR"
|
|
246
|
+
install_hook_script
|
|
247
|
+
write_env
|
|
248
|
+
merge_hooks_json
|
|
249
|
+
echo "Installed Subconscious Cursor hooks into $CURSOR_DIR"
|
|
250
|
+
echo "Restart Cursor to reload hooks.json."
|
|
251
|
+
;;
|
|
252
|
+
uninstall)
|
|
253
|
+
uninstall_hooks
|
|
254
|
+
echo "Removed Subconscious Cursor hooks from $CURSOR_DIR"
|
|
255
|
+
;;
|
|
256
|
+
status)
|
|
257
|
+
status
|
|
258
|
+
;;
|
|
259
|
+
esac
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ── Subconscious API Gateway — OpenCode setup ─────────────────────────────────
|
|
3
|
+
# Point OpenCode at your gateway. Writes a provider config with session headers
|
|
4
|
+
# so the gateway can correlate OpenCode requests 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 config
|
|
9
|
+
# ./install.sh uninstall # revert to default opencode config
|
|
10
|
+
#
|
|
11
|
+
# Writes ~/.opencode/opencode.json (user config). Does not touch project-level
|
|
12
|
+
# opencode.json files. Restart opencode after install.
|
|
13
|
+
#
|
|
14
|
+
# ── What this does under the hood ────────────────────────────────────────────
|
|
15
|
+
# Equivalent manual setup (writes ~/.opencode/opencode.json + env var):
|
|
16
|
+
#
|
|
17
|
+
# export SUBCONSCIOUS_API_KEY=sk-gw-...
|
|
18
|
+
# cat > ~/.opencode/opencode.json <<'EOF'
|
|
19
|
+
# {
|
|
20
|
+
# "$schema": "https://opencode.ai/config.json",
|
|
21
|
+
# "provider": {
|
|
22
|
+
# "subconscious": {
|
|
23
|
+
# "npm": "@ai-sdk/openai-compatible",
|
|
24
|
+
# "name": "Subconscious Gateway",
|
|
25
|
+
# "options": {
|
|
26
|
+
# "baseURL": "https://your-gateway.example/v1",
|
|
27
|
+
# "apiKey": "{env:SUBCONSCIOUS_API_KEY}",
|
|
28
|
+
# "headers": { "x-subconscious-client": "opencode" }
|
|
29
|
+
# },
|
|
30
|
+
# "models": {
|
|
31
|
+
# "subconscious/glm-5.2": {
|
|
32
|
+
# "name": "subconscious/glm-5.2",
|
|
33
|
+
# "tools": true,
|
|
34
|
+
# "limit": { "context": 5000000, "output": 65536 }
|
|
35
|
+
# }
|
|
36
|
+
# }
|
|
37
|
+
# }
|
|
38
|
+
# },
|
|
39
|
+
# "model": "subconscious/subconscious/glm-5.2"
|
|
40
|
+
# }
|
|
41
|
+
# EOF
|
|
42
|
+
# opencode
|
|
43
|
+
#
|
|
44
|
+
# The x-subconscious-client header tells the gateway to classify traffic as
|
|
45
|
+
# OpenCode. OpenCode also sends native x-session-affinity / x-session-id
|
|
46
|
+
# headers for conversation correlation. Model limit.context drives auto
|
|
47
|
+
# compaction (default on); custom providers do not inherit models.dev limits.
|
|
48
|
+
#
|
|
49
|
+
# Also installs a plugin that reports compactions to the gateway. OpenCode
|
|
50
|
+
# summarizes through the configured provider, so without it the gateway counts
|
|
51
|
+
# that summarization as a normal main-thread turn.
|
|
52
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
set -euo pipefail
|
|
55
|
+
|
|
56
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
57
|
+
|
|
58
|
+
# Load shared env from coding-agents/.env (gitignored) or env.example.
|
|
59
|
+
SHARED_ENV="${MBTA_ENV_FILE:-${SCRIPT_DIR}/../.env}"
|
|
60
|
+
[[ -f "$SHARED_ENV" ]] || SHARED_ENV="${SCRIPT_DIR}/../env.example"
|
|
61
|
+
if [[ -f "$SHARED_ENV" ]]; then set -a; source "$SHARED_ENV"; set +a; fi
|
|
62
|
+
|
|
63
|
+
COMMAND="install"
|
|
64
|
+
GATEWAY_URL="${GATEWAY_URL:-}"
|
|
65
|
+
API_KEY="${OPENCODE_API_KEY:-${API_KEY:-}}"
|
|
66
|
+
MODEL="${MODEL:-subconscious/glm-5.2}"
|
|
67
|
+
# OpenCode auto-compaction uses model limit.context. Custom openai-compatible
|
|
68
|
+
# providers do not get that from baseURL/models.dev; set it explicitly.
|
|
69
|
+
CONTEXT_LIMIT="${OPENCODE_CONTEXT_LIMIT:-5000000}"
|
|
70
|
+
OUTPUT_LIMIT="${OPENCODE_OUTPUT_LIMIT:-65536}"
|
|
71
|
+
|
|
72
|
+
usage() {
|
|
73
|
+
cat <<'EOF'
|
|
74
|
+
Usage:
|
|
75
|
+
install.sh [install] --gateway-url URL --api-key KEY [--model MODEL]
|
|
76
|
+
[--context-limit N] [--output-limit N]
|
|
77
|
+
install.sh uninstall
|
|
78
|
+
install.sh status
|
|
79
|
+
|
|
80
|
+
`install` is the default subcommand and may be omitted.
|
|
81
|
+
|
|
82
|
+
Writes an opencode.json that points opencode at your Subconscious gateway with
|
|
83
|
+
x-subconscious-client: opencode and x-session-affinity/x-session-id session
|
|
84
|
+
headers so the gateway can group requests into Conversations.
|
|
85
|
+
|
|
86
|
+
Also sets model limit.context / limit.output so OpenCode auto-compaction
|
|
87
|
+
respects the gateway window (compaction.auto stays enabled / default), and
|
|
88
|
+
installs a plugin that reports compactions so the dashboard can restart context
|
|
89
|
+
accounting at the right turn.
|
|
90
|
+
|
|
91
|
+
Requires: jq. Restart opencode after install.
|
|
92
|
+
EOF
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
while [[ $# -gt 0 ]]; do
|
|
96
|
+
case "$1" in
|
|
97
|
+
install|uninstall|status)
|
|
98
|
+
COMMAND="$1"
|
|
99
|
+
shift
|
|
100
|
+
;;
|
|
101
|
+
--gateway-url)
|
|
102
|
+
GATEWAY_URL="${2:-}"
|
|
103
|
+
shift 2
|
|
104
|
+
;;
|
|
105
|
+
--api-key)
|
|
106
|
+
API_KEY="${2:-}"
|
|
107
|
+
shift 2
|
|
108
|
+
;;
|
|
109
|
+
--model)
|
|
110
|
+
MODEL="${2:-}"
|
|
111
|
+
shift 2
|
|
112
|
+
;;
|
|
113
|
+
--context-limit)
|
|
114
|
+
CONTEXT_LIMIT="${2:-}"
|
|
115
|
+
shift 2
|
|
116
|
+
;;
|
|
117
|
+
--output-limit)
|
|
118
|
+
OUTPUT_LIMIT="${2:-}"
|
|
119
|
+
shift 2
|
|
120
|
+
;;
|
|
121
|
+
-h|--help)
|
|
122
|
+
usage
|
|
123
|
+
exit 0
|
|
124
|
+
;;
|
|
125
|
+
*)
|
|
126
|
+
echo "unknown argument: $1" >&2
|
|
127
|
+
usage >&2
|
|
128
|
+
exit 1
|
|
129
|
+
;;
|
|
130
|
+
esac
|
|
131
|
+
done
|
|
132
|
+
|
|
133
|
+
DEFAULT_SUBCONSCIOUS_MODELS="subconscious/glm-5.2
|
|
134
|
+
subconscious/tim-qwen3.6-27b
|
|
135
|
+
subconscious/deepseek-v4-flash-marathon"
|
|
136
|
+
SUPPORTED_MODELS=()
|
|
137
|
+
|
|
138
|
+
add_supported_model() {
|
|
139
|
+
local model_id="$1" existing
|
|
140
|
+
[[ -n "$model_id" ]] || return 0
|
|
141
|
+
if [[ ! "$model_id" =~ ^[-A-Za-z0-9._:/+]+$ ]]; then
|
|
142
|
+
echo "error: invalid model id: $model_id" >&2
|
|
143
|
+
exit 1
|
|
144
|
+
fi
|
|
145
|
+
if [[ "${#SUPPORTED_MODELS[@]}" -gt 0 ]]; then
|
|
146
|
+
for existing in "${SUPPORTED_MODELS[@]}"; do
|
|
147
|
+
[[ "$existing" == "$model_id" ]] && return 0
|
|
148
|
+
done
|
|
149
|
+
fi
|
|
150
|
+
SUPPORTED_MODELS+=("$model_id")
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
add_supported_model "$MODEL"
|
|
154
|
+
while IFS= read -r model_id; do
|
|
155
|
+
add_supported_model "$model_id"
|
|
156
|
+
done <<< "${SUBCONSCIOUS_MODELS:-$DEFAULT_SUBCONSCIOUS_MODELS}"
|
|
157
|
+
|
|
158
|
+
MODELS_JSON=""
|
|
159
|
+
for model_id in "${SUPPORTED_MODELS[@]}"; do
|
|
160
|
+
model_json="\"${model_id}\":{\"name\":\"${model_id}\",\"tools\":true,\"limit\":{\"context\":${CONTEXT_LIMIT},\"output\":${OUTPUT_LIMIT}}}"
|
|
161
|
+
if [[ -n "$MODELS_JSON" ]]; then
|
|
162
|
+
MODELS_JSON="${MODELS_JSON},${model_json}"
|
|
163
|
+
else
|
|
164
|
+
MODELS_JSON="$model_json"
|
|
165
|
+
fi
|
|
166
|
+
done
|
|
167
|
+
|
|
168
|
+
OPENCODE_DIR="${HOME}/.opencode"
|
|
169
|
+
OPENCODE_CONFIG="${OPENCODE_DIR}/opencode.json"
|
|
170
|
+
MARKER='x-subconscious-client'
|
|
171
|
+
# Plugins load from the XDG config dir, which is separate from the legacy ~/.opencode
|
|
172
|
+
# config path above. Docs: https://opencode.ai/docs/plugins/
|
|
173
|
+
PLUGIN_DIR="${XDG_CONFIG_HOME:-${HOME}/.config}/opencode/plugins"
|
|
174
|
+
PLUGIN_FILE="${PLUGIN_DIR}/subconscious-compaction.ts"
|
|
175
|
+
PLUGIN_SOURCE="${SCRIPT_DIR}/subconscious-compaction.ts"
|
|
176
|
+
|
|
177
|
+
require_cmds() {
|
|
178
|
+
local missing=0
|
|
179
|
+
for c in jq; do
|
|
180
|
+
if ! command -v "$c" >/dev/null 2>&1; then
|
|
181
|
+
echo "missing required command: $c" >&2
|
|
182
|
+
missing=1
|
|
183
|
+
fi
|
|
184
|
+
done
|
|
185
|
+
if [[ "$missing" -ne 0 ]]; then
|
|
186
|
+
exit 1
|
|
187
|
+
fi
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
write_config() {
|
|
191
|
+
mkdir -p "$OPENCODE_DIR"
|
|
192
|
+
local base_url="${GATEWAY_URL%/}/v1"
|
|
193
|
+
local config
|
|
194
|
+
config=$(cat <<EOF
|
|
195
|
+
{
|
|
196
|
+
"\$schema": "https://opencode.ai/config.json",
|
|
197
|
+
"provider": {
|
|
198
|
+
"subconscious": {
|
|
199
|
+
"npm": "@ai-sdk/openai-compatible",
|
|
200
|
+
"name": "Subconscious Gateway",
|
|
201
|
+
"options": {
|
|
202
|
+
"baseURL": "${base_url}",
|
|
203
|
+
"apiKey": "{env:SUBCONSCIOUS_API_KEY}",
|
|
204
|
+
"headers": {
|
|
205
|
+
"x-subconscious-client": "opencode"
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
"models": {${MODELS_JSON}}
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
"model": "subconscious/${MODEL}"
|
|
212
|
+
}
|
|
213
|
+
EOF
|
|
214
|
+
)
|
|
215
|
+
echo "$config" >"$OPENCODE_CONFIG"
|
|
216
|
+
# Also export the API key into the shell env file for interactive sessions. The plugin
|
|
217
|
+
# reads the gateway URL from the same file.
|
|
218
|
+
local env_file="${OPENCODE_DIR}/subconscious.env"
|
|
219
|
+
umask 077
|
|
220
|
+
cat >"$env_file" <<EOF
|
|
221
|
+
# Generated by ol-runbook/coding-agents/opencode/install.sh — do not commit secrets.
|
|
222
|
+
export SUBCONSCIOUS_API_KEY='${API_KEY}'
|
|
223
|
+
export SUBCONSCIOUS_GATEWAY_URL='${GATEWAY_URL%/}'
|
|
224
|
+
EOF
|
|
225
|
+
chmod 600 "$env_file"
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
write_plugin() {
|
|
229
|
+
if [[ ! -f "$PLUGIN_SOURCE" ]]; then
|
|
230
|
+
echo "warning: ${PLUGIN_SOURCE} missing; skipping compaction plugin" >&2
|
|
231
|
+
return
|
|
232
|
+
fi
|
|
233
|
+
mkdir -p "$PLUGIN_DIR"
|
|
234
|
+
cp "$PLUGIN_SOURCE" "$PLUGIN_FILE"
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
uninstall_config() {
|
|
238
|
+
if [[ -f "$OPENCODE_CONFIG" ]] && grep -q "$MARKER" "$OPENCODE_CONFIG" 2>/dev/null; then
|
|
239
|
+
rm -f "$OPENCODE_CONFIG"
|
|
240
|
+
fi
|
|
241
|
+
rm -f "${OPENCODE_DIR}/subconscious.env"
|
|
242
|
+
rm -f "$PLUGIN_FILE"
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
status() {
|
|
246
|
+
echo "scope: user"
|
|
247
|
+
echo "opencode dir: $OPENCODE_DIR"
|
|
248
|
+
echo "config: $OPENCODE_CONFIG"
|
|
249
|
+
if [[ -f "$PLUGIN_FILE" ]]; then
|
|
250
|
+
echo "compaction plugin: $PLUGIN_FILE (installed)"
|
|
251
|
+
else
|
|
252
|
+
echo "compaction plugin: not installed"
|
|
253
|
+
fi
|
|
254
|
+
if [[ -f "$OPENCODE_CONFIG" ]] && grep -q "$MARKER" "$OPENCODE_CONFIG" 2>/dev/null; then
|
|
255
|
+
echo "status: installed"
|
|
256
|
+
echo "model: $(jq -r '.model // "unset"' "$OPENCODE_CONFIG" 2>/dev/null || echo 'unknown')"
|
|
257
|
+
echo "context limit: $(jq -r '
|
|
258
|
+
(.model // "") as $m
|
|
259
|
+
| ($m | sub("^subconscious/"; "")) as $id
|
|
260
|
+
| .provider.subconscious.models[$id].limit.context // "unset"
|
|
261
|
+
' "$OPENCODE_CONFIG" 2>/dev/null || echo 'unknown')"
|
|
262
|
+
echo "compaction.auto: $(jq -r '.compaction.auto // true' "$OPENCODE_CONFIG" 2>/dev/null || echo 'unknown')"
|
|
263
|
+
else
|
|
264
|
+
echo "status: not installed"
|
|
265
|
+
fi
|
|
266
|
+
if [[ -f "${OPENCODE_DIR}/subconscious.env" ]]; then
|
|
267
|
+
echo "env: ${OPENCODE_DIR}/subconscious.env (present)"
|
|
268
|
+
else
|
|
269
|
+
echo "env: missing"
|
|
270
|
+
fi
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
case "$COMMAND" in
|
|
274
|
+
install)
|
|
275
|
+
require_cmds
|
|
276
|
+
if [[ -z "$GATEWAY_URL" || -z "$API_KEY" ]]; then
|
|
277
|
+
echo "--gateway-url and --api-key are required for install" >&2
|
|
278
|
+
exit 1
|
|
279
|
+
fi
|
|
280
|
+
write_config
|
|
281
|
+
write_plugin
|
|
282
|
+
echo "Installed Subconscious OpenCode config at $OPENCODE_CONFIG"
|
|
283
|
+
if [[ -f "$PLUGIN_FILE" ]]; then
|
|
284
|
+
echo "Installed compaction reporting plugin at $PLUGIN_FILE"
|
|
285
|
+
fi
|
|
286
|
+
echo "Source the env file before launching opencode:"
|
|
287
|
+
echo " source ${OPENCODE_DIR}/subconscious.env"
|
|
288
|
+
echo "Or export SUBCONSCIOUS_API_KEY in your shell profile."
|
|
289
|
+
echo "Restart any running opencode sessions."
|
|
290
|
+
;;
|
|
291
|
+
uninstall)
|
|
292
|
+
uninstall_config
|
|
293
|
+
echo "Removed Subconscious OpenCode config from $OPENCODE_DIR"
|
|
294
|
+
;;
|
|
295
|
+
status)
|
|
296
|
+
status
|
|
297
|
+
;;
|
|
298
|
+
esac
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Point OpenCode at the Subconscious gateway — ephemerally.
|
|
3
|
+
#
|
|
4
|
+
# Exports SUBCONSCIOUS_API_KEY and OPENCODE_CONFIG_CONTENT as env vars so
|
|
5
|
+
# nothing is written to ~/.opencode/opencode.json. OpenCode reads the
|
|
6
|
+
# config from the OPENCODE_CONFIG_CONTENT env var at startup.
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# ./run.sh # uses GATEWAY_URL/API_KEY from ../.env
|
|
10
|
+
# ./run.sh -- auth # pass args through to opencode
|
|
11
|
+
#
|
|
12
|
+
# Config: copy ../env.example to ../.env and edit. .env is gitignored.
|
|
13
|
+
# All agents share one coding-agents/.env file.
|
|
14
|
+
#
|
|
15
|
+
# Or source it to just export the env:
|
|
16
|
+
# source run.sh
|
|
17
|
+
|
|
18
|
+
set -euo pipefail
|
|
19
|
+
|
|
20
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
21
|
+
|
|
22
|
+
# Load shared env from coding-agents/.env (gitignored) or env.example.
|
|
23
|
+
SHARED_ENV="${MBTA_ENV_FILE:-${SCRIPT_DIR}/../.env}"
|
|
24
|
+
[[ -f "$SHARED_ENV" ]] || SHARED_ENV="${SCRIPT_DIR}/../env.example"
|
|
25
|
+
if [[ -f "$SHARED_ENV" ]]; then set -a; source "$SHARED_ENV"; set +a; fi
|
|
26
|
+
|
|
27
|
+
GATEWAY_URL="${GATEWAY_URL:-}"
|
|
28
|
+
API_KEY="${OPENCODE_API_KEY:-${API_KEY:-}}"
|
|
29
|
+
MODEL="${MODEL:-subconscious/glm-5.2}"
|
|
30
|
+
CONTEXT_LIMIT="${OPENCODE_CONTEXT_LIMIT:-5000000}"
|
|
31
|
+
OUTPUT_LIMIT="${OPENCODE_OUTPUT_LIMIT:-65536}"
|
|
32
|
+
|
|
33
|
+
DEFAULT_SUBCONSCIOUS_MODELS="subconscious/glm-5.2
|
|
34
|
+
subconscious/tim-qwen3.6-27b
|
|
35
|
+
subconscious/deepseek-v4-flash-marathon"
|
|
36
|
+
SUPPORTED_MODELS=()
|
|
37
|
+
|
|
38
|
+
add_supported_model() {
|
|
39
|
+
local model_id="$1" existing
|
|
40
|
+
[[ -n "$model_id" ]] || return 0
|
|
41
|
+
if [[ ! "$model_id" =~ ^[-A-Za-z0-9._:/+]+$ ]]; then
|
|
42
|
+
echo "error: invalid model id: $model_id" >&2
|
|
43
|
+
exit 1
|
|
44
|
+
fi
|
|
45
|
+
if [[ "${#SUPPORTED_MODELS[@]}" -gt 0 ]]; then
|
|
46
|
+
for existing in "${SUPPORTED_MODELS[@]}"; do
|
|
47
|
+
[[ "$existing" == "$model_id" ]] && return 0
|
|
48
|
+
done
|
|
49
|
+
fi
|
|
50
|
+
SUPPORTED_MODELS+=("$model_id")
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
add_supported_model "$MODEL"
|
|
54
|
+
while IFS= read -r model_id; do
|
|
55
|
+
add_supported_model "$model_id"
|
|
56
|
+
done <<< "${SUBCONSCIOUS_MODELS:-$DEFAULT_SUBCONSCIOUS_MODELS}"
|
|
57
|
+
|
|
58
|
+
MODELS_JSON=""
|
|
59
|
+
for model_id in "${SUPPORTED_MODELS[@]}"; do
|
|
60
|
+
model_json="\"${model_id}\":{\"name\":\"${model_id}\",\"tools\":true,\"limit\":{\"context\":${CONTEXT_LIMIT},\"output\":${OUTPUT_LIMIT}}}"
|
|
61
|
+
if [[ -n "$MODELS_JSON" ]]; then
|
|
62
|
+
MODELS_JSON="${MODELS_JSON},${model_json}"
|
|
63
|
+
else
|
|
64
|
+
MODELS_JSON="$model_json"
|
|
65
|
+
fi
|
|
66
|
+
done
|
|
67
|
+
|
|
68
|
+
if [[ -z "$GATEWAY_URL" || -z "$API_KEY" ]]; then
|
|
69
|
+
echo "error: GATEWAY_URL and API_KEY must be set in ../.env" >&2
|
|
70
|
+
exit 1
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
# Parse args (only when executed, not sourced)
|
|
74
|
+
PASSTHRU=()
|
|
75
|
+
if [[ "${BASH_SOURCE[0]:-$0}" == "${0}" ]]; then
|
|
76
|
+
while [[ $# -gt 0 ]]; do
|
|
77
|
+
case "$1" in
|
|
78
|
+
--)
|
|
79
|
+
shift
|
|
80
|
+
PASSTHRU+=("$@")
|
|
81
|
+
break
|
|
82
|
+
;;
|
|
83
|
+
*)
|
|
84
|
+
PASSTHRU+=("$1")
|
|
85
|
+
shift
|
|
86
|
+
;;
|
|
87
|
+
esac
|
|
88
|
+
done
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
BASE_URL="${GATEWAY_URL%/}/v1"
|
|
92
|
+
|
|
93
|
+
export SUBCONSCIOUS_API_KEY="$API_KEY"
|
|
94
|
+
# Read by the compaction plugin when it is installed. run.sh writes nothing to disk, so
|
|
95
|
+
# use install.sh if you want compaction reporting.
|
|
96
|
+
export SUBCONSCIOUS_GATEWAY_URL="${GATEWAY_URL%/}"
|
|
97
|
+
export OPENCODE_CONFIG_CONTENT=$(cat <<EOF
|
|
98
|
+
{"\$schema":"https://opencode.ai/config.json","provider":{"subconscious":{"npm":"@ai-sdk/openai-compatible","name":"Subconscious Gateway","options":{"baseURL":"${BASE_URL}","apiKey":"{env:SUBCONSCIOUS_API_KEY}","headers":{"x-subconscious-client":"opencode"}},"models":{${MODELS_JSON}}}},"model":"subconscious/${MODEL}"}
|
|
99
|
+
EOF
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# If sourced, just export env and return.
|
|
103
|
+
if [[ "${BASH_SOURCE[0]:-$0}" != "${0}" ]]; then
|
|
104
|
+
return 0 2>/dev/null || true
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
exec opencode ${PASSTHRU[@]+"${PASSTHRU[@]}"}
|