@ruvnet/open-claude-code 2.1.1 → 2.1.2
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 +48 -1
- package/package.json +1 -1
- package/src/core/agent-loop.mjs +22 -3
- package/src/skills/loader.mjs +4 -2
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
<img alt="Nightly" src="https://img.shields.io/badge/nightly-verified_releases-brightgreen?style=flat-square" />
|
|
18
18
|
</p>
|
|
19
19
|
|
|
20
|
-
> **
|
|
20
|
+
> **Release automation:** The nightly workflow gates publication on source validation, tests, AI analysis, and registry checks. A successful run is the evidence of completion; failed runs do not advance checkpoints. Capability proposals require review before implementation. See the release workflow and ADR-004.
|
|
21
21
|
|
|
22
22
|
---
|
|
23
23
|
|
|
@@ -44,6 +44,53 @@ npx @ruvnet/open-claude-code "what files are in this directory?"
|
|
|
44
44
|
|
|
45
45
|
---
|
|
46
46
|
|
|
47
|
+
|
|
48
|
+
## Skills and Claude Code plugin
|
|
49
|
+
|
|
50
|
+
Three shared skills cover CLI usage, diagnosis, and release verification. Choose one installation method to avoid duplicate skills.
|
|
51
|
+
|
|
52
|
+
### Install with npx skills
|
|
53
|
+
|
|
54
|
+
The tested skills installer requires Node 22.20 or newer (Node 24 recommended). OCC itself requires Node 20 or newer.
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npx skills@1.7.2 add ruvnet/open-claude-code --agent claude-code --skill occ-use occ-diagnose occ-verify-release
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
OCC 2.1.2 also loads these project skills from `.claude/skills`, including the installer's symlinks. Project skills override user skills. Invoke them with OCC's Skill tool or slash commands.
|
|
61
|
+
|
|
62
|
+
### Install the native Claude Code plugin
|
|
63
|
+
|
|
64
|
+
In Claude Code:
|
|
65
|
+
|
|
66
|
+
```text
|
|
67
|
+
/plugin marketplace add ruvnet/open-claude-code
|
|
68
|
+
/plugin install occ@ruvnet-occ
|
|
69
|
+
/occ:occ-use
|
|
70
|
+
/occ:occ-diagnose
|
|
71
|
+
/occ:occ-verify-release
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Restart Claude Code after installation if the current session has not loaded the new skills.
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
claude plugin update occ@ruvnet-occ
|
|
78
|
+
claude plugin uninstall occ@ruvnet-occ
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The `occ` plugin uses the same `skills/` sources. It installs no hooks, MCP servers, credentials, or background processes. It guides use of the separately published CLI. This native Claude Code package does not imply compatibility between OCC's custom PluginLoader and all third party Claude plugins.
|
|
82
|
+
|
|
83
|
+
### Reproduce installation tests
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# From a repository checkout, with Node 24 and Claude Code installed:
|
|
87
|
+
node scripts/plugin-e2e.mjs
|
|
88
|
+
# Test public GitHub distribution after publishing:
|
|
89
|
+
node scripts/plugin-e2e.mjs ruvnet/open-claude-code
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The suite installs and removes real skills and plugins in disposable profiles, checks all three Claude skill expansions with a local model fixture, and verifies OCC discovery. Live provider behavior is tested separately by `scripts/live-package-e2e.mjs`. Tested installer: skills 1.7.2; Claude Code: 2.1.283.
|
|
93
|
+
|
|
47
94
|
## 🧠 What Is This?
|
|
48
95
|
|
|
49
96
|
**Open Claude Code** is a ground-up open source rebuild of Anthropic's [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code), informed by [ruDevolution's](https://github.com/ruvnet/rudevolution) AI-powered decompilation of the published npm package.
|
package/package.json
CHANGED
package/src/core/agent-loop.mjs
CHANGED
|
@@ -12,11 +12,22 @@ const MAX_TOOL_RECURSION_DEPTH = 50;
|
|
|
12
12
|
|
|
13
13
|
export function createAgentLoop({ model, tools, permissions, settings, hooks, cascade = null }) {
|
|
14
14
|
const contextManager = new ContextManager(settings.maxContextTokens || 180000);
|
|
15
|
+
for (const key of ['allowedTools', 'disallowedTools']) {
|
|
16
|
+
if (settings[key] != null && (!Array.isArray(settings[key]) ||
|
|
17
|
+
settings[key].some(name => typeof name !== 'string' || !name.trim()))) {
|
|
18
|
+
throw new TypeError(key + ' must be an array of nonempty tool names');
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const allowedNames = new Set(settings.allowedTools || []);
|
|
22
|
+
const deniedNames = new Set(settings.disallowedTools || []);
|
|
23
|
+
const toolAllowed = name => !deniedNames.has(name) &&
|
|
24
|
+
(allowedNames.size === 0 || allowedNames.has(name));
|
|
25
|
+
const availableTools = () => (tools.list?.() || []).filter(tool => toolAllowed(tool.name));
|
|
15
26
|
|
|
16
27
|
// Build system prompt using the new builder
|
|
17
28
|
const promptResult = buildSystemPrompt({
|
|
18
29
|
cwd: process.cwd(),
|
|
19
|
-
tools:
|
|
30
|
+
tools: availableTools(),
|
|
20
31
|
override: settings.systemPromptOverride,
|
|
21
32
|
addDirs: settings.addDirs,
|
|
22
33
|
});
|
|
@@ -113,7 +124,7 @@ export function createAgentLoop({ model, tools, permissions, settings, hooks, ca
|
|
|
113
124
|
try {
|
|
114
125
|
if (settings.stream !== false && provider === 'anthropic') {
|
|
115
126
|
// Streaming mode
|
|
116
|
-
response = await callApiStreaming(provider, activeModel, state,
|
|
127
|
+
response = await callApiStreaming(provider, activeModel, state, availableTools(), settings);
|
|
117
128
|
const collectedContent = [];
|
|
118
129
|
let currentText = '';
|
|
119
130
|
let currentThinking = '';
|
|
@@ -140,7 +151,7 @@ export function createAgentLoop({ model, tools, permissions, settings, hooks, ca
|
|
|
140
151
|
response = response.accumulated;
|
|
141
152
|
} else {
|
|
142
153
|
// Non-streaming mode
|
|
143
|
-
response = await callApi(provider, activeModel, state,
|
|
154
|
+
response = await callApi(provider, activeModel, state, availableTools(), settings);
|
|
144
155
|
}
|
|
145
156
|
} catch (err) {
|
|
146
157
|
recordOptOutcome(false);
|
|
@@ -180,6 +191,14 @@ export function createAgentLoop({ model, tools, permissions, settings, hooks, ca
|
|
|
180
191
|
const toolResults = [];
|
|
181
192
|
|
|
182
193
|
for (const block of toolUseBlocks) {
|
|
194
|
+
// Enforce before hooks and dispatch, even if the model invents an
|
|
195
|
+
// unadvertised call. Deny rules take precedence over allow rules.
|
|
196
|
+
if (!toolAllowed(block.name)) {
|
|
197
|
+
yield { type: 'hookPermissionResult', tool: block.name, allowed: false };
|
|
198
|
+
toolResults.push({ type: 'tool_result', tool_use_id: block.id,
|
|
199
|
+
is_error: true, content: 'Tool blocked by configured allow/deny list' });
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
183
202
|
// Run pre-tool hooks
|
|
184
203
|
if (hooks) {
|
|
185
204
|
const hookResult = await hooks.runPreToolUse(block.name, block.input);
|
package/src/skills/loader.mjs
CHANGED
|
@@ -29,12 +29,13 @@ export class SkillsLoader {
|
|
|
29
29
|
*/
|
|
30
30
|
load(cwd = process.cwd()) {
|
|
31
31
|
const isDefaultCwd = cwd === process.cwd();
|
|
32
|
+
this.skills.clear();
|
|
32
33
|
this.searchPaths = [path.join(cwd, '.claude', 'skills')];
|
|
33
34
|
|
|
34
35
|
if (isDefaultCwd) {
|
|
35
36
|
const globalSkills = path.join(process.env.HOME || '', '.claude', 'skills');
|
|
36
37
|
if (globalSkills !== this.searchPaths[0]) {
|
|
37
|
-
this.searchPaths.
|
|
38
|
+
this.searchPaths.unshift(globalSkills);
|
|
38
39
|
}
|
|
39
40
|
}
|
|
40
41
|
|
|
@@ -49,7 +50,8 @@ export class SkillsLoader {
|
|
|
49
50
|
try {
|
|
50
51
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
51
52
|
for (const entry of entries) {
|
|
52
|
-
|
|
53
|
+
// skills CLI installs directory symlinks by default.
|
|
54
|
+
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
53
55
|
|
|
54
56
|
const skillFile = path.join(dir, entry.name, 'SKILL.md');
|
|
55
57
|
try {
|