hippo-memory 0.22.1 → 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/README.md +26 -15
- package/dist/capture.d.ts.map +1 -1
- package/dist/capture.js +48 -19
- package/dist/capture.js.map +1 -1
- package/dist/cli.js +313 -40
- package/dist/cli.js.map +1 -1
- package/dist/hooks.d.ts +66 -15
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +382 -59
- package/dist/hooks.js.map +1 -1
- package/dist/postinstall.d.ts +2 -0
- package/dist/postinstall.d.ts.map +1 -0
- package/dist/postinstall.js +13 -0
- package/dist/postinstall.js.map +1 -0
- package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
- package/extensions/openclaw-plugin/package.json +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +3 -1
- package/scripts/postinstall.cjs +16 -0
package/dist/cli.js
CHANGED
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
import * as path from 'path';
|
|
28
28
|
import * as fs from 'fs';
|
|
29
29
|
import * as os from 'os';
|
|
30
|
-
import { execSync } from 'child_process';
|
|
31
|
-
import { installJsonHooks, uninstallJsonHooks, resolveJsonHookPaths, detectInstalledTools, defaultSleepLogPath, } from './hooks.js';
|
|
30
|
+
import { execSync, spawn } from 'child_process';
|
|
31
|
+
import { installJsonHooks, uninstallJsonHooks, resolveJsonHookPaths, detectInstalledTools, defaultSleepLogPath, ensureCodexWrapperInstalled, installCodexWrapper, uninstallCodexWrapper, resolveCodexSessionTranscript, resolveCodexWrapperPaths, } from './hooks.js';
|
|
32
32
|
import { createMemory, calculateStrength, calculateRewardFactor, deriveHalfLife, resolveConfidence, applyOutcome, computeSchemaFit, Layer, DECISION_HALF_LIFE_DAYS, } from './memory.js';
|
|
33
33
|
import { getHippoRoot, isInitialized, initStore, writeEntry, readEntry, deleteEntry, loadAllEntries, loadSearchEntries, loadIndex, saveIndex, loadStats, updateStats, saveActiveTaskSnapshot, loadActiveTaskSnapshot, clearActiveTaskSnapshot, appendSessionEvent, listSessionEvents, listMemoryConflicts, resolveConflict, saveSessionHandoff, loadLatestHandoff, loadHandoffById, } from './store.js';
|
|
34
34
|
import { markRetrieved, estimateTokens, hybridSearch, physicsSearch, explainMatch, textOverlap } from './search.js';
|
|
@@ -67,6 +67,10 @@ function parseArgs(argv) {
|
|
|
67
67
|
let i = 0;
|
|
68
68
|
while (i < rest.length) {
|
|
69
69
|
const part = rest[i];
|
|
70
|
+
if (part === '--') {
|
|
71
|
+
args.push(...rest.slice(i + 1));
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
70
74
|
if (part.startsWith('--')) {
|
|
71
75
|
const key = part.slice(2);
|
|
72
76
|
const next = rest[i + 1];
|
|
@@ -279,19 +283,19 @@ function autoInstallHooks(quiet) {
|
|
|
279
283
|
if (hook === 'claude-code' || hook === 'opencode') {
|
|
280
284
|
const result = installJsonHooks(hook);
|
|
281
285
|
if (result.installedSessionEnd) {
|
|
282
|
-
console.log(` Auto-installed hippo
|
|
286
|
+
console.log(` Auto-installed hippo session-end SessionEnd hook in ${hook} settings`);
|
|
283
287
|
}
|
|
284
288
|
if (result.installedSessionStart) {
|
|
285
289
|
console.log(` Auto-installed hippo last-sleep SessionStart hook in ${hook} settings`);
|
|
286
290
|
}
|
|
287
|
-
if (result.installedSessionCapture) {
|
|
288
|
-
console.log(` Auto-installed hippo capture SessionEnd hook in ${hook} settings`);
|
|
289
|
-
}
|
|
290
291
|
if (result.migratedFromStop) {
|
|
291
292
|
console.log(` Migrated legacy Stop hook → SessionEnd (no longer runs every turn)`);
|
|
292
293
|
}
|
|
293
|
-
if (result.
|
|
294
|
-
console.log(` Migrated
|
|
294
|
+
if (result.migratedSplitSessionEnd) {
|
|
295
|
+
console.log(` Migrated split sleep+capture SessionEnd entries → single detached hippo session-end`);
|
|
296
|
+
}
|
|
297
|
+
else if (result.migratedLegacySessionEnd) {
|
|
298
|
+
console.log(` Migrated legacy SessionEnd entry to the new detached form`);
|
|
295
299
|
}
|
|
296
300
|
}
|
|
297
301
|
}
|
|
@@ -798,6 +802,229 @@ function cmdLastSleep(flags) {
|
|
|
798
802
|
catch { /* non-fatal */ }
|
|
799
803
|
}
|
|
800
804
|
}
|
|
805
|
+
/**
|
|
806
|
+
* SessionEnd entry point. Claude Code / OpenCode fire this on /exit while
|
|
807
|
+
* tearing down the TUI, which kills any child that is still running when
|
|
808
|
+
* the parent returns. Running sleep + capture synchronously here means both
|
|
809
|
+
* get SIGTERM'd mid-consolidation.
|
|
810
|
+
*
|
|
811
|
+
* So we do the minimum inline (read stdin for transcript_path), then spawn
|
|
812
|
+
* a fully detached Node child that runs sleep → capture and exit the parent
|
|
813
|
+
* immediately. The child writes to the log file and survives TUI teardown;
|
|
814
|
+
* the next SessionStart reads the log via `hippo last-sleep`.
|
|
815
|
+
*/
|
|
816
|
+
function cmdSessionEnd(hippoRoot, flags) {
|
|
817
|
+
const logFile = typeof flags['log-file'] === 'string' ? flags['log-file'] : null;
|
|
818
|
+
// Read stdin synchronously. The SessionEnd hook payload carries
|
|
819
|
+
// `transcript_path` as JSON; we extract it here and pass it to the worker
|
|
820
|
+
// via argv so the detached child doesn't need to inherit stdin.
|
|
821
|
+
let transcriptPath = null;
|
|
822
|
+
try {
|
|
823
|
+
const stdinText = fs.readFileSync(0, 'utf8');
|
|
824
|
+
if (stdinText && stdinText.trim().startsWith('{')) {
|
|
825
|
+
const payload = JSON.parse(stdinText);
|
|
826
|
+
if (typeof payload.transcript_path === 'string') {
|
|
827
|
+
transcriptPath = payload.transcript_path;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
catch {
|
|
832
|
+
// No stdin, not JSON, or read failure — capture will fall back to
|
|
833
|
+
// transcript auto-discovery.
|
|
834
|
+
}
|
|
835
|
+
const workerArgs = [process.argv[1], '__session-end-worker'];
|
|
836
|
+
if (logFile)
|
|
837
|
+
workerArgs.push('--log-file', logFile);
|
|
838
|
+
if (transcriptPath)
|
|
839
|
+
workerArgs.push('--transcript', transcriptPath);
|
|
840
|
+
try {
|
|
841
|
+
const child = spawn(process.execPath, workerArgs, {
|
|
842
|
+
detached: true,
|
|
843
|
+
stdio: 'ignore',
|
|
844
|
+
windowsHide: true,
|
|
845
|
+
});
|
|
846
|
+
child.unref();
|
|
847
|
+
}
|
|
848
|
+
catch (err) {
|
|
849
|
+
// If spawn fails, run inline as a last resort — better late output than
|
|
850
|
+
// no consolidation at all.
|
|
851
|
+
cmdSessionEndWorker(hippoRoot, flags);
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* Detached worker that runs sleep, then capture. Invoked via the internal
|
|
857
|
+
* `__session-end-worker` subcommand (not user-facing). Failures in one stage
|
|
858
|
+
* do not block the other.
|
|
859
|
+
*/
|
|
860
|
+
function cmdSessionEndWorker(hippoRoot, flags) {
|
|
861
|
+
try {
|
|
862
|
+
cmdSleep(hippoRoot, flags);
|
|
863
|
+
}
|
|
864
|
+
catch {
|
|
865
|
+
// sleep errors are already tee'd to the log file via cmdSleep's
|
|
866
|
+
// `[hippo] sleep failed: ...` line. Continue to capture regardless.
|
|
867
|
+
}
|
|
868
|
+
try {
|
|
869
|
+
const captureOpts = {
|
|
870
|
+
source: 'last-session',
|
|
871
|
+
transcriptPath: typeof flags['transcript'] === 'string'
|
|
872
|
+
? flags['transcript']
|
|
873
|
+
: undefined,
|
|
874
|
+
logFile: typeof flags['log-file'] === 'string'
|
|
875
|
+
? flags['log-file']
|
|
876
|
+
: undefined,
|
|
877
|
+
dryRun: false,
|
|
878
|
+
global: false,
|
|
879
|
+
};
|
|
880
|
+
cmdCapture(hippoRoot, captureOpts);
|
|
881
|
+
}
|
|
882
|
+
catch {
|
|
883
|
+
// Same treatment — the failure line is already in the log.
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
function loadCodexWrapperMetadata() {
|
|
887
|
+
const { metadataPath } = resolveCodexWrapperPaths();
|
|
888
|
+
if (!fs.existsSync(metadataPath)) {
|
|
889
|
+
throw new Error('Codex wrapper is not installed. Run `hippo hook install codex` first.');
|
|
890
|
+
}
|
|
891
|
+
return JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
|
|
892
|
+
}
|
|
893
|
+
function quoteCmdArg(arg) {
|
|
894
|
+
if (arg.length === 0)
|
|
895
|
+
return '""';
|
|
896
|
+
if (!/[ \t"&()^<>|]/.test(arg))
|
|
897
|
+
return arg;
|
|
898
|
+
return `"${arg.replace(/"/g, '""')}"`;
|
|
899
|
+
}
|
|
900
|
+
function spawnRealCodex(realCodexPath, forwardArgs, cwd) {
|
|
901
|
+
const ext = path.extname(realCodexPath).toLowerCase();
|
|
902
|
+
if (process.platform === 'win32' && ext === '.ps1') {
|
|
903
|
+
return spawn('powershell.exe', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', realCodexPath, ...forwardArgs], { cwd, stdio: 'inherit', windowsHide: false });
|
|
904
|
+
}
|
|
905
|
+
if (process.platform === 'win32' && (ext === '.cmd' || ext === '.bat')) {
|
|
906
|
+
const command = `"${realCodexPath}"${forwardArgs.length > 0 ? ` ${forwardArgs.map(quoteCmdArg).join(' ')}` : ''}`;
|
|
907
|
+
return spawn('cmd.exe', ['/d', '/s', '/c', command], { cwd, stdio: 'inherit', windowsHide: false });
|
|
908
|
+
}
|
|
909
|
+
return spawn(realCodexPath, forwardArgs, { cwd, stdio: 'inherit', windowsHide: false });
|
|
910
|
+
}
|
|
911
|
+
function cmdCodexRun(hippoRoot, args) {
|
|
912
|
+
const metadata = loadCodexWrapperMetadata();
|
|
913
|
+
const startedAtMs = Date.now();
|
|
914
|
+
const historyPath = metadata.historyPath;
|
|
915
|
+
const startOffsetBytes = fs.existsSync(historyPath) ? fs.statSync(historyPath).size : 0;
|
|
916
|
+
try {
|
|
917
|
+
cmdLastSleep({ path: metadata.logFile });
|
|
918
|
+
}
|
|
919
|
+
catch {
|
|
920
|
+
// best-effort only
|
|
921
|
+
}
|
|
922
|
+
const child = spawnRealCodex(metadata.realCodexPath, args, process.cwd());
|
|
923
|
+
child.on('error', (err) => {
|
|
924
|
+
console.error(`Failed to launch Codex: ${err.message}`);
|
|
925
|
+
process.exit(1);
|
|
926
|
+
});
|
|
927
|
+
child.on('exit', (code, signal) => {
|
|
928
|
+
const workerArgs = [
|
|
929
|
+
process.argv[1],
|
|
930
|
+
'__codex-session-end-worker',
|
|
931
|
+
'--codex-home',
|
|
932
|
+
path.dirname(historyPath),
|
|
933
|
+
'--history-path',
|
|
934
|
+
historyPath,
|
|
935
|
+
'--start-offset',
|
|
936
|
+
String(startOffsetBytes),
|
|
937
|
+
'--started-at',
|
|
938
|
+
String(startedAtMs),
|
|
939
|
+
'--log-file',
|
|
940
|
+
metadata.logFile,
|
|
941
|
+
];
|
|
942
|
+
try {
|
|
943
|
+
const worker = spawn(process.execPath, workerArgs, {
|
|
944
|
+
detached: true,
|
|
945
|
+
stdio: 'ignore',
|
|
946
|
+
windowsHide: true,
|
|
947
|
+
});
|
|
948
|
+
worker.unref();
|
|
949
|
+
}
|
|
950
|
+
catch {
|
|
951
|
+
// Fall back to the inline path if the detached worker cannot be created.
|
|
952
|
+
cmdCodexSessionEndWorker(hippoRoot, {
|
|
953
|
+
'codex-home': path.dirname(historyPath),
|
|
954
|
+
'history-path': historyPath,
|
|
955
|
+
'start-offset': String(startOffsetBytes),
|
|
956
|
+
'started-at': String(startedAtMs),
|
|
957
|
+
'log-file': metadata.logFile,
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
if (signal) {
|
|
961
|
+
try {
|
|
962
|
+
process.kill(process.pid, signal);
|
|
963
|
+
}
|
|
964
|
+
catch {
|
|
965
|
+
process.exit(1);
|
|
966
|
+
}
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
process.exit(code ?? 0);
|
|
970
|
+
});
|
|
971
|
+
}
|
|
972
|
+
function cmdCodexSessionEndWorker(hippoRoot, flags) {
|
|
973
|
+
const logFile = typeof flags['log-file'] === 'string' ? flags['log-file'] : undefined;
|
|
974
|
+
try {
|
|
975
|
+
cmdSleep(hippoRoot, logFile ? { 'log-file': logFile } : {});
|
|
976
|
+
}
|
|
977
|
+
catch {
|
|
978
|
+
// sleep errors are already written via cmdSleep
|
|
979
|
+
}
|
|
980
|
+
try {
|
|
981
|
+
const codexHome = typeof flags['codex-home'] === 'string'
|
|
982
|
+
? flags['codex-home']
|
|
983
|
+
: path.join(os.homedir(), '.codex');
|
|
984
|
+
const historyPath = typeof flags['history-path'] === 'string'
|
|
985
|
+
? flags['history-path']
|
|
986
|
+
: path.join(codexHome, 'history.jsonl');
|
|
987
|
+
const startOffsetBytes = parseInt(String(flags['start-offset'] ?? '0'), 10) || 0;
|
|
988
|
+
const startedAtMs = parseInt(String(flags['started-at'] ?? Date.now()), 10) || Date.now();
|
|
989
|
+
const transcriptPath = resolveCodexSessionTranscript({
|
|
990
|
+
codexHome,
|
|
991
|
+
historyPath,
|
|
992
|
+
startOffsetBytes,
|
|
993
|
+
startedAtMs,
|
|
994
|
+
}) ?? undefined;
|
|
995
|
+
const captureOpts = {
|
|
996
|
+
source: 'last-session',
|
|
997
|
+
transcriptPath,
|
|
998
|
+
logFile,
|
|
999
|
+
dryRun: false,
|
|
1000
|
+
global: false,
|
|
1001
|
+
};
|
|
1002
|
+
cmdCapture(hippoRoot, captureOpts);
|
|
1003
|
+
}
|
|
1004
|
+
catch {
|
|
1005
|
+
// capture path logs its own failures
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
function shouldAutoInstallCodexWrapper(currentCommand, currentArgs) {
|
|
1009
|
+
if (process.env.HIPPO_SKIP_AUTO_INTEGRATIONS === '1')
|
|
1010
|
+
return false;
|
|
1011
|
+
if (!['context', 'remember', 'recall', 'sleep', 'capture', 'outcome', 'status', 'init'].includes(currentCommand)) {
|
|
1012
|
+
return false;
|
|
1013
|
+
}
|
|
1014
|
+
if (currentCommand === 'init' && currentArgs.includes('--no-hooks'))
|
|
1015
|
+
return false;
|
|
1016
|
+
return true;
|
|
1017
|
+
}
|
|
1018
|
+
function maybeAutoInstallCodexWrapper(currentCommand, currentArgs) {
|
|
1019
|
+
if (!shouldAutoInstallCodexWrapper(currentCommand, currentArgs))
|
|
1020
|
+
return;
|
|
1021
|
+
try {
|
|
1022
|
+
ensureCodexWrapperInstalled();
|
|
1023
|
+
}
|
|
1024
|
+
catch {
|
|
1025
|
+
// best-effort only
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
801
1028
|
function cmdStatus(hippoRoot) {
|
|
802
1029
|
requireInit(hippoRoot);
|
|
803
1030
|
const entries = loadAllEntries(hippoRoot);
|
|
@@ -1914,7 +2141,7 @@ hippo capture --stdin <<< '<decisions, errors, lessons — 2-5 bullets>'
|
|
|
1914
2141
|
'codex': {
|
|
1915
2142
|
file: 'AGENTS.md',
|
|
1916
2143
|
description: 'OpenAI Codex',
|
|
1917
|
-
content: `
|
|
2144
|
+
content: `
|
|
1918
2145
|
## Project Memory (Hippo)
|
|
1919
2146
|
|
|
1920
2147
|
At the start of every task, run:
|
|
@@ -1933,7 +2160,8 @@ On task completion:
|
|
|
1933
2160
|
hippo outcome --good
|
|
1934
2161
|
\`\`\`
|
|
1935
2162
|
|
|
1936
|
-
When
|
|
2163
|
+
When Hippo's Codex wrapper is installed, session-end capture runs automatically.
|
|
2164
|
+
If the wrapper is not installed, capture a brief summary manually:
|
|
1937
2165
|
\`\`\`bash
|
|
1938
2166
|
hippo capture --stdin <<< '<decisions, errors, lessons — 2-5 bullets>'
|
|
1939
2167
|
\`\`\`
|
|
@@ -2094,21 +2322,26 @@ function cmdHook(args, flags) {
|
|
|
2094
2322
|
if (target === 'claude-code' || target === 'opencode') {
|
|
2095
2323
|
const result = installJsonHooks(target);
|
|
2096
2324
|
if (result.installedSessionEnd) {
|
|
2097
|
-
console.log(`Installed hippo
|
|
2325
|
+
console.log(`Installed hippo session-end SessionEnd hook in ${result.target} settings`);
|
|
2098
2326
|
}
|
|
2099
2327
|
if (result.installedSessionStart) {
|
|
2100
2328
|
console.log(`Installed hippo last-sleep SessionStart hook in ${result.target} settings`);
|
|
2101
2329
|
}
|
|
2102
|
-
if (result.installedSessionCapture) {
|
|
2103
|
-
console.log(`Installed hippo capture SessionEnd hook in ${result.target} settings`);
|
|
2104
|
-
}
|
|
2105
2330
|
if (result.migratedFromStop) {
|
|
2106
2331
|
console.log(`Migrated legacy Stop hook → SessionEnd (was running every turn; now fires once on session exit)`);
|
|
2107
2332
|
}
|
|
2108
|
-
if (result.
|
|
2109
|
-
console.log(`Migrated
|
|
2333
|
+
if (result.migratedSplitSessionEnd) {
|
|
2334
|
+
console.log(`Migrated split sleep+capture SessionEnd entries → single detached hippo session-end`);
|
|
2335
|
+
}
|
|
2336
|
+
else if (result.migratedLegacySessionEnd) {
|
|
2337
|
+
console.log(`Migrated legacy SessionEnd entry to the new detached form`);
|
|
2110
2338
|
}
|
|
2111
2339
|
}
|
|
2340
|
+
else if (target === 'codex') {
|
|
2341
|
+
const result = installCodexWrapper();
|
|
2342
|
+
console.log(`Installed Codex session-end integration -> ${result.metadataPath}`);
|
|
2343
|
+
console.log(` Wrapped detected Codex launcher at ${result.commandPath}`);
|
|
2344
|
+
}
|
|
2112
2345
|
return;
|
|
2113
2346
|
}
|
|
2114
2347
|
if (subcommand === 'uninstall') {
|
|
@@ -2118,25 +2351,32 @@ function cmdHook(args, flags) {
|
|
|
2118
2351
|
}
|
|
2119
2352
|
const hook = HOOKS[target];
|
|
2120
2353
|
const filepath = path.resolve(process.cwd(), hook.file);
|
|
2121
|
-
if (
|
|
2122
|
-
|
|
2123
|
-
|
|
2354
|
+
if (fs.existsSync(filepath)) {
|
|
2355
|
+
const existing = fs.readFileSync(filepath, 'utf8');
|
|
2356
|
+
if (existing.includes(HOOK_MARKERS.start)) {
|
|
2357
|
+
const re = new RegExp(`\\n?${escapeRegex(HOOK_MARKERS.start)}[\\s\\S]*?${escapeRegex(HOOK_MARKERS.end)}\\n?`, 'g');
|
|
2358
|
+
const cleaned = existing.replace(re, '\n').replace(/\n{3,}/g, '\n\n').trim();
|
|
2359
|
+
fs.writeFileSync(filepath, cleaned + '\n', 'utf8');
|
|
2360
|
+
console.log(`Removed Hippo hook from ${hook.file}`);
|
|
2361
|
+
}
|
|
2362
|
+
else {
|
|
2363
|
+
console.log(`No Hippo hook found in ${hook.file}.`);
|
|
2364
|
+
}
|
|
2124
2365
|
}
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
console.log(`No Hippo hook found in ${hook.file}.`);
|
|
2128
|
-
return;
|
|
2366
|
+
else {
|
|
2367
|
+
console.log(`${hook.file} not found, skipping agent-instructions uninstall.`);
|
|
2129
2368
|
}
|
|
2130
|
-
const re = new RegExp(`\\n?${escapeRegex(HOOK_MARKERS.start)}[\\s\\S]*?${escapeRegex(HOOK_MARKERS.end)}\\n?`, 'g');
|
|
2131
|
-
const cleaned = existing.replace(re, '\n').replace(/\n{3,}/g, '\n\n').trim();
|
|
2132
|
-
fs.writeFileSync(filepath, cleaned + '\n', 'utf8');
|
|
2133
|
-
console.log(`Removed Hippo hook from ${hook.file}`);
|
|
2134
2369
|
// For JSON-hook tools, also strip their SessionEnd/SessionStart entries.
|
|
2135
2370
|
if (target === 'claude-code' || target === 'opencode') {
|
|
2136
2371
|
if (uninstallJsonHooks(target)) {
|
|
2137
2372
|
console.log(`Removed hippo hooks from ${target} settings`);
|
|
2138
2373
|
}
|
|
2139
2374
|
}
|
|
2375
|
+
else if (target === 'codex') {
|
|
2376
|
+
if (uninstallCodexWrapper()) {
|
|
2377
|
+
console.log('Removed Codex wrapper integration');
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2140
2380
|
return;
|
|
2141
2381
|
}
|
|
2142
2382
|
console.error('Usage: hippo hook <install|uninstall|list> [target]');
|
|
@@ -2154,6 +2394,7 @@ function cmdSetup(flags) {
|
|
|
2154
2394
|
console.log('Hippo setup -- configuring SessionEnd + SessionStart hooks');
|
|
2155
2395
|
console.log('');
|
|
2156
2396
|
const jsonTools = tools.filter((t) => t.kind === 'json-hook' && (t.detected || forceAll));
|
|
2397
|
+
const wrapperTools = tools.filter((t) => t.kind === 'wrapper' && (t.detected || forceAll));
|
|
2157
2398
|
const skipped = tools.filter((t) => t.kind === 'json-hook' && !t.detected && !forceAll);
|
|
2158
2399
|
const markdownTools = tools.filter((t) => t.kind === 'markdown-instruction' && t.detected);
|
|
2159
2400
|
const pluginTools = tools.filter((t) => t.kind === 'plugin' && t.detected);
|
|
@@ -2172,14 +2413,14 @@ function cmdSetup(flags) {
|
|
|
2172
2413
|
const result = installJsonHooks(tool.name);
|
|
2173
2414
|
const bits = [];
|
|
2174
2415
|
if (result.installedSessionEnd)
|
|
2175
|
-
bits.push('SessionEnd (
|
|
2176
|
-
if (result.installedSessionCapture)
|
|
2177
|
-
bits.push('SessionEnd (capture)');
|
|
2416
|
+
bits.push('SessionEnd (session-end)');
|
|
2178
2417
|
if (result.installedSessionStart)
|
|
2179
2418
|
bits.push('SessionStart');
|
|
2180
2419
|
if (result.migratedFromStop)
|
|
2181
2420
|
bits.push('migrated legacy Stop');
|
|
2182
|
-
if (result.
|
|
2421
|
+
if (result.migratedSplitSessionEnd)
|
|
2422
|
+
bits.push('migrated split SessionEnd → session-end');
|
|
2423
|
+
else if (result.migratedLegacySessionEnd)
|
|
2183
2424
|
bits.push('migrated legacy SessionEnd');
|
|
2184
2425
|
if (bits.length === 0) {
|
|
2185
2426
|
console.log(` ${tool.name.padEnd(14)} already configured (${result.settingsPath})`);
|
|
@@ -2191,6 +2432,24 @@ function cmdSetup(flags) {
|
|
|
2191
2432
|
for (const tool of skipped) {
|
|
2192
2433
|
console.log(` ${tool.name.padEnd(14)} not detected at ${tool.configDir} -- skipping`);
|
|
2193
2434
|
}
|
|
2435
|
+
for (const tool of wrapperTools) {
|
|
2436
|
+
if (dryRun) {
|
|
2437
|
+
console.log(`[dry-run] would wrap the detected ${tool.name} launcher in place`);
|
|
2438
|
+
continue;
|
|
2439
|
+
}
|
|
2440
|
+
if (tool.name === 'codex') {
|
|
2441
|
+
const result = ensureCodexWrapperInstalled();
|
|
2442
|
+
if (result.status === 'installed') {
|
|
2443
|
+
console.log(` ${tool.name.padEnd(14)} wrapped launcher -> ${result.commandPath}`);
|
|
2444
|
+
}
|
|
2445
|
+
else if (result.status === 'already-installed') {
|
|
2446
|
+
console.log(` ${tool.name.padEnd(14)} already wrapped -> ${result.commandPath}`);
|
|
2447
|
+
}
|
|
2448
|
+
else {
|
|
2449
|
+
console.log(` ${tool.name.padEnd(14)} not found on PATH -- skipping`);
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2194
2453
|
if (pluginTools.length > 0) {
|
|
2195
2454
|
console.log('');
|
|
2196
2455
|
console.log('Plugin-based tools (hook API via plugin, not JSON):');
|
|
@@ -2215,8 +2474,7 @@ function installClaudeCodeSessionEndHook() {
|
|
|
2215
2474
|
const result = installJsonHooks('claude-code');
|
|
2216
2475
|
return {
|
|
2217
2476
|
installed: result.installedSessionEnd ||
|
|
2218
|
-
result.installedSessionStart
|
|
2219
|
-
result.installedSessionCapture,
|
|
2477
|
+
result.installedSessionStart,
|
|
2220
2478
|
migratedFromStop: result.migratedFromStop,
|
|
2221
2479
|
};
|
|
2222
2480
|
}
|
|
@@ -2421,13 +2679,15 @@ Commands:
|
|
|
2421
2679
|
available SessionEnd+SessionStart hooks
|
|
2422
2680
|
--all Install for every JSON-hook tool, even if not detected
|
|
2423
2681
|
--dry-run Show what would be installed without writing
|
|
2424
|
-
last-sleep Print the last 'hippo sleep --log-file' output and clear it
|
|
2425
|
-
--path <p> Log path (default: ~/.hippo/logs/last-sleep.log)
|
|
2426
|
-
--keep Print without clearing
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
hook
|
|
2430
|
-
|
|
2682
|
+
last-sleep Print the last 'hippo sleep --log-file' output and clear it
|
|
2683
|
+
--path <p> Log path (default: ~/.hippo/logs/last-sleep.log)
|
|
2684
|
+
--keep Print without clearing
|
|
2685
|
+
codex-run [-- ...args] Launch real Codex behind Hippo's session-end wrapper
|
|
2686
|
+
hook <sub> [target] Manage framework integrations
|
|
2687
|
+
hook list Show available hooks
|
|
2688
|
+
hook install <target> Install hook (claude-code|codex|cursor|openclaw|opencode|pi)
|
|
2689
|
+
claude-code/opencode install SessionEnd+SessionStart;
|
|
2690
|
+
codex wraps the detected launcher in place
|
|
2431
2691
|
hook uninstall <target> Remove hook
|
|
2432
2692
|
decide "<decision>" Record an architectural decision (90-day half-life)
|
|
2433
2693
|
--context "<why>" Why this decision was made
|
|
@@ -2489,6 +2749,7 @@ Examples:
|
|
|
2489
2749
|
const { command, args, flags } = parseArgs(process.argv);
|
|
2490
2750
|
const hippoRoot = getHippoRoot(process.cwd());
|
|
2491
2751
|
async function main() {
|
|
2752
|
+
maybeAutoInstallCodexWrapper(command, args);
|
|
2492
2753
|
switch (command) {
|
|
2493
2754
|
case 'init':
|
|
2494
2755
|
cmdInit(hippoRoot, flags);
|
|
@@ -2517,6 +2778,18 @@ async function main() {
|
|
|
2517
2778
|
case 'last-sleep':
|
|
2518
2779
|
cmdLastSleep(flags);
|
|
2519
2780
|
break;
|
|
2781
|
+
case 'session-end':
|
|
2782
|
+
cmdSessionEnd(hippoRoot, flags);
|
|
2783
|
+
break;
|
|
2784
|
+
case '__session-end-worker':
|
|
2785
|
+
cmdSessionEndWorker(hippoRoot, flags);
|
|
2786
|
+
break;
|
|
2787
|
+
case 'codex-run':
|
|
2788
|
+
cmdCodexRun(hippoRoot, args);
|
|
2789
|
+
break;
|
|
2790
|
+
case '__codex-session-end-worker':
|
|
2791
|
+
cmdCodexSessionEndWorker(hippoRoot, flags);
|
|
2792
|
+
break;
|
|
2520
2793
|
case 'dedup':
|
|
2521
2794
|
cmdDedup(hippoRoot, flags);
|
|
2522
2795
|
break;
|