Skip to content

Commit ec4a04f

Browse files
committed
update adding claude.md
1 parent 37adb74 commit ec4a04f

11 files changed

Lines changed: 1194 additions & 47 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/bin/sh
2+
if grep -q "caliber" .git/hooks/pre-commit 2>/dev/null; then
3+
exit 0
4+
fi
5+
FLAG="/tmp/caliber-nudge-$(echo "$PWD" | shasum | cut -c1-8)"
6+
find /tmp -maxdepth 1 -name "caliber-nudge-*" -mmin +120 -delete 2>/dev/null
7+
if [ -f "$FLAG" ]; then
8+
exit 0
9+
fi
10+
touch "$FLAG"
11+
printf '{"decision":"block","reason":"Caliber agent config sync is not set up on this machine. Tell the user: This project uses Caliber to keep agent configs in sync — it is not set up yet. Want me to run /setup-caliber? (~30 seconds)"}'

.claude/settings.json

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
{
2+
"hooks": {
3+
"Stop": [
4+
{
5+
"matcher": "",
6+
"hooks": [
7+
{
8+
"type": "command",
9+
"command": ".claude/hooks/caliber-check-sync.sh",
10+
"description": "Caliber: offer setup if not configured"
11+
}
12+
]
13+
}
14+
],
15+
"PostToolUse": [
16+
{
17+
"matcher": "",
18+
"hooks": [
19+
{
20+
"type": "command",
21+
"command": "caliber learn observe",
22+
"description": "Caliber: recording tool usage for session learning"
23+
}
24+
]
25+
}
26+
],
27+
"PostToolUseFailure": [
28+
{
29+
"matcher": "",
30+
"hooks": [
31+
{
32+
"type": "command",
33+
"command": "caliber learn observe --failure",
34+
"description": "Caliber: recording tool failure for session learning"
35+
}
36+
]
37+
}
38+
],
39+
"UserPromptSubmit": [
40+
{
41+
"matcher": "",
42+
"hooks": [
43+
{
44+
"type": "command",
45+
"command": "caliber learn observe --prompt",
46+
"description": "Caliber: recording user prompt for correction detection"
47+
}
48+
]
49+
}
50+
],
51+
"SessionEnd": [
52+
{
53+
"matcher": "",
54+
"hooks": [
55+
{
56+
"type": "command",
57+
"command": "caliber learn finalize --auto",
58+
"description": "Caliber: finalizing session learnings"
59+
}
60+
]
61+
},
62+
{
63+
"matcher": "",
64+
"hooks": [
65+
{
66+
"type": "command",
67+
"command": "caliber refresh --quiet",
68+
"description": "Caliber: auto-refreshing docs based on code changes"
69+
}
70+
]
71+
}
72+
]
73+
},
74+
"permissions": {
75+
"allow": [
76+
"Bash(git *)"
77+
]
78+
}
79+
}
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
---
2+
name: cloudlinux-api
3+
description: Uses the Detain\Cloudlinux\Cloudlinux client (from detain/cloudlinux-licensing) for license operations — isLicensed(), license(), remove(), licenseList(), kcareList(), imunifyList(). Use when user says 'check license', 'activate IP', 'remove license', 'list licenses', or adds bin/ scripts. Do NOT use for Plugin hook wiring or EventDispatcher registration.
4+
---
5+
# CloudLinux API
6+
7+
## Critical
8+
9+
- **Always** instantiate with `new Cloudlinux(CLOUDLINUX_LOGIN, CLOUDLINUX_KEY)` — never hard-code credentials.
10+
- **Always** `use Detain\Cloudlinux\Cloudlinux;` at the top of the file.
11+
- **Never** call the XML-RPC endpoint directly from new code — use the `Cloudlinux` client methods.
12+
- `isLicensed($ip, true)` returns an **array** of active type IDs; `isLicensed($ip)` (no second arg) returns bool. Pass `true` whenever you need to inspect which types are active.
13+
- `remove()` without a type ID removes **all** licenses on that IP. Always pass `$type` when you only want to remove one tier.
14+
- After every mutating call (`license()`, `remove()`), check `$response === false` for failure — the client returns `false`, not an exception, on API error.
15+
- Log every API call with `myadmin_log()` before and after; log with `request_log()` after mutation calls.
16+
17+
## Instructions
18+
19+
### Step 1 — Bootstrap (bin/ scripts)
20+
21+
For standalone CLI scripts under `bin/`, load the environment with:
22+
23+
```php
24+
#!/usr/bin/env php
25+
<?php
26+
use Detain\Cloudlinux\Cloudlinux;
27+
28+
require_once __DIR__.'/../../../../include/functions.inc.php';
29+
```
30+
31+
Verify `CLOUDLINUX_LOGIN` and `CLOUDLINUX_KEY` are defined constants after the require before proceeding.
32+
33+
### Step 2 — Bootstrap (src/ helpers)
34+
35+
For helper functions in `src/cloudlinux.inc.php`, use:
36+
37+
```php
38+
<?php
39+
use Detain\Cloudlinux\Cloudlinux;
40+
```
41+
42+
No require needed — Composer autoload handles it at runtime via the plugin loader.
43+
44+
### Step 3 — Instantiate the client
45+
46+
Always create a fresh client per operation:
47+
48+
```php
49+
$cl = new Cloudlinux(CLOUDLINUX_LOGIN, CLOUDLINUX_KEY);
50+
```
51+
52+
### Step 4 — Check if an IP is licensed
53+
54+
```php
55+
$cl = new Cloudlinux(CLOUDLINUX_LOGIN, CLOUDLINUX_KEY);
56+
$response = $cl->isLicensed($ipAddress, true); // true = return array of active type IDs
57+
// $response is an array like [0 => 1, 1 => 16] or false/empty when unlicensed
58+
if (is_array($response) && in_array($typeId, array_values($response))) {
59+
// already has this type licensed
60+
}
61+
```
62+
63+
### Step 5 — Activate a license
64+
65+
```php
66+
myadmin_log('licenses', 'info', 'Activating license for '.$ipAddress, __LINE__, __FILE__);
67+
$cl = new Cloudlinux(CLOUDLINUX_LOGIN, CLOUDLINUX_KEY);
68+
$response = $cl->license($ipAddress, $typeId);
69+
request_log('licenses', $custid, __FUNCTION__, 'cloudlinux', 'license', [$ipAddress, $typeId], $response, $serviceId);
70+
myadmin_log('licenses', 'info', 'Response: '.json_encode($response), __LINE__, __FILE__);
71+
if ($response === false) {
72+
// handle error
73+
}
74+
```
75+
76+
Type IDs: `1`=CloudLinux, `16`=KernelCare, `40`=ImunityAV+, `41`=Imunify360 single, `42`=up to 30 users, `43`=up to 250 users, `49`=unlimited.
77+
78+
### Step 6 — Remove a license
79+
80+
```php
81+
myadmin_log('cloudlinux', 'info', "Deactivate CloudLinux({$ipAddress}, {$type}) called", __LINE__, __FILE__);
82+
$cl = new Cloudlinux(CLOUDLINUX_LOGIN, CLOUDLINUX_KEY);
83+
if ($type == false) {
84+
$response = $cl->remove($ipAddress); // removes ALL types on this IP
85+
} else {
86+
$response = $cl->remove($ipAddress, $type); // removes only the given type
87+
}
88+
if (!isset($response['success']) || $response['success'] !== true) {
89+
// send failure email (see Common Issues below)
90+
}
91+
request_log('licenses', false, __FUNCTION__, 'cloudlinux', 'removeLicense', [$ipAddress, $type], $response);
92+
myadmin_log('cloudlinux', 'info', "Deactivate response: ".json_encode($response), __LINE__, __FILE__);
93+
```
94+
95+
### Step 7 — List all licenses
96+
97+
Use the helper function already registered in `src/cloudlinux.inc.php`:
98+
99+
```php
100+
function_requirements('get_cloudlinux_licenses');
101+
$licenses = get_cloudlinux_licenses();
102+
print_r($licenses);
103+
```
104+
105+
Or call directly:
106+
107+
```php
108+
$cl = new Cloudlinux(CLOUDLINUX_LOGIN, CLOUDLINUX_KEY);
109+
$licenses = $cl->licenseList(); // all CloudLinux licenses
110+
$kcare = $cl->kcareList(); // KernelCare only
111+
$imunify = $cl->imunifyList(); // Imunify360 only
112+
```
113+
114+
### Step 8 — Failure email on deactivation error
115+
116+
Mirror the pattern from `src/cloudlinux.inc.php:deactivate_cloudlinux()`:
117+
118+
```php
119+
if (!isset($response['success']) || $response['success'] !== true) {
120+
$bodyRows = [];
121+
$bodyRows[] = 'License IP: '.$ipAddress.' unable to deactivate.';
122+
$bodyRows[] = 'Deactivation Response: .'.json_encode($response);
123+
$subject = 'Cloudlinux License Deactivation Issue IP: '.$ipAddress;
124+
$smartyE = new TFSmarty();
125+
$smartyE->assign('h1', 'Cloudlinux License Deactivation');
126+
$smartyE->assign('body_rows', $bodyRows);
127+
$msg = $smartyE->fetch('email/client/client_email.tpl');
128+
(new \MyAdmin\Mail())->multiMail($subject, $msg, false, 'client/client_email.tpl');
129+
}
130+
```
131+
132+
## Examples
133+
134+
**User says:** "Add a bin script to activate a CloudLinux license for a given IP"
135+
136+
**Actions taken:**
137+
1. Create `bin/activate_cloudlinux.php`
138+
2. Add shebang + `use` + require bootstrap
139+
3. Instantiate client with constants
140+
4. Call `isLicensed()` to check first, then `license()` if needed
141+
5. Print result
142+
143+
**Result:**
144+
145+
```php
146+
#!/usr/bin/env php
147+
<?php
148+
use Detain\Cloudlinux\Cloudlinux;
149+
150+
require_once __DIR__.'/../../../../include/functions.inc.php';
151+
152+
$ipAddress = $_SERVER['argv'][1];
153+
$typeId = isset($_SERVER['argv'][2]) ? (int)$_SERVER['argv'][2] : 1;
154+
155+
$cl = new Cloudlinux(CLOUDLINUX_LOGIN, CLOUDLINUX_KEY);
156+
$existing = $cl->isLicensed($ipAddress, true);
157+
if (is_array($existing) && in_array($typeId, array_values($existing))) {
158+
echo "IP {$ipAddress} already has type {$typeId} licensed.\n";
159+
exit(0);
160+
}
161+
$response = $cl->license($ipAddress, $typeId);
162+
if ($response === false) {
163+
echo "Error licensing {$ipAddress}.\n";
164+
exit(1);
165+
}
166+
print_r($response);
167+
echo "Success licensing {$ipAddress} type {$typeId}.\n";
168+
```
169+
170+
---
171+
172+
**User says:** "Write a helper to deactivate KernelCare for an IP"
173+
174+
**Result:** Add to `src/cloudlinux.inc.php`:
175+
176+
```php
177+
function deactivate_kcare($ipAddress)
178+
{
179+
return deactivate_cloudlinux($ipAddress, 16);
180+
}
181+
```
182+
183+
(delegates to the existing `deactivate_cloudlinux()` with type `16`.)
184+
185+
## Common Issues
186+
187+
**`Call to undefined constant CLOUDLINUX_LOGIN`**
188+
The `include/functions.inc.php` bootstrap was not loaded, or it was loaded but the settings have not been initialized. In bin/ scripts, verify the require path resolves: `realpath(__DIR__.'/../../../../include/functions.inc.php')` should exist. In src/ helpers called via plugin loader, `function_requirements()` must have been called first.
189+
190+
**`$response === false` on `license()` or `remove()`**
191+
The XML-RPC call failed (network, bad credentials, or IP already in that state). Check `CLOUDLINUX_LOGIN` and `CLOUDLINUX_KEY` constants are correct. Run `php bin/cloudlinux_check.php <ip>` to verify connectivity. The client does not throw — it returns `false`.
192+
193+
**`isLicensed()` returns `false` but IP is definitely licensed**
194+
You called `isLicensed($ip)` without the `true` second argument — it returns bool. Use `isLicensed($ip, true)` to get the array of active type IDs.
195+
196+
**`in_array($typeId, $response)` always false even though licensed**
197+
Use `in_array($typeId, array_values($response))` — the response array may have non-sequential keys.
198+
199+
**`Class 'TFSmarty' not found` in deactivation error email**
200+
The function is being called outside the MyAdmin request context. `TFSmarty` is only available after the full MyAdmin bootstrap. In CLI/bin scripts, skip the email and only log with `myadmin_log()`.
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
---
2+
name: find-skills
3+
description: Discovers and installs community skills from the public registry. Use when the user mentions a technology, framework, or task that could benefit from specialized skills not yet installed, asks 'how do I do X', 'find a skill for X', or starts work in a new technology area. Proactively suggest when the user's task involves tools or frameworks without existing skills.
4+
---
5+
6+
# Find Skills
7+
8+
Search the public skill registry for community-contributed skills
9+
relevant to the user's current task and install them into this project.
10+
11+
## Instructions
12+
13+
1. Identify the key technologies, frameworks, or task types from the
14+
user's request that might have community skills available
15+
2. Ask the user: "Would you like me to search for community skills
16+
for [identified technologies]?"
17+
3. If the user agrees, run:
18+
```bash
19+
caliber skills --query "<relevant terms>"
20+
```
21+
This outputs the top 5 matching skills with scores and descriptions.
22+
4. Present the results to the user and ask which ones to install
23+
5. Install the selected skills:
24+
```bash
25+
caliber skills --install <slug1>,<slug2>
26+
```
27+
6. Read the installed SKILL.md files to load them into your current
28+
context so you can use them immediately in this session
29+
7. Summarize what was installed and continue with the user's task
30+
31+
## Examples
32+
33+
User: "let's build a web app using React"
34+
-> "I notice you want to work with React. Would you like me to search
35+
for community skills that could help with React development?"
36+
-> If yes: run `caliber skills --query "react frontend"`
37+
-> Show the user the results, ask which to install
38+
-> Run `caliber skills --install <selected-slugs>`
39+
-> Read the installed files and continue
40+
41+
User: "help me set up Docker for this project"
42+
-> "Would you like me to search for Docker-related skills?"
43+
-> If yes: run `caliber skills --query "docker deployment"`
44+
45+
User: "I need to write tests for this Python ML pipeline"
46+
-> "Would you like me to find skills for Python ML testing?"
47+
-> If yes: run `caliber skills --query "python machine-learning testing"`
48+
49+
## When NOT to trigger
50+
51+
- The user is working within an already well-configured area
52+
- You already suggested skills for this technology in this session
53+
- The user is in the middle of urgent debugging or time-sensitive work
54+
- The technology is too generic (e.g. just "code" or "programming")

0 commit comments

Comments
 (0)