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.
- 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/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 +5 -3
- package/bin/windows/hook.cjs +4 -10
- package/bin/windows/launch.js +22 -9
- package/bin/windows/setup.js +2 -8
- package/package.json +2 -3
- 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
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/bin/profiles.js
CHANGED
|
@@ -36,6 +36,10 @@ const PREVIOUS_DEFAULT_GATEWAYS = new Set([
|
|
|
36
36
|
const CONFIG_OVERRIDE = process.env.SUBC_CONFIG_DIR?.trim();
|
|
37
37
|
const CONFIG_DIR = CONFIG_OVERRIDE || path.join(os.homedir(), '.subconscious');
|
|
38
38
|
export const PROFILES_DIR = path.join(CONFIG_DIR, 'profiles');
|
|
39
|
+
/** Absolute path of the subc config dir (~/.subconscious or $SUBC_CONFIG_DIR). */
|
|
40
|
+
export function configDir() {
|
|
41
|
+
return CONFIG_DIR;
|
|
42
|
+
}
|
|
39
43
|
const LEGACY_PROFILES_DIR = CONFIG_OVERRIDE
|
|
40
44
|
? null
|
|
41
45
|
: path.join(os.homedir(), '.subcon', 'profiles');
|
package/bin/runbook/README.md
CHANGED
|
@@ -8,12 +8,11 @@ the default gateway, model capabilities, and the settings every agent uses
|
|
|
8
8
|
|
|
9
9
|
| Agent | Launch | Setup |
|
|
10
10
|
| --- | --- | --- |
|
|
11
|
+
| `opencode` | binary | `install.sh` (leftover status/uninstall) |
|
|
11
12
|
| `claude-code` | binary | `install.sh` (leftover status/uninstall) |
|
|
12
13
|
| `codex` | `run.sh` | `install.sh` (hooks) |
|
|
13
|
-
| `opencode` | binary | `install.sh` (leftover status/uninstall) |
|
|
14
|
-
| `cursor` | none | `install.sh` (hooks) |
|
|
15
|
-
| `copilot` | none | `install.sh` (provider and hooks) |
|
|
16
14
|
| `pi` | `run.sh` | `install.sh` (provider and extensions) |
|
|
15
|
+
| `copilot` | none | `install.sh` (provider and hooks) |
|
|
17
16
|
| `deepseek-harness` | `run.sh` | none |
|
|
18
17
|
|
|
19
18
|
## What `agent.json` holds and who reads it
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"help": {
|
|
30
30
|
"usage": "subc [-p NAME] claude [help|status|uninstall] [Claude arguments...]",
|
|
31
|
-
"behavior": "Launches Claude Code with the active Subconscious profile and model picker. Persistent files are leftover-only: subc claude uninstall.",
|
|
31
|
+
"behavior": "Launches Claude Code with the active Subconscious profile and model picker. Sessions run with CLAUDE_CONFIG_DIR=~/.subconscious/claude-code, which shares the setup your plain claude uses (~/.claude, or your CLAUDE_CONFIG_DIR): CLAUDE.md, skills, plugins, hooks, MCP servers and session history. Only the model pick is kept apart, so a /model choice in subc does not change plain Claude. Persistent files are leftover-only: subc claude uninstall.",
|
|
32
32
|
"options": [
|
|
33
33
|
["help", "Show this help"],
|
|
34
34
|
["status", "Inspect leftover ~/.claude/subconscious-gateway.env"],
|
|
@@ -113,6 +113,18 @@
|
|
|
113
113
|
"type": "integer",
|
|
114
114
|
"min": 1
|
|
115
115
|
},
|
|
116
|
+
{
|
|
117
|
+
"name": "CLAUDE_CODE_ISOLATE",
|
|
118
|
+
"flag": "--no-isolate",
|
|
119
|
+
"flag_value": "off",
|
|
120
|
+
"default": "on",
|
|
121
|
+
"strict": true,
|
|
122
|
+
"choices": ["on", "off"],
|
|
123
|
+
"help": "Use ~/.claude directly, so a /model pick in subc also changes plain Claude Code",
|
|
124
|
+
"label": "Isolated Claude config",
|
|
125
|
+
"description": "When on, subc claude sessions use ~/.subconscious/claude-code as the Claude config dir. It links back to ~/.claude for CLAUDE.md, skills, hooks and session history, copies the plugin index, rebuilds settings.json from ~/.claude/settings.json on each launch, and keeps only the subc model pick apart.",
|
|
126
|
+
"comment": "CLAUDE_CODE_ISOLATE=off shares ~/.claude with plain Claude Code."
|
|
127
|
+
},
|
|
116
128
|
{ "name": "MAX_CONCURRENT_SUBAGENTS" },
|
|
117
129
|
{
|
|
118
130
|
"name": "MAX_SUBAGENT_SPAWN_DEPTH",
|
|
@@ -147,6 +159,12 @@
|
|
|
147
159
|
"override": ["ANTHROPIC_SMALL_FAST_MODEL"],
|
|
148
160
|
"description": "Small/fast background model."
|
|
149
161
|
},
|
|
162
|
+
{
|
|
163
|
+
"name": "CLAUDE_CONFIG_DIR",
|
|
164
|
+
"value": "{claudeConfigDir}",
|
|
165
|
+
"optional": true,
|
|
166
|
+
"description": "Claude Code config dir for subc sessions, prepared before each launch to share the user's own Claude config (~/.claude, or a CLAUDE_CONFIG_DIR they set, which becomes the source rather than the launch dir). When CLAUDE_CODE_ISOLATE=off, the user's own CLAUDE_CONFIG_DIR is kept, or none is set, so sessions use it directly."
|
|
167
|
+
},
|
|
150
168
|
{
|
|
151
169
|
"name": "CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY",
|
|
152
170
|
"value": "0",
|
|
@@ -233,6 +251,12 @@
|
|
|
233
251
|
},
|
|
234
252
|
"description": "Replaces the built-in model picker with the live catalog and allowlists only those models."
|
|
235
253
|
},
|
|
254
|
+
{
|
|
255
|
+
"kind": "file",
|
|
256
|
+
"path": "~/.subconscious/claude-code",
|
|
257
|
+
"lifetime": "persistent",
|
|
258
|
+
"description": "Claude Code config dir for subc sessions (~/.subconscious/claude-code, honoring $SUBC_CONFIG_DIR), set via CLAUDE_CONFIG_DIR. Each launch links CLAUDE.md, history.jsonl and keybindings.json (when present) and the rules, agents, commands, skills, output-styles, hooks, projects, file-history, todos, plans and ide folders (created in ~/.claude when missing) back to ~/.claude, copies the plugin index files (plugin changes made inside subc stay in subc and reset on the next launch), rebuilds settings.json from ~/.claude/settings.json (keeping the subc model pick, dropping apiKeyHelper and env names subc sets), and copies MCP servers, trust and onboarding state from ~/.claude.json (never login state). Settings or MCP servers changed inside a subc session are replaced on the next launch; that launch names them and saves them in settings.discarded.json or mcp.discarded.json in this dir. subc never deletes it and never writes ~/.claude settings; it refuses to launch when this dir resolves to your own config. To remove it, delete the links first: rm -r with a trailing slash, or PowerShell Remove-Item -Recurse, follows them into ~/.claude. Opt out with CLAUDE_CODE_ISOLATE=off or subc claude --no-isolate."
|
|
259
|
+
},
|
|
236
260
|
{
|
|
237
261
|
"kind": "file",
|
|
238
262
|
"path": "~/.claude/subconscious-gateway.env",
|
package/bin/runbook/shared.json
CHANGED
package/bin/sessions.js
CHANGED
|
@@ -3,7 +3,7 @@ import os from 'node:os';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { AGENTS, agentById, agentCommandName } from './agent-data.js';
|
|
5
5
|
import { resolveAgent, runAgent } from './agents.js';
|
|
6
|
-
|
|
6
|
+
import { claudeSourceDir } from './claude-config.js';
|
|
7
7
|
import { c } from './colors.js';
|
|
8
8
|
import { runCommand } from './command-output.js';
|
|
9
9
|
|
|
@@ -451,8 +451,10 @@ export async function discoverSessions(options = {}) {
|
|
|
451
451
|
const home = options.home || os.homedir();
|
|
452
452
|
const max = options.max || 500;
|
|
453
453
|
const execute = options.execute || runCommand;
|
|
454
|
+
// Plain claude's folder: ~/.claude, or the user's CLAUDE_CONFIG_DIR.
|
|
455
|
+
const claudeDir = claudeSourceDir({ env: options.env, home });
|
|
454
456
|
const roots = options.roots || {
|
|
455
|
-
claude: path.join(
|
|
457
|
+
claude: path.join(claudeDir, 'projects'),
|
|
456
458
|
codex: path.join(home, '.codex', 'sessions'),
|
|
457
459
|
pi: path.join(home, '.pi', 'agent', 'sessions'),
|
|
458
460
|
};
|
|
@@ -464,7 +466,7 @@ export async function discoverSessions(options = {}) {
|
|
|
464
466
|
codex: path.join(roots.codex, '.session-index.jsonl'),
|
|
465
467
|
}
|
|
466
468
|
: {
|
|
467
|
-
claude: path.join(
|
|
469
|
+
claude: path.join(claudeDir, 'history.jsonl'),
|
|
468
470
|
codex: path.join(home, '.codex', 'session_index.jsonl'),
|
|
469
471
|
});
|
|
470
472
|
const claude = await discoverClaudeIndex(roots.claude, indexes.claude, max);
|
package/bin/windows/hook.cjs
CHANGED
|
@@ -18,12 +18,10 @@ async function main() {
|
|
|
18
18
|
});
|
|
19
19
|
try { payload = JSON.parse(input); } catch { /* fail open */ }
|
|
20
20
|
const event = payload?.hook_event_name || '';
|
|
21
|
-
const response = client === '
|
|
22
|
-
? (event === 'preCompact' ? {} : { continue: true, permission: 'allow' })
|
|
23
|
-
: client === 'copilot' ? { continue: true } : {};
|
|
21
|
+
const response = client === 'copilot' ? { continue: true } : {};
|
|
24
22
|
try {
|
|
25
23
|
const config = JSON.parse(await fs.readFile(configPath, 'utf8'));
|
|
26
|
-
const cid = payload?.
|
|
24
|
+
const cid = payload?.session_id;
|
|
27
25
|
if (!cid || typeof cid !== 'string' || !config.apiKey || !config.gatewayUrl) return;
|
|
28
26
|
const post = async body => {
|
|
29
27
|
const controller = new AbortController();
|
|
@@ -36,19 +34,15 @@ async function main() {
|
|
|
36
34
|
await res.body?.cancel();
|
|
37
35
|
} catch { /* fail open */ } finally { clearTimeout(timer); }
|
|
38
36
|
};
|
|
39
|
-
if (
|
|
37
|
+
if (client === 'copilot' && event === 'UserPromptSubmit') {
|
|
40
38
|
if (typeof payload.prompt !== 'string' || !payload.prompt) return;
|
|
41
|
-
const root = payload.
|
|
39
|
+
const root = payload.cwd;
|
|
42
40
|
await post({ event: 'conversation_ensure', prompt: payload.prompt, workspace: typeof root === 'string' ? path.win32.basename(root) : undefined });
|
|
43
41
|
}
|
|
44
42
|
if (client === 'codex' && ['PreCompact', 'PostCompact'].includes(event)) {
|
|
45
43
|
const phase = event === 'PreCompact' ? 'start' : 'end';
|
|
46
44
|
await post({ event: 'conversation_compaction', phase, dedupe_key: `${cid}:${phase}:${payload.turn_id || randomUUID()}`, metadata: { trigger: payload.trigger, turn_id: payload.turn_id } });
|
|
47
45
|
}
|
|
48
|
-
if (client === 'cursor' && event === 'preCompact') {
|
|
49
|
-
const metadata = Object.fromEntries(['trigger', 'context_tokens', 'context_window_size', 'context_usage_percent', 'message_count', 'messages_to_compact', 'is_first_compaction'].filter(k => payload[k] != null).map(k => [k, payload[k]]));
|
|
50
|
-
await post({ event: 'conversation_compaction', phase: 'point', dedupe_key: `${cid}:${payload.generation_id ?? 'gen'}:${payload.is_first_compaction ?? false}`, metadata });
|
|
51
|
-
}
|
|
52
46
|
if (client === 'copilot' && ['PreCompact', 'UserPromptSubmit'].includes(event)) {
|
|
53
47
|
const pendingDir = path.join(path.dirname(configPath), 'subconscious-compact-pending');
|
|
54
48
|
const pending = path.join(pendingDir, createHash('sha256').update(cid).digest('hex') + '.json');
|
package/bin/windows/launch.js
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
resolveInputs,
|
|
10
10
|
takeFlags,
|
|
11
11
|
} from '../agent-launch.js';
|
|
12
|
+
import { prepareClaudeLaunch } from '../claude-config.js';
|
|
12
13
|
import { claudePickerSettings } from '../claude-picker.js';
|
|
13
14
|
import { modelIds, positiveInteger } from '../launch-values.js';
|
|
14
15
|
import { modelSupportsVision } from '../model-capabilities.js';
|
|
@@ -56,11 +57,18 @@ function claudeLaunch(agent, argv, env) {
|
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
function plannedLaunch(agent, argv, env) {
|
|
59
|
-
|
|
60
|
-
agent
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
if (agent.id !== 'claude-code') {
|
|
61
|
+
const plan = launchPlan(agent, { env, args: argv });
|
|
62
|
+
return { command: plan.argv[0], args: plan.argv.slice(1), env: plan.env };
|
|
63
|
+
}
|
|
64
|
+
const plan = claudeLaunch(agent, argv, env);
|
|
65
|
+
return {
|
|
66
|
+
command: plan.argv[0],
|
|
67
|
+
args: plan.argv.slice(1),
|
|
68
|
+
env: plan.env,
|
|
69
|
+
// Runs only when the launch executes, so planning never touches disk.
|
|
70
|
+
prepare: (options) => prepareClaudeLaunch(agent, plan.env, env, options),
|
|
71
|
+
};
|
|
64
72
|
}
|
|
65
73
|
|
|
66
74
|
function codexCatalog(env, context, maxContext, compact) {
|
|
@@ -266,12 +274,17 @@ export async function windowsLaunch(
|
|
|
266
274
|
throw new Error(`No native Windows launcher is available for ${id}`);
|
|
267
275
|
}
|
|
268
276
|
|
|
269
|
-
export async function executeWindowsLaunch(spec, run) {
|
|
277
|
+
export async function executeWindowsLaunch(spec, run, prepareOptions) {
|
|
270
278
|
try {
|
|
279
|
+
const prepared = spec.prepare?.(prepareOptions);
|
|
280
|
+
for (const warning of prepared?.warnings ?? []) {
|
|
281
|
+
console.error(` ${warning}`);
|
|
282
|
+
}
|
|
283
|
+
// Claude Code would write into the user's own config there.
|
|
284
|
+
if (prepared?.refused) return 1;
|
|
285
|
+
const env = { ...spec.env, ...prepared?.env };
|
|
271
286
|
const options =
|
|
272
|
-
spec.input === undefined
|
|
273
|
-
? { env: spec.env }
|
|
274
|
-
: { env: spec.env, input: spec.input };
|
|
287
|
+
spec.input === undefined ? { env } : { env, input: spec.input };
|
|
275
288
|
return await run(spec.command, spec.args, options);
|
|
276
289
|
} finally {
|
|
277
290
|
await spec.cleanup?.();
|
package/bin/windows/setup.js
CHANGED
|
@@ -78,7 +78,6 @@ export function mergeHooks(document, client, command) {
|
|
|
78
78
|
if (command) {
|
|
79
79
|
const events = {
|
|
80
80
|
codex: ['PreCompact', 'PostCompact'],
|
|
81
|
-
cursor: ['beforeSubmitPrompt', 'preCompact'],
|
|
82
81
|
copilot: ['UserPromptSubmit', 'PreCompact'],
|
|
83
82
|
}[client];
|
|
84
83
|
for (const event of events) {
|
|
@@ -99,7 +98,6 @@ export function mergeHooks(document, client, command) {
|
|
|
99
98
|
}
|
|
100
99
|
}
|
|
101
100
|
result.hooks = hooks;
|
|
102
|
-
if (client === 'cursor' && command) result.version ??= 1;
|
|
103
101
|
return result;
|
|
104
102
|
}
|
|
105
103
|
|
|
@@ -136,11 +134,11 @@ export async function windowsSetup(
|
|
|
136
134
|
const apiKey = options['--api-key'] || value(env, 'API_KEY');
|
|
137
135
|
const installing = action === 'install';
|
|
138
136
|
const removing = action === 'uninstall';
|
|
139
|
-
const needsKey = ['codex', '
|
|
137
|
+
const needsKey = ['codex', 'copilot', 'pi'].includes(id);
|
|
140
138
|
if (installing && needsKey && !apiKey)
|
|
141
139
|
throw new Error('An API key is required for setup. Run subc login first.');
|
|
142
140
|
|
|
143
|
-
if (['codex', '
|
|
141
|
+
if (['codex', 'copilot'].includes(id)) {
|
|
144
142
|
const dir =
|
|
145
143
|
id === 'codex'
|
|
146
144
|
? env.CODEX_DIR || env.CODEX_HOME || path.join(home, '.codex')
|
|
@@ -243,10 +241,6 @@ export async function windowsSetup(
|
|
|
243
241
|
await fs.rm(script, { force: true });
|
|
244
242
|
await fs.rm(config, { force: true });
|
|
245
243
|
}
|
|
246
|
-
if (installing && id === 'cursor')
|
|
247
|
-
log(
|
|
248
|
-
`In Cursor Settings, use ${gatewayUrl}/v1, your API key, and model ${value(env, 'MODEL', defaults.model)}. Restart Cursor to load hooks.`,
|
|
249
|
-
);
|
|
250
244
|
if (installing && id === 'copilot')
|
|
251
245
|
log(
|
|
252
246
|
'Restart VS Code, select a Subconscious model, and enter your API key when prompted.',
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "subconscious-cli",
|
|
3
|
-
"version": "6.
|
|
4
|
-
"description": "CLI for Subconscious — run Subconscious Code, Claude Code, Codex, OpenCode, DeepSeek Harness,
|
|
3
|
+
"version": "6.3.0",
|
|
4
|
+
"description": "CLI for Subconscious — run Subconscious Code, Claude Code, Codex, OpenCode, DeepSeek Harness, Copilot, and Pi",
|
|
5
5
|
"bin": {
|
|
6
6
|
"subc": "bin/cli.js"
|
|
7
7
|
},
|
|
@@ -40,7 +40,6 @@
|
|
|
40
40
|
"claude-code",
|
|
41
41
|
"opencode",
|
|
42
42
|
"codex",
|
|
43
|
-
"cursor",
|
|
44
43
|
"copilot",
|
|
45
44
|
"pi",
|
|
46
45
|
"deepseek-harness",
|
|
@@ -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
|