@ucsandman/legcli 0.8.0 → 0.9.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/CHANGELOG.md +45 -0
- package/NOTICE +8 -0
- package/README.md +601 -560
- package/bin/fake-agent.mjs +4 -4
- package/bin/leg.mjs +21 -12
- package/docs/DECISIONS.md +20 -2
- package/docs/ERRORS.md +71 -0
- package/docs/README.md +2 -0
- package/docs/REUSE.md +1 -1
- package/docs/VOCABULARY.md +21 -0
- package/docs/board-guide.md +13 -0
- package/docs/cli-contracts.md +22 -1
- package/docs/concepts.md +42 -3
- package/docs/configuration.md +22 -1
- package/docs/faq.md +19 -0
- package/docs/getting-started.md +272 -251
- package/docs/harness.md +319 -0
- package/fixtures/verified.json +1 -1
- package/package.json +7 -3
- package/scripts/build-docs-site.mjs +11 -4
- package/scripts/check-branding.mjs +118 -0
- package/scripts/check-claims.mjs +1 -1
- package/scripts/license-sign.mjs +1 -1
- package/scripts/limits-table.mjs +1 -1
- package/scripts/live-limits.mjs +1 -1
- package/scripts/npm-publish-gate.mjs +114 -0
- package/scripts/probe.mjs +4 -3
- package/scripts/seed-fake-cards.mjs +4 -3
- package/scripts/seed-floor-board.mjs +5 -4
- package/scripts/seed-wes-board.mjs +5 -4
- package/scripts/stripe-setup.mjs +1 -1
- package/scripts/sync-harness-engine.mjs +159 -0
- package/scripts/sync-leg-agents.mjs +127 -0
- package/src/accounts.mjs +1 -2
- package/src/adapters/codex.mjs +1 -1
- package/src/attach.mjs +75 -19
- package/src/auth.mjs +2 -2
- package/src/board/board.js +3 -3
- package/src/board/sessions.js +77 -3
- package/src/bundle.mjs +54 -8
- package/src/chain.mjs +1 -1
- package/src/contract.mjs +4 -3
- package/src/fsx.mjs +5 -2
- package/src/handoff.mjs +6 -6
- package/src/harness/cli.mjs +281 -0
- package/src/harness/fingerprint.mjs +68 -0
- package/src/harness/index.mjs +407 -0
- package/src/harness/registry.mjs +124 -0
- package/src/harness/vendor/agnostic-ai/LICENSE +21 -0
- package/src/harness/vendor/agnostic-ai/UPSTREAM.json +30 -0
- package/src/harness/vendor/agnostic-ai/core/safety/guards.json +96 -0
- package/src/harness/vendor/agnostic-ai/core/templates/targets.json +252 -0
- package/src/harness/vendor/agnostic-ai/engine/harness/README.md +199 -0
- package/src/harness/vendor/agnostic-ai/engine/harness/apply.cjs +247 -0
- package/src/harness/vendor/agnostic-ai/engine/harness/bundle.cjs +243 -0
- package/src/harness/vendor/agnostic-ai/engine/harness/capture.cjs +119 -0
- package/src/harness/vendor/agnostic-ai/engine/harness/common.cjs +375 -0
- package/src/harness/vendor/agnostic-ai/engine/harness/index.cjs +55 -0
- package/src/harness/vendor/agnostic-ai/engine/harness/sources/claude.cjs +330 -0
- package/src/harness/vendor/agnostic-ai/engine/harness/sources/codex.cjs +314 -0
- package/src/harness/vendor/agnostic-ai/engine/harness/status.cjs +171 -0
- package/src/harness/vendor/agnostic-ai/engine/harness/targets/agy.cjs +113 -0
- package/src/harness/vendor/agnostic-ai/engine/harness/targets/claude.cjs +158 -0
- package/src/harness/vendor/agnostic-ai/engine/harness/targets/codex.cjs +832 -0
- package/src/harness/vendor/agnostic-ai/engine/harness/targets/cursor.cjs +87 -0
- package/src/harness/vendor/agnostic-ai/engine/harness/targets/gemini.cjs +128 -0
- package/src/harness/vendor/agnostic-ai/engine/harness/targets/generic.cjs +424 -0
- package/src/harness/vendor/agnostic-ai/engine/harness/toml.cjs +149 -0
- package/src/harness/vendor/agnostic-ai/engine/hooks/shim.cjs +431 -0
- package/src/hook.mjs +49 -49
- package/src/land.mjs +7 -35
- package/src/launcher.mjs +38 -26
- package/src/ledger.mjs +6 -6
- package/src/license.mjs +10 -9
- package/src/live-capture.mjs +1 -1
- package/src/mergequeue.mjs +5 -5
- package/src/orchestrator.mjs +28 -4
- package/src/preferences.mjs +37 -3
- package/src/redact.mjs +1 -1
- package/src/resume.mjs +17 -15
- package/src/runner.mjs +2 -2
- package/src/scheduler.mjs +1 -1
- package/src/server.mjs +38 -10
- package/src/session-detail.mjs +15 -1
- package/src/sessions.mjs +6 -3
- package/src/share.mjs +2 -2
- package/src/stations/agent.mjs +1 -1
- package/src/sync/dashclaw.mjs +4 -4
- package/src/synthesis.mjs +165 -0
- package/src/taps/agy.mjs +2 -2
- package/src/taps/claude-usage.mjs +1 -1
- package/src/taps/claude.mjs +170 -170
- package/src/taps/codex.mjs +286 -286
- package/src/taps/grok.mjs +2 -2
- package/src/trust.mjs +205 -36
- package/src/usage.mjs +5 -1
- package/src/worktree.mjs +5 -4
- package/fixtures/live/agy/attempt-1-scratch-workspace.out.log +0 -1
- package/fixtures/live/agy/err.log +0 -0
- package/fixtures/live/agy/out.log +0 -1
- package/fixtures/live/agy/supervisor.log +0 -2
- package/fixtures/live/claude/err.log +0 -0
- package/fixtures/live/claude/out.log +0 -1
- package/fixtures/live/claude/supervisor.log +0 -2
- package/fixtures/live/codex/err.log +0 -1
- package/fixtures/live/codex/out.log +0 -8
- package/fixtures/live/codex/supervisor.log +0 -2
- package/fixtures/live/grok/err.log +0 -32
- package/fixtures/live/grok/out.log +0 -7
- package/fixtures/live/grok/supervisor.log +0 -2
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"_note": "SINGLE SOURCE OF TRUTH for all safety policy. Consumed read-only by agent/governance/guard.py (Python), engine/hooks/secret-guard.cjs, engine/hooks/dashclaw-guard.cjs (Node), and the dashboard. Do not hardcode policy anywhere else.",
|
|
5
|
+
"guards": {
|
|
6
|
+
"secretScan": {
|
|
7
|
+
"enabled": true,
|
|
8
|
+
"blockedFiles": [
|
|
9
|
+
"**/.env",
|
|
10
|
+
"**/.env.*",
|
|
11
|
+
"**/.secrets.env",
|
|
12
|
+
"**/.secrets",
|
|
13
|
+
"**/id_rsa",
|
|
14
|
+
"**/id_ed25519",
|
|
15
|
+
"**/id_ecdsa",
|
|
16
|
+
"**/id_dsa",
|
|
17
|
+
"**/*.pem",
|
|
18
|
+
"**/*.pfx",
|
|
19
|
+
"**/*.key",
|
|
20
|
+
"**/*.p12",
|
|
21
|
+
"**/credentials.json",
|
|
22
|
+
"**/.aws/credentials",
|
|
23
|
+
"**/.npmrc",
|
|
24
|
+
"**/.dashclaw-local/secrets/**"
|
|
25
|
+
],
|
|
26
|
+
"secretPathRegexes": [
|
|
27
|
+
"(?i)(^|[\\s=\"'`/\\\\(@<>:,*&|;])\\.env(rc)?\\b",
|
|
28
|
+
"(?i)\\.secrets(\\.env)?\\b",
|
|
29
|
+
"(?i)\\bid_(rsa|ed25519|ecdsa|dsa)\\b",
|
|
30
|
+
"(?i)\\.(pem|pfx|p12|key)\\b",
|
|
31
|
+
"(?i)\\bcredentials\\.json\\b",
|
|
32
|
+
"(?i)\\.aws[\\\\/]credentials\\b",
|
|
33
|
+
"(?i)\\.dashclaw-local[\\\\/]secrets"
|
|
34
|
+
],
|
|
35
|
+
"sensitivePatterns": [
|
|
36
|
+
"(?i)sk_live_[0-9a-zA-Z]{24,}",
|
|
37
|
+
"(?i)sk-[0-9a-zA-Z]{20,}",
|
|
38
|
+
"(?i)ghp_[0-9a-zA-Z]{36}",
|
|
39
|
+
"(?i)gho_[0-9a-zA-Z]{36}",
|
|
40
|
+
"(?i)xox[baprs]-[0-9a-zA-Z-]{10,}",
|
|
41
|
+
"(?i)AIza[0-9A-Za-z-_]{35}",
|
|
42
|
+
"(?i)-----BEGIN [A-Z ]*PRIVATE KEY-----",
|
|
43
|
+
"(?i)bearer\\s+[a-zA-Z0-9_\\-\\.]{20,}"
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
"processControl": {
|
|
47
|
+
"protectedProcesses": [
|
|
48
|
+
"explorer.exe",
|
|
49
|
+
"system",
|
|
50
|
+
"powershell.exe",
|
|
51
|
+
"pwsh.exe",
|
|
52
|
+
"cmd.exe",
|
|
53
|
+
"svchost.exe",
|
|
54
|
+
"claude.exe",
|
|
55
|
+
"agy.exe",
|
|
56
|
+
"node.exe"
|
|
57
|
+
],
|
|
58
|
+
"blockedCommands": [
|
|
59
|
+
"(?i)taskkill.*\\/(im\\s+(explorer|svchost|system)|f)",
|
|
60
|
+
"(?i)rmdir.*\\/s.*\\/q.*[c-z]:\\\\(windows|users)?$",
|
|
61
|
+
"(?i)format\\s+[c-z]:"
|
|
62
|
+
]
|
|
63
|
+
},
|
|
64
|
+
"hardStops": {
|
|
65
|
+
"_note": "Matched anywhere in the command string; order-independent. These require explicit human approval.",
|
|
66
|
+
"requireApprovalPatterns": [
|
|
67
|
+
"(?i)\\bgit\\s+push\\b.*(--force\\b|--force-with-lease\\b|\\s-f\\b)",
|
|
68
|
+
"(?i)\\bgit\\s+push\\s+-f\\b",
|
|
69
|
+
"(?i)\\bgit\\s+reset\\s+.*--hard\\b",
|
|
70
|
+
"(?i)\\bgit\\s+clean\\s+.*-[a-z]*f",
|
|
71
|
+
"(?i)\\brm\\s+-[a-z]*r[a-z]*f|\\brm\\s+-[a-z]*f[a-z]*r",
|
|
72
|
+
"(?i)\\brmdir\\s+/s\\b|\\bRemove-Item\\b.*-Recurse",
|
|
73
|
+
"(?i)\\b(drop\\s+(database|table)|truncate\\s+table)\\b",
|
|
74
|
+
"(?i)\\bnpm\\s+publish\\b",
|
|
75
|
+
"(?i)\\bnpm\\s+audit\\s+fix\\s+--force\\b",
|
|
76
|
+
"(?i)\\bvercel\\s+(--prod|deploy\\s+--prod)\\b",
|
|
77
|
+
"(?i)\\brailway\\s+up\\b",
|
|
78
|
+
"(?i)\\brender\\s+deploy\\b",
|
|
79
|
+
"(?i)\\bstripe\\s+(delete|refund|payout)\\b",
|
|
80
|
+
"(?i)\\b(deploy|migrate)\\s+(prod|production)\\b"
|
|
81
|
+
]
|
|
82
|
+
},
|
|
83
|
+
"dashclaw": {
|
|
84
|
+
"enabled": true,
|
|
85
|
+
"autoDiscover": true,
|
|
86
|
+
"defaultRiskThreshold": 50,
|
|
87
|
+
"hardBlockRiskThreshold": 90,
|
|
88
|
+
"failClosed": true,
|
|
89
|
+
"adoptLocalhostPortsAllowed": false,
|
|
90
|
+
"baseUrl": "https://my-dashclaw.vercel.app",
|
|
91
|
+
"predictiveRiskEnabled": true,
|
|
92
|
+
"autoScanBlock": true,
|
|
93
|
+
"orgHalt": false
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "2.0.0",
|
|
3
|
+
"_note": "Every AI coding client the harness can be ported to. Field meanings: engine/harness/README.md. Paths are ~-relative; `home` present on disk means the client is installed. Do not hardcode a client path anywhere else.",
|
|
4
|
+
"targets": [
|
|
5
|
+
{
|
|
6
|
+
"id": "claude",
|
|
7
|
+
"name": "Claude Code",
|
|
8
|
+
"category": "CLI Agent",
|
|
9
|
+
"home": "~/.claude",
|
|
10
|
+
"adapter": "claude",
|
|
11
|
+
"dialect": "claude",
|
|
12
|
+
"rulesFile": "~/.claude/agnostic-rules.md",
|
|
13
|
+
"traitsFile": "~/.claude/SOUL.md",
|
|
14
|
+
"hooksConfigFile": "~/.claude/settings.json",
|
|
15
|
+
"skillsDir": "~/.claude/skills",
|
|
16
|
+
"sharedSkillDirs": [
|
|
17
|
+
"~/.agents/skills"
|
|
18
|
+
],
|
|
19
|
+
"agentsDir": "~/.claude/agents",
|
|
20
|
+
"commandsDir": "~/.claude/commands",
|
|
21
|
+
"mcpConfigFile": "~/.claude.json",
|
|
22
|
+
"mcpFormat": "mcpServers-json",
|
|
23
|
+
"preamble": "# Agnostic Rules — Global Working Agreement (Claude Code)\n\nGENERATED by agnostic-ai. Source of truth is the captured harness (or core/rules/). Loaded via an @import line in ~/.claude/CLAUDE.md.\n"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"id": "codex",
|
|
27
|
+
"name": "Codex CLI",
|
|
28
|
+
"category": "CLI Agent",
|
|
29
|
+
"home": "~/.codex",
|
|
30
|
+
"adapter": "codex",
|
|
31
|
+
"dialect": "codex",
|
|
32
|
+
"rulesFile": "~/.codex/AGENTS.md",
|
|
33
|
+
"hooksConfigFile": "~/.codex/config.toml",
|
|
34
|
+
"skillsDir": "~/.codex/skills",
|
|
35
|
+
"sharedSkillDirs": [
|
|
36
|
+
"~/.agents/skills"
|
|
37
|
+
],
|
|
38
|
+
"agentsDir": "~/.codex/agents",
|
|
39
|
+
"commandsDir": "~/.codex/prompts",
|
|
40
|
+
"mcpConfigFile": "~/.codex/config.toml",
|
|
41
|
+
"mcpFormat": "codex-toml",
|
|
42
|
+
"permissionsFile": "~/.codex/rules/agnostic.rules",
|
|
43
|
+
"preamble": "# AGENTS.md — Global Working Agreement (Codex CLI)\n\nGENERATED by agnostic-ai from the captured harness. Do not hand-edit; edit the source client and run `npm run port`.\n"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"id": "gemini",
|
|
47
|
+
"name": "Gemini CLI",
|
|
48
|
+
"category": "CLI Agent",
|
|
49
|
+
"home": "~/.gemini",
|
|
50
|
+
"adapter": "gemini",
|
|
51
|
+
"dialect": "gemini",
|
|
52
|
+
"rulesFile": "~/.gemini/GEMINI.md",
|
|
53
|
+
"traitsFile": "~/.gemini/config/SOUL.md",
|
|
54
|
+
"hooksConfigFile": "~/.gemini/settings.json",
|
|
55
|
+
"skillsDir": "~/.gemini/skills",
|
|
56
|
+
"commandsDir": "~/.gemini/commands",
|
|
57
|
+
"mcpConfigFile": "~/.gemini/settings.json",
|
|
58
|
+
"mcpFormat": "mcpServers-json",
|
|
59
|
+
"preamble": "# GEMINI.md — Global Working Agreement (Gemini CLI)\n\nGENERATED by agnostic-ai from the captured harness. Do not hand-edit; edit the source client and run `npm run port`.\n"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"id": "agy",
|
|
63
|
+
"name": "Antigravity CLI",
|
|
64
|
+
"category": "CLI Agent",
|
|
65
|
+
"home": "~/.gemini/antigravity-cli",
|
|
66
|
+
"adapter": "agy",
|
|
67
|
+
"dialect": "agy",
|
|
68
|
+
"rulesFile": "~/.gemini/GEMINI.md",
|
|
69
|
+
"traitsFile": "~/.gemini/config/SOUL.md",
|
|
70
|
+
"hooksConfigFile": "~/.gemini/config/hooks.json",
|
|
71
|
+
"skillsDir": "~/.gemini/config/skills",
|
|
72
|
+
"agentsDir": "~/.gemini/config/agents",
|
|
73
|
+
"commandsDir": "~/.gemini/config/commands",
|
|
74
|
+
"mcpConfigFile": "~/.gemini/config/mcp_config.json",
|
|
75
|
+
"mcpFormat": "mcpServers-json",
|
|
76
|
+
"preamble": "# GEMINI.md — Global Working Agreement (Gemini CLI)\n\nGENERATED by agnostic-ai from the captured harness. Do not hand-edit; edit the source client and run `npm run port`.\n"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"id": "cursor",
|
|
80
|
+
"name": "Cursor",
|
|
81
|
+
"category": "Agentic IDE",
|
|
82
|
+
"home": "~/.cursor",
|
|
83
|
+
"adapter": "cursor",
|
|
84
|
+
"dialect": "cursor",
|
|
85
|
+
"rulesFile": "~/.cursor/rules/global-rules.mdc",
|
|
86
|
+
"hooksConfigFile": "~/.cursor/hooks.json",
|
|
87
|
+
"skillsDir": "~/.cursor/skills",
|
|
88
|
+
"agentsDir": "~/.cursor/agents",
|
|
89
|
+
"commandsDir": "~/.cursor/commands",
|
|
90
|
+
"mcpConfigFile": "~/.cursor/mcp.json",
|
|
91
|
+
"mcpFormat": "mcpServers-json",
|
|
92
|
+
"preamble": "---\ndescription: \"Global Working Agreement and safety rules\"\nglobs: \"*\"\nalwaysApply: true\n---\n\n# Cursor Rules — Global Working Agreement\n\nGENERATED by agnostic-ai from the captured harness.\n"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"id": "windsurf",
|
|
96
|
+
"name": "Windsurf (Cascade)",
|
|
97
|
+
"category": "Agentic IDE",
|
|
98
|
+
"home": "~/.windsurf",
|
|
99
|
+
"dialect": "windsurf",
|
|
100
|
+
"rulesFile": "~/.windsurf/rules/global-rules.md",
|
|
101
|
+
"skillsDir": "~/.windsurf/skills",
|
|
102
|
+
"mcpConfigFile": "~/.codeium/windsurf/mcp_config.json",
|
|
103
|
+
"mcpFormat": "mcpServers-json",
|
|
104
|
+
"preamble": "# Windsurf Rules — Global Working Agreement\n\nGENERATED by agnostic-ai from the captured harness.\n"
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"id": "copilot",
|
|
108
|
+
"name": "GitHub Copilot",
|
|
109
|
+
"category": "VS Code Extension",
|
|
110
|
+
"home": "~/.copilot",
|
|
111
|
+
"dialect": "copilot",
|
|
112
|
+
"rulesFile": "~/.github/copilot-instructions.md",
|
|
113
|
+
"skillsDir": "~/.copilot/skills",
|
|
114
|
+
"preamble": "# GitHub Copilot Global Instructions\n\nGENERATED by agnostic-ai from the captured harness.\n"
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"id": "cline",
|
|
118
|
+
"name": "Cline",
|
|
119
|
+
"category": "VS Code Extension",
|
|
120
|
+
"home": "~/.cline",
|
|
121
|
+
"dialect": "cline",
|
|
122
|
+
"rulesFile": "~/.cline/prompts/global-rules.md",
|
|
123
|
+
"skillsDir": "~/.cline/skills",
|
|
124
|
+
"mcpConfigFile": "~/.cline/cline_mcp_settings.json",
|
|
125
|
+
"mcpFormat": "mcpServers-json",
|
|
126
|
+
"preamble": "# Cline — Global Working Agreement\n\nGENERATED by agnostic-ai from the captured harness.\n"
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"id": "aider",
|
|
130
|
+
"name": "Aider",
|
|
131
|
+
"category": "CLI Agent",
|
|
132
|
+
"home": "~/.aider",
|
|
133
|
+
"dialect": "aider",
|
|
134
|
+
"rulesFile": "~/.aider.conventions.md",
|
|
135
|
+
"preamble": "# Aider — Conventions & Global Working Agreement\n\nGENERATED by agnostic-ai from the captured harness.\n"
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"id": "openhands",
|
|
139
|
+
"name": "OpenHands",
|
|
140
|
+
"category": "Autonomous Agent",
|
|
141
|
+
"home": "~/.openhands",
|
|
142
|
+
"dialect": "openhands",
|
|
143
|
+
"rulesFile": "~/.openhands/AGENTS.md",
|
|
144
|
+
"skillsDir": "~/.openhands/skills",
|
|
145
|
+
"sharedSkillDirs": [
|
|
146
|
+
"~/.agents/skills"
|
|
147
|
+
],
|
|
148
|
+
"preamble": "# AGENTS.md — OpenHands Global Agreement\n\nGENERATED by agnostic-ai from the captured harness.\n"
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"id": "goose",
|
|
152
|
+
"name": "Goose (Block)",
|
|
153
|
+
"category": "Autonomous Agent",
|
|
154
|
+
"home": "~/.config/goose",
|
|
155
|
+
"dialect": "goose",
|
|
156
|
+
"rulesFile": "~/.config/goose/.goosehints",
|
|
157
|
+
"skillsDir": "~/.config/goose/skills",
|
|
158
|
+
"preamble": "# .goosehints — Goose Global System Hints\n\nGENERATED by agnostic-ai from the captured harness.\n"
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
"id": "continue",
|
|
162
|
+
"name": "Continue.dev",
|
|
163
|
+
"category": "VS Code Extension",
|
|
164
|
+
"home": "~/.continue",
|
|
165
|
+
"dialect": "continue",
|
|
166
|
+
"rulesFile": "~/.continue/rules/global-rules.md",
|
|
167
|
+
"skillsDir": "~/.continue/skills",
|
|
168
|
+
"preamble": "# Continue.dev — Global Agent Rules\n\nGENERATED by agnostic-ai from the captured harness.\n"
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"id": "zed",
|
|
172
|
+
"name": "Zed AI",
|
|
173
|
+
"category": "Agentic IDE",
|
|
174
|
+
"home": "~/.config/zed",
|
|
175
|
+
"dialect": "zed",
|
|
176
|
+
"rulesFile": "~/.config/zed/AGENTS.md",
|
|
177
|
+
"preamble": "# AGENTS.md — Zed AI Global Instructions\n\nGENERATED by agnostic-ai from the captured harness.\n"
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
"id": "opencode",
|
|
181
|
+
"name": "OpenCode",
|
|
182
|
+
"category": "CLI Agent",
|
|
183
|
+
"home": "~/.config/opencode",
|
|
184
|
+
"dialect": "opencode",
|
|
185
|
+
"rulesFile": "~/.config/opencode/AGENTS.md",
|
|
186
|
+
"skillsDir": "~/.config/opencode/skills",
|
|
187
|
+
"sharedSkillDirs": [
|
|
188
|
+
"~/.agents/skills"
|
|
189
|
+
],
|
|
190
|
+
"commandsDir": "~/.config/opencode/commands",
|
|
191
|
+
"mcpConfigFile": "~/.config/opencode/opencode.json",
|
|
192
|
+
"mcpFormat": "opencode-json",
|
|
193
|
+
"preamble": "# AGENTS.md — OpenCode Global Instructions\n\nGENERATED by agnostic-ai from the captured harness.\n"
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
"id": "trae",
|
|
197
|
+
"name": "Trae (ByteDance)",
|
|
198
|
+
"category": "Agentic IDE",
|
|
199
|
+
"home": "~/.trae",
|
|
200
|
+
"dialect": "trae",
|
|
201
|
+
"rulesFile": "~/.trae/user_rules/user_rules.md",
|
|
202
|
+
"skillsDir": "~/.trae/skills",
|
|
203
|
+
"preamble": "# user_rules.md — Trae User Rules\n\nGENERATED by agnostic-ai from the captured harness.\n"
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
"id": "amazonq",
|
|
207
|
+
"name": "Amazon Q Developer",
|
|
208
|
+
"category": "Enterprise Assistant",
|
|
209
|
+
"home": "~/.amazonq",
|
|
210
|
+
"dialect": "amazonq",
|
|
211
|
+
"rulesFile": "~/.amazonq/rules/global-rules.md",
|
|
212
|
+
"preamble": "# Amazon Q Developer — Global Rules\n\nGENERATED by agnostic-ai from the captured harness.\n"
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
"id": "cody",
|
|
216
|
+
"name": "Sourcegraph Cody",
|
|
217
|
+
"category": "Enterprise Assistant",
|
|
218
|
+
"home": "~/.sourcegraph",
|
|
219
|
+
"dialect": "cody",
|
|
220
|
+
"rulesFile": "~/.sourcegraph/rules/global-rules.rule.md",
|
|
221
|
+
"preamble": "# Sourcegraph Cody — Global Rules\n\nGENERATED by agnostic-ai from the captured harness.\n"
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
"id": "openclaw",
|
|
225
|
+
"name": "OpenClaw",
|
|
226
|
+
"category": "Autonomous Agent",
|
|
227
|
+
"home": "~/.openclaw",
|
|
228
|
+
"dialect": "openclaw",
|
|
229
|
+
"rulesFile": "~/.openclaw/SYSTEM.md",
|
|
230
|
+
"skillsDir": "~/.openclaw/skills",
|
|
231
|
+
"preamble": "# SYSTEM.md — OpenClaw Agent System Prompt\n\nGENERATED by agnostic-ai from the captured harness.\n"
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
"id": "hermes",
|
|
235
|
+
"name": "Hermes Agent",
|
|
236
|
+
"category": "Autonomous Agent",
|
|
237
|
+
"home": "~/.hermes",
|
|
238
|
+
"dialect": "hermes",
|
|
239
|
+
"rulesFile": "~/.hermes/agent_system.md",
|
|
240
|
+
"skillsDir": "~/.hermes/skills",
|
|
241
|
+
"preamble": "# agent_system.md — Hermes System Prompt\n\nGENERATED by agnostic-ai from the captured harness.\n"
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
"id": "generic",
|
|
245
|
+
"name": "Generic LLM System Card",
|
|
246
|
+
"category": "Local / API",
|
|
247
|
+
"dialect": "generic",
|
|
248
|
+
"rulesFile": "storage/compiled/system_prompt.md",
|
|
249
|
+
"preamble": "# System Prompt for Autonomous LLM Agents\n\nGENERATED by agnostic-ai from the captured harness.\n"
|
|
250
|
+
}
|
|
251
|
+
]
|
|
252
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# engine/harness — the port engine
|
|
2
|
+
|
|
3
|
+
One harness, captured from the client you actually use, applied to every other
|
|
4
|
+
client so it behaves the same. This directory is the contract every adapter is
|
|
5
|
+
written against. Read it before adding a source or a target.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
capture (sources/<client>.cjs) -> harness/ bundle -> apply (targets/<client>.cjs)
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
- `capture.cjs` reads the live config of ONE client (the *source*) into the
|
|
12
|
+
client-neutral bundle under `<repo>/harness/`.
|
|
13
|
+
- `apply.cjs` renders the bundle into every other installed client (the
|
|
14
|
+
*targets*), one guarded write per file, and records what it dropped and why.
|
|
15
|
+
- `status.cjs` reads every target from disk and reports, per component, whether
|
|
16
|
+
it is in sync. Nothing here is asserted from memory.
|
|
17
|
+
- `cli.cjs` `capture | apply | port | status | explain`.
|
|
18
|
+
- `index.cjs` the engine as a library: the entry a host product requires (see
|
|
19
|
+
"Embedding" below).
|
|
20
|
+
- `bundle.cjs` load / save / validate the bundle; secret scan.
|
|
21
|
+
- `common.cjs` shared helpers (paths, guarded writes, links, managed regions,
|
|
22
|
+
frontmatter, TOML strings).
|
|
23
|
+
|
|
24
|
+
## Embedding
|
|
25
|
+
|
|
26
|
+
A host product (Leg, github.com/ucsandman/legcli, is the first) runs this
|
|
27
|
+
directory as a library and owns everything around it. Nothing in the engine
|
|
28
|
+
reads a repo file when the host passes the equivalent:
|
|
29
|
+
|
|
30
|
+
```js
|
|
31
|
+
const engine = require('agnostic-ai/engine/harness/index.cjs');
|
|
32
|
+
engine.configure({
|
|
33
|
+
brand: { id: 'host', mark: 'GENERATED by Host', region: 'host' }, // the ownership claim in every generated file
|
|
34
|
+
secretPatterns: [/* regexes; replaces core/safety/guards.json */],
|
|
35
|
+
shimPath: '/abs/path/to/the/hook/shim', // what non-Codex hooks point at
|
|
36
|
+
importRoots: [], // dirs besides home an @import may resolve into
|
|
37
|
+
});
|
|
38
|
+
const registry = engine.loadRegistry(home, { targets: myTargets }); // replaces core/templates/targets.json
|
|
39
|
+
const port = { ...myPolicy, baseDir: myDir }; // replaces core/port.json; addenda resolve against baseDir
|
|
40
|
+
const { bundle, warnings } = engine.capture({ from: 'claude', home, outDir, registry, port });
|
|
41
|
+
const report = engine.apply({ bundle, home, registry, port, storageDir, to: ['codex'] });
|
|
42
|
+
engine.status({ bundle, home, registry, port, storageDir });
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`bundle.fingerprint(b)` is a content hash of everything a target renders from
|
|
46
|
+
(the manifest's timestamp excluded); `save()` records it in
|
|
47
|
+
`manifest.fingerprint`, so a host can answer "did the harness change since the
|
|
48
|
+
last apply" without diffing files. `common.GENERATED_MARK` is a live getter
|
|
49
|
+
over the configured brand. Node's ESM loader imports `index.cjs` directly.
|
|
50
|
+
Regression: `engine/tests/reg-harness.cjs` section 10.
|
|
51
|
+
|
|
52
|
+
## The bundle (`<repo>/harness/`)
|
|
53
|
+
|
|
54
|
+
Client-neutral, human-readable, gitignored by default (it holds machine paths).
|
|
55
|
+
The Claude Code dialect is the canonical one for hooks and tool names because
|
|
56
|
+
Codex adopted a near-clone of it and every other client is mapped from it.
|
|
57
|
+
|
|
58
|
+
| File | Shape |
|
|
59
|
+
|---|---|
|
|
60
|
+
| `manifest.json` | `{ version, source, sourceHome, capturedAt, components: {rules, hooks, skills, agents, commands, mcp, permissions} }` (counts) |
|
|
61
|
+
| `rules.md` | The global working agreement with every `@import` inlined and the source client's own header stripped. Plain markdown, no client name in the prose that the port can avoid. |
|
|
62
|
+
| `identity.md` | Optional. SOUL.md / traits. |
|
|
63
|
+
| `hooks.json` | `{ "dialect": "claude", "events": { "<Event>": [ { "matcher"?: "A\|B", "hooks": [ { "type": "command", "command": "...", "timeout"?: 10, "statusMessage"?: "...", "async"?: false } ] } ] } }`. Events and matcher tokens use Claude Code names (`PreToolUse`, `Bash`, `Edit`, ...). |
|
|
64
|
+
| `mcp.json` | `{ "servers": { "<name>": { "transport": "stdio" \| "http" \| "sse", "command"?, "args"?: [], "env"?: {K: V}, "cwd"?, "url"?, "headers"?: {} } } }`. A secret-looking env value is replaced at capture by `"${NAME}"` and reported; targets render that as "read NAME from the environment". |
|
|
65
|
+
| `agents/<name>.md` | Frontmatter `name`, `description`, `model` (`fable` \| `opus` \| `sonnet` \| `haiku` \| `inherit` \| raw id), `tools` (comma list, Claude names), `readonly` (`true` when no edit tool is listed); body = the agent's instructions. |
|
|
66
|
+
| `commands/<name>.md` | Frontmatter `description`, `argument-hint`; body = the prompt. |
|
|
67
|
+
| `skills.json` | `{ "sourceDir": "<abs>", "skills": [ { "name", "path": "<abs dir with SKILL.md>" } ] }`. Skills are never copied: targets link each one, so an edit at the source is live everywhere. |
|
|
68
|
+
| `permissions.json` | `{ "allow": [...], "deny": [...], "ask": [...] }` in Claude pattern syntax (`Bash(git *)`). Targets map what they can and drop the rest with a reason. |
|
|
69
|
+
|
|
70
|
+
`bundle.cjs` validates the shape and refuses to save a bundle that carries a
|
|
71
|
+
value matching `core/safety/guards.json -> sensitivePatterns`.
|
|
72
|
+
|
|
73
|
+
## `core/port.json`
|
|
74
|
+
|
|
75
|
+
The user-editable port policy (tracked; a template user commits their own):
|
|
76
|
+
|
|
77
|
+
```jsonc
|
|
78
|
+
{
|
|
79
|
+
"source": "auto", // "auto" | "claude" | "codex"
|
|
80
|
+
"targets": "installed", // "installed" | "all" | ["codex", "gemini"]
|
|
81
|
+
"rules": { "dropSectionsForTargets": ["Delegation and Model Routing"] },
|
|
82
|
+
"hooks": {
|
|
83
|
+
"exclude": [ { "match": "<regex on the command>", "reason": "..." } ],
|
|
84
|
+
"extra": { "<targetId>": { "<Event>": [ <group> ] } } // target-only hooks appended after the ported ones
|
|
85
|
+
},
|
|
86
|
+
"skills": { "exclude": { "<name>": "reason" } },
|
|
87
|
+
"mcp": { "exclude": { "<name>": "reason" } },
|
|
88
|
+
"agents": { "modelLadder": { "<targetId>": { "fable": ["<model>", "<effort>"], "opus": [...], "sonnet": [...], "haiku": [...] } } }
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Target registry (`core/templates/targets.json`)
|
|
93
|
+
|
|
94
|
+
Every client is one entry. Paths are `~`-relative and expanded with
|
|
95
|
+
`common.expandPath`. Presence of `home` on disk means "installed".
|
|
96
|
+
|
|
97
|
+
| Field | Meaning |
|
|
98
|
+
|---|---|
|
|
99
|
+
| `id`, `name`, `category` | identity |
|
|
100
|
+
| `home` | the client's config dir; absent on disk = not installed = skipped by `"targets": "installed"` |
|
|
101
|
+
| `adapter` | which `targets/<adapter>.cjs` renders it (`generic` when omitted) |
|
|
102
|
+
| `rulesFile`, `preamble`, `traitsFile` | rules surface (every target has one) |
|
|
103
|
+
| `hooksConfigFile` | hook config the adapter manages (omit = client has no hooks) |
|
|
104
|
+
| `skillsDir`, `sharedSkillDirs` | where skills are linked; dirs the client already reads natively (a skill living there is not linked twice) |
|
|
105
|
+
| `agentsDir`, `commandsDir` | subagent and slash-command surfaces |
|
|
106
|
+
| `mcpConfigFile`, `mcpFormat` | MCP surface; `mcpFormat` is `mcpServers-json` (Claude/Cursor/Windsurf/Cline/Gemini shape), `codex-toml`, `opencode-json` |
|
|
107
|
+
| `dialect` | hook payload dialect for `engine/hooks/shim.cjs` (`claude`, `codex`, `gemini`, `agy`, `cursor`) |
|
|
108
|
+
|
|
109
|
+
## Adapter contract
|
|
110
|
+
|
|
111
|
+
### `sources/<id>.cjs`
|
|
112
|
+
|
|
113
|
+
```js
|
|
114
|
+
module.exports = {
|
|
115
|
+
id: 'claude',
|
|
116
|
+
// Returns { bundle, warnings: [string] }. Never throws on a missing optional
|
|
117
|
+
// surface (no agents dir = zero agents). Throws only when the rules file is missing.
|
|
118
|
+
capture({ home, target, port }) {}
|
|
119
|
+
};
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`bundle` is the in-memory shape of the directory above: `{ manifest, rules,
|
|
123
|
+
identity, hooks, mcp, agents: [{name, meta, body}], commands: [{name, meta,
|
|
124
|
+
body}], skills, permissions }`. Use `bundle.createBundle()` to get an empty one.
|
|
125
|
+
|
|
126
|
+
### `targets/<id>.cjs`
|
|
127
|
+
|
|
128
|
+
```js
|
|
129
|
+
module.exports = {
|
|
130
|
+
id: 'codex',
|
|
131
|
+
components: ['rules', 'identity', 'hooks', 'skills', 'agents', 'commands', 'mcp', 'permissions'],
|
|
132
|
+
rules(ctx) {}, identity(ctx) {}, hooks(ctx) {}, skills(ctx) {}, agents(ctx) {}, commands(ctx) {}, mcp(ctx) {}, permissions(ctx) {}
|
|
133
|
+
};
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Every component function receives the same `ctx`:
|
|
137
|
+
|
|
138
|
+
| Field | What |
|
|
139
|
+
|---|---|
|
|
140
|
+
| `target` | the registry entry with every path already expanded to an absolute path |
|
|
141
|
+
| `bundle` | the loaded bundle |
|
|
142
|
+
| `port` | `core/port.json` |
|
|
143
|
+
| `home` | home dir being written (tests pass a temp dir; production passes `os.homedir()`) |
|
|
144
|
+
| `check` | true = report only, write nothing |
|
|
145
|
+
| `dryRun` | true = print what would change, write nothing (same as check but verbose) |
|
|
146
|
+
| `force` | true = overwrite hand-edited files |
|
|
147
|
+
| `state` | mutable object persisted to `storage/harness-state.json`; use `ctx.state.files[path] = sha` via `ctx.write` and `ctx.state.owned[targetId]` for links / entries the adapter created so it can prune them later |
|
|
148
|
+
| `backup(path)` | copy a file into the backups dir before an adapter prunes it; returns the backup path |
|
|
149
|
+
| `write(path, content, opts)` | the guarded writer: backs up, refuses hand edits unless `force`, honours `check`; returns `{ action: 'written' \| 'unchanged' \| 'would-write' \| 'skipped-hand-edited', backup }` |
|
|
150
|
+
| `link(src, dest)` | junction / symlink one directory; returns `{ action }`; never replaces a real directory |
|
|
151
|
+
| `log(line)` | human line for the CLI |
|
|
152
|
+
|
|
153
|
+
And returns a `ComponentResult`:
|
|
154
|
+
|
|
155
|
+
```js
|
|
156
|
+
{
|
|
157
|
+
status: 'synced' | 'written' | 'stale' | 'skipped' | 'unsupported' | 'error',
|
|
158
|
+
files: [ { path, action } ], // every file touched or inspected
|
|
159
|
+
dropped: [ { item, reason } ], // what was NOT ported and why (shown by `explain`)
|
|
160
|
+
note: 'one line for the status table', // optional
|
|
161
|
+
error: 'message' // when status === 'error'
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Rules for every adapter:
|
|
166
|
+
|
|
167
|
+
1. **One writer per file.** A file the adapter manages carries
|
|
168
|
+
`GENERATED by agnostic-ai` within its first five lines (the preamble; a
|
|
169
|
+
title line may come first) or a marked region
|
|
170
|
+
`# >>> agnostic-ai <component> start` / `# <<< agnostic-ai <component> end`
|
|
171
|
+
(files the user also edits, like `config.toml` and `settings.json` arrays).
|
|
172
|
+
Everything outside the region is preserved byte for byte.
|
|
173
|
+
2. **Idempotent.** A second run with no source change writes nothing and
|
|
174
|
+
`--check` exits 0. Regions are rebuilt deterministically.
|
|
175
|
+
3. **Never destroy user content.** `ctx.write` backs up before overwriting and
|
|
176
|
+
refuses a hand-edited file without `force`; links never replace a real
|
|
177
|
+
directory; prune only what `ctx.state.owned` says we created.
|
|
178
|
+
4. **Every drop is explained.** A hook, skill, agent or server that cannot be
|
|
179
|
+
ported lands in `dropped` with a reason a stranger understands.
|
|
180
|
+
5. **Facts come from disk.** A model id, a hook trust hash, a skill list is read
|
|
181
|
+
from the target's own files, never asserted from memory. Where a hash scheme
|
|
182
|
+
is reproduced (Codex hook trust), keep a self-test against a value the client
|
|
183
|
+
itself wrote.
|
|
184
|
+
6. **Secrets never move.** An env value that looks like a token is written as an
|
|
185
|
+
environment reference, and the user is told which variable to export.
|
|
186
|
+
|
|
187
|
+
## Hook dialect shim (`engine/hooks/shim.cjs`)
|
|
188
|
+
|
|
189
|
+
Hooks are written once, in the Claude Code dialect (JSON on stdin, decision on
|
|
190
|
+
stdout / exit 2). Clients that speak another dialect run them through the shim:
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
node engine/hooks/shim.cjs --client cursor --event preToolUse -- node ~/.claude/hooks/rm-guard.cjs ++ node ~/.claude/hooks/scope-lock.cjs
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
The shim translates the incoming payload to the Claude shape, runs each command
|
|
197
|
+
in the chain (first deny wins), and translates the decision back. Fail-open: a
|
|
198
|
+
crashed guard or unparseable payload yields "no opinion", never a deny that wedges
|
|
199
|
+
the client. Codex needs no shim.
|