tim-hooks 0.1.0-beta.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 +31 -0
- package/dist/cadence-runner.d.ts +11 -0
- package/dist/cadence-runner.d.ts.map +1 -0
- package/dist/cadence-runner.js +30 -0
- package/dist/cadence-runner.js.map +1 -0
- package/dist/cadence.d.ts +10 -0
- package/dist/cadence.d.ts.map +1 -0
- package/dist/cadence.js +36 -0
- package/dist/cadence.js.map +1 -0
- package/dist/checkpoint.d.ts +56 -0
- package/dist/checkpoint.d.ts.map +1 -0
- package/dist/checkpoint.js +232 -0
- package/dist/checkpoint.js.map +1 -0
- package/dist/claude-stop.d.ts +32 -0
- package/dist/claude-stop.d.ts.map +1 -0
- package/dist/claude-stop.js +213 -0
- package/dist/claude-stop.js.map +1 -0
- package/dist/constants.d.ts +5 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +8 -0
- package/dist/constants.js.map +1 -0
- package/dist/delta.d.ts +7 -0
- package/dist/delta.d.ts.map +1 -0
- package/dist/delta.js +53 -0
- package/dist/delta.js.map +1 -0
- package/dist/hooks.d.ts +39 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +136 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +88 -0
- package/dist/index.js.map +1 -0
- package/dist/marker.d.ts +179 -0
- package/dist/marker.d.ts.map +1 -0
- package/dist/marker.js +590 -0
- package/dist/marker.js.map +1 -0
- package/dist/phantom-recovery.d.ts +13 -0
- package/dist/phantom-recovery.d.ts.map +1 -0
- package/dist/phantom-recovery.js +113 -0
- package/dist/phantom-recovery.js.map +1 -0
- package/dist/project-creation.d.ts +48 -0
- package/dist/project-creation.d.ts.map +1 -0
- package/dist/project-creation.js +265 -0
- package/dist/project-creation.js.map +1 -0
- package/dist/prompt-submit.d.ts +16 -0
- package/dist/prompt-submit.d.ts.map +1 -0
- package/dist/prompt-submit.js +69 -0
- package/dist/prompt-submit.js.map +1 -0
- package/dist/rebalance.d.ts +17 -0
- package/dist/rebalance.d.ts.map +1 -0
- package/dist/rebalance.js +91 -0
- package/dist/rebalance.js.map +1 -0
- package/dist/session-hooks.d.ts +53 -0
- package/dist/session-hooks.d.ts.map +1 -0
- package/dist/session-hooks.js +167 -0
- package/dist/session-hooks.js.map +1 -0
- package/dist/update-check.d.ts +5 -0
- package/dist/update-check.d.ts.map +1 -0
- package/dist/update-check.js +66 -0
- package/dist/update-check.js.map +1 -0
- package/package.json +24 -0
- package/scripts/README-hermes-statusline.md +47 -0
- package/scripts/hermes-cli-tim-statusline.patch +75 -0
- package/scripts/lib/resolve-tim-cli.sh +30 -0
- package/scripts/post-commit.sh +30 -0
- package/scripts/tim-claude-session-start.sh +26 -0
- package/scripts/tim-cursor-inject.sh +17 -0
- package/scripts/tim-hermes-session-cache.sh +32 -0
- package/scripts/tim-hermes-statusline.sh +28 -0
- package/scripts/tim-post-commit.sh +14 -0
- package/scripts/tim-session-start.sh +104 -0
- package/scripts/tim-statusline.sh +16 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.rebalanceBatch = rebalanceBatch;
|
|
4
|
+
const tim_store_1 = require("tim-store");
|
|
5
|
+
const marker_js_1 = require("./marker.js");
|
|
6
|
+
const STOP_WORDS = new Set([
|
|
7
|
+
'about', 'after', 'also', 'been', 'from', 'have', 'into', 'just', 'more', 'some',
|
|
8
|
+
'than', 'that', 'the', 'their', 'them', 'then', 'there', 'these', 'they', 'this',
|
|
9
|
+
'those', 'very', 'what', 'when', 'which', 'with', 'your',
|
|
10
|
+
]);
|
|
11
|
+
function exchangeSummaryText(entry) {
|
|
12
|
+
return `${entry.title} ${entry.content}`.trim();
|
|
13
|
+
}
|
|
14
|
+
function keywords(text) {
|
|
15
|
+
const out = new Set();
|
|
16
|
+
for (const raw of text.toLowerCase().split(/[^a-z0-9]+/)) {
|
|
17
|
+
if (raw.length >= 4 && !STOP_WORDS.has(raw))
|
|
18
|
+
out.add(raw);
|
|
19
|
+
}
|
|
20
|
+
return out;
|
|
21
|
+
}
|
|
22
|
+
function shareKeywords(a, b) {
|
|
23
|
+
const ka = keywords(exchangeSummaryText(a));
|
|
24
|
+
const kb = keywords(exchangeSummaryText(b));
|
|
25
|
+
for (const k of ka) {
|
|
26
|
+
if (kb.has(k))
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
async function userExchanges(store, batchId) {
|
|
32
|
+
return (await store.getChildrenBySeq(batchId)).filter(u => u.metadata.role === 'user');
|
|
33
|
+
}
|
|
34
|
+
async function agentReply(store, user) {
|
|
35
|
+
const replies = await store.getChildren(user.id);
|
|
36
|
+
return replies.find(r => r.metadata.role === 'agent') ?? null;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Scan exchange-batch boundaries; move the first exchange of batch N (+ agent reply)
|
|
40
|
+
* into batch N-1 when it is topically related to the last exchange of N-1.
|
|
41
|
+
*/
|
|
42
|
+
async function rebalanceBatch(store, sessionId, opts = {}) {
|
|
43
|
+
const skipped = [];
|
|
44
|
+
let resolvedCwd = opts.cwd;
|
|
45
|
+
if (!resolvedCwd) {
|
|
46
|
+
const session = await store.read(sessionId);
|
|
47
|
+
if (typeof session?.metadata.cwd === 'string')
|
|
48
|
+
resolvedCwd = session.metadata.cwd;
|
|
49
|
+
}
|
|
50
|
+
if (resolvedCwd && (0, marker_js_1.isSessionLocked)(resolvedCwd)) {
|
|
51
|
+
return { moved: 0, skipped: [{ reason: 'locked' }] };
|
|
52
|
+
}
|
|
53
|
+
const session = await store.read(sessionId);
|
|
54
|
+
if (!session || session.metadata.kind !== 'session') {
|
|
55
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
56
|
+
}
|
|
57
|
+
const exNode = await (0, tim_store_1.findChildByKind)(store, sessionId, tim_store_1.KIND_EXCHANGES_ROOT);
|
|
58
|
+
if (!exNode)
|
|
59
|
+
return { moved: 0, skipped };
|
|
60
|
+
const batches = await store.getChildByKind(exNode.id, tim_store_1.KIND_EXCHANGE_BATCH);
|
|
61
|
+
if (batches.length < 2) {
|
|
62
|
+
return { moved: 0, skipped: [{ reason: 'no-boundary' }] };
|
|
63
|
+
}
|
|
64
|
+
let moved = 0;
|
|
65
|
+
for (let i = 1; i < batches.length; i++) {
|
|
66
|
+
const prev = batches[i - 1];
|
|
67
|
+
const curr = batches[i];
|
|
68
|
+
const batchIndex = typeof curr.metadata.batch_index === 'number' ? curr.metadata.batch_index : i + 1;
|
|
69
|
+
const prevUsers = await userExchanges(store, prev.id);
|
|
70
|
+
const currUsers = await userExchanges(store, curr.id);
|
|
71
|
+
if (currUsers.length < 2) {
|
|
72
|
+
skipped.push({ reason: 'single-exchange-batch', batchIndex });
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const lastPrev = prevUsers[prevUsers.length - 1];
|
|
76
|
+
const firstCurr = currUsers[0];
|
|
77
|
+
if (!lastPrev || !firstCurr)
|
|
78
|
+
continue;
|
|
79
|
+
if (!shareKeywords(lastPrev, firstCurr)) {
|
|
80
|
+
skipped.push({ reason: 'unrelated', batchIndex });
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const agent = await agentReply(store, firstCurr);
|
|
84
|
+
store.curate().moveEntry(firstCurr.id, prev.id);
|
|
85
|
+
if (agent)
|
|
86
|
+
store.curate().moveEntry(agent.id, prev.id);
|
|
87
|
+
moved += 1;
|
|
88
|
+
}
|
|
89
|
+
return { moved, skipped };
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=rebalance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rebalance.js","sourceRoot":"","sources":["../src/rebalance.ts"],"names":[],"mappings":";;AA2DA,wCA2DC;AApHD,yCAImB;AACnB,2CAA8C;AAE9C,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC;IACzB,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAChF,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM;IAChF,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM;CACzD,CAAC,CAAC;AAYH,SAAS,mBAAmB,CAAC,KAAY;IACvC,OAAO,GAAG,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC;AAClD,CAAC;AAED,SAAS,QAAQ,CAAC,IAAY;IAC5B,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAC;IAC9B,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,YAAY,CAAC,EAAE,CAAC;QACzD,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC5D,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,aAAa,CAAC,CAAQ,EAAE,CAAQ;IACvC,MAAM,EAAE,GAAG,QAAQ,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5C,MAAM,EAAE,GAAG,QAAQ,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5C,KAAK,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;QACnB,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;IAC7B,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,KAAK,UAAU,aAAa,CAAC,KAAe,EAAE,OAAe;IAC3D,OAAO,CAAC,MAAM,KAAK,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC;AACzF,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,KAAe,EAAE,IAAW;IACpD,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjD,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,KAAK,OAAO,CAAC,IAAI,IAAI,CAAC;AAChE,CAAC;AAED;;;GAGG;AACI,KAAK,UAAU,cAAc,CAClC,KAAe,EACf,SAAiB,EACjB,OAAyB,EAAE;IAE3B,MAAM,OAAO,GAAoB,EAAE,CAAC;IACpC,IAAI,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC;IAC3B,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC5C,IAAI,OAAO,OAAO,EAAE,QAAQ,CAAC,GAAG,KAAK,QAAQ;YAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC;IACpF,CAAC;IACD,IAAI,WAAW,IAAI,IAAA,2BAAe,EAAC,WAAW,CAAC,EAAE,CAAC;QAChD,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;IACvD,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC5C,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QACpD,MAAM,IAAI,KAAK,CAAC,sBAAsB,SAAS,EAAE,CAAC,CAAC;IACrD,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,IAAA,2BAAe,EAAC,KAAK,EAAE,SAAS,EAAE,+BAAmB,CAAC,CAAC;IAC5E,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC;IAE1C,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,EAAE,+BAAmB,CAAC,CAAC;IAC3E,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC,EAAE,CAAC;IAC5D,CAAC;IAED,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACxC,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,GAAG,CAAC,CAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAE,CAAC;QACzB,MAAM,UAAU,GACd,OAAO,IAAI,CAAC,QAAQ,CAAC,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QAEpF,MAAM,SAAS,GAAG,MAAM,aAAa,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;QACtD,MAAM,SAAS,GAAG,MAAM,aAAa,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;QAEtD,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACzB,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,uBAAuB,EAAE,UAAU,EAAE,CAAC,CAAC;YAC9D,SAAS;QACX,CAAC;QAED,MAAM,QAAQ,GAAG,SAAS,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACjD,MAAM,SAAS,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;QAC/B,IAAI,CAAC,QAAQ,IAAI,CAAC,SAAS;YAAE,SAAS;QAEtC,IAAI,CAAC,aAAa,CAAC,QAAQ,EAAE,SAAS,CAAC,EAAE,CAAC;YACxC,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC,CAAC;YAClD,SAAS;QACX,CAAC;QAED,MAAM,KAAK,GAAG,MAAM,UAAU,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;QACjD,KAAK,CAAC,MAAM,EAAE,CAAC,SAAS,CAAC,SAAS,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;QAChD,IAAI,KAAK;YAAE,KAAK,CAAC,MAAM,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;QACvD,KAAK,IAAI,CAAC,CAAC;IACb,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;AAC5B,CAAC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { TimStore } from 'tim-store';
|
|
2
|
+
export interface SpawnContext {
|
|
3
|
+
sessionId: string;
|
|
4
|
+
cwd: string;
|
|
5
|
+
}
|
|
6
|
+
export type Spawner = (command: string, ctx: SpawnContext) => void;
|
|
7
|
+
export type SessionStopReason = 'spawned' | 'no-marker' | 'below-threshold' | 'locked' | 'spawn-failed';
|
|
8
|
+
export interface SessionStopResult {
|
|
9
|
+
spawned: boolean;
|
|
10
|
+
reason: SessionStopReason;
|
|
11
|
+
pending?: number;
|
|
12
|
+
}
|
|
13
|
+
export { DEFAULT_SUMMARIZER_TIMEOUT_SEC } from './constants.js';
|
|
14
|
+
export declare function summarizerLogPath(cwd: string): string;
|
|
15
|
+
/** Shell snippet: trap lock release, timeout, run tim-summarizer CLI with log append. */
|
|
16
|
+
export declare function buildSummarizerCommand(sessionId: string, lockPath: string, logPath: string, timeoutSec?: number): string;
|
|
17
|
+
/** Detached spawn with log dir creation and spawn-error capture (does not throw). */
|
|
18
|
+
export declare const spawnSummarizer: Spawner;
|
|
19
|
+
/** @deprecated Use spawnSummarizer */
|
|
20
|
+
export declare const detachedSpawner: Spawner;
|
|
21
|
+
export interface MaybeSpawnSummarizerOptions {
|
|
22
|
+
spawn?: Spawner;
|
|
23
|
+
/** Skip pending threshold — use when a batch just filled (live trigger). */
|
|
24
|
+
batchFull?: boolean;
|
|
25
|
+
timeoutSec?: number;
|
|
26
|
+
}
|
|
27
|
+
/** Shared spawn gate for session-stop hook and live batch-full trigger. */
|
|
28
|
+
export declare function maybeSpawnSummarizer(store: TimStore, cwd: string, opts?: MaybeSpawnSummarizerOptions): Promise<SessionStopResult>;
|
|
29
|
+
export declare function onSessionStop(store: TimStore, cwd: string, opts?: {
|
|
30
|
+
spawn?: Spawner;
|
|
31
|
+
timeoutSec?: number;
|
|
32
|
+
}): Promise<SessionStopResult>;
|
|
33
|
+
export declare const DEFAULT_PROJECT_SUMMARY_THRESHOLD = 5;
|
|
34
|
+
/** Shell snippet: run tim-summarizer in --project-summary mode for a label. */
|
|
35
|
+
export declare function buildProjectSummaryCommand(label: string, logPath: string, timeoutSec?: number): string;
|
|
36
|
+
export type ProjectSummaryReason = 'spawned' | 'no-label' | 'no-sessions' | 'below-threshold' | 'spawn-failed';
|
|
37
|
+
export interface ProjectSummaryResult {
|
|
38
|
+
spawned: boolean;
|
|
39
|
+
reason: ProjectSummaryReason;
|
|
40
|
+
count?: number;
|
|
41
|
+
}
|
|
42
|
+
export interface MaybeSpawnProjectSummaryOptions {
|
|
43
|
+
spawn?: Spawner;
|
|
44
|
+
threshold?: number;
|
|
45
|
+
timeoutSec?: number;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Gate + detached spawn for periodic project-summary generation.
|
|
49
|
+
* Fires only when sessions-so-far is a positive multiple of the threshold.
|
|
50
|
+
* Fire-and-forget — never throws.
|
|
51
|
+
*/
|
|
52
|
+
export declare function maybeSpawnProjectSummary(store: TimStore, cwd: string, label: string | null, opts?: MaybeSpawnProjectSummaryOptions): Promise<ProjectSummaryResult>;
|
|
53
|
+
//# sourceMappingURL=session-hooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-hooks.d.ts","sourceRoot":"","sources":["../src/session-hooks.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAU1C,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,KAAK,IAAI,CAAC;AAEnE,MAAM,MAAM,iBAAiB,GACzB,SAAS,GACT,WAAW,GACX,iBAAiB,GACjB,QAAQ,GACR,cAAc,CAAC;AAEnB,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,OAAO,EAAE,8BAA8B,EAAE,MAAM,gBAAgB,CAAC;AAEhE,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,yFAAyF;AACzF,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,UAAU,GAAE,MAAuC,GAClD,MAAM,CAOR;AAED,qFAAqF;AACrF,eAAO,MAAM,eAAe,EAAE,OAkC7B,CAAC;AAEF,sCAAsC;AACtC,eAAO,MAAM,eAAe,EAAE,OAAyB,CAAC;AAExD,MAAM,WAAW,2BAA2B;IAC1C,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,4EAA4E;IAC5E,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,2EAA2E;AAC3E,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,QAAQ,EACf,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,2BAAgC,GACrC,OAAO,CAAC,iBAAiB,CAAC,CA4B5B;AAED,wBAAsB,aAAa,CACjC,KAAK,EAAE,QAAQ,EACf,GAAG,EAAE,MAAM,EACX,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAO,GAClD,OAAO,CAAC,iBAAiB,CAAC,CAE5B;AAED,eAAO,MAAM,iCAAiC,IAAI,CAAC;AAEnD,+EAA+E;AAC/E,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EACf,UAAU,GAAE,MAAuC,GAClD,MAAM,CAIR;AAED,MAAM,MAAM,oBAAoB,GAC5B,SAAS,GACT,UAAU,GACV,aAAa,GACb,iBAAiB,GACjB,cAAc,CAAC;AAEnB,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,oBAAoB,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,+BAA+B;IAC9C,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,wBAAsB,wBAAwB,CAC5C,KAAK,EAAE,QAAQ,EACf,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,GAAG,IAAI,EACpB,IAAI,GAAE,+BAAoC,GACzC,OAAO,CAAC,oBAAoB,CAAC,CAqB/B"}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.DEFAULT_PROJECT_SUMMARY_THRESHOLD = exports.detachedSpawner = exports.spawnSummarizer = exports.DEFAULT_SUMMARIZER_TIMEOUT_SEC = void 0;
|
|
37
|
+
exports.summarizerLogPath = summarizerLogPath;
|
|
38
|
+
exports.buildSummarizerCommand = buildSummarizerCommand;
|
|
39
|
+
exports.maybeSpawnSummarizer = maybeSpawnSummarizer;
|
|
40
|
+
exports.onSessionStop = onSessionStop;
|
|
41
|
+
exports.buildProjectSummaryCommand = buildProjectSummaryCommand;
|
|
42
|
+
exports.maybeSpawnProjectSummary = maybeSpawnProjectSummary;
|
|
43
|
+
const child_process_1 = require("child_process");
|
|
44
|
+
const fs = __importStar(require("fs"));
|
|
45
|
+
const path = __importStar(require("path"));
|
|
46
|
+
const marker_js_1 = require("./marker.js");
|
|
47
|
+
const constants_js_1 = require("./constants.js");
|
|
48
|
+
var constants_js_2 = require("./constants.js");
|
|
49
|
+
Object.defineProperty(exports, "DEFAULT_SUMMARIZER_TIMEOUT_SEC", { enumerable: true, get: function () { return constants_js_2.DEFAULT_SUMMARIZER_TIMEOUT_SEC; } });
|
|
50
|
+
function summarizerLogPath(cwd) {
|
|
51
|
+
return path.join(cwd, '.tim', 'summarizer.log');
|
|
52
|
+
}
|
|
53
|
+
/** Shell snippet: trap lock release, timeout, run tim-summarizer CLI with log append. */
|
|
54
|
+
function buildSummarizerCommand(sessionId, lockPath, logPath, timeoutSec = constants_js_1.DEFAULT_SUMMARIZER_TIMEOUT_SEC) {
|
|
55
|
+
const q = (s) => JSON.stringify(s);
|
|
56
|
+
const cmd = 'node ' + JSON.stringify(path.resolve(__dirname, '..', '..', 'tim-summarizer', 'dist', 'summarize.js'));
|
|
57
|
+
return (`{ trap ${q(`rm -f ${lockPath}`)} EXIT; ` +
|
|
58
|
+
`timeout ${timeoutSec} env TIM_SESSION_ID=${q(sessionId)} ${cmd} >>${q(logPath)} 2>&1; }`);
|
|
59
|
+
}
|
|
60
|
+
/** Detached spawn with log dir creation and spawn-error capture (does not throw). */
|
|
61
|
+
const spawnSummarizer = (command, ctx) => {
|
|
62
|
+
const timDir = path.join(ctx.cwd, '.tim');
|
|
63
|
+
try {
|
|
64
|
+
fs.mkdirSync(timDir, { recursive: true });
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
/* ignore */
|
|
68
|
+
}
|
|
69
|
+
const logPath = summarizerLogPath(ctx.cwd);
|
|
70
|
+
try {
|
|
71
|
+
const child = (0, child_process_1.spawn)(command, {
|
|
72
|
+
shell: true,
|
|
73
|
+
cwd: ctx.cwd,
|
|
74
|
+
detached: true,
|
|
75
|
+
stdio: 'ignore',
|
|
76
|
+
env: { ...process.env, TIM_SESSION_ID: ctx.sessionId },
|
|
77
|
+
});
|
|
78
|
+
child.on('error', err => {
|
|
79
|
+
try {
|
|
80
|
+
fs.appendFileSync(logPath, `[${new Date().toISOString()}] spawn error: ${err.message}\n`);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
/* ignore */
|
|
84
|
+
}
|
|
85
|
+
(0, marker_js_1.releaseLock)(ctx.cwd);
|
|
86
|
+
});
|
|
87
|
+
child.unref();
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
91
|
+
try {
|
|
92
|
+
fs.appendFileSync(logPath, `[${new Date().toISOString()}] spawn failed: ${msg}\n`);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
/* ignore */
|
|
96
|
+
}
|
|
97
|
+
(0, marker_js_1.releaseLock)(ctx.cwd);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
exports.spawnSummarizer = spawnSummarizer;
|
|
101
|
+
/** @deprecated Use spawnSummarizer */
|
|
102
|
+
exports.detachedSpawner = exports.spawnSummarizer;
|
|
103
|
+
/** Shared spawn gate for session-stop hook and live batch-full trigger. */
|
|
104
|
+
async function maybeSpawnSummarizer(store, cwd, opts = {}) {
|
|
105
|
+
const spawn = opts.spawn ?? exports.spawnSummarizer;
|
|
106
|
+
const marker = (0, marker_js_1.detectProject)(cwd);
|
|
107
|
+
if (!marker)
|
|
108
|
+
return { spawned: false, reason: 'no-marker' };
|
|
109
|
+
const reconciled = await (0, marker_js_1.reconcileMarker)(store, cwd);
|
|
110
|
+
const pending = reconciled.exchanges - reconciled.batches_summarized * reconciled.batch_size;
|
|
111
|
+
if (!opts.batchFull && pending < reconciled.batch_size) {
|
|
112
|
+
return { spawned: false, reason: 'below-threshold', pending };
|
|
113
|
+
}
|
|
114
|
+
if (!(0, marker_js_1.acquireLock)(cwd))
|
|
115
|
+
return { spawned: false, reason: 'locked', pending };
|
|
116
|
+
const lockPath = path.join(cwd, marker_js_1.MARKER_LOCK);
|
|
117
|
+
const logPath = summarizerLogPath(cwd);
|
|
118
|
+
const timeoutSec = opts.timeoutSec ?? constants_js_1.DEFAULT_SUMMARIZER_TIMEOUT_SEC;
|
|
119
|
+
try {
|
|
120
|
+
spawn(buildSummarizerCommand(reconciled.session, lockPath, logPath, timeoutSec), {
|
|
121
|
+
sessionId: reconciled.session,
|
|
122
|
+
cwd,
|
|
123
|
+
});
|
|
124
|
+
return { spawned: true, reason: 'spawned', pending };
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
(0, marker_js_1.releaseLock)(cwd);
|
|
128
|
+
return { spawned: false, reason: 'spawn-failed', pending };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async function onSessionStop(store, cwd, opts = {}) {
|
|
132
|
+
return maybeSpawnSummarizer(store, cwd, opts);
|
|
133
|
+
}
|
|
134
|
+
exports.DEFAULT_PROJECT_SUMMARY_THRESHOLD = 5;
|
|
135
|
+
/** Shell snippet: run tim-summarizer in --project-summary mode for a label. */
|
|
136
|
+
function buildProjectSummaryCommand(label, logPath, timeoutSec = constants_js_1.DEFAULT_SUMMARIZER_TIMEOUT_SEC) {
|
|
137
|
+
const q = (s) => JSON.stringify(s);
|
|
138
|
+
const cmd = 'node ' + JSON.stringify(path.resolve(__dirname, '..', '..', 'tim-summarizer', 'dist', 'summarize.js'));
|
|
139
|
+
return `timeout ${timeoutSec} ${cmd} --project-summary ${q(label)} >>${q(logPath)} 2>&1`;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Gate + detached spawn for periodic project-summary generation.
|
|
143
|
+
* Fires only when sessions-so-far is a positive multiple of the threshold.
|
|
144
|
+
* Fire-and-forget — never throws.
|
|
145
|
+
*/
|
|
146
|
+
async function maybeSpawnProjectSummary(store, cwd, label, opts = {}) {
|
|
147
|
+
if (!label)
|
|
148
|
+
return { spawned: false, reason: 'no-label' };
|
|
149
|
+
const count = await store.countSessionSummaries(label);
|
|
150
|
+
if (count <= 0)
|
|
151
|
+
return { spawned: false, reason: 'no-sessions', count };
|
|
152
|
+
const threshold = opts.threshold ?? exports.DEFAULT_PROJECT_SUMMARY_THRESHOLD;
|
|
153
|
+
if (threshold <= 0 || count % threshold !== 0) {
|
|
154
|
+
return { spawned: false, reason: 'below-threshold', count };
|
|
155
|
+
}
|
|
156
|
+
const spawn = opts.spawn ?? exports.spawnSummarizer;
|
|
157
|
+
const logPath = summarizerLogPath(cwd);
|
|
158
|
+
const timeoutSec = opts.timeoutSec ?? constants_js_1.DEFAULT_SUMMARIZER_TIMEOUT_SEC;
|
|
159
|
+
try {
|
|
160
|
+
spawn(buildProjectSummaryCommand(label, logPath, timeoutSec), { sessionId: label, cwd });
|
|
161
|
+
return { spawned: true, reason: 'spawned', count };
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return { spawned: false, reason: 'spawn-failed', count };
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=session-hooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-hooks.js","sourceRoot":"","sources":["../src/session-hooks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update-check.d.ts","sourceRoot":"","sources":["../src/update-check.ts"],"names":[],"mappings":"AA4CA,wBAAsB,kBAAkB,CAAC,OAAO,GAAE;IAAE,cAAc,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAW1G;AAED,wBAAsB,0BAA0B,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAEzE"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getUpdateCheckLine = getUpdateCheckLine;
|
|
4
|
+
exports.getUpdateCheckLineBriefing = getUpdateCheckLineBriefing;
|
|
5
|
+
const node_fs_1 = require("node:fs");
|
|
6
|
+
const node_path_1 = require("node:path");
|
|
7
|
+
const tim_core_1 = require("tim-core");
|
|
8
|
+
const ONE_DAY_MS = 86_400_000;
|
|
9
|
+
const PACKAGE_NAME = 'tim-mcp';
|
|
10
|
+
const DEFAULT_FETCH_TIMEOUT_MS = 3000;
|
|
11
|
+
const DEFAULT_BRIEFING_TIMEOUT_MS = 500;
|
|
12
|
+
function timMcpPackagePath() {
|
|
13
|
+
return (0, node_path_1.join)(__dirname, '..', '..', 'tim-mcp', 'package.json');
|
|
14
|
+
}
|
|
15
|
+
function installedVersion() {
|
|
16
|
+
try {
|
|
17
|
+
const pkg = JSON.parse((0, node_fs_1.readFileSync)(timMcpPackagePath(), 'utf8'));
|
|
18
|
+
return pkg.version ?? '0.0.0';
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return '0.0.0';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async function fetchLatestVersion(timeoutMs = DEFAULT_FETCH_TIMEOUT_MS) {
|
|
25
|
+
const controller = new AbortController();
|
|
26
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
27
|
+
try {
|
|
28
|
+
const res = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`, { signal: controller.signal });
|
|
29
|
+
if (!res.ok)
|
|
30
|
+
return null;
|
|
31
|
+
const data = await res.json();
|
|
32
|
+
return data.version ?? null;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
clearTimeout(timer);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function raceWithTimeout(promise, timeoutMs) {
|
|
42
|
+
return new Promise((resolve) => {
|
|
43
|
+
const timer = setTimeout(() => resolve(null), timeoutMs);
|
|
44
|
+
promise.then((v) => { clearTimeout(timer); resolve(v); }, () => { clearTimeout(timer); resolve(null); });
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
async function getUpdateCheckLine(options = {}) {
|
|
48
|
+
const config = (0, tim_core_1.loadConfig)();
|
|
49
|
+
if (config.updateCheck === false)
|
|
50
|
+
return null;
|
|
51
|
+
const lastAt = config.updateCheckLastAt;
|
|
52
|
+
if (lastAt && Date.now() - new Date(lastAt).getTime() < ONE_DAY_MS)
|
|
53
|
+
return null;
|
|
54
|
+
const latest = await fetchLatestVersion(options.fetchTimeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS);
|
|
55
|
+
(0, tim_core_1.saveConfig)({ ...(0, tim_core_1.loadConfig)(), updateCheckLastAt: new Date().toISOString() });
|
|
56
|
+
if (!latest)
|
|
57
|
+
return null;
|
|
58
|
+
const installed = installedVersion();
|
|
59
|
+
if (latest === installed)
|
|
60
|
+
return null;
|
|
61
|
+
return `TIM ${latest} available (installed: ${installed}) — npm i -g ${PACKAGE_NAME}`;
|
|
62
|
+
}
|
|
63
|
+
async function getUpdateCheckLineBriefing() {
|
|
64
|
+
return raceWithTimeout(getUpdateCheckLine(), DEFAULT_BRIEFING_TIMEOUT_MS);
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=update-check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update-check.js","sourceRoot":"","sources":["../src/update-check.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tim-hooks",
|
|
3
|
+
"version": "0.1.0-beta.0",
|
|
4
|
+
"description": "TIM hook scripts and checkpoint orchestration",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist/**/*",
|
|
9
|
+
"!dist/**/__tests__/**",
|
|
10
|
+
"scripts/**/*"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"pretest": "npm --prefix ../.. run build",
|
|
15
|
+
"test": "vitest run"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"tim-core": "0.1.0-beta.0",
|
|
19
|
+
"tim-store": "0.1.0-beta.0"
|
|
20
|
+
},
|
|
21
|
+
"optionalDependencies": {
|
|
22
|
+
"fastembed": "^1.0.0"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Hermes status bar (TIM)
|
|
2
|
+
|
|
3
|
+
Hermes has **no** Claude-style `statusLine` in `settings.json`. Persistent project/batch info uses the **Hermes CLI TUI status bar**, which calls a hook script on refresh.
|
|
4
|
+
|
|
5
|
+
## Mechanism
|
|
6
|
+
|
|
7
|
+
| Piece | Role |
|
|
8
|
+
|-------|------|
|
|
9
|
+
| `tim-hermes-session-cache.sh` | `pre_llm_call` — writes `~/.tim/.session-cache` (`session_id`, `cwd`). Returns `{}`. |
|
|
10
|
+
| `tim-hermes-statusline.sh` | Called by patched Hermes `cli.py` — prints JSON: `{device, project, o_node, counter}` |
|
|
11
|
+
| `hermes-cli-tim-statusline.patch` | Reference only — install uses programmatic `cli.py` patch (line drift safe) |
|
|
12
|
+
| `tim statusline --format hermes` | Core formatter (used by the shell script) |
|
|
13
|
+
|
|
14
|
+
`pre_llm_call` **`context`** injection is for turn prompts (see `tim-session-start.sh`), not the status bar.
|
|
15
|
+
|
|
16
|
+
## Install (one command)
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
cd ~/projects/tim && npx tsc -b # once, if developing from source
|
|
20
|
+
node packages/tim-cli/dist/cli.js setup-hermes-statusline
|
|
21
|
+
# or after npm link -g: tim setup-hermes-statusline
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Idempotent: safe to re-run. Options: `--dry-run`, `--skip-build`, env `HERMES_AGENT_DIR`.
|
|
25
|
+
|
|
26
|
+
Manual steps (what the command does):
|
|
27
|
+
|
|
28
|
+
1. Symlink `tim-hermes-*.sh` → `~/.hermes/agent-hooks/`
|
|
29
|
+
2. Insert `tim-hermes-session-cache.sh` into `~/.hermes/config.yaml` `pre_llm_call` (after `o9k-startup.sh`)
|
|
30
|
+
3. Patch `~/.hermes/hermes-agent/cli.py` (`_get_tim_status` + status-bar fragments)
|
|
31
|
+
4. `npx tsc -b` when run from TIM monorepo
|
|
32
|
+
|
|
33
|
+
## Verify
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# JSON for status bar
|
|
37
|
+
bash packages/tim-hooks/scripts/tim-hermes-statusline.sh | jq .
|
|
38
|
+
|
|
39
|
+
# Text line (debug)
|
|
40
|
+
node packages/tim-cli/dist/cli.js statusline --cwd ~/projects/tim
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Restart Hermes after patching `cli.py`.
|
|
44
|
+
|
|
45
|
+
## Claude Code
|
|
46
|
+
|
|
47
|
+
Do **not** use `statusLine` in `~/.claude/settings.json` for TIM — that is Claude-only. Use this Hermes path instead.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
diff --git a/cli.py b/cli.py
|
|
2
|
+
--- a/cli.py
|
|
3
|
+
+++ b/cli.py
|
|
4
|
+
@@ -2821,6 +2821,22 @@ class HermesCLI:
|
|
5
|
+
|
|
6
|
+
return snapshot
|
|
7
|
+
|
|
8
|
+
+ def _get_tim_status(self) -> Dict[str, str]:
|
|
9
|
+
+ """Call tim-hermes-statusline.sh for TIM project / batch counter."""
|
|
10
|
+
+ try:
|
|
11
|
+
+ import subprocess, json
|
|
12
|
+
+ script = os.path.expanduser("~/.hermes/agent-hooks/tim-hermes-statusline.sh")
|
|
13
|
+
+ if not os.path.isfile(script):
|
|
14
|
+
+ return {}
|
|
15
|
+
+ result = subprocess.run(
|
|
16
|
+
+ ["bash", script], capture_output=True, text=True, timeout=3
|
|
17
|
+
+ )
|
|
18
|
+
+ if result.returncode == 0 and result.stdout.strip():
|
|
19
|
+
+ return json.loads(result.stdout)
|
|
20
|
+
+ except Exception:
|
|
21
|
+
+ pass
|
|
22
|
+
+ return {}
|
|
23
|
+
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def _status_bar_display_width(text: str) -> int:
|
|
26
|
+
"""Return terminal cell width for status-bar text.
|
|
27
|
+
@@ -3030,14 +3046,27 @@ class HermesCLI:
|
|
28
|
+
return []
|
|
29
|
+
try:
|
|
30
|
+
snapshot = self._get_status_bar_snapshot()
|
|
31
|
+
width = self._get_tui_terminal_width()
|
|
32
|
+
duration_label = snapshot["duration"]
|
|
33
|
+
|
|
34
|
+
+ tim = self._get_tim_status()
|
|
35
|
+
+ tim_prefix = ""
|
|
36
|
+
+ if tim:
|
|
37
|
+
+ parts = []
|
|
38
|
+
+ if tim.get("device"):
|
|
39
|
+
+ parts.append(tim["device"])
|
|
40
|
+
+ proj = tim.get("project", "")
|
|
41
|
+
+ o_node = tim.get("o_node", "")
|
|
42
|
+
+ if o_node:
|
|
43
|
+
+ proj = f"{proj} → {o_node}"
|
|
44
|
+
+ if proj:
|
|
45
|
+
+ parts.append(proj)
|
|
46
|
+
+ if tim.get("counter"):
|
|
47
|
+
+ parts.append(tim["counter"])
|
|
48
|
+
+ if parts:
|
|
49
|
+
+ tim_prefix = " │ ".join(parts)
|
|
50
|
+
+
|
|
51
|
+
if width < 52:
|
|
52
|
+
frags = [
|
|
53
|
+
("class:status-bar", " ⚕ "),
|
|
54
|
+
@@ -3075,7 +3104,11 @@ class HermesCLI:
|
|
55
|
+
|
|
56
|
+
bar_style = self._status_bar_context_style(percent)
|
|
57
|
+
compressions = snapshot.get("compressions", 0)
|
|
58
|
+
- frags = [
|
|
59
|
+
+ frags = []
|
|
60
|
+
+ if tim_prefix:
|
|
61
|
+
+ frags.append(("class:status-bar-strong", f" {tim_prefix}"))
|
|
62
|
+
+ frags.append(("class:status-bar-dim", " │ "))
|
|
63
|
+
+ frags.extend([
|
|
64
|
+
("class:status-bar", " ⚕ "),
|
|
65
|
+
("class:status-bar-strong", snapshot["model_short"]),
|
|
66
|
+
("class:status-bar-dim", " │ "),
|
|
67
|
+
@@ -3084,7 +3117,7 @@ class HermesCLI:
|
|
68
|
+
(bar_style, self._build_context_bar(percent)),
|
|
69
|
+
("class:status-bar-dim", " "),
|
|
70
|
+
(bar_style, percent_label),
|
|
71
|
+
- ]
|
|
72
|
+
+ ])
|
|
73
|
+
if compressions:
|
|
74
|
+
frags.append(("class:status-bar-dim", " │ "))
|
|
75
|
+
frags.append((self._compression_count_style(compressions), f"🗜️ {compressions}"))
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Shared TIM CLI discovery for hook entrypoints.
|
|
3
|
+
|
|
4
|
+
TIM_HOOKS_SCRIPTS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
5
|
+
|
|
6
|
+
resolve_tim_cli() {
|
|
7
|
+
if [[ -n "${TIM_CLI:-}" ]]; then
|
|
8
|
+
printf '%s\n' "$TIM_CLI"
|
|
9
|
+
return 0
|
|
10
|
+
fi
|
|
11
|
+
|
|
12
|
+
if command -v tim >/dev/null 2>&1; then
|
|
13
|
+
command -v tim
|
|
14
|
+
return 0
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
local candidate="${TIM_HOOKS_SCRIPTS_DIR}/../../tim-cli/dist/cli.js"
|
|
18
|
+
[[ -f "$candidate" ]] || return 1
|
|
19
|
+
printf '%s\n' "$candidate"
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
run_tim_cli() {
|
|
23
|
+
local tim_cli
|
|
24
|
+
tim_cli="$(resolve_tim_cli)" || return 1
|
|
25
|
+
if [[ "$tim_cli" == *.js ]]; then
|
|
26
|
+
node "$tim_cli" "$@"
|
|
27
|
+
else
|
|
28
|
+
"$tim_cli" "$@"
|
|
29
|
+
fi
|
|
30
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# post-commit.sh — Global git post-commit: record commit to TIM Commits section.
|
|
3
|
+
#
|
|
4
|
+
# Walk-up .tim-project via `tim record-commit`. Silent skip when no marker.
|
|
5
|
+
# Must not fail the commit (errors swallowed).
|
|
6
|
+
#
|
|
7
|
+
# Install:
|
|
8
|
+
# mkdir -p ~/.hermes/git-hooks
|
|
9
|
+
# ln -sf ~/projects/tim/packages/tim-hooks/scripts/post-commit.sh ~/.hermes/git-hooks/post-commit
|
|
10
|
+
# git config --global core.hooksPath ~/.hermes/git-hooks
|
|
11
|
+
#
|
|
12
|
+
# Requires: git, node. Override CLI with TIM_CLI.
|
|
13
|
+
set -euo pipefail
|
|
14
|
+
|
|
15
|
+
SCRIPT_PATH="${BASH_SOURCE[0]}"
|
|
16
|
+
while [[ -L "$SCRIPT_PATH" ]]; do
|
|
17
|
+
SCRIPT_LINK_DIR="$(cd -P "$(dirname "$SCRIPT_PATH")" && pwd)"
|
|
18
|
+
SCRIPT_PATH="$(readlink "$SCRIPT_PATH")"
|
|
19
|
+
[[ "$SCRIPT_PATH" == /* ]] || SCRIPT_PATH="$SCRIPT_LINK_DIR/$SCRIPT_PATH"
|
|
20
|
+
done
|
|
21
|
+
SCRIPT_DIR="$(cd -P "$(dirname "$SCRIPT_PATH")" && pwd)"
|
|
22
|
+
unset SCRIPT_PATH SCRIPT_LINK_DIR
|
|
23
|
+
# shellcheck source=lib/resolve-tim-cli.sh
|
|
24
|
+
source "$SCRIPT_DIR/lib/resolve-tim-cli.sh"
|
|
25
|
+
|
|
26
|
+
repo_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
|
27
|
+
[[ -z "$repo_root" ]] && exit 0
|
|
28
|
+
|
|
29
|
+
run_tim_cli record-commit --cwd "$repo_root" >/dev/null 2>&1 || true
|
|
30
|
+
exit 0
|