@caliber-ai/cli 0.21.1 → 0.23.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/dist/bin.js +460 -65
- package/dist/bin.js.map +1 -1
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -30,11 +30,15 @@ __export(constants_exports, {
|
|
|
30
30
|
CALIBER_DIR: () => CALIBER_DIR,
|
|
31
31
|
CLI_CALLBACK_PORT: () => CLI_CALLBACK_PORT,
|
|
32
32
|
FRONTEND_URL: () => FRONTEND_URL,
|
|
33
|
+
LEARNING_DIR: () => LEARNING_DIR,
|
|
34
|
+
LEARNING_MAX_EVENTS: () => LEARNING_MAX_EVENTS,
|
|
35
|
+
LEARNING_SESSION_FILE: () => LEARNING_SESSION_FILE,
|
|
36
|
+
LEARNING_STATE_FILE: () => LEARNING_STATE_FILE,
|
|
33
37
|
MANIFEST_FILE: () => MANIFEST_FILE
|
|
34
38
|
});
|
|
35
39
|
import path2 from "path";
|
|
36
40
|
import os2 from "os";
|
|
37
|
-
var API_URL, FRONTEND_URL, CLI_CALLBACK_PORT, AUTH_DIR, AUTH_FILE, CALIBER_DIR, MANIFEST_FILE, BACKUPS_DIR;
|
|
41
|
+
var API_URL, FRONTEND_URL, CLI_CALLBACK_PORT, AUTH_DIR, AUTH_FILE, CALIBER_DIR, MANIFEST_FILE, BACKUPS_DIR, LEARNING_DIR, LEARNING_SESSION_FILE, LEARNING_STATE_FILE, LEARNING_MAX_EVENTS;
|
|
38
42
|
var init_constants = __esm({
|
|
39
43
|
"src/constants.ts"() {
|
|
40
44
|
"use strict";
|
|
@@ -46,6 +50,10 @@ var init_constants = __esm({
|
|
|
46
50
|
CALIBER_DIR = ".caliber";
|
|
47
51
|
MANIFEST_FILE = path2.join(CALIBER_DIR, "manifest.json");
|
|
48
52
|
BACKUPS_DIR = path2.join(CALIBER_DIR, "backups");
|
|
53
|
+
LEARNING_DIR = path2.join(CALIBER_DIR, "learning");
|
|
54
|
+
LEARNING_SESSION_FILE = "current-session.jsonl";
|
|
55
|
+
LEARNING_STATE_FILE = "state.json";
|
|
56
|
+
LEARNING_MAX_EVENTS = 500;
|
|
49
57
|
}
|
|
50
58
|
});
|
|
51
59
|
|
|
@@ -75,8 +83,8 @@ if (dsn) {
|
|
|
75
83
|
|
|
76
84
|
// src/cli.ts
|
|
77
85
|
import { Command } from "commander";
|
|
78
|
-
import
|
|
79
|
-
import
|
|
86
|
+
import fs25 from "fs";
|
|
87
|
+
import path22 from "path";
|
|
80
88
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
81
89
|
|
|
82
90
|
// src/commands/init.ts
|
|
@@ -583,11 +591,18 @@ function readExistingConfigs(dir) {
|
|
|
583
591
|
const skillsDir = path7.join(dir, ".claude", "skills");
|
|
584
592
|
if (fs6.existsSync(skillsDir)) {
|
|
585
593
|
try {
|
|
586
|
-
const
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
594
|
+
const entries = fs6.readdirSync(skillsDir);
|
|
595
|
+
const skills = [];
|
|
596
|
+
for (const entry of entries) {
|
|
597
|
+
const entryPath = path7.join(skillsDir, entry);
|
|
598
|
+
const skillMdPath = path7.join(entryPath, "SKILL.md");
|
|
599
|
+
if (fs6.statSync(entryPath).isDirectory() && fs6.existsSync(skillMdPath)) {
|
|
600
|
+
skills.push({ filename: `${entry}/SKILL.md`, content: fs6.readFileSync(skillMdPath, "utf-8") });
|
|
601
|
+
} else if (entry.endsWith(".md")) {
|
|
602
|
+
skills.push({ filename: entry, content: fs6.readFileSync(entryPath, "utf-8") });
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
if (skills.length > 0) configs.claudeSkills = skills;
|
|
591
606
|
} catch {
|
|
592
607
|
}
|
|
593
608
|
}
|
|
@@ -612,10 +627,10 @@ function readExistingConfigs(dir) {
|
|
|
612
627
|
const slugs = fs6.readdirSync(cursorSkillsDir).filter((f) => {
|
|
613
628
|
return fs6.statSync(path7.join(cursorSkillsDir, f)).isDirectory();
|
|
614
629
|
});
|
|
615
|
-
configs.cursorSkills = slugs.filter((slug) => fs6.existsSync(path7.join(cursorSkillsDir, slug, "SKILL.md"))).map((
|
|
616
|
-
|
|
630
|
+
configs.cursorSkills = slugs.filter((slug) => fs6.existsSync(path7.join(cursorSkillsDir, slug, "SKILL.md"))).map((name) => ({
|
|
631
|
+
name,
|
|
617
632
|
filename: "SKILL.md",
|
|
618
|
-
content: fs6.readFileSync(path7.join(cursorSkillsDir,
|
|
633
|
+
content: fs6.readFileSync(path7.join(cursorSkillsDir, name, "SKILL.md"), "utf-8")
|
|
619
634
|
}));
|
|
620
635
|
} catch {
|
|
621
636
|
}
|
|
@@ -950,9 +965,9 @@ async function getValidToken() {
|
|
|
950
965
|
}
|
|
951
966
|
return refreshed;
|
|
952
967
|
}
|
|
953
|
-
async function apiRequest(
|
|
968
|
+
async function apiRequest(path24, options = {}) {
|
|
954
969
|
let token = await getValidToken();
|
|
955
|
-
let resp = await fetch(`${API_URL}${
|
|
970
|
+
let resp = await fetch(`${API_URL}${path24}`, {
|
|
956
971
|
method: options.method || "GET",
|
|
957
972
|
headers: {
|
|
958
973
|
"Content-Type": "application/json",
|
|
@@ -966,7 +981,7 @@ async function apiRequest(path21, options = {}) {
|
|
|
966
981
|
throw new Error("Session expired. Run `caliber login` to re-authenticate.");
|
|
967
982
|
}
|
|
968
983
|
token = refreshed;
|
|
969
|
-
resp = await fetch(`${API_URL}${
|
|
984
|
+
resp = await fetch(`${API_URL}${path24}`, {
|
|
970
985
|
method: options.method || "GET",
|
|
971
986
|
headers: {
|
|
972
987
|
"Content-Type": "application/json",
|
|
@@ -982,9 +997,9 @@ async function apiRequest(path21, options = {}) {
|
|
|
982
997
|
const json = await resp.json();
|
|
983
998
|
return json.data;
|
|
984
999
|
}
|
|
985
|
-
async function apiStream(
|
|
1000
|
+
async function apiStream(path24, body, onChunk, onComplete, onError, onStatus) {
|
|
986
1001
|
let token = await getValidToken();
|
|
987
|
-
let resp = await fetch(`${API_URL}${
|
|
1002
|
+
let resp = await fetch(`${API_URL}${path24}`, {
|
|
988
1003
|
method: "POST",
|
|
989
1004
|
headers: {
|
|
990
1005
|
"Content-Type": "application/json",
|
|
@@ -998,7 +1013,7 @@ async function apiStream(path21, body, onChunk, onComplete, onError, onStatus) {
|
|
|
998
1013
|
throw new Error("Session expired. Run `caliber login` to re-authenticate.");
|
|
999
1014
|
}
|
|
1000
1015
|
token = refreshed;
|
|
1001
|
-
resp = await fetch(`${API_URL}${
|
|
1016
|
+
resp = await fetch(`${API_URL}${path24}`, {
|
|
1002
1017
|
method: "POST",
|
|
1003
1018
|
headers: {
|
|
1004
1019
|
"Content-Type": "application/json",
|
|
@@ -1126,12 +1141,18 @@ function writeClaudeConfig(config) {
|
|
|
1126
1141
|
fs9.writeFileSync("CLAUDE.md", config.claudeMd);
|
|
1127
1142
|
written.push("CLAUDE.md");
|
|
1128
1143
|
if (config.skills?.length) {
|
|
1129
|
-
const skillsDir = path10.join(".claude", "skills");
|
|
1130
|
-
if (!fs9.existsSync(skillsDir)) fs9.mkdirSync(skillsDir, { recursive: true });
|
|
1131
1144
|
for (const skill of config.skills) {
|
|
1132
|
-
const
|
|
1133
|
-
|
|
1134
|
-
|
|
1145
|
+
const skillDir = path10.join(".claude", "skills", skill.name);
|
|
1146
|
+
if (!fs9.existsSync(skillDir)) fs9.mkdirSync(skillDir, { recursive: true });
|
|
1147
|
+
const skillPath = path10.join(skillDir, "SKILL.md");
|
|
1148
|
+
const frontmatter = [
|
|
1149
|
+
"---",
|
|
1150
|
+
`name: ${skill.name}`,
|
|
1151
|
+
`description: ${skill.description}`,
|
|
1152
|
+
"---",
|
|
1153
|
+
""
|
|
1154
|
+
].join("\n");
|
|
1155
|
+
fs9.writeFileSync(skillPath, frontmatter + skill.content);
|
|
1135
1156
|
written.push(skillPath);
|
|
1136
1157
|
}
|
|
1137
1158
|
}
|
|
@@ -1171,10 +1192,17 @@ function writeCursorConfig(config) {
|
|
|
1171
1192
|
}
|
|
1172
1193
|
if (config.skills?.length) {
|
|
1173
1194
|
for (const skill of config.skills) {
|
|
1174
|
-
const skillDir = path11.join(".cursor", "skills", skill.
|
|
1195
|
+
const skillDir = path11.join(".cursor", "skills", skill.name);
|
|
1175
1196
|
if (!fs10.existsSync(skillDir)) fs10.mkdirSync(skillDir, { recursive: true });
|
|
1176
1197
|
const skillPath = path11.join(skillDir, "SKILL.md");
|
|
1177
|
-
|
|
1198
|
+
const frontmatter = [
|
|
1199
|
+
"---",
|
|
1200
|
+
`name: ${skill.name}`,
|
|
1201
|
+
`description: ${skill.description}`,
|
|
1202
|
+
"---",
|
|
1203
|
+
""
|
|
1204
|
+
].join("\n");
|
|
1205
|
+
fs10.writeFileSync(skillPath, frontmatter + skill.content);
|
|
1178
1206
|
written.push(skillPath);
|
|
1179
1207
|
}
|
|
1180
1208
|
}
|
|
@@ -1186,9 +1214,7 @@ function writeCursorConfig(config) {
|
|
|
1186
1214
|
try {
|
|
1187
1215
|
if (fs10.existsSync(mcpPath)) {
|
|
1188
1216
|
const existing = JSON.parse(fs10.readFileSync(mcpPath, "utf-8"));
|
|
1189
|
-
if (existing.mcpServers)
|
|
1190
|
-
existingServers = existing.mcpServers;
|
|
1191
|
-
}
|
|
1217
|
+
if (existing.mcpServers) existingServers = existing.mcpServers;
|
|
1192
1218
|
}
|
|
1193
1219
|
} catch {
|
|
1194
1220
|
}
|
|
@@ -1681,7 +1707,7 @@ async function initCommand(options) {
|
|
|
1681
1707
|
}
|
|
1682
1708
|
if (Array.isArray(ec.cursorSkills)) {
|
|
1683
1709
|
for (const skill of ec.cursorSkills) {
|
|
1684
|
-
localConfigs.push(`.cursor/skills/${skill.
|
|
1710
|
+
localConfigs.push(`.cursor/skills/${skill.name}/SKILL.md`);
|
|
1685
1711
|
}
|
|
1686
1712
|
}
|
|
1687
1713
|
let existingSetup = null;
|
|
@@ -2050,11 +2076,11 @@ function printSetupSummary(setup) {
|
|
|
2050
2076
|
const skills = claude.skills;
|
|
2051
2077
|
if (Array.isArray(skills) && skills.length > 0) {
|
|
2052
2078
|
for (const skill of skills) {
|
|
2053
|
-
const skillPath = `.claude/skills/${skill.name
|
|
2079
|
+
const skillPath = `.claude/skills/${skill.name}/SKILL.md`;
|
|
2054
2080
|
const icon = fs17.existsSync(skillPath) ? chalk3.yellow("~") : chalk3.green("+");
|
|
2055
2081
|
const desc = getDescription(skillPath);
|
|
2056
2082
|
console.log(` ${icon} ${chalk3.bold(skillPath)}`);
|
|
2057
|
-
console.log(chalk3.dim(` ${desc ||
|
|
2083
|
+
console.log(chalk3.dim(` ${desc || skill.description || skill.name}`));
|
|
2058
2084
|
console.log("");
|
|
2059
2085
|
}
|
|
2060
2086
|
}
|
|
@@ -2070,16 +2096,11 @@ function printSetupSummary(setup) {
|
|
|
2070
2096
|
const cursorSkills = cursor.skills;
|
|
2071
2097
|
if (Array.isArray(cursorSkills) && cursorSkills.length > 0) {
|
|
2072
2098
|
for (const skill of cursorSkills) {
|
|
2073
|
-
const skillPath = `.cursor/skills/${skill.
|
|
2099
|
+
const skillPath = `.cursor/skills/${skill.name}/SKILL.md`;
|
|
2074
2100
|
const icon = fs17.existsSync(skillPath) ? chalk3.yellow("~") : chalk3.green("+");
|
|
2075
2101
|
const desc = getDescription(skillPath);
|
|
2076
2102
|
console.log(` ${icon} ${chalk3.bold(skillPath)}`);
|
|
2077
|
-
|
|
2078
|
-
console.log(chalk3.dim(` ${desc}`));
|
|
2079
|
-
} else {
|
|
2080
|
-
const firstLine = skill.content.split("\n").filter((l) => l.trim() && !l.trim().startsWith("#") && !l.trim().startsWith("---"))[0];
|
|
2081
|
-
if (firstLine) console.log(chalk3.dim(` ${firstLine.trim().slice(0, 80)}`));
|
|
2082
|
-
}
|
|
2103
|
+
console.log(chalk3.dim(` ${desc || skill.description || skill.name}`));
|
|
2083
2104
|
console.log("");
|
|
2084
2105
|
}
|
|
2085
2106
|
}
|
|
@@ -2110,9 +2131,14 @@ function printSetupSummary(setup) {
|
|
|
2110
2131
|
console.log(` ${chalk3.green("+")} ${chalk3.dim("new")} ${chalk3.yellow("~")} ${chalk3.dim("modified")} ${chalk3.red("-")} ${chalk3.dim("removed")}`);
|
|
2111
2132
|
console.log("");
|
|
2112
2133
|
}
|
|
2113
|
-
function
|
|
2114
|
-
const
|
|
2115
|
-
|
|
2134
|
+
function buildSkillContent(skill) {
|
|
2135
|
+
const frontmatter = `---
|
|
2136
|
+
name: ${skill.name}
|
|
2137
|
+
description: ${skill.description}
|
|
2138
|
+
---
|
|
2139
|
+
|
|
2140
|
+
`;
|
|
2141
|
+
return frontmatter + skill.content;
|
|
2116
2142
|
}
|
|
2117
2143
|
function collectSetupFiles(setup) {
|
|
2118
2144
|
const files = [];
|
|
@@ -2123,8 +2149,7 @@ function collectSetupFiles(setup) {
|
|
|
2123
2149
|
const skills = claude.skills;
|
|
2124
2150
|
if (Array.isArray(skills)) {
|
|
2125
2151
|
for (const skill of skills) {
|
|
2126
|
-
|
|
2127
|
-
files.push({ path: skillPath, content: skill.content });
|
|
2152
|
+
files.push({ path: `.claude/skills/${skill.name}/SKILL.md`, content: buildSkillContent(skill) });
|
|
2128
2153
|
}
|
|
2129
2154
|
}
|
|
2130
2155
|
}
|
|
@@ -2133,7 +2158,7 @@ function collectSetupFiles(setup) {
|
|
|
2133
2158
|
const cursorSkills = cursor.skills;
|
|
2134
2159
|
if (Array.isArray(cursorSkills)) {
|
|
2135
2160
|
for (const skill of cursorSkills) {
|
|
2136
|
-
files.push({ path: `.cursor/skills/${skill.
|
|
2161
|
+
files.push({ path: `.cursor/skills/${skill.name}/SKILL.md`, content: buildSkillContent(skill) });
|
|
2137
2162
|
}
|
|
2138
2163
|
}
|
|
2139
2164
|
const rules = cursor.rules;
|
|
@@ -2255,13 +2280,13 @@ function scanLocalState(dir) {
|
|
|
2255
2280
|
const cursorSkillsDir = path16.join(dir, ".cursor", "skills");
|
|
2256
2281
|
if (fs18.existsSync(cursorSkillsDir)) {
|
|
2257
2282
|
try {
|
|
2258
|
-
for (const
|
|
2259
|
-
const skillFile = path16.join(cursorSkillsDir,
|
|
2283
|
+
for (const name of fs18.readdirSync(cursorSkillsDir)) {
|
|
2284
|
+
const skillFile = path16.join(cursorSkillsDir, name, "SKILL.md");
|
|
2260
2285
|
if (fs18.existsSync(skillFile)) {
|
|
2261
2286
|
items.push({
|
|
2262
2287
|
type: "skill",
|
|
2263
2288
|
platform: "cursor",
|
|
2264
|
-
name: `${
|
|
2289
|
+
name: `${name}/SKILL.md`,
|
|
2265
2290
|
contentHash: hashFile(skillFile),
|
|
2266
2291
|
path: skillFile
|
|
2267
2292
|
});
|
|
@@ -2499,7 +2524,7 @@ function getSkillPath(platform, slug) {
|
|
|
2499
2524
|
if (platform === "cursor") {
|
|
2500
2525
|
return join(".cursor", "skills", slug, "SKILL.md");
|
|
2501
2526
|
}
|
|
2502
|
-
return join(".claude", "skills",
|
|
2527
|
+
return join(".claude", "skills", slug, "SKILL.md");
|
|
2503
2528
|
}
|
|
2504
2529
|
async function recommendCommand(options) {
|
|
2505
2530
|
const auth2 = getStoredAuth();
|
|
@@ -3354,10 +3379,374 @@ async function reviewCommand(message, options) {
|
|
|
3354
3379
|
await submitReview({ rating, bestPart, biggestGap, wouldRecommend });
|
|
3355
3380
|
}
|
|
3356
3381
|
|
|
3382
|
+
// src/commands/learn.ts
|
|
3383
|
+
import chalk14 from "chalk";
|
|
3384
|
+
|
|
3385
|
+
// src/learner/stdin.ts
|
|
3386
|
+
var STDIN_TIMEOUT_MS = 5e3;
|
|
3387
|
+
function readStdin() {
|
|
3388
|
+
return new Promise((resolve2, reject) => {
|
|
3389
|
+
if (process.stdin.isTTY) {
|
|
3390
|
+
resolve2("");
|
|
3391
|
+
return;
|
|
3392
|
+
}
|
|
3393
|
+
const chunks = [];
|
|
3394
|
+
const timer = setTimeout(() => {
|
|
3395
|
+
process.stdin.removeAllListeners();
|
|
3396
|
+
process.stdin.destroy();
|
|
3397
|
+
resolve2(Buffer.concat(chunks).toString("utf-8"));
|
|
3398
|
+
}, STDIN_TIMEOUT_MS);
|
|
3399
|
+
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
3400
|
+
process.stdin.on("end", () => {
|
|
3401
|
+
clearTimeout(timer);
|
|
3402
|
+
resolve2(Buffer.concat(chunks).toString("utf-8"));
|
|
3403
|
+
});
|
|
3404
|
+
process.stdin.on("error", (err) => {
|
|
3405
|
+
clearTimeout(timer);
|
|
3406
|
+
reject(err);
|
|
3407
|
+
});
|
|
3408
|
+
process.stdin.resume();
|
|
3409
|
+
});
|
|
3410
|
+
}
|
|
3411
|
+
|
|
3412
|
+
// src/learner/storage.ts
|
|
3413
|
+
init_constants();
|
|
3414
|
+
import fs22 from "fs";
|
|
3415
|
+
import path19 from "path";
|
|
3416
|
+
var MAX_RESPONSE_LENGTH = 2e3;
|
|
3417
|
+
var DEFAULT_STATE = {
|
|
3418
|
+
sessionId: null,
|
|
3419
|
+
eventCount: 0,
|
|
3420
|
+
lastAnalysisTimestamp: null
|
|
3421
|
+
};
|
|
3422
|
+
function ensureLearningDir() {
|
|
3423
|
+
if (!fs22.existsSync(LEARNING_DIR)) {
|
|
3424
|
+
fs22.mkdirSync(LEARNING_DIR, { recursive: true });
|
|
3425
|
+
}
|
|
3426
|
+
}
|
|
3427
|
+
function sessionFilePath() {
|
|
3428
|
+
return path19.join(LEARNING_DIR, LEARNING_SESSION_FILE);
|
|
3429
|
+
}
|
|
3430
|
+
function stateFilePath() {
|
|
3431
|
+
return path19.join(LEARNING_DIR, LEARNING_STATE_FILE);
|
|
3432
|
+
}
|
|
3433
|
+
function truncateResponse(response) {
|
|
3434
|
+
const str = JSON.stringify(response);
|
|
3435
|
+
if (str.length <= MAX_RESPONSE_LENGTH) return response;
|
|
3436
|
+
return { _truncated: str.slice(0, MAX_RESPONSE_LENGTH) };
|
|
3437
|
+
}
|
|
3438
|
+
function appendEvent(event) {
|
|
3439
|
+
ensureLearningDir();
|
|
3440
|
+
const truncated = { ...event, tool_response: truncateResponse(event.tool_response) };
|
|
3441
|
+
const filePath = sessionFilePath();
|
|
3442
|
+
fs22.appendFileSync(filePath, JSON.stringify(truncated) + "\n");
|
|
3443
|
+
const count = getEventCount();
|
|
3444
|
+
if (count > LEARNING_MAX_EVENTS) {
|
|
3445
|
+
const lines = fs22.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
3446
|
+
const kept = lines.slice(lines.length - LEARNING_MAX_EVENTS);
|
|
3447
|
+
fs22.writeFileSync(filePath, kept.join("\n") + "\n");
|
|
3448
|
+
}
|
|
3449
|
+
}
|
|
3450
|
+
function readAllEvents() {
|
|
3451
|
+
const filePath = sessionFilePath();
|
|
3452
|
+
if (!fs22.existsSync(filePath)) return [];
|
|
3453
|
+
const lines = fs22.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
3454
|
+
return lines.map((line) => JSON.parse(line));
|
|
3455
|
+
}
|
|
3456
|
+
function getEventCount() {
|
|
3457
|
+
const filePath = sessionFilePath();
|
|
3458
|
+
if (!fs22.existsSync(filePath)) return 0;
|
|
3459
|
+
const content = fs22.readFileSync(filePath, "utf-8");
|
|
3460
|
+
return content.split("\n").filter(Boolean).length;
|
|
3461
|
+
}
|
|
3462
|
+
function clearSession() {
|
|
3463
|
+
const filePath = sessionFilePath();
|
|
3464
|
+
if (fs22.existsSync(filePath)) fs22.unlinkSync(filePath);
|
|
3465
|
+
}
|
|
3466
|
+
function readState2() {
|
|
3467
|
+
const filePath = stateFilePath();
|
|
3468
|
+
if (!fs22.existsSync(filePath)) return { ...DEFAULT_STATE };
|
|
3469
|
+
try {
|
|
3470
|
+
return JSON.parse(fs22.readFileSync(filePath, "utf-8"));
|
|
3471
|
+
} catch {
|
|
3472
|
+
return { ...DEFAULT_STATE };
|
|
3473
|
+
}
|
|
3474
|
+
}
|
|
3475
|
+
function writeState2(state) {
|
|
3476
|
+
ensureLearningDir();
|
|
3477
|
+
fs22.writeFileSync(stateFilePath(), JSON.stringify(state, null, 2));
|
|
3478
|
+
}
|
|
3479
|
+
function resetState() {
|
|
3480
|
+
writeState2({ ...DEFAULT_STATE });
|
|
3481
|
+
}
|
|
3482
|
+
|
|
3483
|
+
// src/learner/writer.ts
|
|
3484
|
+
import fs23 from "fs";
|
|
3485
|
+
import path20 from "path";
|
|
3486
|
+
var LEARNED_START = "<!-- caliber:learned -->";
|
|
3487
|
+
var LEARNED_END = "<!-- /caliber:learned -->";
|
|
3488
|
+
function writeLearnedContent(update) {
|
|
3489
|
+
const written = [];
|
|
3490
|
+
if (update.claudeMdLearnedSection) {
|
|
3491
|
+
writeLearnedSection(update.claudeMdLearnedSection);
|
|
3492
|
+
written.push("CLAUDE.md");
|
|
3493
|
+
}
|
|
3494
|
+
if (update.skills?.length) {
|
|
3495
|
+
for (const skill of update.skills) {
|
|
3496
|
+
const skillPath = writeLearnedSkill(skill);
|
|
3497
|
+
written.push(skillPath);
|
|
3498
|
+
}
|
|
3499
|
+
}
|
|
3500
|
+
return written;
|
|
3501
|
+
}
|
|
3502
|
+
function writeLearnedSection(content) {
|
|
3503
|
+
const claudeMdPath = "CLAUDE.md";
|
|
3504
|
+
let existing = "";
|
|
3505
|
+
if (fs23.existsSync(claudeMdPath)) {
|
|
3506
|
+
existing = fs23.readFileSync(claudeMdPath, "utf-8");
|
|
3507
|
+
}
|
|
3508
|
+
const section = `${LEARNED_START}
|
|
3509
|
+
${content}
|
|
3510
|
+
${LEARNED_END}`;
|
|
3511
|
+
const startIdx = existing.indexOf(LEARNED_START);
|
|
3512
|
+
const endIdx = existing.indexOf(LEARNED_END);
|
|
3513
|
+
let updated;
|
|
3514
|
+
if (startIdx !== -1 && endIdx !== -1) {
|
|
3515
|
+
updated = existing.slice(0, startIdx) + section + existing.slice(endIdx + LEARNED_END.length);
|
|
3516
|
+
} else {
|
|
3517
|
+
const separator = existing.endsWith("\n") || existing === "" ? "" : "\n";
|
|
3518
|
+
updated = existing + separator + "\n" + section + "\n";
|
|
3519
|
+
}
|
|
3520
|
+
fs23.writeFileSync(claudeMdPath, updated);
|
|
3521
|
+
}
|
|
3522
|
+
function writeLearnedSkill(skill) {
|
|
3523
|
+
const skillDir = path20.join(".claude", "skills", skill.name);
|
|
3524
|
+
if (!fs23.existsSync(skillDir)) fs23.mkdirSync(skillDir, { recursive: true });
|
|
3525
|
+
const skillPath = path20.join(skillDir, "SKILL.md");
|
|
3526
|
+
if (!skill.isNew && fs23.existsSync(skillPath)) {
|
|
3527
|
+
const existing = fs23.readFileSync(skillPath, "utf-8");
|
|
3528
|
+
fs23.writeFileSync(skillPath, existing.trimEnd() + "\n\n" + skill.content);
|
|
3529
|
+
} else {
|
|
3530
|
+
const frontmatter = [
|
|
3531
|
+
"---",
|
|
3532
|
+
`name: ${skill.name}`,
|
|
3533
|
+
`description: ${skill.description}`,
|
|
3534
|
+
"---",
|
|
3535
|
+
""
|
|
3536
|
+
].join("\n");
|
|
3537
|
+
fs23.writeFileSync(skillPath, frontmatter + skill.content);
|
|
3538
|
+
}
|
|
3539
|
+
return skillPath;
|
|
3540
|
+
}
|
|
3541
|
+
function readLearnedSection() {
|
|
3542
|
+
const claudeMdPath = "CLAUDE.md";
|
|
3543
|
+
if (!fs23.existsSync(claudeMdPath)) return null;
|
|
3544
|
+
const content = fs23.readFileSync(claudeMdPath, "utf-8");
|
|
3545
|
+
const startIdx = content.indexOf(LEARNED_START);
|
|
3546
|
+
const endIdx = content.indexOf(LEARNED_END);
|
|
3547
|
+
if (startIdx === -1 || endIdx === -1) return null;
|
|
3548
|
+
return content.slice(startIdx + LEARNED_START.length, endIdx).trim();
|
|
3549
|
+
}
|
|
3550
|
+
|
|
3551
|
+
// src/lib/learning-hooks.ts
|
|
3552
|
+
import fs24 from "fs";
|
|
3553
|
+
import path21 from "path";
|
|
3554
|
+
var SETTINGS_PATH2 = path21.join(".claude", "settings.json");
|
|
3555
|
+
var HOOK_CONFIGS = [
|
|
3556
|
+
{
|
|
3557
|
+
event: "PostToolUse",
|
|
3558
|
+
command: "caliber learn observe",
|
|
3559
|
+
description: "Caliber: recording tool usage for session learning"
|
|
3560
|
+
},
|
|
3561
|
+
{
|
|
3562
|
+
event: "PostToolUseFailure",
|
|
3563
|
+
command: "caliber learn observe --failure",
|
|
3564
|
+
description: "Caliber: recording tool failure for session learning"
|
|
3565
|
+
},
|
|
3566
|
+
{
|
|
3567
|
+
event: "SessionEnd",
|
|
3568
|
+
command: "caliber learn finalize",
|
|
3569
|
+
description: "Caliber: finalizing session learnings"
|
|
3570
|
+
}
|
|
3571
|
+
];
|
|
3572
|
+
function readSettings2() {
|
|
3573
|
+
if (!fs24.existsSync(SETTINGS_PATH2)) return {};
|
|
3574
|
+
try {
|
|
3575
|
+
return JSON.parse(fs24.readFileSync(SETTINGS_PATH2, "utf-8"));
|
|
3576
|
+
} catch {
|
|
3577
|
+
return {};
|
|
3578
|
+
}
|
|
3579
|
+
}
|
|
3580
|
+
function writeSettings2(settings) {
|
|
3581
|
+
const dir = path21.dirname(SETTINGS_PATH2);
|
|
3582
|
+
if (!fs24.existsSync(dir)) fs24.mkdirSync(dir, { recursive: true });
|
|
3583
|
+
fs24.writeFileSync(SETTINGS_PATH2, JSON.stringify(settings, null, 2));
|
|
3584
|
+
}
|
|
3585
|
+
function hasLearningHook(matchers, command) {
|
|
3586
|
+
return matchers.some((entry) => entry.hooks?.some((h) => h.command === command));
|
|
3587
|
+
}
|
|
3588
|
+
function areLearningHooksInstalled() {
|
|
3589
|
+
const settings = readSettings2();
|
|
3590
|
+
if (!settings.hooks) return false;
|
|
3591
|
+
return HOOK_CONFIGS.every((cfg) => {
|
|
3592
|
+
const matchers = settings.hooks[cfg.event];
|
|
3593
|
+
return Array.isArray(matchers) && hasLearningHook(matchers, cfg.command);
|
|
3594
|
+
});
|
|
3595
|
+
}
|
|
3596
|
+
function installLearningHooks() {
|
|
3597
|
+
if (areLearningHooksInstalled()) {
|
|
3598
|
+
return { installed: false, alreadyInstalled: true };
|
|
3599
|
+
}
|
|
3600
|
+
const settings = readSettings2();
|
|
3601
|
+
if (!settings.hooks) settings.hooks = {};
|
|
3602
|
+
for (const cfg of HOOK_CONFIGS) {
|
|
3603
|
+
if (!Array.isArray(settings.hooks[cfg.event])) {
|
|
3604
|
+
settings.hooks[cfg.event] = [];
|
|
3605
|
+
}
|
|
3606
|
+
if (!hasLearningHook(settings.hooks[cfg.event], cfg.command)) {
|
|
3607
|
+
settings.hooks[cfg.event].push({
|
|
3608
|
+
matcher: "",
|
|
3609
|
+
hooks: [{ type: "command", command: cfg.command, description: cfg.description }]
|
|
3610
|
+
});
|
|
3611
|
+
}
|
|
3612
|
+
}
|
|
3613
|
+
writeSettings2(settings);
|
|
3614
|
+
return { installed: true, alreadyInstalled: false };
|
|
3615
|
+
}
|
|
3616
|
+
function removeLearningHooks() {
|
|
3617
|
+
const settings = readSettings2();
|
|
3618
|
+
if (!settings.hooks) return { removed: false, notFound: true };
|
|
3619
|
+
let removedAny = false;
|
|
3620
|
+
for (const cfg of HOOK_CONFIGS) {
|
|
3621
|
+
const matchers = settings.hooks[cfg.event];
|
|
3622
|
+
if (!Array.isArray(matchers)) continue;
|
|
3623
|
+
const idx = matchers.findIndex((entry) => entry.hooks?.some((h) => h.command === cfg.command));
|
|
3624
|
+
if (idx !== -1) {
|
|
3625
|
+
matchers.splice(idx, 1);
|
|
3626
|
+
removedAny = true;
|
|
3627
|
+
if (matchers.length === 0) delete settings.hooks[cfg.event];
|
|
3628
|
+
}
|
|
3629
|
+
}
|
|
3630
|
+
if (settings.hooks && Object.keys(settings.hooks).length === 0) {
|
|
3631
|
+
delete settings.hooks;
|
|
3632
|
+
}
|
|
3633
|
+
if (!removedAny) return { removed: false, notFound: true };
|
|
3634
|
+
writeSettings2(settings);
|
|
3635
|
+
return { removed: true, notFound: false };
|
|
3636
|
+
}
|
|
3637
|
+
|
|
3638
|
+
// src/commands/learn.ts
|
|
3639
|
+
async function learnObserveCommand(options) {
|
|
3640
|
+
try {
|
|
3641
|
+
const raw = await readStdin();
|
|
3642
|
+
if (!raw.trim()) return;
|
|
3643
|
+
const hookData = JSON.parse(raw);
|
|
3644
|
+
const event = {
|
|
3645
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3646
|
+
session_id: hookData.session_id || "unknown",
|
|
3647
|
+
hook_event_name: options.failure ? "PostToolUseFailure" : "PostToolUse",
|
|
3648
|
+
tool_name: hookData.tool_name || "unknown",
|
|
3649
|
+
tool_input: hookData.tool_input || {},
|
|
3650
|
+
tool_response: hookData.tool_response || {},
|
|
3651
|
+
tool_use_id: hookData.tool_use_id || "",
|
|
3652
|
+
cwd: hookData.cwd || process.cwd()
|
|
3653
|
+
};
|
|
3654
|
+
appendEvent(event);
|
|
3655
|
+
const state = readState2();
|
|
3656
|
+
state.eventCount++;
|
|
3657
|
+
if (!state.sessionId) state.sessionId = event.session_id;
|
|
3658
|
+
writeState2(state);
|
|
3659
|
+
} catch {
|
|
3660
|
+
}
|
|
3661
|
+
}
|
|
3662
|
+
async function learnFinalizeCommand() {
|
|
3663
|
+
try {
|
|
3664
|
+
const auth2 = getStoredAuth();
|
|
3665
|
+
if (!auth2) {
|
|
3666
|
+
clearSession();
|
|
3667
|
+
resetState();
|
|
3668
|
+
return;
|
|
3669
|
+
}
|
|
3670
|
+
const events = readAllEvents();
|
|
3671
|
+
if (!events.length) {
|
|
3672
|
+
clearSession();
|
|
3673
|
+
resetState();
|
|
3674
|
+
return;
|
|
3675
|
+
}
|
|
3676
|
+
const existingConfigs = readExistingConfigs(process.cwd());
|
|
3677
|
+
const existingLearnedSection = readLearnedSection();
|
|
3678
|
+
const existingSkills = existingConfigs.claudeSkills || [];
|
|
3679
|
+
const response = await apiRequest("/api/learn/analyze", {
|
|
3680
|
+
method: "POST",
|
|
3681
|
+
body: {
|
|
3682
|
+
events,
|
|
3683
|
+
existingClaudeMd: existingConfigs.claudeMd || "",
|
|
3684
|
+
existingLearnedSection,
|
|
3685
|
+
existingSkills
|
|
3686
|
+
}
|
|
3687
|
+
});
|
|
3688
|
+
if (response.claudeMdLearnedSection || response.skills?.length) {
|
|
3689
|
+
writeLearnedContent({
|
|
3690
|
+
claudeMdLearnedSection: response.claudeMdLearnedSection,
|
|
3691
|
+
skills: response.skills
|
|
3692
|
+
});
|
|
3693
|
+
}
|
|
3694
|
+
} catch {
|
|
3695
|
+
} finally {
|
|
3696
|
+
clearSession();
|
|
3697
|
+
resetState();
|
|
3698
|
+
}
|
|
3699
|
+
}
|
|
3700
|
+
async function learnInstallCommand() {
|
|
3701
|
+
const result = installLearningHooks();
|
|
3702
|
+
if (result.alreadyInstalled) {
|
|
3703
|
+
console.log(chalk14.dim("Learning hooks already installed."));
|
|
3704
|
+
return;
|
|
3705
|
+
}
|
|
3706
|
+
console.log(chalk14.green("\u2713") + " Learning hooks installed in .claude/settings.json");
|
|
3707
|
+
console.log(chalk14.dim(" PostToolUse, PostToolUseFailure, and SessionEnd hooks active."));
|
|
3708
|
+
console.log(chalk14.dim(" Session learnings will be written to CLAUDE.md and skills."));
|
|
3709
|
+
}
|
|
3710
|
+
async function learnRemoveCommand() {
|
|
3711
|
+
const result = removeLearningHooks();
|
|
3712
|
+
if (result.notFound) {
|
|
3713
|
+
console.log(chalk14.dim("Learning hooks not found."));
|
|
3714
|
+
return;
|
|
3715
|
+
}
|
|
3716
|
+
console.log(chalk14.green("\u2713") + " Learning hooks removed from .claude/settings.json");
|
|
3717
|
+
}
|
|
3718
|
+
async function learnStatusCommand() {
|
|
3719
|
+
const installed = areLearningHooksInstalled();
|
|
3720
|
+
const state = readState2();
|
|
3721
|
+
const eventCount = getEventCount();
|
|
3722
|
+
console.log(chalk14.bold("Session Learning Status"));
|
|
3723
|
+
console.log();
|
|
3724
|
+
if (installed) {
|
|
3725
|
+
console.log(chalk14.green("\u2713") + " Learning hooks are " + chalk14.green("installed"));
|
|
3726
|
+
} else {
|
|
3727
|
+
console.log(chalk14.dim("\u2717") + " Learning hooks are " + chalk14.yellow("not installed"));
|
|
3728
|
+
console.log(chalk14.dim(" Run `caliber learn install` to enable session learning."));
|
|
3729
|
+
}
|
|
3730
|
+
console.log();
|
|
3731
|
+
console.log(`Events recorded: ${chalk14.cyan(String(eventCount))}`);
|
|
3732
|
+
console.log(`Total this session: ${chalk14.cyan(String(state.eventCount))}`);
|
|
3733
|
+
if (state.lastAnalysisTimestamp) {
|
|
3734
|
+
console.log(`Last analysis: ${chalk14.cyan(state.lastAnalysisTimestamp)}`);
|
|
3735
|
+
} else {
|
|
3736
|
+
console.log(`Last analysis: ${chalk14.dim("none")}`);
|
|
3737
|
+
}
|
|
3738
|
+
const learnedSection = readLearnedSection();
|
|
3739
|
+
if (learnedSection) {
|
|
3740
|
+
const lineCount = learnedSection.split("\n").filter(Boolean).length;
|
|
3741
|
+
console.log(`
|
|
3742
|
+
Learned items in CLAUDE.md: ${chalk14.cyan(String(lineCount))}`);
|
|
3743
|
+
}
|
|
3744
|
+
}
|
|
3745
|
+
|
|
3357
3746
|
// src/cli.ts
|
|
3358
|
-
var __dirname2 =
|
|
3747
|
+
var __dirname2 = path22.dirname(fileURLToPath3(import.meta.url));
|
|
3359
3748
|
var pkg3 = JSON.parse(
|
|
3360
|
-
|
|
3749
|
+
fs25.readFileSync(path22.resolve(__dirname2, "..", "package.json"), "utf-8")
|
|
3361
3750
|
);
|
|
3362
3751
|
var program = new Command();
|
|
3363
3752
|
var displayVersion = process.env.CALIBER_LOCAL ? `${pkg3.version}-local` : pkg3.version;
|
|
@@ -3377,24 +3766,30 @@ var hooks = program.command("hooks").description("Manage Claude Code session hoo
|
|
|
3377
3766
|
hooks.command("install").description("Install auto-refresh SessionEnd hook").action(hooksInstallCommand);
|
|
3378
3767
|
hooks.command("remove").description("Remove auto-refresh SessionEnd hook").action(hooksRemoveCommand);
|
|
3379
3768
|
hooks.command("status").description("Check if auto-refresh hook is installed").action(hooksStatusCommand);
|
|
3769
|
+
var learn = program.command("learn").description("Session learning \u2014 observe tool usage and extract reusable instructions");
|
|
3770
|
+
learn.command("observe").description("Record a tool event from stdin (called by hooks)").option("--failure", "Mark event as a tool failure").action(learnObserveCommand);
|
|
3771
|
+
learn.command("finalize").description("Analyze session events and update CLAUDE.md (called on SessionEnd)").action(learnFinalizeCommand);
|
|
3772
|
+
learn.command("install").description("Install learning hooks into .claude/settings.json").action(learnInstallCommand);
|
|
3773
|
+
learn.command("remove").description("Remove learning hooks from .claude/settings.json").action(learnRemoveCommand);
|
|
3774
|
+
learn.command("status").description("Show learning system status").action(learnStatusCommand);
|
|
3380
3775
|
|
|
3381
3776
|
// src/utils/version-check.ts
|
|
3382
|
-
import
|
|
3383
|
-
import
|
|
3777
|
+
import fs26 from "fs";
|
|
3778
|
+
import path23 from "path";
|
|
3384
3779
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
3385
3780
|
import { execSync as execSync5 } from "child_process";
|
|
3386
|
-
import
|
|
3781
|
+
import chalk15 from "chalk";
|
|
3387
3782
|
import ora10 from "ora";
|
|
3388
3783
|
import confirm3 from "@inquirer/confirm";
|
|
3389
|
-
var __dirname_vc =
|
|
3784
|
+
var __dirname_vc = path23.dirname(fileURLToPath4(import.meta.url));
|
|
3390
3785
|
var pkg4 = JSON.parse(
|
|
3391
|
-
|
|
3786
|
+
fs26.readFileSync(path23.resolve(__dirname_vc, "..", "package.json"), "utf-8")
|
|
3392
3787
|
);
|
|
3393
3788
|
function getInstalledVersion() {
|
|
3394
3789
|
try {
|
|
3395
3790
|
const globalRoot = execSync5("npm root -g", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
3396
|
-
const pkgPath =
|
|
3397
|
-
return JSON.parse(
|
|
3791
|
+
const pkgPath = path23.join(globalRoot, "@caliber-ai", "cli", "package.json");
|
|
3792
|
+
return JSON.parse(fs26.readFileSync(pkgPath, "utf-8")).version;
|
|
3398
3793
|
} catch {
|
|
3399
3794
|
return null;
|
|
3400
3795
|
}
|
|
@@ -3417,17 +3812,17 @@ async function checkForUpdates() {
|
|
|
3417
3812
|
const isInteractive = process.stdin.isTTY === true;
|
|
3418
3813
|
if (!isInteractive) {
|
|
3419
3814
|
console.log(
|
|
3420
|
-
|
|
3815
|
+
chalk15.yellow(
|
|
3421
3816
|
`
|
|
3422
3817
|
Update available: ${current} -> ${latest}
|
|
3423
|
-
Run ${
|
|
3818
|
+
Run ${chalk15.bold("npm install -g @caliber-ai/cli")} to upgrade.
|
|
3424
3819
|
`
|
|
3425
3820
|
)
|
|
3426
3821
|
);
|
|
3427
3822
|
return;
|
|
3428
3823
|
}
|
|
3429
3824
|
console.log(
|
|
3430
|
-
|
|
3825
|
+
chalk15.yellow(`
|
|
3431
3826
|
Update available: ${current} -> ${latest}`)
|
|
3432
3827
|
);
|
|
3433
3828
|
const shouldUpdate = await confirm3({ message: "Would you like to update now? (Y/n)", default: true });
|
|
@@ -3441,13 +3836,13 @@ Update available: ${current} -> ${latest}`)
|
|
|
3441
3836
|
const installed = getInstalledVersion();
|
|
3442
3837
|
if (installed !== latest) {
|
|
3443
3838
|
spinner.fail(`Update incomplete \u2014 got ${installed ?? "unknown"}, expected ${latest}`);
|
|
3444
|
-
console.log(
|
|
3839
|
+
console.log(chalk15.yellow(`Run ${chalk15.bold(`npm install -g @caliber-ai/cli@${latest}`)} manually.
|
|
3445
3840
|
`));
|
|
3446
3841
|
return;
|
|
3447
3842
|
}
|
|
3448
|
-
spinner.succeed(
|
|
3843
|
+
spinner.succeed(chalk15.green(`Updated to ${latest}`));
|
|
3449
3844
|
const args = process.argv.slice(2);
|
|
3450
|
-
console.log(
|
|
3845
|
+
console.log(chalk15.dim(`
|
|
3451
3846
|
Restarting: caliber ${args.join(" ")}
|
|
3452
3847
|
`));
|
|
3453
3848
|
execSync5(`caliber ${args.map((a) => JSON.stringify(a)).join(" ")}`, {
|
|
@@ -3458,10 +3853,10 @@ Restarting: caliber ${args.join(" ")}
|
|
|
3458
3853
|
} catch (err) {
|
|
3459
3854
|
spinner.fail("Update failed");
|
|
3460
3855
|
const msg = err instanceof Error ? err.message : "";
|
|
3461
|
-
if (msg && !msg.includes("SIGTERM")) console.log(
|
|
3856
|
+
if (msg && !msg.includes("SIGTERM")) console.log(chalk15.dim(` ${msg.split("\n")[0]}`));
|
|
3462
3857
|
console.log(
|
|
3463
|
-
|
|
3464
|
-
`Run ${
|
|
3858
|
+
chalk15.yellow(
|
|
3859
|
+
`Run ${chalk15.bold(`npm install -g @caliber-ai/cli@${latest}`)} manually to upgrade.
|
|
3465
3860
|
`
|
|
3466
3861
|
)
|
|
3467
3862
|
);
|