@caliber-ai/cli 0.22.0 → 0.24.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 +476 -91
- 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
|
|
@@ -84,7 +92,7 @@ import chalk3 from "chalk";
|
|
|
84
92
|
import ora2 from "ora";
|
|
85
93
|
import readline from "readline";
|
|
86
94
|
import select from "@inquirer/select";
|
|
87
|
-
import
|
|
95
|
+
import fs18 from "fs";
|
|
88
96
|
|
|
89
97
|
// src/auth/token-store.ts
|
|
90
98
|
init_constants();
|
|
@@ -957,9 +965,9 @@ async function getValidToken() {
|
|
|
957
965
|
}
|
|
958
966
|
return refreshed;
|
|
959
967
|
}
|
|
960
|
-
async function apiRequest(
|
|
968
|
+
async function apiRequest(path24, options = {}) {
|
|
961
969
|
let token = await getValidToken();
|
|
962
|
-
let resp = await fetch(`${API_URL}${
|
|
970
|
+
let resp = await fetch(`${API_URL}${path24}`, {
|
|
963
971
|
method: options.method || "GET",
|
|
964
972
|
headers: {
|
|
965
973
|
"Content-Type": "application/json",
|
|
@@ -973,7 +981,7 @@ async function apiRequest(path21, options = {}) {
|
|
|
973
981
|
throw new Error("Session expired. Run `caliber login` to re-authenticate.");
|
|
974
982
|
}
|
|
975
983
|
token = refreshed;
|
|
976
|
-
resp = await fetch(`${API_URL}${
|
|
984
|
+
resp = await fetch(`${API_URL}${path24}`, {
|
|
977
985
|
method: options.method || "GET",
|
|
978
986
|
headers: {
|
|
979
987
|
"Content-Type": "application/json",
|
|
@@ -989,9 +997,9 @@ async function apiRequest(path21, options = {}) {
|
|
|
989
997
|
const json = await resp.json();
|
|
990
998
|
return json.data;
|
|
991
999
|
}
|
|
992
|
-
async function apiStream(
|
|
1000
|
+
async function apiStream(path24, body, onChunk, onComplete, onError, onStatus) {
|
|
993
1001
|
let token = await getValidToken();
|
|
994
|
-
let resp = await fetch(`${API_URL}${
|
|
1002
|
+
let resp = await fetch(`${API_URL}${path24}`, {
|
|
995
1003
|
method: "POST",
|
|
996
1004
|
headers: {
|
|
997
1005
|
"Content-Type": "application/json",
|
|
@@ -1005,7 +1013,7 @@ async function apiStream(path21, body, onChunk, onComplete, onError, onStatus) {
|
|
|
1005
1013
|
throw new Error("Session expired. Run `caliber login` to re-authenticate.");
|
|
1006
1014
|
}
|
|
1007
1015
|
token = refreshed;
|
|
1008
|
-
resp = await fetch(`${API_URL}${
|
|
1016
|
+
resp = await fetch(`${API_URL}${path24}`, {
|
|
1009
1017
|
method: "POST",
|
|
1010
1018
|
headers: {
|
|
1011
1019
|
"Content-Type": "application/json",
|
|
@@ -1510,25 +1518,112 @@ function removeHook() {
|
|
|
1510
1518
|
return { removed: true, notFound: false };
|
|
1511
1519
|
}
|
|
1512
1520
|
|
|
1513
|
-
// src/lib/
|
|
1514
|
-
init_constants();
|
|
1521
|
+
// src/lib/learning-hooks.ts
|
|
1515
1522
|
import fs16 from "fs";
|
|
1516
1523
|
import path15 from "path";
|
|
1524
|
+
var SETTINGS_PATH2 = path15.join(".claude", "settings.json");
|
|
1525
|
+
var HOOK_CONFIGS = [
|
|
1526
|
+
{
|
|
1527
|
+
event: "PostToolUse",
|
|
1528
|
+
command: "caliber learn observe",
|
|
1529
|
+
description: "Caliber: recording tool usage for session learning"
|
|
1530
|
+
},
|
|
1531
|
+
{
|
|
1532
|
+
event: "PostToolUseFailure",
|
|
1533
|
+
command: "caliber learn observe --failure",
|
|
1534
|
+
description: "Caliber: recording tool failure for session learning"
|
|
1535
|
+
},
|
|
1536
|
+
{
|
|
1537
|
+
event: "SessionEnd",
|
|
1538
|
+
command: "caliber learn finalize",
|
|
1539
|
+
description: "Caliber: finalizing session learnings"
|
|
1540
|
+
}
|
|
1541
|
+
];
|
|
1542
|
+
function readSettings2() {
|
|
1543
|
+
if (!fs16.existsSync(SETTINGS_PATH2)) return {};
|
|
1544
|
+
try {
|
|
1545
|
+
return JSON.parse(fs16.readFileSync(SETTINGS_PATH2, "utf-8"));
|
|
1546
|
+
} catch {
|
|
1547
|
+
return {};
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
function writeSettings2(settings) {
|
|
1551
|
+
const dir = path15.dirname(SETTINGS_PATH2);
|
|
1552
|
+
if (!fs16.existsSync(dir)) fs16.mkdirSync(dir, { recursive: true });
|
|
1553
|
+
fs16.writeFileSync(SETTINGS_PATH2, JSON.stringify(settings, null, 2));
|
|
1554
|
+
}
|
|
1555
|
+
function hasLearningHook(matchers, command) {
|
|
1556
|
+
return matchers.some((entry) => entry.hooks?.some((h) => h.command === command));
|
|
1557
|
+
}
|
|
1558
|
+
function areLearningHooksInstalled() {
|
|
1559
|
+
const settings = readSettings2();
|
|
1560
|
+
if (!settings.hooks) return false;
|
|
1561
|
+
return HOOK_CONFIGS.every((cfg) => {
|
|
1562
|
+
const matchers = settings.hooks[cfg.event];
|
|
1563
|
+
return Array.isArray(matchers) && hasLearningHook(matchers, cfg.command);
|
|
1564
|
+
});
|
|
1565
|
+
}
|
|
1566
|
+
function installLearningHooks() {
|
|
1567
|
+
if (areLearningHooksInstalled()) {
|
|
1568
|
+
return { installed: false, alreadyInstalled: true };
|
|
1569
|
+
}
|
|
1570
|
+
const settings = readSettings2();
|
|
1571
|
+
if (!settings.hooks) settings.hooks = {};
|
|
1572
|
+
for (const cfg of HOOK_CONFIGS) {
|
|
1573
|
+
if (!Array.isArray(settings.hooks[cfg.event])) {
|
|
1574
|
+
settings.hooks[cfg.event] = [];
|
|
1575
|
+
}
|
|
1576
|
+
if (!hasLearningHook(settings.hooks[cfg.event], cfg.command)) {
|
|
1577
|
+
settings.hooks[cfg.event].push({
|
|
1578
|
+
matcher: "",
|
|
1579
|
+
hooks: [{ type: "command", command: cfg.command, description: cfg.description }]
|
|
1580
|
+
});
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
writeSettings2(settings);
|
|
1584
|
+
return { installed: true, alreadyInstalled: false };
|
|
1585
|
+
}
|
|
1586
|
+
function removeLearningHooks() {
|
|
1587
|
+
const settings = readSettings2();
|
|
1588
|
+
if (!settings.hooks) return { removed: false, notFound: true };
|
|
1589
|
+
let removedAny = false;
|
|
1590
|
+
for (const cfg of HOOK_CONFIGS) {
|
|
1591
|
+
const matchers = settings.hooks[cfg.event];
|
|
1592
|
+
if (!Array.isArray(matchers)) continue;
|
|
1593
|
+
const idx = matchers.findIndex((entry) => entry.hooks?.some((h) => h.command === cfg.command));
|
|
1594
|
+
if (idx !== -1) {
|
|
1595
|
+
matchers.splice(idx, 1);
|
|
1596
|
+
removedAny = true;
|
|
1597
|
+
if (matchers.length === 0) delete settings.hooks[cfg.event];
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
if (settings.hooks && Object.keys(settings.hooks).length === 0) {
|
|
1601
|
+
delete settings.hooks;
|
|
1602
|
+
}
|
|
1603
|
+
if (!removedAny) return { removed: false, notFound: true };
|
|
1604
|
+
writeSettings2(settings);
|
|
1605
|
+
return { removed: true, notFound: false };
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
// src/lib/state.ts
|
|
1609
|
+
init_constants();
|
|
1610
|
+
import fs17 from "fs";
|
|
1611
|
+
import path16 from "path";
|
|
1517
1612
|
import { execSync as execSync3 } from "child_process";
|
|
1518
|
-
var STATE_FILE =
|
|
1613
|
+
var STATE_FILE = path16.join(CALIBER_DIR, ".caliber-state.json");
|
|
1519
1614
|
function readState() {
|
|
1520
1615
|
try {
|
|
1521
|
-
if (!
|
|
1522
|
-
return JSON.parse(
|
|
1616
|
+
if (!fs17.existsSync(STATE_FILE)) return null;
|
|
1617
|
+
return JSON.parse(fs17.readFileSync(STATE_FILE, "utf-8"));
|
|
1523
1618
|
} catch {
|
|
1524
1619
|
return null;
|
|
1525
1620
|
}
|
|
1526
1621
|
}
|
|
1527
1622
|
function writeState(state) {
|
|
1528
|
-
if (!
|
|
1529
|
-
|
|
1623
|
+
if (!fs17.existsSync(CALIBER_DIR)) {
|
|
1624
|
+
fs17.mkdirSync(CALIBER_DIR, { recursive: true });
|
|
1530
1625
|
}
|
|
1531
|
-
|
|
1626
|
+
fs17.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
|
|
1532
1627
|
}
|
|
1533
1628
|
function getCurrentHeadSha() {
|
|
1534
1629
|
try {
|
|
@@ -1885,6 +1980,13 @@ async function initCommand(options) {
|
|
|
1885
1980
|
} else if (hookResult.alreadyInstalled) {
|
|
1886
1981
|
console.log(chalk3.dim(" Auto-refresh hook already installed"));
|
|
1887
1982
|
}
|
|
1983
|
+
const learnResult = installLearningHooks();
|
|
1984
|
+
if (learnResult.installed) {
|
|
1985
|
+
console.log(` ${chalk3.green("\u2713")} Learning hooks installed \u2014 session insights captured automatically`);
|
|
1986
|
+
console.log(chalk3.dim(" Run `caliber learn remove` to disable"));
|
|
1987
|
+
} else if (learnResult.alreadyInstalled) {
|
|
1988
|
+
console.log(chalk3.dim(" Learning hooks already installed"));
|
|
1989
|
+
}
|
|
1888
1990
|
}
|
|
1889
1991
|
try {
|
|
1890
1992
|
let projectId = existingProjectId;
|
|
@@ -2016,8 +2118,8 @@ function openReview(method, stagedFiles) {
|
|
|
2016
2118
|
} else {
|
|
2017
2119
|
for (const file of stagedFiles) {
|
|
2018
2120
|
if (file.currentPath) {
|
|
2019
|
-
const currentLines =
|
|
2020
|
-
const proposedLines =
|
|
2121
|
+
const currentLines = fs18.readFileSync(file.currentPath, "utf-8").split("\n");
|
|
2122
|
+
const proposedLines = fs18.readFileSync(file.proposedPath, "utf-8").split("\n");
|
|
2021
2123
|
const patch = createTwoFilesPatch(file.relativePath, file.relativePath, currentLines.join("\n"), proposedLines.join("\n"));
|
|
2022
2124
|
let added = 0, removed = 0;
|
|
2023
2125
|
for (const line of patch.split("\n")) {
|
|
@@ -2026,7 +2128,7 @@ function openReview(method, stagedFiles) {
|
|
|
2026
2128
|
}
|
|
2027
2129
|
console.log(` ${chalk3.yellow("~")} ${file.relativePath} ${chalk3.green(`+${added}`)} ${chalk3.red(`-${removed}`)}`);
|
|
2028
2130
|
} else {
|
|
2029
|
-
const lines =
|
|
2131
|
+
const lines = fs18.readFileSync(file.proposedPath, "utf-8").split("\n").length;
|
|
2030
2132
|
console.log(` ${chalk3.green("+")} ${file.relativePath} ${chalk3.dim(`${lines} lines`)}`);
|
|
2031
2133
|
}
|
|
2032
2134
|
}
|
|
@@ -2057,7 +2159,7 @@ function printSetupSummary(setup) {
|
|
|
2057
2159
|
};
|
|
2058
2160
|
if (claude) {
|
|
2059
2161
|
if (claude.claudeMd) {
|
|
2060
|
-
const icon =
|
|
2162
|
+
const icon = fs18.existsSync("CLAUDE.md") ? chalk3.yellow("~") : chalk3.green("+");
|
|
2061
2163
|
const desc = getDescription("CLAUDE.md");
|
|
2062
2164
|
console.log(` ${icon} ${chalk3.bold("CLAUDE.md")}`);
|
|
2063
2165
|
if (desc) {
|
|
@@ -2069,7 +2171,7 @@ function printSetupSummary(setup) {
|
|
|
2069
2171
|
if (Array.isArray(skills) && skills.length > 0) {
|
|
2070
2172
|
for (const skill of skills) {
|
|
2071
2173
|
const skillPath = `.claude/skills/${skill.name}/SKILL.md`;
|
|
2072
|
-
const icon =
|
|
2174
|
+
const icon = fs18.existsSync(skillPath) ? chalk3.yellow("~") : chalk3.green("+");
|
|
2073
2175
|
const desc = getDescription(skillPath);
|
|
2074
2176
|
console.log(` ${icon} ${chalk3.bold(skillPath)}`);
|
|
2075
2177
|
console.log(chalk3.dim(` ${desc || skill.description || skill.name}`));
|
|
@@ -2079,7 +2181,7 @@ function printSetupSummary(setup) {
|
|
|
2079
2181
|
}
|
|
2080
2182
|
if (cursor) {
|
|
2081
2183
|
if (cursor.cursorrules) {
|
|
2082
|
-
const icon =
|
|
2184
|
+
const icon = fs18.existsSync(".cursorrules") ? chalk3.yellow("~") : chalk3.green("+");
|
|
2083
2185
|
const desc = getDescription(".cursorrules");
|
|
2084
2186
|
console.log(` ${icon} ${chalk3.bold(".cursorrules")}`);
|
|
2085
2187
|
if (desc) console.log(chalk3.dim(` ${desc}`));
|
|
@@ -2089,7 +2191,7 @@ function printSetupSummary(setup) {
|
|
|
2089
2191
|
if (Array.isArray(cursorSkills) && cursorSkills.length > 0) {
|
|
2090
2192
|
for (const skill of cursorSkills) {
|
|
2091
2193
|
const skillPath = `.cursor/skills/${skill.name}/SKILL.md`;
|
|
2092
|
-
const icon =
|
|
2194
|
+
const icon = fs18.existsSync(skillPath) ? chalk3.yellow("~") : chalk3.green("+");
|
|
2093
2195
|
const desc = getDescription(skillPath);
|
|
2094
2196
|
console.log(` ${icon} ${chalk3.bold(skillPath)}`);
|
|
2095
2197
|
console.log(chalk3.dim(` ${desc || skill.description || skill.name}`));
|
|
@@ -2100,7 +2202,7 @@ function printSetupSummary(setup) {
|
|
|
2100
2202
|
if (Array.isArray(rules) && rules.length > 0) {
|
|
2101
2203
|
for (const rule of rules) {
|
|
2102
2204
|
const rulePath = `.cursor/rules/${rule.filename}`;
|
|
2103
|
-
const icon =
|
|
2205
|
+
const icon = fs18.existsSync(rulePath) ? chalk3.yellow("~") : chalk3.green("+");
|
|
2104
2206
|
const desc = getDescription(rulePath);
|
|
2105
2207
|
console.log(` ${icon} ${chalk3.bold(rulePath)}`);
|
|
2106
2208
|
if (desc) {
|
|
@@ -2197,16 +2299,16 @@ function undoCommand() {
|
|
|
2197
2299
|
|
|
2198
2300
|
// src/commands/status.ts
|
|
2199
2301
|
import chalk5 from "chalk";
|
|
2200
|
-
import
|
|
2302
|
+
import fs20 from "fs";
|
|
2201
2303
|
|
|
2202
2304
|
// src/scanner/index.ts
|
|
2203
|
-
import
|
|
2204
|
-
import
|
|
2305
|
+
import fs19 from "fs";
|
|
2306
|
+
import path17 from "path";
|
|
2205
2307
|
import crypto4 from "crypto";
|
|
2206
2308
|
function scanLocalState(dir) {
|
|
2207
2309
|
const items = [];
|
|
2208
|
-
const claudeMdPath =
|
|
2209
|
-
if (
|
|
2310
|
+
const claudeMdPath = path17.join(dir, "CLAUDE.md");
|
|
2311
|
+
if (fs19.existsSync(claudeMdPath)) {
|
|
2210
2312
|
items.push({
|
|
2211
2313
|
type: "rule",
|
|
2212
2314
|
platform: "claude",
|
|
@@ -2215,10 +2317,10 @@ function scanLocalState(dir) {
|
|
|
2215
2317
|
path: claudeMdPath
|
|
2216
2318
|
});
|
|
2217
2319
|
}
|
|
2218
|
-
const skillsDir =
|
|
2219
|
-
if (
|
|
2220
|
-
for (const file of
|
|
2221
|
-
const filePath =
|
|
2320
|
+
const skillsDir = path17.join(dir, ".claude", "skills");
|
|
2321
|
+
if (fs19.existsSync(skillsDir)) {
|
|
2322
|
+
for (const file of fs19.readdirSync(skillsDir).filter((f) => f.endsWith(".md"))) {
|
|
2323
|
+
const filePath = path17.join(skillsDir, file);
|
|
2222
2324
|
items.push({
|
|
2223
2325
|
type: "skill",
|
|
2224
2326
|
platform: "claude",
|
|
@@ -2228,10 +2330,10 @@ function scanLocalState(dir) {
|
|
|
2228
2330
|
});
|
|
2229
2331
|
}
|
|
2230
2332
|
}
|
|
2231
|
-
const mcpJsonPath =
|
|
2232
|
-
if (
|
|
2333
|
+
const mcpJsonPath = path17.join(dir, ".mcp.json");
|
|
2334
|
+
if (fs19.existsSync(mcpJsonPath)) {
|
|
2233
2335
|
try {
|
|
2234
|
-
const mcpJson = JSON.parse(
|
|
2336
|
+
const mcpJson = JSON.parse(fs19.readFileSync(mcpJsonPath, "utf-8"));
|
|
2235
2337
|
if (mcpJson.mcpServers) {
|
|
2236
2338
|
for (const name of Object.keys(mcpJson.mcpServers)) {
|
|
2237
2339
|
items.push({
|
|
@@ -2246,8 +2348,8 @@ function scanLocalState(dir) {
|
|
|
2246
2348
|
} catch {
|
|
2247
2349
|
}
|
|
2248
2350
|
}
|
|
2249
|
-
const cursorrulesPath =
|
|
2250
|
-
if (
|
|
2351
|
+
const cursorrulesPath = path17.join(dir, ".cursorrules");
|
|
2352
|
+
if (fs19.existsSync(cursorrulesPath)) {
|
|
2251
2353
|
items.push({
|
|
2252
2354
|
type: "rule",
|
|
2253
2355
|
platform: "cursor",
|
|
@@ -2256,10 +2358,10 @@ function scanLocalState(dir) {
|
|
|
2256
2358
|
path: cursorrulesPath
|
|
2257
2359
|
});
|
|
2258
2360
|
}
|
|
2259
|
-
const cursorRulesDir =
|
|
2260
|
-
if (
|
|
2261
|
-
for (const file of
|
|
2262
|
-
const filePath =
|
|
2361
|
+
const cursorRulesDir = path17.join(dir, ".cursor", "rules");
|
|
2362
|
+
if (fs19.existsSync(cursorRulesDir)) {
|
|
2363
|
+
for (const file of fs19.readdirSync(cursorRulesDir).filter((f) => f.endsWith(".mdc"))) {
|
|
2364
|
+
const filePath = path17.join(cursorRulesDir, file);
|
|
2263
2365
|
items.push({
|
|
2264
2366
|
type: "rule",
|
|
2265
2367
|
platform: "cursor",
|
|
@@ -2269,12 +2371,12 @@ function scanLocalState(dir) {
|
|
|
2269
2371
|
});
|
|
2270
2372
|
}
|
|
2271
2373
|
}
|
|
2272
|
-
const cursorSkillsDir =
|
|
2273
|
-
if (
|
|
2374
|
+
const cursorSkillsDir = path17.join(dir, ".cursor", "skills");
|
|
2375
|
+
if (fs19.existsSync(cursorSkillsDir)) {
|
|
2274
2376
|
try {
|
|
2275
|
-
for (const name of
|
|
2276
|
-
const skillFile =
|
|
2277
|
-
if (
|
|
2377
|
+
for (const name of fs19.readdirSync(cursorSkillsDir)) {
|
|
2378
|
+
const skillFile = path17.join(cursorSkillsDir, name, "SKILL.md");
|
|
2379
|
+
if (fs19.existsSync(skillFile)) {
|
|
2278
2380
|
items.push({
|
|
2279
2381
|
type: "skill",
|
|
2280
2382
|
platform: "cursor",
|
|
@@ -2287,10 +2389,10 @@ function scanLocalState(dir) {
|
|
|
2287
2389
|
} catch {
|
|
2288
2390
|
}
|
|
2289
2391
|
}
|
|
2290
|
-
const cursorMcpPath =
|
|
2291
|
-
if (
|
|
2392
|
+
const cursorMcpPath = path17.join(dir, ".cursor", "mcp.json");
|
|
2393
|
+
if (fs19.existsSync(cursorMcpPath)) {
|
|
2292
2394
|
try {
|
|
2293
|
-
const mcpJson = JSON.parse(
|
|
2395
|
+
const mcpJson = JSON.parse(fs19.readFileSync(cursorMcpPath, "utf-8"));
|
|
2294
2396
|
if (mcpJson.mcpServers) {
|
|
2295
2397
|
for (const name of Object.keys(mcpJson.mcpServers)) {
|
|
2296
2398
|
items.push({
|
|
@@ -2334,7 +2436,7 @@ function compareState(serverItems, localItems) {
|
|
|
2334
2436
|
return { installed, missing, outdated, extra };
|
|
2335
2437
|
}
|
|
2336
2438
|
function hashFile(filePath) {
|
|
2337
|
-
const text =
|
|
2439
|
+
const text = fs19.readFileSync(filePath, "utf-8");
|
|
2338
2440
|
return crypto4.createHash("sha256").update(JSON.stringify({ text })).digest("hex");
|
|
2339
2441
|
}
|
|
2340
2442
|
function hashJson(obj) {
|
|
@@ -2366,7 +2468,7 @@ async function statusCommand(options) {
|
|
|
2366
2468
|
}
|
|
2367
2469
|
console.log(` Files managed: ${chalk5.cyan(manifest.entries.length.toString())}`);
|
|
2368
2470
|
for (const entry of manifest.entries) {
|
|
2369
|
-
const exists =
|
|
2471
|
+
const exists = fs20.existsSync(entry.path);
|
|
2370
2472
|
const icon = exists ? chalk5.green("\u2713") : chalk5.red("\u2717");
|
|
2371
2473
|
console.log(` ${icon} ${entry.path} (${entry.action})`);
|
|
2372
2474
|
}
|
|
@@ -3001,8 +3103,8 @@ async function diffCommand(options) {
|
|
|
3001
3103
|
}
|
|
3002
3104
|
|
|
3003
3105
|
// src/commands/refresh.ts
|
|
3004
|
-
import
|
|
3005
|
-
import
|
|
3106
|
+
import fs22 from "fs";
|
|
3107
|
+
import path19 from "path";
|
|
3006
3108
|
import chalk11 from "chalk";
|
|
3007
3109
|
import ora8 from "ora";
|
|
3008
3110
|
|
|
@@ -3079,37 +3181,37 @@ function collectDiff(lastSha) {
|
|
|
3079
3181
|
}
|
|
3080
3182
|
|
|
3081
3183
|
// src/writers/refresh.ts
|
|
3082
|
-
import
|
|
3083
|
-
import
|
|
3184
|
+
import fs21 from "fs";
|
|
3185
|
+
import path18 from "path";
|
|
3084
3186
|
function writeRefreshDocs(docs) {
|
|
3085
3187
|
const written = [];
|
|
3086
3188
|
if (docs.claudeMd) {
|
|
3087
|
-
|
|
3189
|
+
fs21.writeFileSync("CLAUDE.md", docs.claudeMd);
|
|
3088
3190
|
written.push("CLAUDE.md");
|
|
3089
3191
|
}
|
|
3090
3192
|
if (docs.readmeMd) {
|
|
3091
|
-
|
|
3193
|
+
fs21.writeFileSync("README.md", docs.readmeMd);
|
|
3092
3194
|
written.push("README.md");
|
|
3093
3195
|
}
|
|
3094
3196
|
if (docs.cursorrules) {
|
|
3095
|
-
|
|
3197
|
+
fs21.writeFileSync(".cursorrules", docs.cursorrules);
|
|
3096
3198
|
written.push(".cursorrules");
|
|
3097
3199
|
}
|
|
3098
3200
|
if (docs.cursorRules) {
|
|
3099
|
-
const rulesDir =
|
|
3100
|
-
if (!
|
|
3201
|
+
const rulesDir = path18.join(".cursor", "rules");
|
|
3202
|
+
if (!fs21.existsSync(rulesDir)) fs21.mkdirSync(rulesDir, { recursive: true });
|
|
3101
3203
|
for (const rule of docs.cursorRules) {
|
|
3102
|
-
const filePath =
|
|
3103
|
-
|
|
3204
|
+
const filePath = path18.join(rulesDir, rule.filename);
|
|
3205
|
+
fs21.writeFileSync(filePath, rule.content);
|
|
3104
3206
|
written.push(filePath);
|
|
3105
3207
|
}
|
|
3106
3208
|
}
|
|
3107
3209
|
if (docs.claudeSkills) {
|
|
3108
|
-
const skillsDir =
|
|
3109
|
-
if (!
|
|
3210
|
+
const skillsDir = path18.join(".claude", "skills");
|
|
3211
|
+
if (!fs21.existsSync(skillsDir)) fs21.mkdirSync(skillsDir, { recursive: true });
|
|
3110
3212
|
for (const skill of docs.claudeSkills) {
|
|
3111
|
-
const filePath =
|
|
3112
|
-
|
|
3213
|
+
const filePath = path18.join(skillsDir, skill.filename);
|
|
3214
|
+
fs21.writeFileSync(filePath, skill.content);
|
|
3113
3215
|
written.push(filePath);
|
|
3114
3216
|
}
|
|
3115
3217
|
}
|
|
@@ -3123,11 +3225,11 @@ function log(quiet, ...args) {
|
|
|
3123
3225
|
function discoverGitRepos(parentDir) {
|
|
3124
3226
|
const repos = [];
|
|
3125
3227
|
try {
|
|
3126
|
-
const entries =
|
|
3228
|
+
const entries = fs22.readdirSync(parentDir, { withFileTypes: true });
|
|
3127
3229
|
for (const entry of entries) {
|
|
3128
3230
|
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
3129
|
-
const childPath =
|
|
3130
|
-
if (
|
|
3231
|
+
const childPath = path19.join(parentDir, entry.name);
|
|
3232
|
+
if (fs22.existsSync(path19.join(childPath, ".git"))) {
|
|
3131
3233
|
repos.push(childPath);
|
|
3132
3234
|
}
|
|
3133
3235
|
}
|
|
@@ -3230,7 +3332,7 @@ async function refreshCommand(options) {
|
|
|
3230
3332
|
`));
|
|
3231
3333
|
const originalDir = process.cwd();
|
|
3232
3334
|
for (const repo of repos) {
|
|
3233
|
-
const repoName =
|
|
3335
|
+
const repoName = path19.basename(repo);
|
|
3234
3336
|
try {
|
|
3235
3337
|
process.chdir(repo);
|
|
3236
3338
|
await refreshSingleRepo(repo, { ...options, label: repoName });
|
|
@@ -3371,10 +3473,287 @@ async function reviewCommand(message, options) {
|
|
|
3371
3473
|
await submitReview({ rating, bestPart, biggestGap, wouldRecommend });
|
|
3372
3474
|
}
|
|
3373
3475
|
|
|
3476
|
+
// src/commands/learn.ts
|
|
3477
|
+
import chalk14 from "chalk";
|
|
3478
|
+
|
|
3479
|
+
// src/learner/stdin.ts
|
|
3480
|
+
var STDIN_TIMEOUT_MS = 5e3;
|
|
3481
|
+
function readStdin() {
|
|
3482
|
+
return new Promise((resolve2, reject) => {
|
|
3483
|
+
if (process.stdin.isTTY) {
|
|
3484
|
+
resolve2("");
|
|
3485
|
+
return;
|
|
3486
|
+
}
|
|
3487
|
+
const chunks = [];
|
|
3488
|
+
const timer = setTimeout(() => {
|
|
3489
|
+
process.stdin.removeAllListeners();
|
|
3490
|
+
process.stdin.destroy();
|
|
3491
|
+
resolve2(Buffer.concat(chunks).toString("utf-8"));
|
|
3492
|
+
}, STDIN_TIMEOUT_MS);
|
|
3493
|
+
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
3494
|
+
process.stdin.on("end", () => {
|
|
3495
|
+
clearTimeout(timer);
|
|
3496
|
+
resolve2(Buffer.concat(chunks).toString("utf-8"));
|
|
3497
|
+
});
|
|
3498
|
+
process.stdin.on("error", (err) => {
|
|
3499
|
+
clearTimeout(timer);
|
|
3500
|
+
reject(err);
|
|
3501
|
+
});
|
|
3502
|
+
process.stdin.resume();
|
|
3503
|
+
});
|
|
3504
|
+
}
|
|
3505
|
+
|
|
3506
|
+
// src/learner/storage.ts
|
|
3507
|
+
init_constants();
|
|
3508
|
+
import fs23 from "fs";
|
|
3509
|
+
import path20 from "path";
|
|
3510
|
+
var MAX_RESPONSE_LENGTH = 2e3;
|
|
3511
|
+
var DEFAULT_STATE = {
|
|
3512
|
+
sessionId: null,
|
|
3513
|
+
eventCount: 0,
|
|
3514
|
+
lastAnalysisTimestamp: null
|
|
3515
|
+
};
|
|
3516
|
+
function ensureLearningDir() {
|
|
3517
|
+
if (!fs23.existsSync(LEARNING_DIR)) {
|
|
3518
|
+
fs23.mkdirSync(LEARNING_DIR, { recursive: true });
|
|
3519
|
+
}
|
|
3520
|
+
}
|
|
3521
|
+
function sessionFilePath() {
|
|
3522
|
+
return path20.join(LEARNING_DIR, LEARNING_SESSION_FILE);
|
|
3523
|
+
}
|
|
3524
|
+
function stateFilePath() {
|
|
3525
|
+
return path20.join(LEARNING_DIR, LEARNING_STATE_FILE);
|
|
3526
|
+
}
|
|
3527
|
+
function truncateResponse(response) {
|
|
3528
|
+
const str = JSON.stringify(response);
|
|
3529
|
+
if (str.length <= MAX_RESPONSE_LENGTH) return response;
|
|
3530
|
+
return { _truncated: str.slice(0, MAX_RESPONSE_LENGTH) };
|
|
3531
|
+
}
|
|
3532
|
+
function appendEvent(event) {
|
|
3533
|
+
ensureLearningDir();
|
|
3534
|
+
const truncated = { ...event, tool_response: truncateResponse(event.tool_response) };
|
|
3535
|
+
const filePath = sessionFilePath();
|
|
3536
|
+
fs23.appendFileSync(filePath, JSON.stringify(truncated) + "\n");
|
|
3537
|
+
const count = getEventCount();
|
|
3538
|
+
if (count > LEARNING_MAX_EVENTS) {
|
|
3539
|
+
const lines = fs23.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
3540
|
+
const kept = lines.slice(lines.length - LEARNING_MAX_EVENTS);
|
|
3541
|
+
fs23.writeFileSync(filePath, kept.join("\n") + "\n");
|
|
3542
|
+
}
|
|
3543
|
+
}
|
|
3544
|
+
function readAllEvents() {
|
|
3545
|
+
const filePath = sessionFilePath();
|
|
3546
|
+
if (!fs23.existsSync(filePath)) return [];
|
|
3547
|
+
const lines = fs23.readFileSync(filePath, "utf-8").split("\n").filter(Boolean);
|
|
3548
|
+
return lines.map((line) => JSON.parse(line));
|
|
3549
|
+
}
|
|
3550
|
+
function getEventCount() {
|
|
3551
|
+
const filePath = sessionFilePath();
|
|
3552
|
+
if (!fs23.existsSync(filePath)) return 0;
|
|
3553
|
+
const content = fs23.readFileSync(filePath, "utf-8");
|
|
3554
|
+
return content.split("\n").filter(Boolean).length;
|
|
3555
|
+
}
|
|
3556
|
+
function clearSession() {
|
|
3557
|
+
const filePath = sessionFilePath();
|
|
3558
|
+
if (fs23.existsSync(filePath)) fs23.unlinkSync(filePath);
|
|
3559
|
+
}
|
|
3560
|
+
function readState2() {
|
|
3561
|
+
const filePath = stateFilePath();
|
|
3562
|
+
if (!fs23.existsSync(filePath)) return { ...DEFAULT_STATE };
|
|
3563
|
+
try {
|
|
3564
|
+
return JSON.parse(fs23.readFileSync(filePath, "utf-8"));
|
|
3565
|
+
} catch {
|
|
3566
|
+
return { ...DEFAULT_STATE };
|
|
3567
|
+
}
|
|
3568
|
+
}
|
|
3569
|
+
function writeState2(state) {
|
|
3570
|
+
ensureLearningDir();
|
|
3571
|
+
fs23.writeFileSync(stateFilePath(), JSON.stringify(state, null, 2));
|
|
3572
|
+
}
|
|
3573
|
+
function resetState() {
|
|
3574
|
+
writeState2({ ...DEFAULT_STATE });
|
|
3575
|
+
}
|
|
3576
|
+
|
|
3577
|
+
// src/learner/writer.ts
|
|
3578
|
+
import fs24 from "fs";
|
|
3579
|
+
import path21 from "path";
|
|
3580
|
+
var LEARNED_START = "<!-- caliber:learned -->";
|
|
3581
|
+
var LEARNED_END = "<!-- /caliber:learned -->";
|
|
3582
|
+
function writeLearnedContent(update) {
|
|
3583
|
+
const written = [];
|
|
3584
|
+
if (update.claudeMdLearnedSection) {
|
|
3585
|
+
writeLearnedSection(update.claudeMdLearnedSection);
|
|
3586
|
+
written.push("CLAUDE.md");
|
|
3587
|
+
}
|
|
3588
|
+
if (update.skills?.length) {
|
|
3589
|
+
for (const skill of update.skills) {
|
|
3590
|
+
const skillPath = writeLearnedSkill(skill);
|
|
3591
|
+
written.push(skillPath);
|
|
3592
|
+
}
|
|
3593
|
+
}
|
|
3594
|
+
return written;
|
|
3595
|
+
}
|
|
3596
|
+
function writeLearnedSection(content) {
|
|
3597
|
+
const claudeMdPath = "CLAUDE.md";
|
|
3598
|
+
let existing = "";
|
|
3599
|
+
if (fs24.existsSync(claudeMdPath)) {
|
|
3600
|
+
existing = fs24.readFileSync(claudeMdPath, "utf-8");
|
|
3601
|
+
}
|
|
3602
|
+
const section = `${LEARNED_START}
|
|
3603
|
+
${content}
|
|
3604
|
+
${LEARNED_END}`;
|
|
3605
|
+
const startIdx = existing.indexOf(LEARNED_START);
|
|
3606
|
+
const endIdx = existing.indexOf(LEARNED_END);
|
|
3607
|
+
let updated;
|
|
3608
|
+
if (startIdx !== -1 && endIdx !== -1) {
|
|
3609
|
+
updated = existing.slice(0, startIdx) + section + existing.slice(endIdx + LEARNED_END.length);
|
|
3610
|
+
} else {
|
|
3611
|
+
const separator = existing.endsWith("\n") || existing === "" ? "" : "\n";
|
|
3612
|
+
updated = existing + separator + "\n" + section + "\n";
|
|
3613
|
+
}
|
|
3614
|
+
fs24.writeFileSync(claudeMdPath, updated);
|
|
3615
|
+
}
|
|
3616
|
+
function writeLearnedSkill(skill) {
|
|
3617
|
+
const skillDir = path21.join(".claude", "skills", skill.name);
|
|
3618
|
+
if (!fs24.existsSync(skillDir)) fs24.mkdirSync(skillDir, { recursive: true });
|
|
3619
|
+
const skillPath = path21.join(skillDir, "SKILL.md");
|
|
3620
|
+
if (!skill.isNew && fs24.existsSync(skillPath)) {
|
|
3621
|
+
const existing = fs24.readFileSync(skillPath, "utf-8");
|
|
3622
|
+
fs24.writeFileSync(skillPath, existing.trimEnd() + "\n\n" + skill.content);
|
|
3623
|
+
} else {
|
|
3624
|
+
const frontmatter = [
|
|
3625
|
+
"---",
|
|
3626
|
+
`name: ${skill.name}`,
|
|
3627
|
+
`description: ${skill.description}`,
|
|
3628
|
+
"---",
|
|
3629
|
+
""
|
|
3630
|
+
].join("\n");
|
|
3631
|
+
fs24.writeFileSync(skillPath, frontmatter + skill.content);
|
|
3632
|
+
}
|
|
3633
|
+
return skillPath;
|
|
3634
|
+
}
|
|
3635
|
+
function readLearnedSection() {
|
|
3636
|
+
const claudeMdPath = "CLAUDE.md";
|
|
3637
|
+
if (!fs24.existsSync(claudeMdPath)) return null;
|
|
3638
|
+
const content = fs24.readFileSync(claudeMdPath, "utf-8");
|
|
3639
|
+
const startIdx = content.indexOf(LEARNED_START);
|
|
3640
|
+
const endIdx = content.indexOf(LEARNED_END);
|
|
3641
|
+
if (startIdx === -1 || endIdx === -1) return null;
|
|
3642
|
+
return content.slice(startIdx + LEARNED_START.length, endIdx).trim();
|
|
3643
|
+
}
|
|
3644
|
+
|
|
3645
|
+
// src/commands/learn.ts
|
|
3646
|
+
async function learnObserveCommand(options) {
|
|
3647
|
+
try {
|
|
3648
|
+
const raw = await readStdin();
|
|
3649
|
+
if (!raw.trim()) return;
|
|
3650
|
+
const hookData = JSON.parse(raw);
|
|
3651
|
+
const event = {
|
|
3652
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3653
|
+
session_id: hookData.session_id || "unknown",
|
|
3654
|
+
hook_event_name: options.failure ? "PostToolUseFailure" : "PostToolUse",
|
|
3655
|
+
tool_name: hookData.tool_name || "unknown",
|
|
3656
|
+
tool_input: hookData.tool_input || {},
|
|
3657
|
+
tool_response: hookData.tool_response || {},
|
|
3658
|
+
tool_use_id: hookData.tool_use_id || "",
|
|
3659
|
+
cwd: hookData.cwd || process.cwd()
|
|
3660
|
+
};
|
|
3661
|
+
appendEvent(event);
|
|
3662
|
+
const state = readState2();
|
|
3663
|
+
state.eventCount++;
|
|
3664
|
+
if (!state.sessionId) state.sessionId = event.session_id;
|
|
3665
|
+
writeState2(state);
|
|
3666
|
+
} catch {
|
|
3667
|
+
}
|
|
3668
|
+
}
|
|
3669
|
+
async function learnFinalizeCommand() {
|
|
3670
|
+
try {
|
|
3671
|
+
const auth2 = getStoredAuth();
|
|
3672
|
+
if (!auth2) {
|
|
3673
|
+
clearSession();
|
|
3674
|
+
resetState();
|
|
3675
|
+
return;
|
|
3676
|
+
}
|
|
3677
|
+
const events = readAllEvents();
|
|
3678
|
+
if (!events.length) {
|
|
3679
|
+
clearSession();
|
|
3680
|
+
resetState();
|
|
3681
|
+
return;
|
|
3682
|
+
}
|
|
3683
|
+
const existingConfigs = readExistingConfigs(process.cwd());
|
|
3684
|
+
const existingLearnedSection = readLearnedSection();
|
|
3685
|
+
const existingSkills = existingConfigs.claudeSkills || [];
|
|
3686
|
+
const response = await apiRequest("/api/learn/analyze", {
|
|
3687
|
+
method: "POST",
|
|
3688
|
+
body: {
|
|
3689
|
+
events,
|
|
3690
|
+
existingClaudeMd: existingConfigs.claudeMd || "",
|
|
3691
|
+
existingLearnedSection,
|
|
3692
|
+
existingSkills
|
|
3693
|
+
}
|
|
3694
|
+
});
|
|
3695
|
+
if (response.claudeMdLearnedSection || response.skills?.length) {
|
|
3696
|
+
writeLearnedContent({
|
|
3697
|
+
claudeMdLearnedSection: response.claudeMdLearnedSection,
|
|
3698
|
+
skills: response.skills
|
|
3699
|
+
});
|
|
3700
|
+
}
|
|
3701
|
+
} catch {
|
|
3702
|
+
} finally {
|
|
3703
|
+
clearSession();
|
|
3704
|
+
resetState();
|
|
3705
|
+
}
|
|
3706
|
+
}
|
|
3707
|
+
async function learnInstallCommand() {
|
|
3708
|
+
const result = installLearningHooks();
|
|
3709
|
+
if (result.alreadyInstalled) {
|
|
3710
|
+
console.log(chalk14.dim("Learning hooks already installed."));
|
|
3711
|
+
return;
|
|
3712
|
+
}
|
|
3713
|
+
console.log(chalk14.green("\u2713") + " Learning hooks installed in .claude/settings.json");
|
|
3714
|
+
console.log(chalk14.dim(" PostToolUse, PostToolUseFailure, and SessionEnd hooks active."));
|
|
3715
|
+
console.log(chalk14.dim(" Session learnings will be written to CLAUDE.md and skills."));
|
|
3716
|
+
}
|
|
3717
|
+
async function learnRemoveCommand() {
|
|
3718
|
+
const result = removeLearningHooks();
|
|
3719
|
+
if (result.notFound) {
|
|
3720
|
+
console.log(chalk14.dim("Learning hooks not found."));
|
|
3721
|
+
return;
|
|
3722
|
+
}
|
|
3723
|
+
console.log(chalk14.green("\u2713") + " Learning hooks removed from .claude/settings.json");
|
|
3724
|
+
}
|
|
3725
|
+
async function learnStatusCommand() {
|
|
3726
|
+
const installed = areLearningHooksInstalled();
|
|
3727
|
+
const state = readState2();
|
|
3728
|
+
const eventCount = getEventCount();
|
|
3729
|
+
console.log(chalk14.bold("Session Learning Status"));
|
|
3730
|
+
console.log();
|
|
3731
|
+
if (installed) {
|
|
3732
|
+
console.log(chalk14.green("\u2713") + " Learning hooks are " + chalk14.green("installed"));
|
|
3733
|
+
} else {
|
|
3734
|
+
console.log(chalk14.dim("\u2717") + " Learning hooks are " + chalk14.yellow("not installed"));
|
|
3735
|
+
console.log(chalk14.dim(" Run `caliber learn install` to enable session learning."));
|
|
3736
|
+
}
|
|
3737
|
+
console.log();
|
|
3738
|
+
console.log(`Events recorded: ${chalk14.cyan(String(eventCount))}`);
|
|
3739
|
+
console.log(`Total this session: ${chalk14.cyan(String(state.eventCount))}`);
|
|
3740
|
+
if (state.lastAnalysisTimestamp) {
|
|
3741
|
+
console.log(`Last analysis: ${chalk14.cyan(state.lastAnalysisTimestamp)}`);
|
|
3742
|
+
} else {
|
|
3743
|
+
console.log(`Last analysis: ${chalk14.dim("none")}`);
|
|
3744
|
+
}
|
|
3745
|
+
const learnedSection = readLearnedSection();
|
|
3746
|
+
if (learnedSection) {
|
|
3747
|
+
const lineCount = learnedSection.split("\n").filter(Boolean).length;
|
|
3748
|
+
console.log(`
|
|
3749
|
+
Learned items in CLAUDE.md: ${chalk14.cyan(String(lineCount))}`);
|
|
3750
|
+
}
|
|
3751
|
+
}
|
|
3752
|
+
|
|
3374
3753
|
// src/cli.ts
|
|
3375
|
-
var __dirname2 =
|
|
3754
|
+
var __dirname2 = path22.dirname(fileURLToPath3(import.meta.url));
|
|
3376
3755
|
var pkg3 = JSON.parse(
|
|
3377
|
-
|
|
3756
|
+
fs25.readFileSync(path22.resolve(__dirname2, "..", "package.json"), "utf-8")
|
|
3378
3757
|
);
|
|
3379
3758
|
var program = new Command();
|
|
3380
3759
|
var displayVersion = process.env.CALIBER_LOCAL ? `${pkg3.version}-local` : pkg3.version;
|
|
@@ -3394,24 +3773,30 @@ var hooks = program.command("hooks").description("Manage Claude Code session hoo
|
|
|
3394
3773
|
hooks.command("install").description("Install auto-refresh SessionEnd hook").action(hooksInstallCommand);
|
|
3395
3774
|
hooks.command("remove").description("Remove auto-refresh SessionEnd hook").action(hooksRemoveCommand);
|
|
3396
3775
|
hooks.command("status").description("Check if auto-refresh hook is installed").action(hooksStatusCommand);
|
|
3776
|
+
var learn = program.command("learn").description("Session learning \u2014 observe tool usage and extract reusable instructions");
|
|
3777
|
+
learn.command("observe").description("Record a tool event from stdin (called by hooks)").option("--failure", "Mark event as a tool failure").action(learnObserveCommand);
|
|
3778
|
+
learn.command("finalize").description("Analyze session events and update CLAUDE.md (called on SessionEnd)").action(learnFinalizeCommand);
|
|
3779
|
+
learn.command("install").description("Install learning hooks into .claude/settings.json").action(learnInstallCommand);
|
|
3780
|
+
learn.command("remove").description("Remove learning hooks from .claude/settings.json").action(learnRemoveCommand);
|
|
3781
|
+
learn.command("status").description("Show learning system status").action(learnStatusCommand);
|
|
3397
3782
|
|
|
3398
3783
|
// src/utils/version-check.ts
|
|
3399
|
-
import
|
|
3400
|
-
import
|
|
3784
|
+
import fs26 from "fs";
|
|
3785
|
+
import path23 from "path";
|
|
3401
3786
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
3402
3787
|
import { execSync as execSync5 } from "child_process";
|
|
3403
|
-
import
|
|
3788
|
+
import chalk15 from "chalk";
|
|
3404
3789
|
import ora10 from "ora";
|
|
3405
3790
|
import confirm3 from "@inquirer/confirm";
|
|
3406
|
-
var __dirname_vc =
|
|
3791
|
+
var __dirname_vc = path23.dirname(fileURLToPath4(import.meta.url));
|
|
3407
3792
|
var pkg4 = JSON.parse(
|
|
3408
|
-
|
|
3793
|
+
fs26.readFileSync(path23.resolve(__dirname_vc, "..", "package.json"), "utf-8")
|
|
3409
3794
|
);
|
|
3410
3795
|
function getInstalledVersion() {
|
|
3411
3796
|
try {
|
|
3412
3797
|
const globalRoot = execSync5("npm root -g", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
3413
|
-
const pkgPath =
|
|
3414
|
-
return JSON.parse(
|
|
3798
|
+
const pkgPath = path23.join(globalRoot, "@caliber-ai", "cli", "package.json");
|
|
3799
|
+
return JSON.parse(fs26.readFileSync(pkgPath, "utf-8")).version;
|
|
3415
3800
|
} catch {
|
|
3416
3801
|
return null;
|
|
3417
3802
|
}
|
|
@@ -3434,17 +3819,17 @@ async function checkForUpdates() {
|
|
|
3434
3819
|
const isInteractive = process.stdin.isTTY === true;
|
|
3435
3820
|
if (!isInteractive) {
|
|
3436
3821
|
console.log(
|
|
3437
|
-
|
|
3822
|
+
chalk15.yellow(
|
|
3438
3823
|
`
|
|
3439
3824
|
Update available: ${current} -> ${latest}
|
|
3440
|
-
Run ${
|
|
3825
|
+
Run ${chalk15.bold("npm install -g @caliber-ai/cli")} to upgrade.
|
|
3441
3826
|
`
|
|
3442
3827
|
)
|
|
3443
3828
|
);
|
|
3444
3829
|
return;
|
|
3445
3830
|
}
|
|
3446
3831
|
console.log(
|
|
3447
|
-
|
|
3832
|
+
chalk15.yellow(`
|
|
3448
3833
|
Update available: ${current} -> ${latest}`)
|
|
3449
3834
|
);
|
|
3450
3835
|
const shouldUpdate = await confirm3({ message: "Would you like to update now? (Y/n)", default: true });
|
|
@@ -3458,13 +3843,13 @@ Update available: ${current} -> ${latest}`)
|
|
|
3458
3843
|
const installed = getInstalledVersion();
|
|
3459
3844
|
if (installed !== latest) {
|
|
3460
3845
|
spinner.fail(`Update incomplete \u2014 got ${installed ?? "unknown"}, expected ${latest}`);
|
|
3461
|
-
console.log(
|
|
3846
|
+
console.log(chalk15.yellow(`Run ${chalk15.bold(`npm install -g @caliber-ai/cli@${latest}`)} manually.
|
|
3462
3847
|
`));
|
|
3463
3848
|
return;
|
|
3464
3849
|
}
|
|
3465
|
-
spinner.succeed(
|
|
3850
|
+
spinner.succeed(chalk15.green(`Updated to ${latest}`));
|
|
3466
3851
|
const args = process.argv.slice(2);
|
|
3467
|
-
console.log(
|
|
3852
|
+
console.log(chalk15.dim(`
|
|
3468
3853
|
Restarting: caliber ${args.join(" ")}
|
|
3469
3854
|
`));
|
|
3470
3855
|
execSync5(`caliber ${args.map((a) => JSON.stringify(a)).join(" ")}`, {
|
|
@@ -3475,10 +3860,10 @@ Restarting: caliber ${args.join(" ")}
|
|
|
3475
3860
|
} catch (err) {
|
|
3476
3861
|
spinner.fail("Update failed");
|
|
3477
3862
|
const msg = err instanceof Error ? err.message : "";
|
|
3478
|
-
if (msg && !msg.includes("SIGTERM")) console.log(
|
|
3863
|
+
if (msg && !msg.includes("SIGTERM")) console.log(chalk15.dim(` ${msg.split("\n")[0]}`));
|
|
3479
3864
|
console.log(
|
|
3480
|
-
|
|
3481
|
-
`Run ${
|
|
3865
|
+
chalk15.yellow(
|
|
3866
|
+
`Run ${chalk15.bold(`npm install -g @caliber-ai/cli@${latest}`)} manually to upgrade.
|
|
3482
3867
|
`
|
|
3483
3868
|
)
|
|
3484
3869
|
);
|