subconscious-cli 0.2.1 → 0.3.1

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.
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/env bash
2
+ # Point the `claude` CLI at the Subconscious gateway — ephemerally.
3
+ #
4
+ # Uses env vars only (ANTHROPIC_BASE_URL / ANTHROPIC_AUTH_TOKEN) so nothing is
5
+ # written to ~/.claude/ config files.
6
+ #
7
+ # Usage:
8
+ # ./run.sh # uses GATEWAY_URL/API_KEY from ../.env
9
+ # ./run.sh --continue # pass args through to claude
10
+ # ./run.sh --compact-window 1000000 -- --continue
11
+ # ./run.sh --model subconscious/glm-5.2 -p "hi"
12
+ #
13
+ # Config: copy ../env.example to ../.env and edit. .env is gitignored.
14
+ # Profile env is injected by subc. A sibling .env is only used when SUBC_ENV_FILE is unset.
15
+ #
16
+ # Or source it to just export the env:
17
+ # source run.sh
18
+
19
+ set -euo pipefail
20
+
21
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
22
+
23
+ # Load shared env from SUBC_ENV_FILE, or a sibling .env / env.example.
24
+ SHARED_ENV="${SUBC_ENV_FILE:-${SCRIPT_DIR}/../.env}"
25
+ [[ -f "$SHARED_ENV" ]] || SHARED_ENV="${SCRIPT_DIR}/../env.example"
26
+ if [[ -f "$SHARED_ENV" ]]; then set -a; source "$SHARED_ENV"; set +a; fi
27
+
28
+ DEFAULT_MODEL="subconscious/glm-5.2"
29
+ DEFAULT_COMPACT_WINDOW="1000000"
30
+ DEFAULT_MAX_CONTEXT_TOKENS="3000000"
31
+ DEFAULT_MAX_CONCURRENT_SUBAGENTS="4"
32
+ DEFAULT_MAX_SUBAGENT_SPAWN_DEPTH="1"
33
+ DEFAULT_OPUS_MODEL="subconscious/glm-5.2"
34
+ DEFAULT_SONNET_MODEL="subconscious/tim-qwen3.6-27b"
35
+ DEFAULT_HAIKU_MODEL="subconscious/deepseek-v4-flash-marathon"
36
+
37
+ GATEWAY_URL="${GATEWAY_URL:-}"
38
+ API_KEY="${CLAUDE_CODE_API_KEY:-${API_KEY:-}}"
39
+ MODEL="${MODEL:-$DEFAULT_MODEL}"
40
+ COMPACT_WINDOW="${CLAUDE_CODE_AUTO_COMPACT_WINDOW:-${COMPACT_WINDOW:-$DEFAULT_COMPACT_WINDOW}}"
41
+ MAX_CONTEXT_TOKENS="${CLAUDE_CODE_MAX_CONTEXT_TOKENS:-${MAX_CONTEXT_TOKENS:-$DEFAULT_MAX_CONTEXT_TOKENS}}"
42
+ MAX_CONCURRENT_SUBAGENTS="${MAX_CONCURRENT_SUBAGENTS:-$DEFAULT_MAX_CONCURRENT_SUBAGENTS}"
43
+ MAX_SUBAGENT_SPAWN_DEPTH="${MAX_SUBAGENT_SPAWN_DEPTH:-$DEFAULT_MAX_SUBAGENT_SPAWN_DEPTH}"
44
+ OPUS_MODEL="${ANTHROPIC_DEFAULT_OPUS_MODEL:-$DEFAULT_OPUS_MODEL}"
45
+ OPUS_MODEL_NAME="${ANTHROPIC_DEFAULT_OPUS_MODEL_NAME:-$OPUS_MODEL}"
46
+ OPUS_MODEL_DESCRIPTION="${ANTHROPIC_DEFAULT_OPUS_MODEL_DESCRIPTION:-Subconscious default model}"
47
+ SONNET_MODEL="${ANTHROPIC_DEFAULT_SONNET_MODEL:-$DEFAULT_SONNET_MODEL}"
48
+ SONNET_MODEL_NAME="${ANTHROPIC_DEFAULT_SONNET_MODEL_NAME:-$SONNET_MODEL}"
49
+ SONNET_MODEL_DESCRIPTION="${ANTHROPIC_DEFAULT_SONNET_MODEL_DESCRIPTION:-Subconscious TIM Qwen 3.6 27B}"
50
+ HAIKU_MODEL="${ANTHROPIC_DEFAULT_HAIKU_MODEL:-$DEFAULT_HAIKU_MODEL}"
51
+ HAIKU_MODEL_NAME="${ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME:-$HAIKU_MODEL}"
52
+ HAIKU_MODEL_DESCRIPTION="${ANTHROPIC_DEFAULT_HAIKU_MODEL_DESCRIPTION:-Subconscious DeepSeek V4 Flash Marathon}"
53
+
54
+ usage() {
55
+ cat <<'EOF'
56
+ Usage:
57
+ run.sh [options] [-- CLAUDE_ARGS...]
58
+ run.sh [options] CLAUDE_ARGS...
59
+
60
+ Options (same as install.sh; flags override env for this run):
61
+ --gateway-url URL Gateway origin (e.g. https://gateway.example)
62
+ --api-key KEY Gateway API key (sk-gw-...)
63
+ --model MODEL Model name (default: subconscious/glm-5.2)
64
+ --compact-window N CLAUDE_CODE_AUTO_COMPACT_WINDOW (default: 1000000; Claude Code clamps to 100000–1000000)
65
+ See https://code.claude.com/docs/en/env-vars and
66
+ https://code.claude.com/docs/en/context-window#set-the-auto-compact-window
67
+ --max-context-tokens N CLAUDE_CODE_MAX_CONTEXT_TOKENS (default: 3000000)
68
+ See https://code.claude.com/docs/en/env-vars
69
+ -h, --help Show this help
70
+
71
+ Anything after --, or unrecognized flags/args, is passed through to `claude`.
72
+ EOF
73
+ }
74
+
75
+ # Parse args (only when executed, not sourced)
76
+ PASSTHRU=()
77
+ if [[ "${BASH_SOURCE[0]:-$0}" == "${0}" ]]; then
78
+ while [[ $# -gt 0 ]]; do
79
+ case "$1" in
80
+ --gateway-url)
81
+ GATEWAY_URL="${2:-}"
82
+ shift 2
83
+ ;;
84
+ --api-key)
85
+ API_KEY="${2:-}"
86
+ shift 2
87
+ ;;
88
+ --model)
89
+ MODEL="${2:-}"
90
+ shift 2
91
+ ;;
92
+ --compact-window)
93
+ COMPACT_WINDOW="${2:-}"
94
+ shift 2
95
+ ;;
96
+ --max-context-tokens)
97
+ MAX_CONTEXT_TOKENS="${2:-}"
98
+ shift 2
99
+ ;;
100
+ -h|--help)
101
+ usage
102
+ exit 0
103
+ ;;
104
+ --)
105
+ shift
106
+ PASSTHRU+=("$@")
107
+ break
108
+ ;;
109
+ *)
110
+ PASSTHRU+=("$1")
111
+ shift
112
+ ;;
113
+ esac
114
+ done
115
+ fi
116
+
117
+ if [[ -z "$GATEWAY_URL" || -z "$API_KEY" ]]; then
118
+ echo "error: GATEWAY_URL and API_KEY must be set in ../.env (or pass --gateway-url / --api-key)" >&2
119
+ exit 1
120
+ fi
121
+
122
+ # Optional CLAUDE_GATEWAY_URL in shared .env overrides GATEWAY_URL for Claude only
123
+ # (CLI --gateway-url already updated GATEWAY_URL above).
124
+ EFFECTIVE_GATEWAY_URL="${CLAUDE_GATEWAY_URL:-$GATEWAY_URL}"
125
+
126
+ export ANTHROPIC_BASE_URL="$EFFECTIVE_GATEWAY_URL"
127
+ export ANTHROPIC_AUTH_TOKEN="$API_KEY"
128
+ export ANTHROPIC_MODEL="${ANTHROPIC_MODEL:-$MODEL}"
129
+ export ANTHROPIC_SMALL_FAST_MODEL="${ANTHROPIC_SMALL_FAST_MODEL:-$MODEL}"
130
+ export ANTHROPIC_DEFAULT_OPUS_MODEL="${ANTHROPIC_DEFAULT_OPUS_MODEL:-$OPUS_MODEL}"
131
+ export ANTHROPIC_DEFAULT_OPUS_MODEL_NAME="${ANTHROPIC_DEFAULT_OPUS_MODEL_NAME:-$OPUS_MODEL_NAME}"
132
+ export ANTHROPIC_DEFAULT_OPUS_MODEL_DESCRIPTION="${ANTHROPIC_DEFAULT_OPUS_MODEL_DESCRIPTION:-$OPUS_MODEL_DESCRIPTION}"
133
+ export ANTHROPIC_DEFAULT_SONNET_MODEL="${ANTHROPIC_DEFAULT_SONNET_MODEL:-$SONNET_MODEL}"
134
+ export ANTHROPIC_DEFAULT_SONNET_MODEL_NAME="${ANTHROPIC_DEFAULT_SONNET_MODEL_NAME:-$SONNET_MODEL_NAME}"
135
+ export ANTHROPIC_DEFAULT_SONNET_MODEL_DESCRIPTION="${ANTHROPIC_DEFAULT_SONNET_MODEL_DESCRIPTION:-$SONNET_MODEL_DESCRIPTION}"
136
+ export ANTHROPIC_DEFAULT_HAIKU_MODEL="${ANTHROPIC_DEFAULT_HAIKU_MODEL:-$HAIKU_MODEL}"
137
+ export ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME="${ANTHROPIC_DEFAULT_HAIKU_MODEL_NAME:-$HAIKU_MODEL_NAME}"
138
+ export ANTHROPIC_DEFAULT_HAIKU_MODEL_DESCRIPTION="${ANTHROPIC_DEFAULT_HAIKU_MODEL_DESCRIPTION:-$HAIKU_MODEL_DESCRIPTION}"
139
+ export CLAUDE_CODE_SUBAGENT_MODEL="${CLAUDE_CODE_SUBAGENT_MODEL:-$MODEL}"
140
+ export CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS="${MAX_CONCURRENT_SUBAGENTS}"
141
+ export CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH="${MAX_SUBAGENT_SPAWN_DEPTH}"
142
+ export CLAUDE_CODE_AUTO_COMPACT_WINDOW="${COMPACT_WINDOW}"
143
+ export CLAUDE_CODE_MAX_CONTEXT_TOKENS="${MAX_CONTEXT_TOKENS}"
144
+ # Claude Code purpose attribution only: api_request logs → gateway POST /v1/logs
145
+ # (not a general logs API). Docs:
146
+ # https://code.claude.com/docs/en/monitoring-usage#api-request-event
147
+ # Requires Claude Code >= 2.1.152.
148
+ export CLAUDE_CODE_ENABLE_TELEMETRY="${CLAUDE_CODE_ENABLE_TELEMETRY:-1}"
149
+ export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC="${CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC:-1}"
150
+ export OTEL_LOGS_EXPORTER="${OTEL_LOGS_EXPORTER:-otlp}"
151
+ export OTEL_EXPORTER_OTLP_PROTOCOL="${OTEL_EXPORTER_OTLP_PROTOCOL:-http/json}"
152
+ export OTEL_EXPORTER_OTLP_LOGS_ENDPOINT="${EFFECTIVE_GATEWAY_URL%/}/v1/logs"
153
+ export OTEL_EXPORTER_OTLP_HEADERS="x-api-key=${API_KEY}"
154
+ export OTEL_EXPORTER_OTLP_TIMEOUT="${OTEL_EXPORTER_OTLP_TIMEOUT:-2000}"
155
+ export OTEL_LOGS_EXPORT_INTERVAL="${OTEL_LOGS_EXPORT_INTERVAL:-2000}"
156
+ # Do not set OTEL_TRACES_EXPORTER / OTEL_METRICS_EXPORTER (including to "none") -
157
+ # Claude's telemetry init can abort the whole pipeline on unknown exporter types.
158
+ # If sourced, export env and return (caller can unset when done).
159
+ if [[ "${BASH_SOURCE[0]:-$0}" != "${0}" ]]; then
160
+ return 0 2>/dev/null || true
161
+ fi
162
+
163
+ # If executed, launch claude with any passed-through args.
164
+ exec claude ${PASSTHRU[@]+"${PASSTHRU[@]}"}
@@ -0,0 +1,112 @@
1
+ #!/usr/bin/env bash
2
+ # Fail-open Codex hook: report PreCompact / PostCompact to the gateway.
3
+ # Never blocks compaction (do not return continue: false).
4
+ #
5
+ # Docs: https://developers.openai.com/codex/hooks
6
+ #
7
+ # PreCompact -> conversation_compaction { phase: start }
8
+ # PostCompact -> conversation_compaction { phase: end }
9
+ #
10
+ # Custom (non-OpenAI-named) providers compact locally through the configured
11
+ # Responses endpoint, so the summarization request hits the gateway between
12
+ # these two signals. conversation_id is the hook session_id, which Codex
13
+ # examples show as thr_… — the same value the gateway uses as thread-id
14
+ # grouping. Capture after upgrades if that ever diverges.
15
+ #
16
+ # Stdin: Codex hook JSON. Stdout: empty success (or {} ). Exit 0 always.
17
+
18
+ set -u
19
+
20
+ CONFIG="${SUBCONSCIOUS_HOOKS_ENV:-${HOME}/.codex/subconscious-hooks.env}"
21
+ if [[ -f "$CONFIG" ]]; then
22
+ # shellcheck disable=SC1090
23
+ source "$CONFIG"
24
+ fi
25
+ # Older overwrite-style setup also wrote subconscious.env; keep reading it.
26
+ if [[ -f "${HOME}/.codex/subconscious.env" ]]; then
27
+ # shellcheck disable=SC1090
28
+ source "${HOME}/.codex/subconscious.env"
29
+ fi
30
+
31
+ GATEWAY_URL="${SUBCONSCIOUS_GATEWAY_URL:-}"
32
+ API_KEY="${SUBCONSCIOUS_API_KEY:-}"
33
+
34
+ fail_open() {
35
+ exit 0
36
+ }
37
+
38
+ if [[ -z "$GATEWAY_URL" || -z "$API_KEY" ]]; then
39
+ echo "subconscious codex hook: missing SUBCONSCIOUS_GATEWAY_URL or SUBCONSCIOUS_API_KEY" >&2
40
+ fail_open
41
+ fi
42
+
43
+ for tool in jq curl; do
44
+ if ! command -v "$tool" >/dev/null 2>&1; then
45
+ echo "subconscious codex hook: ${tool} is required" >&2
46
+ fail_open
47
+ fi
48
+ done
49
+
50
+ INPUT="$(cat || true)"
51
+ if [[ -z "$INPUT" ]]; then
52
+ fail_open
53
+ fi
54
+
55
+ # Local fire-log for capture/debug (never blocks).
56
+ FIRE_LOG_DIR="${SUBCONSCIOUS_CODEX_HOOK_LOG_DIR:-${HOME}/.codex/subconscious-hook-logs}"
57
+ mkdir -p "$FIRE_LOG_DIR" 2>/dev/null || true
58
+
59
+ HOOK_EVENT="$(printf '%s' "$INPUT" | jq -r '.hook_event_name // empty' 2>/dev/null || true)"
60
+ case "$HOOK_EVENT" in
61
+ PreCompact) PHASE="start" ;;
62
+ PostCompact) PHASE="end" ;;
63
+ *) fail_open ;;
64
+ esac
65
+
66
+ SESSION_ID="$(printf '%s' "$INPUT" | jq -r '.session_id // empty' 2>/dev/null || true)"
67
+ if [[ -z "$SESSION_ID" ]]; then
68
+ fail_open
69
+ fi
70
+
71
+ {
72
+ printf '%s event=%s session_id=%s payload=' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$HOOK_EVENT" "$SESSION_ID"
73
+ printf '%s\n' "$INPUT"
74
+ } >>"${FIRE_LOG_DIR}/${SESSION_ID}.log" 2>/dev/null || true
75
+
76
+ TURN_ID="$(printf '%s' "$INPUT" | jq -r '.turn_id // empty' 2>/dev/null || true)"
77
+ TRIGGER="$(printf '%s' "$INPUT" | jq -r '.trigger // empty' 2>/dev/null || true)"
78
+ NOW_MS="$(date +%s)-$$-$RANDOM"
79
+ if [[ -n "$TURN_ID" ]]; then
80
+ DEDUPE_KEY="${SESSION_ID}:${PHASE}:${TURN_ID}"
81
+ else
82
+ DEDUPE_KEY="${SESSION_ID}:${PHASE}:${NOW_MS}"
83
+ fi
84
+
85
+ PAYLOAD="$(jq -n \
86
+ --arg conversation_id "$SESSION_ID" \
87
+ --arg phase "$PHASE" \
88
+ --arg hook_event_name "$HOOK_EVENT" \
89
+ --arg dedupe_key "$DEDUPE_KEY" \
90
+ --arg trigger "$TRIGGER" \
91
+ --arg turn_id "$TURN_ID" \
92
+ '{
93
+ event: "conversation_compaction",
94
+ conversation_id: $conversation_id,
95
+ phase: $phase,
96
+ hook_event_name: $hook_event_name,
97
+ dedupe_key: $dedupe_key,
98
+ metadata: {
99
+ trigger: (if $trigger == "" then null else $trigger end),
100
+ turn_id: (if $turn_id == "" then null else $turn_id end)
101
+ } | with_entries(select(.value != null))
102
+ }'
103
+ )"
104
+
105
+ curl -sS -m 2 \
106
+ -H "Authorization: Bearer ${API_KEY}" \
107
+ -H "Content-Type: application/json" \
108
+ -H "x-subconscious-client: codex" \
109
+ -d "$PAYLOAD" \
110
+ "${GATEWAY_URL%/}/v1/agent-hooks" >/dev/null 2>&1 || true
111
+
112
+ fail_open
@@ -0,0 +1,139 @@
1
+ #!/usr/bin/env bash
2
+ # Surgical Codex hook merge/unmerge. Sourced by install.sh and run.sh.
3
+ # Never reads, writes, or deletes ~/.codex/config.toml.
4
+
5
+ CODEX_DIR="${CODEX_DIR:-${HOME}/.codex}"
6
+ HOOKS_JSON="${HOOKS_JSON:-${CODEX_DIR}/hooks.json}"
7
+ HOOK_DST="${HOOK_DST:-${CODEX_DIR}/subconscious-hook.sh}"
8
+ HOOKS_ENV_FILE="${HOOKS_ENV_FILE:-${CODEX_DIR}/subconscious-hooks.env}"
9
+ MARKER="${MARKER:-subconscious-hook.sh}"
10
+
11
+ codex_hooks_have_jq() {
12
+ command -v jq >/dev/null 2>&1
13
+ }
14
+
15
+ codex_hooks_json_valid() {
16
+ [[ -f "$HOOKS_JSON" ]] && jq -e . "$HOOKS_JSON" >/dev/null 2>&1
17
+ }
18
+
19
+ codex_strip_hook_entries() {
20
+ local tmp
21
+ tmp="$(mktemp)"
22
+ jq --arg marker "$MARKER" '
23
+ def strip:
24
+ (. // [])
25
+ | map(
26
+ .hooks = ((.hooks // []) | map(select((.command // "") | tostring | contains($marker) | not)))
27
+ )
28
+ | map(select((.hooks | length) > 0));
29
+ .hooks = (.hooks // {})
30
+ | .hooks |= with_entries(.value |= strip)
31
+ ' "$HOOKS_JSON" >"$tmp"
32
+ mv "$tmp" "$HOOKS_JSON"
33
+ }
34
+
35
+ codex_append_hook_groups() {
36
+ local tmp
37
+ tmp="$(mktemp)"
38
+ jq --arg cmd "$HOOK_DST" '
39
+ def group($msg):
40
+ [{ "hooks": [{ "type": "command", "command": $cmd, "timeout": 3, "statusMessage": $msg }] }];
41
+ .hooks = (.hooks // {})
42
+ | .hooks.PreCompact = ((.hooks.PreCompact // []) + group("Reporting compaction start"))
43
+ | .hooks.PostCompact = ((.hooks.PostCompact // []) + group("Reporting compaction end"))
44
+ ' "$HOOKS_JSON" >"$tmp"
45
+ mv "$tmp" "$HOOKS_JSON"
46
+ }
47
+
48
+ codex_write_hook_script() {
49
+ mkdir -p "$CODEX_DIR"
50
+ cp "$HOOK_SRC" "$HOOK_DST"
51
+ chmod +x "$HOOK_DST"
52
+ }
53
+
54
+ codex_write_hooks_env() {
55
+ umask 077
56
+ cat >"$HOOKS_ENV_FILE" <<EOF
57
+ # Generated by subconscious-cli Codex integration — do not commit secrets.
58
+ export SUBCONSCIOUS_GATEWAY_URL='${GATEWAY_URL%/}'
59
+ export SUBCONSCIOUS_API_KEY='${API_KEY}'
60
+ EOF
61
+ chmod 600 "$HOOKS_ENV_FILE"
62
+ }
63
+
64
+ # codex_ensure_hooks [strict|best-effort]
65
+ codex_ensure_hooks() {
66
+ local mode="${1:-strict}"
67
+ if ! codex_hooks_have_jq; then
68
+ echo "warning: jq is required to merge Codex hooks without overwriting ~/.codex/hooks.json" >&2
69
+ if [[ "$mode" == "strict" ]]; then
70
+ echo "missing required command: jq" >&2
71
+ return 1
72
+ fi
73
+ return 0
74
+ fi
75
+ if [[ -z "${GATEWAY_URL:-}" || -z "${API_KEY:-}" ]]; then
76
+ echo "--gateway-url and --api-key are required to install Codex hooks" >&2
77
+ return 1
78
+ fi
79
+ if [[ -z "${HOOK_SRC:-}" || ! -f "$HOOK_SRC" ]]; then
80
+ echo "warning: Codex hook script missing; skipping hooks" >&2
81
+ return 0
82
+ fi
83
+ mkdir -p "$CODEX_DIR"
84
+ if [[ -f "$HOOKS_JSON" ]] && ! codex_hooks_json_valid; then
85
+ echo "warning: ${HOOKS_JSON} is not valid JSON; leaving it unchanged" >&2
86
+ if [[ "$mode" == "strict" ]]; then
87
+ return 1
88
+ fi
89
+ return 0
90
+ fi
91
+ if [[ ! -f "$HOOKS_JSON" ]]; then
92
+ printf '%s\n' '{"hooks":{}}' >"$HOOKS_JSON"
93
+ fi
94
+ codex_write_hook_script
95
+ codex_write_hooks_env
96
+ codex_strip_hook_entries
97
+ codex_append_hook_groups
98
+ echo "Merged Subconscious Codex hooks into $HOOKS_JSON"
99
+ echo "Trust new hooks in Codex with /hooks (or --dangerously-bypass-hook-trust)."
100
+ }
101
+
102
+ codex_uninstall_hooks() {
103
+ if [[ -f "$HOOKS_JSON" ]] && codex_hooks_have_jq && codex_hooks_json_valid; then
104
+ codex_strip_hook_entries
105
+ echo "Removed Subconscious entries from $HOOKS_JSON"
106
+ elif [[ -f "$HOOKS_JSON" ]]; then
107
+ echo "warning: ${HOOKS_JSON} is not valid JSON; leaving it unchanged" >&2
108
+ else
109
+ echo "No Codex hooks.json at $HOOKS_JSON"
110
+ fi
111
+ rm -f "$HOOK_DST" "$HOOKS_ENV_FILE"
112
+ rm -f "${CODEX_DIR}/subconscious.env"
113
+ }
114
+
115
+ codex_hooks_status() {
116
+ echo "scope: user"
117
+ echo "codex dir: $CODEX_DIR"
118
+ echo "hooks.json: $HOOKS_JSON"
119
+ if [[ -f "$HOOKS_JSON" ]] && codex_hooks_have_jq && jq -e --arg m "$MARKER" '
120
+ [.hooks[]?[]?.hooks[]? | select((.command // "") | tostring | contains($m))] | length > 0
121
+ ' "$HOOKS_JSON" >/dev/null 2>&1; then
122
+ echo "hooks: installed"
123
+ else
124
+ echo "hooks: not installed"
125
+ fi
126
+ if [[ -x "$HOOK_DST" ]]; then
127
+ echo "hook script: $HOOK_DST (executable)"
128
+ else
129
+ echo "hook script: missing"
130
+ fi
131
+ if [[ -f "$HOOKS_ENV_FILE" ]]; then
132
+ echo "env: $HOOKS_ENV_FILE (present)"
133
+ else
134
+ echo "env: missing"
135
+ fi
136
+ if [[ -f "${CODEX_DIR}/config.toml" ]] && grep -q 'model_provider = "subconscious"' "${CODEX_DIR}/config.toml" 2>/dev/null; then
137
+ echo "legacy config.toml: contains a Subconscious provider (this CLI no longer manages that file)"
138
+ fi
139
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "description": "Report Codex auto/manual compaction to the Subconscious API Gateway.",
3
+ "hooks": {
4
+ "PreCompact": [
5
+ {
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "HOOK_SH_PATH",
10
+ "timeout": 3,
11
+ "statusMessage": "Reporting compaction start"
12
+ }
13
+ ]
14
+ }
15
+ ],
16
+ "PostCompact": [
17
+ {
18
+ "hooks": [
19
+ {
20
+ "type": "command",
21
+ "command": "HOOK_SH_PATH",
22
+ "timeout": 3,
23
+ "statusMessage": "Reporting compaction end"
24
+ }
25
+ ]
26
+ }
27
+ ]
28
+ }
29
+ }
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env bash
2
+ # ── Subconscious API Gateway — Codex hooks ────────────────────────────────────
3
+ # Merge Subconscious compaction hooks into ~/.codex/hooks.json without touching
4
+ # ~/.codex/config.toml. Launch Codex with `subc codex`.
5
+ #
6
+ # subc codex install # merge hooks
7
+ # subc codex status
8
+ # subc codex uninstall # strip only Subconscious hook entries
9
+ # ─────────────────────────────────────────────────────────────────────────────
10
+
11
+ set -euo pipefail
12
+
13
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14
+ HOOK_SRC="${SCRIPT_DIR}/hook.sh"
15
+
16
+ SHARED_ENV="${SUBC_ENV_FILE:-${SCRIPT_DIR}/../.env}"
17
+ [[ -f "$SHARED_ENV" ]] || SHARED_ENV="${SCRIPT_DIR}/../env.example"
18
+ if [[ -f "$SHARED_ENV" ]]; then set -a; source "$SHARED_ENV"; set +a; fi
19
+
20
+ # shellcheck source=hooks-lib.sh
21
+ source "${SCRIPT_DIR}/hooks-lib.sh"
22
+
23
+ COMMAND="install"
24
+ GATEWAY_URL="${GATEWAY_URL:-}"
25
+ API_KEY="${CODEX_API_KEY:-${API_KEY:-}}"
26
+
27
+ usage() {
28
+ cat <<'EOF'
29
+ Usage:
30
+ subc codex install [--gateway-url URL] [--api-key KEY]
31
+ subc codex status
32
+ subc codex uninstall
33
+
34
+ Merges Subconscious compaction hooks into ~/.codex/hooks.json. Does not write
35
+ config.toml. Launch Codex with subc codex.
36
+ EOF
37
+ }
38
+
39
+ while [[ $# -gt 0 ]]; do
40
+ case "$1" in
41
+ install|uninstall|status)
42
+ COMMAND="$1"
43
+ shift
44
+ ;;
45
+ --gateway-url)
46
+ GATEWAY_URL="${2:-}"
47
+ shift 2
48
+ ;;
49
+ --api-key)
50
+ API_KEY="${2:-}"
51
+ shift 2
52
+ ;;
53
+ --model)
54
+ shift 2
55
+ ;;
56
+ -h|--help|help)
57
+ usage
58
+ exit 0
59
+ ;;
60
+ --)
61
+ shift
62
+ break
63
+ ;;
64
+ *)
65
+ echo "unknown argument: $1 (launch options belong on subc codex, not subc codex install)" >&2
66
+ usage >&2
67
+ exit 1
68
+ ;;
69
+ esac
70
+ done
71
+
72
+ case "$COMMAND" in
73
+ install)
74
+ codex_ensure_hooks strict
75
+ ;;
76
+ uninstall)
77
+ codex_uninstall_hooks
78
+ ;;
79
+ status)
80
+ codex_hooks_status
81
+ ;;
82
+ esac