@caliber-ai/cli 0.22.0 → 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 +405 -27
- 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
|
|
@@ -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",
|
|
@@ -3371,10 +3379,374 @@ async function reviewCommand(message, options) {
|
|
|
3371
3379
|
await submitReview({ rating, bestPart, biggestGap, wouldRecommend });
|
|
3372
3380
|
}
|
|
3373
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
|
+
|
|
3374
3746
|
// src/cli.ts
|
|
3375
|
-
var __dirname2 =
|
|
3747
|
+
var __dirname2 = path22.dirname(fileURLToPath3(import.meta.url));
|
|
3376
3748
|
var pkg3 = JSON.parse(
|
|
3377
|
-
|
|
3749
|
+
fs25.readFileSync(path22.resolve(__dirname2, "..", "package.json"), "utf-8")
|
|
3378
3750
|
);
|
|
3379
3751
|
var program = new Command();
|
|
3380
3752
|
var displayVersion = process.env.CALIBER_LOCAL ? `${pkg3.version}-local` : pkg3.version;
|
|
@@ -3394,24 +3766,30 @@ var hooks = program.command("hooks").description("Manage Claude Code session hoo
|
|
|
3394
3766
|
hooks.command("install").description("Install auto-refresh SessionEnd hook").action(hooksInstallCommand);
|
|
3395
3767
|
hooks.command("remove").description("Remove auto-refresh SessionEnd hook").action(hooksRemoveCommand);
|
|
3396
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);
|
|
3397
3775
|
|
|
3398
3776
|
// src/utils/version-check.ts
|
|
3399
|
-
import
|
|
3400
|
-
import
|
|
3777
|
+
import fs26 from "fs";
|
|
3778
|
+
import path23 from "path";
|
|
3401
3779
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
3402
3780
|
import { execSync as execSync5 } from "child_process";
|
|
3403
|
-
import
|
|
3781
|
+
import chalk15 from "chalk";
|
|
3404
3782
|
import ora10 from "ora";
|
|
3405
3783
|
import confirm3 from "@inquirer/confirm";
|
|
3406
|
-
var __dirname_vc =
|
|
3784
|
+
var __dirname_vc = path23.dirname(fileURLToPath4(import.meta.url));
|
|
3407
3785
|
var pkg4 = JSON.parse(
|
|
3408
|
-
|
|
3786
|
+
fs26.readFileSync(path23.resolve(__dirname_vc, "..", "package.json"), "utf-8")
|
|
3409
3787
|
);
|
|
3410
3788
|
function getInstalledVersion() {
|
|
3411
3789
|
try {
|
|
3412
3790
|
const globalRoot = execSync5("npm root -g", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
3413
|
-
const pkgPath =
|
|
3414
|
-
return JSON.parse(
|
|
3791
|
+
const pkgPath = path23.join(globalRoot, "@caliber-ai", "cli", "package.json");
|
|
3792
|
+
return JSON.parse(fs26.readFileSync(pkgPath, "utf-8")).version;
|
|
3415
3793
|
} catch {
|
|
3416
3794
|
return null;
|
|
3417
3795
|
}
|
|
@@ -3434,17 +3812,17 @@ async function checkForUpdates() {
|
|
|
3434
3812
|
const isInteractive = process.stdin.isTTY === true;
|
|
3435
3813
|
if (!isInteractive) {
|
|
3436
3814
|
console.log(
|
|
3437
|
-
|
|
3815
|
+
chalk15.yellow(
|
|
3438
3816
|
`
|
|
3439
3817
|
Update available: ${current} -> ${latest}
|
|
3440
|
-
Run ${
|
|
3818
|
+
Run ${chalk15.bold("npm install -g @caliber-ai/cli")} to upgrade.
|
|
3441
3819
|
`
|
|
3442
3820
|
)
|
|
3443
3821
|
);
|
|
3444
3822
|
return;
|
|
3445
3823
|
}
|
|
3446
3824
|
console.log(
|
|
3447
|
-
|
|
3825
|
+
chalk15.yellow(`
|
|
3448
3826
|
Update available: ${current} -> ${latest}`)
|
|
3449
3827
|
);
|
|
3450
3828
|
const shouldUpdate = await confirm3({ message: "Would you like to update now? (Y/n)", default: true });
|
|
@@ -3458,13 +3836,13 @@ Update available: ${current} -> ${latest}`)
|
|
|
3458
3836
|
const installed = getInstalledVersion();
|
|
3459
3837
|
if (installed !== latest) {
|
|
3460
3838
|
spinner.fail(`Update incomplete \u2014 got ${installed ?? "unknown"}, expected ${latest}`);
|
|
3461
|
-
console.log(
|
|
3839
|
+
console.log(chalk15.yellow(`Run ${chalk15.bold(`npm install -g @caliber-ai/cli@${latest}`)} manually.
|
|
3462
3840
|
`));
|
|
3463
3841
|
return;
|
|
3464
3842
|
}
|
|
3465
|
-
spinner.succeed(
|
|
3843
|
+
spinner.succeed(chalk15.green(`Updated to ${latest}`));
|
|
3466
3844
|
const args = process.argv.slice(2);
|
|
3467
|
-
console.log(
|
|
3845
|
+
console.log(chalk15.dim(`
|
|
3468
3846
|
Restarting: caliber ${args.join(" ")}
|
|
3469
3847
|
`));
|
|
3470
3848
|
execSync5(`caliber ${args.map((a) => JSON.stringify(a)).join(" ")}`, {
|
|
@@ -3475,10 +3853,10 @@ Restarting: caliber ${args.join(" ")}
|
|
|
3475
3853
|
} catch (err) {
|
|
3476
3854
|
spinner.fail("Update failed");
|
|
3477
3855
|
const msg = err instanceof Error ? err.message : "";
|
|
3478
|
-
if (msg && !msg.includes("SIGTERM")) console.log(
|
|
3856
|
+
if (msg && !msg.includes("SIGTERM")) console.log(chalk15.dim(` ${msg.split("\n")[0]}`));
|
|
3479
3857
|
console.log(
|
|
3480
|
-
|
|
3481
|
-
`Run ${
|
|
3858
|
+
chalk15.yellow(
|
|
3859
|
+
`Run ${chalk15.bold(`npm install -g @caliber-ai/cli@${latest}`)} manually to upgrade.
|
|
3482
3860
|
`
|
|
3483
3861
|
)
|
|
3484
3862
|
);
|