@nanonets/graft 0.10.1 → 0.12.1
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 +32 -11
- package/dist/ai/crux.d.ts.map +1 -1
- package/dist/ai/crux.js +7 -6
- package/dist/ai/crux.js.map +1 -1
- package/dist/ai/failure.d.ts +49 -0
- package/dist/ai/failure.d.ts.map +1 -0
- package/dist/ai/failure.js +74 -0
- package/dist/ai/failure.js.map +1 -0
- package/dist/ai/llm/anthropic.d.ts.map +1 -1
- package/dist/ai/llm/anthropic.js +4 -1
- package/dist/ai/llm/anthropic.js.map +1 -1
- package/dist/ai/llm/claude-cli.d.ts +60 -0
- package/dist/ai/llm/claude-cli.d.ts.map +1 -0
- package/dist/ai/llm/claude-cli.js +417 -0
- package/dist/ai/llm/claude-cli.js.map +1 -0
- package/dist/ai/llm/openai.d.ts.map +1 -1
- package/dist/ai/llm/openai.js +2 -0
- package/dist/ai/llm/openai.js.map +1 -1
- package/dist/ai/llm/types.d.ts +9 -0
- package/dist/ai/llm/types.d.ts.map +1 -1
- package/dist/ai/llm/types.js +12 -1
- package/dist/ai/llm/types.js.map +1 -1
- package/dist/ask/ask.d.ts.map +1 -1
- package/dist/ask/ask.js +39 -45
- package/dist/ask/ask.js.map +1 -1
- package/dist/ask/fuse.d.ts +83 -46
- package/dist/ask/fuse.d.ts.map +1 -1
- package/dist/ask/fuse.js +165 -67
- package/dist/ask/fuse.js.map +1 -1
- package/dist/blast/blast-cli.d.ts +30 -0
- package/dist/blast/blast-cli.d.ts.map +1 -0
- package/dist/blast/blast-cli.js +163 -0
- package/dist/blast/blast-cli.js.map +1 -0
- package/dist/blast/blast.d.ts +119 -0
- package/dist/blast/blast.d.ts.map +1 -0
- package/dist/blast/blast.js +383 -0
- package/dist/blast/blast.js.map +1 -0
- package/dist/blast/diff.d.ts +53 -0
- package/dist/blast/diff.d.ts.map +1 -0
- package/dist/blast/diff.js +193 -0
- package/dist/blast/diff.js.map +1 -0
- package/dist/blast/modules.d.ts +33 -0
- package/dist/blast/modules.d.ts.map +1 -0
- package/dist/blast/modules.js +83 -0
- package/dist/blast/modules.js.map +1 -0
- package/dist/blast/name.d.ts +61 -0
- package/dist/blast/name.d.ts.map +1 -0
- package/dist/blast/name.js +233 -0
- package/dist/blast/name.js.map +1 -0
- package/dist/blast/render.d.ts +37 -0
- package/dist/blast/render.d.ts.map +1 -0
- package/dist/blast/render.js +300 -0
- package/dist/blast/render.js.map +1 -0
- package/dist/blast/viz.d.ts +17 -0
- package/dist/blast/viz.d.ts.map +1 -0
- package/dist/blast/viz.js +324 -0
- package/dist/blast/viz.js.map +1 -0
- package/dist/claude/hooks.d.ts.map +1 -1
- package/dist/claude/hooks.js +18 -2
- package/dist/claude/hooks.js.map +1 -1
- package/dist/claude/shim-template.d.ts.map +1 -1
- package/dist/claude/shim-template.js +45 -15
- package/dist/claude/shim-template.js.map +1 -1
- package/dist/claude/state.d.ts +5 -0
- package/dist/claude/state.d.ts.map +1 -1
- package/dist/claude/state.js.map +1 -1
- package/dist/cli-picker.d.ts +44 -5
- package/dist/cli-picker.d.ts.map +1 -1
- package/dist/cli-picker.js +73 -26
- package/dist/cli-picker.js.map +1 -1
- package/dist/cli.js +270 -14
- package/dist/cli.js.map +1 -1
- package/dist/context/build.d.ts +8 -0
- package/dist/context/build.d.ts.map +1 -1
- package/dist/context/build.js +57 -9
- package/dist/context/build.js.map +1 -1
- package/dist/context/check.d.ts.map +1 -1
- package/dist/context/check.js +7 -6
- package/dist/context/check.js.map +1 -1
- package/dist/context/node-file.d.ts.map +1 -1
- package/dist/context/node-file.js +9 -2
- package/dist/context/node-file.js.map +1 -1
- package/dist/graph/bindings.d.ts.map +1 -1
- package/dist/graph/bindings.js +171 -8
- package/dist/graph/bindings.js.map +1 -1
- package/dist/graph/build.d.ts.map +1 -1
- package/dist/graph/build.js +36 -16
- package/dist/graph/build.js.map +1 -1
- package/dist/graph/check.d.ts +4 -0
- package/dist/graph/check.d.ts.map +1 -1
- package/dist/graph/check.js +8 -1
- package/dist/graph/check.js.map +1 -1
- package/dist/graph/container.d.ts +63 -0
- package/dist/graph/container.d.ts.map +1 -0
- package/dist/graph/container.js +185 -0
- package/dist/graph/container.js.map +1 -0
- package/dist/graph/enrich.d.ts +18 -0
- package/dist/graph/enrich.d.ts.map +1 -1
- package/dist/graph/enrich.js +55 -2
- package/dist/graph/enrich.js.map +1 -1
- package/dist/graph/extract.d.ts +3 -1
- package/dist/graph/extract.d.ts.map +1 -1
- package/dist/graph/extract.js +215 -8
- package/dist/graph/extract.js.map +1 -1
- package/dist/graph/generic.d.ts +34 -1
- package/dist/graph/generic.d.ts.map +1 -1
- package/dist/graph/generic.js +152 -3
- package/dist/graph/generic.js.map +1 -1
- package/dist/graph/queries/clojure.scm +58 -0
- package/dist/graph/resolve.d.ts.map +1 -1
- package/dist/graph/resolve.js +132 -2
- package/dist/graph/resolve.js.map +1 -1
- package/dist/graph/scopes.d.ts +2 -2
- package/dist/graph/scopes.d.ts.map +1 -1
- package/dist/graph/scopes.js +8 -6
- package/dist/graph/scopes.js.map +1 -1
- package/dist/graph/source-files.d.ts +13 -4
- package/dist/graph/source-files.d.ts.map +1 -1
- package/dist/graph/source-files.js +35 -12
- package/dist/graph/source-files.js.map +1 -1
- package/dist/graph/types.d.ts +1 -1
- package/dist/graph/types.d.ts.map +1 -1
- package/dist/graph/workspace-cli.d.ts +2 -0
- package/dist/graph/workspace-cli.d.ts.map +1 -1
- package/dist/graph/workspace-cli.js +10 -4
- package/dist/graph/workspace-cli.js.map +1 -1
- package/dist/graph/workspace.d.ts +11 -6
- package/dist/graph/workspace.d.ts.map +1 -1
- package/dist/graph/workspace.js +24 -13
- package/dist/graph/workspace.js.map +1 -1
- package/dist/graph/write.d.ts.map +1 -1
- package/dist/graph/write.js +25 -2
- package/dist/graph/write.js.map +1 -1
- package/dist/hosts/antigravity.d.ts +7 -0
- package/dist/hosts/antigravity.d.ts.map +1 -0
- package/dist/hosts/antigravity.js +42 -0
- package/dist/hosts/antigravity.js.map +1 -0
- package/dist/hosts/init.d.ts.map +1 -1
- package/dist/hosts/init.js +6 -1
- package/dist/hosts/init.js.map +1 -1
- package/dist/hosts/mcp-config.d.ts.map +1 -1
- package/dist/hosts/mcp-config.js +7 -0
- package/dist/hosts/mcp-config.js.map +1 -1
- package/dist/hosts/plan.d.ts +1 -1
- package/dist/hosts/plan.d.ts.map +1 -1
- package/dist/hosts/plan.js +2 -0
- package/dist/hosts/plan.js.map +1 -1
- package/dist/hosts/registry.d.ts.map +1 -1
- package/dist/hosts/registry.js +13 -0
- package/dist/hosts/registry.js.map +1 -1
- package/dist/ingest/fs.d.ts +9 -2
- package/dist/ingest/fs.d.ts.map +1 -1
- package/dist/ingest/fs.js +100 -16
- package/dist/ingest/fs.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +45 -2
- package/dist/mcp/server.js.map +1 -1
- package/dist/telemetry/contract.d.ts +90 -0
- package/dist/telemetry/contract.d.ts.map +1 -0
- package/dist/telemetry/contract.js +203 -0
- package/dist/telemetry/contract.js.map +1 -0
- package/dist/telemetry/flush.d.ts +21 -0
- package/dist/telemetry/flush.d.ts.map +1 -0
- package/dist/telemetry/flush.js +84 -0
- package/dist/telemetry/flush.js.map +1 -0
- package/dist/telemetry/gate.d.ts +16 -0
- package/dist/telemetry/gate.d.ts.map +1 -0
- package/dist/telemetry/gate.js +64 -0
- package/dist/telemetry/gate.js.map +1 -0
- package/dist/telemetry/identity.d.ts +33 -0
- package/dist/telemetry/identity.d.ts.map +1 -0
- package/dist/telemetry/identity.js +80 -0
- package/dist/telemetry/identity.js.map +1 -0
- package/dist/telemetry/index.d.ts +18 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/index.js +16 -0
- package/dist/telemetry/index.js.map +1 -0
- package/dist/telemetry/key.d.ts +21 -0
- package/dist/telemetry/key.d.ts.map +1 -0
- package/dist/telemetry/key.js +49 -0
- package/dist/telemetry/key.js.map +1 -0
- package/dist/telemetry/notice.d.ts +19 -0
- package/dist/telemetry/notice.d.ts.map +1 -0
- package/dist/telemetry/notice.js +90 -0
- package/dist/telemetry/notice.js.map +1 -0
- package/dist/telemetry/queue.d.ts +35 -0
- package/dist/telemetry/queue.d.ts.map +1 -0
- package/dist/telemetry/queue.js +162 -0
- package/dist/telemetry/queue.js.map +1 -0
- package/dist/telemetry/send.d.ts +19 -0
- package/dist/telemetry/send.d.ts.map +1 -0
- package/dist/telemetry/send.js +84 -0
- package/dist/telemetry/send.js.map +1 -0
- package/dist/telemetry/sessions.d.ts +8 -0
- package/dist/telemetry/sessions.d.ts.map +1 -0
- package/dist/telemetry/sessions.js +81 -0
- package/dist/telemetry/sessions.js.map +1 -0
- package/dist/telemetry/track.d.ts +43 -0
- package/dist/telemetry/track.d.ts.map +1 -0
- package/dist/telemetry/track.js +108 -0
- package/dist/telemetry/track.js.map +1 -0
- package/dist/upkeep-run.d.ts +15 -0
- package/dist/upkeep-run.d.ts.map +1 -0
- package/dist/upkeep-run.js +60 -0
- package/dist/upkeep-run.js.map +1 -0
- package/dist/upkeep.d.ts +114 -0
- package/dist/upkeep.d.ts.map +1 -0
- package/dist/upkeep.js +232 -0
- package/dist/upkeep.js.map +1 -0
- package/dist/util/num.d.ts +3 -0
- package/dist/util/num.d.ts.map +1 -0
- package/dist/util/num.js +20 -0
- package/dist/util/num.js.map +1 -0
- package/dist/util/state.d.ts +11 -4
- package/dist/util/state.d.ts.map +1 -1
- package/dist/util/state.js +9 -0
- package/dist/util/state.js.map +1 -1
- package/dist/viewer/app.js +1 -1
- package/dist/viewer/index.html +1 -0
- package/dist/viewer/style.css +23 -2
- package/dist/viz/assemble.d.ts +33 -0
- package/dist/viz/assemble.d.ts.map +1 -1
- package/dist/viz/assemble.js.map +1 -1
- package/dist/viz/export.d.ts +39 -0
- package/dist/viz/export.d.ts.map +1 -0
- package/dist/viz/export.js +98 -0
- package/dist/viz/export.js.map +1 -0
- package/package.json +6 -5
- package/scripts/stamp-telemetry-key.mjs +53 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one network call in graft that is not an LLM request.
|
|
3
|
+
*
|
|
4
|
+
* A single POST of the whole queued batch to PostHog's capture endpoint, with
|
|
5
|
+
* `fetch` and no SDK. `posthog-node` would be the conventional choice, but it is
|
|
6
|
+
* a runtime dependency in a CLI whose install weight users notice, it wants to
|
|
7
|
+
* own batching and flushing (both of which the queue already does, and does
|
|
8
|
+
* differently because we are a short-lived process), and "an analytics library
|
|
9
|
+
* that can see everything" is precisely the objection this whole design exists
|
|
10
|
+
* to disarm. Twenty lines of `fetch` is auditable in a sitting.
|
|
11
|
+
*
|
|
12
|
+
* `$process_person_profile: false` on every event is what makes these ANONYMOUS
|
|
13
|
+
* events in PostHog: no person profile is created, no identity is stored, and
|
|
14
|
+
* the `distinct_id` is only ever the random install UUID.
|
|
15
|
+
*/
|
|
16
|
+
import { posthogKey, posthogHost } from './key.js';
|
|
17
|
+
/** Anything longer and the detached child is just holding a socket open. */
|
|
18
|
+
const SEND_TIMEOUT_MS = 8000;
|
|
19
|
+
/**
|
|
20
|
+
* The events as PostHog wants them — everything the wire body contains EXCEPT
|
|
21
|
+
* the project key.
|
|
22
|
+
*
|
|
23
|
+
* The key is deliberately not in here. `graft telemetry debug` prints this
|
|
24
|
+
* object so a user can audit exactly what graft sends, and a command that exists
|
|
25
|
+
* to be pasted into a bug report must not also paste our ingestion key into it.
|
|
26
|
+
* `sendBatch` adds the key at the moment of the request and nowhere else, which
|
|
27
|
+
* keeps the key entirely out of every code path that can reach a terminal.
|
|
28
|
+
*/
|
|
29
|
+
export function buildBatch(events) {
|
|
30
|
+
return {
|
|
31
|
+
batch: events.map((e) => {
|
|
32
|
+
const ev = e;
|
|
33
|
+
return {
|
|
34
|
+
event: ev.event,
|
|
35
|
+
timestamp: ev.timestamp,
|
|
36
|
+
properties: {
|
|
37
|
+
...(ev.properties ?? {}),
|
|
38
|
+
distinct_id: ev.distinct_id,
|
|
39
|
+
// Anonymous event: PostHog stores it without creating a person.
|
|
40
|
+
$process_person_profile: false,
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The ingest path.
|
|
48
|
+
*
|
|
49
|
+
* `/batch/` only, with no fallback. An earlier revision also tried `/e/`,
|
|
50
|
+
* because assign's `frontend/src/lib/posthog.ts` documents `events.nanonets.com`
|
|
51
|
+
* as serving the older path and answering `400 invalid_payload` to bodies it
|
|
52
|
+
* will not take. Probed directly, that host returns **401** to a well-formed
|
|
53
|
+
* batch with a bad key — so `/batch/` is there and does parse the body, and the
|
|
54
|
+
* historical 400 was gzip from `posthog-js` in a browser, which we never send.
|
|
55
|
+
*
|
|
56
|
+
* The fallback was therefore a second request that could not fire, and a branch
|
|
57
|
+
* production never exercises is worse than no branch. If a future host needs
|
|
58
|
+
* `/e/`, point `GRAFT_POSTHOG_HOST` at it and add the path back with evidence.
|
|
59
|
+
*/
|
|
60
|
+
const INGEST_PATH = '/batch/';
|
|
61
|
+
export async function sendBatch(events) {
|
|
62
|
+
if (events.length === 0)
|
|
63
|
+
return { ok: true };
|
|
64
|
+
const key = posthogKey();
|
|
65
|
+
if (!key)
|
|
66
|
+
return { ok: false, error: 'no key' };
|
|
67
|
+
try {
|
|
68
|
+
const res = await fetch(`${posthogHost()}${INGEST_PATH}`, {
|
|
69
|
+
method: 'POST',
|
|
70
|
+
headers: { 'content-type': 'application/json' },
|
|
71
|
+
// The one place the key is ever attached: the request body itself.
|
|
72
|
+
body: JSON.stringify({ api_key: key, ...buildBatch(events) }),
|
|
73
|
+
signal: AbortSignal.timeout(SEND_TIMEOUT_MS),
|
|
74
|
+
});
|
|
75
|
+
// A 4xx is our bug (a bad key, a malformed body) and retrying it forever
|
|
76
|
+
// would pin the queue at its cap; only 5xx and transport errors go back on
|
|
77
|
+
// the queue — see shouldRequeue in flush.ts.
|
|
78
|
+
return { ok: res.ok, status: res.status };
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
return { ok: false, error: e instanceof Error ? e.name : 'unknown' };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=send.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"send.js","sourceRoot":"","sources":["../../src/telemetry/send.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAEnD,4EAA4E;AAC5E,MAAM,eAAe,GAAG,IAAI,CAAC;AAS7B;;;;;;;;;GASG;AACH,MAAM,UAAU,UAAU,CAAC,MAAiB;IAC1C,OAAO;QACL,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;YACtB,MAAM,EAAE,GAAG,CAAqG,CAAC;YACjH,OAAO;gBACL,KAAK,EAAE,EAAE,CAAC,KAAK;gBACf,SAAS,EAAE,EAAE,CAAC,SAAS;gBACvB,UAAU,EAAE;oBACV,GAAG,CAAC,EAAE,CAAC,UAAU,IAAI,EAAE,CAAC;oBACxB,WAAW,EAAE,EAAE,CAAC,WAAW;oBAC3B,gEAAgE;oBAChE,uBAAuB,EAAE,KAAK;iBAC/B;aACF,CAAC;QACJ,CAAC,CAAC;KACH,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,GAAG,SAAS,CAAC;AAE9B,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,MAAiB;IAC/C,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IAC7C,MAAM,GAAG,GAAG,UAAU,EAAE,CAAC;IACzB,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;IAChD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,WAAW,EAAE,GAAG,WAAW,EAAE,EAAE;YACxD,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,mEAAmE;YACnE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YAC7D,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,eAAe,CAAC;SAC7C,CAAC,CAAC;QACH,yEAAyE;QACzE,2EAA2E;QAC3E,6CAA6C;QAC7C,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC;IAC5C,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC;IACvE,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Untouched for this long and the session is treated as over. */
|
|
2
|
+
export declare const SESSION_IDLE_MS: number;
|
|
3
|
+
/**
|
|
4
|
+
* Queue a `session_summary` for every closed, not-yet-summarised session in this
|
|
5
|
+
* repo. Returns how many were queued (for tests). Never throws.
|
|
6
|
+
*/
|
|
7
|
+
export declare function flushClosedSessions(repo: string, now?: number, home?: string, env?: NodeJS.ProcessEnv): number;
|
|
8
|
+
//# sourceMappingURL=sessions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../../src/telemetry/sessions.ts"],"names":[],"mappings":"AA2BA,kEAAkE;AAClE,eAAO,MAAM,eAAe,QAAqB,CAAC;AAMlD;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,GAAG,SAAa,EAChB,IAAI,CAAC,EAAE,MAAM,EACb,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,GACtB,MAAM,CAoCR"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rolling a finished agent session up into one `session_summary` event.
|
|
3
|
+
*
|
|
4
|
+
* This is the event that justifies the whole exercise. Everything else here
|
|
5
|
+
* counts commands; this one answers the question the product actually rests on —
|
|
6
|
+
* when an agent had both graft and grep available, which did it reach for, and
|
|
7
|
+
* how many tokens did that save? The counters already exist: the Claude Code
|
|
8
|
+
* hooks have been writing `graftReads`, `sourceReads` and `savedTokens` per
|
|
9
|
+
* session into `graft/.cache/session/` since well before telemetry did. Nothing
|
|
10
|
+
* new is measured here; a closed session is simply bucketed and queued.
|
|
11
|
+
*
|
|
12
|
+
* "Closed" is inferred from mtime rather than observed, because there is no
|
|
13
|
+
* reliable end-of-session hook — `Stop` fires per turn, and an editor that
|
|
14
|
+
* crashes fires nothing at all. A session untouched for two hours is over.
|
|
15
|
+
*
|
|
16
|
+
* Called from the session-start hook, which is the one place guaranteed to run
|
|
17
|
+
* again after a session ends. A hook must never touch the network (see the note
|
|
18
|
+
* atop upkeep.ts) and this doesn't: it only appends to the local queue.
|
|
19
|
+
*/
|
|
20
|
+
import { readdirSync, statSync } from 'node:fs';
|
|
21
|
+
import { join } from 'node:path';
|
|
22
|
+
import { cacheDir } from '../util/state.js';
|
|
23
|
+
import { readSession, writeSession } from '../claude/state.js';
|
|
24
|
+
import { countBucket, savedTokensBucket } from './contract.js';
|
|
25
|
+
import { track } from './track.js';
|
|
26
|
+
import { telemetryOn } from './gate.js';
|
|
27
|
+
/** Untouched for this long and the session is treated as over. */
|
|
28
|
+
export const SESSION_IDLE_MS = 2 * 60 * 60 * 1000;
|
|
29
|
+
/** Never roll up more than this in one hook run — a repo with a huge backlog of
|
|
30
|
+
* session files must not slow down a session start. The rest wait for the next. */
|
|
31
|
+
const MAX_PER_RUN = 20;
|
|
32
|
+
/**
|
|
33
|
+
* Queue a `session_summary` for every closed, not-yet-summarised session in this
|
|
34
|
+
* repo. Returns how many were queued (for tests). Never throws.
|
|
35
|
+
*/
|
|
36
|
+
export function flushClosedSessions(repo, now = Date.now(), home, env) {
|
|
37
|
+
try {
|
|
38
|
+
if (!telemetryOn(home, env))
|
|
39
|
+
return 0;
|
|
40
|
+
const dir = join(cacheDir(repo), 'session');
|
|
41
|
+
let files;
|
|
42
|
+
try {
|
|
43
|
+
files = readdirSync(dir).filter((f) => f.endsWith('.json'));
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
let queued = 0;
|
|
49
|
+
for (const file of files.sort()) {
|
|
50
|
+
if (queued >= MAX_PER_RUN)
|
|
51
|
+
break;
|
|
52
|
+
let mtime;
|
|
53
|
+
try {
|
|
54
|
+
mtime = statSync(join(dir, file)).mtimeMs;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (now - mtime < SESSION_IDLE_MS)
|
|
60
|
+
continue;
|
|
61
|
+
const id = file.slice(0, -'.json'.length);
|
|
62
|
+
const s = readSession(repo, id);
|
|
63
|
+
if (s.summarized)
|
|
64
|
+
continue;
|
|
65
|
+
// An empty session — opened, nothing asked — is still a fact worth having:
|
|
66
|
+
// it is the difference between "graft is installed" and "graft is used".
|
|
67
|
+
track('session_summary', {
|
|
68
|
+
graft_reads_bucket: countBucket(s.graftReads ?? 0),
|
|
69
|
+
source_reads_bucket: countBucket(s.sourceReads ?? 0),
|
|
70
|
+
saved_tokens_bucket: savedTokensBucket(s.savedTokens ?? 0),
|
|
71
|
+
}, { repo, host: 'claude-code', home, env });
|
|
72
|
+
writeSession(repo, id, { ...s, summarized: true });
|
|
73
|
+
queued++;
|
|
74
|
+
}
|
|
75
|
+
return queued;
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return 0; // a session start is never worth failing over a metric
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=sessions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessions.js","sourceRoot":"","sources":["../../src/telemetry/sessions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAC/D,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExC,kEAAkE;AAClE,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAElD;oFACoF;AACpF,MAAM,WAAW,GAAG,EAAE,CAAC;AAEvB;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CACjC,IAAY,EACZ,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,EAChB,IAAa,EACb,GAAuB;IAEvB,IAAI,CAAC;QACH,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,GAAG,CAAC;YAAE,OAAO,CAAC,CAAC;QACtC,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC,CAAC;QAC5C,IAAI,KAAe,CAAC;QACpB,IAAI,CAAC;YAAC,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC;YAAC,OAAO,CAAC,CAAC;QAAC,CAAC;QAExF,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;YAChC,IAAI,MAAM,IAAI,WAAW;gBAAE,MAAM;YACjC,IAAI,KAAa,CAAC;YAClB,IAAI,CAAC;gBAAC,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC;gBAAC,SAAS;YAAC,CAAC;YACtE,IAAI,GAAG,GAAG,KAAK,GAAG,eAAe;gBAAE,SAAS;YAE5C,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;YAC1C,MAAM,CAAC,GAAG,WAAW,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAChC,IAAI,CAAC,CAAC,UAAU;gBAAE,SAAS;YAE3B,2EAA2E;YAC3E,yEAAyE;YACzE,KAAK,CACH,iBAAiB,EACjB;gBACE,kBAAkB,EAAE,WAAW,CAAC,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC;gBAClD,mBAAmB,EAAE,WAAW,CAAC,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC;gBACpD,mBAAmB,EAAE,iBAAiB,CAAC,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC;aAC3D,EACD,EAAE,IAAI,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,GAAG,EAAE,CACzC,CAAC;YACF,YAAY,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;YACnD,MAAM,EAAE,CAAC;QACX,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,CAAC,CAAC,uDAAuD;IACnE,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { AgentHost } from './contract.js';
|
|
2
|
+
/** One queued event, exactly as it will be sent. */
|
|
3
|
+
export interface QueuedEvent {
|
|
4
|
+
event: string;
|
|
5
|
+
properties: Record<string, string>;
|
|
6
|
+
/** ISO timestamp, so a batch flushed a day late still lands on the right day. */
|
|
7
|
+
timestamp: string;
|
|
8
|
+
/** The install id, used as PostHog's `distinct_id` at send time. */
|
|
9
|
+
distinct_id: string;
|
|
10
|
+
}
|
|
11
|
+
export interface TrackContext {
|
|
12
|
+
/** Repo root, when the call site has one — supplies `repo_id`. */
|
|
13
|
+
repo?: string;
|
|
14
|
+
/** Which surface is calling. Defaults to the CLI. */
|
|
15
|
+
host?: AgentHost;
|
|
16
|
+
/** Test seam: a scratch `$HOME`. Production callers never pass this. */
|
|
17
|
+
home?: string;
|
|
18
|
+
/** Test seam: an environment to gate against. Production callers never pass
|
|
19
|
+
* this — a real run must be gated on the real `DO_NOT_TRACK` and `CI`. */
|
|
20
|
+
env?: NodeJS.ProcessEnv;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Which agent graft is running under, from the surface plus one env probe.
|
|
24
|
+
*
|
|
25
|
+
* Only the PRESENCE of `CLAUDECODE` is read, never its value — the distinction
|
|
26
|
+
* matters: a value could be anything a user's shell profile put there, presence
|
|
27
|
+
* is a boolean. Anything unrecognised is `cli`, which is the honest answer.
|
|
28
|
+
*/
|
|
29
|
+
export declare function detectHost(explicit?: AgentHost, env?: NodeJS.ProcessEnv): AgentHost;
|
|
30
|
+
/**
|
|
31
|
+
* Record one event, if the contract allows it and the gates are open.
|
|
32
|
+
*
|
|
33
|
+
* Returns the event that was queued, or null — the return value exists for
|
|
34
|
+
* tests and for `graft telemetry debug`, not for control flow at call sites.
|
|
35
|
+
*/
|
|
36
|
+
export declare function track(event: string, props?: Record<string, string | undefined>, ctx?: TrackContext): QueuedEvent | null;
|
|
37
|
+
/**
|
|
38
|
+
* The `first_run` event, fired the once. Separate from `track` because it is the
|
|
39
|
+
* only event whose trigger is "the id did not exist a moment ago", which only
|
|
40
|
+
* `installId()` can know.
|
|
41
|
+
*/
|
|
42
|
+
export declare function trackFirstRunIfNew(ctx?: TrackContext): void;
|
|
43
|
+
//# sourceMappingURL=track.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"track.d.ts","sourceRoot":"","sources":["../../src/telemetry/track.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAM/C,oDAAoD;AACpD,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,iFAAiF;IACjF,SAAS,EAAE,MAAM,CAAC;IAClB,oEAAoE;IACpE,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,kEAAkE;IAClE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qDAAqD;IACrD,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,wEAAwE;IACxE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;+EAC2E;IAC3E,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACzB;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,QAAQ,CAAC,EAAE,SAAS,EAAE,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,SAAS,CAIhG;AAmBD;;;;;GAKG;AACH,wBAAgB,KAAK,CACnB,KAAK,EAAE,MAAM,EACb,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAM,EAC9C,GAAG,GAAE,YAAiB,GACrB,WAAW,GAAG,IAAI,CA0BpB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,GAAE,YAAiB,GAAG,IAAI,CAM/D"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `track()` — the only way an event is ever created, and the place the contract
|
|
3
|
+
* is enforced.
|
|
4
|
+
*
|
|
5
|
+
* Call sites pass a name and some properties; this drops anything the contract
|
|
6
|
+
* (contract.ts) does not list, stamps the common properties, and appends to the
|
|
7
|
+
* local queue. It never sends, never blocks, and never throws — a telemetry bug
|
|
8
|
+
* must be incapable of failing a build or a query.
|
|
9
|
+
*
|
|
10
|
+
* Two rules do the actual privacy work, and both are enforced here rather than
|
|
11
|
+
* asked of callers:
|
|
12
|
+
*
|
|
13
|
+
* • unknown event → dropped whole; unknown property key → dropped, the rest of
|
|
14
|
+
* the event survives. A careless `track('query', { path })` sends `query`
|
|
15
|
+
* without a path.
|
|
16
|
+
* • non-string values → dropped. Everything that crosses the wire is a bucket
|
|
17
|
+
* label or an enum member, so a raw count or a raw message cannot get out
|
|
18
|
+
* even under a key that happens to be allowed.
|
|
19
|
+
*/
|
|
20
|
+
import { EVENTS } from './contract.js';
|
|
21
|
+
import { enqueue } from './queue.js';
|
|
22
|
+
import { telemetryOn } from './gate.js';
|
|
23
|
+
import { installId, repoId } from './identity.js';
|
|
24
|
+
import { runningVersion } from '../upkeep.js';
|
|
25
|
+
/**
|
|
26
|
+
* Which agent graft is running under, from the surface plus one env probe.
|
|
27
|
+
*
|
|
28
|
+
* Only the PRESENCE of `CLAUDECODE` is read, never its value — the distinction
|
|
29
|
+
* matters: a value could be anything a user's shell profile put there, presence
|
|
30
|
+
* is a boolean. Anything unrecognised is `cli`, which is the honest answer.
|
|
31
|
+
*/
|
|
32
|
+
export function detectHost(explicit, env = process.env) {
|
|
33
|
+
if (explicit)
|
|
34
|
+
return explicit;
|
|
35
|
+
if (env.CLAUDECODE !== undefined)
|
|
36
|
+
return 'claude-code';
|
|
37
|
+
return 'cli';
|
|
38
|
+
}
|
|
39
|
+
/** The properties every event carries. Kept in one place so no call site can
|
|
40
|
+
* disagree about them. */
|
|
41
|
+
function commonProps(ctx) {
|
|
42
|
+
const props = {
|
|
43
|
+
app_version: runningVersion(),
|
|
44
|
+
os: process.platform,
|
|
45
|
+
arch: process.arch,
|
|
46
|
+
node_major: String(process.versions.node.split('.')[0]),
|
|
47
|
+
ci: 'false', // an event only exists at all when the CI gate passed
|
|
48
|
+
agent_host: detectHost(ctx.host, ctx.env ?? process.env),
|
|
49
|
+
};
|
|
50
|
+
if (ctx.repo) {
|
|
51
|
+
try {
|
|
52
|
+
props.repo_id = repoId(ctx.repo);
|
|
53
|
+
}
|
|
54
|
+
catch { /* unwritable cache — omit */ }
|
|
55
|
+
}
|
|
56
|
+
return props;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Record one event, if the contract allows it and the gates are open.
|
|
60
|
+
*
|
|
61
|
+
* Returns the event that was queued, or null — the return value exists for
|
|
62
|
+
* tests and for `graft telemetry debug`, not for control flow at call sites.
|
|
63
|
+
*/
|
|
64
|
+
export function track(event, props = {}, ctx = {}) {
|
|
65
|
+
try {
|
|
66
|
+
if (!telemetryOn(ctx.home, ctx.env))
|
|
67
|
+
return null;
|
|
68
|
+
// `Object.hasOwn`, not `EVENTS[event]`: a bare object literal inherits from
|
|
69
|
+
// Object.prototype, so `EVENTS['constructor']` is truthy and would sail past
|
|
70
|
+
// a plain existence check — only failing later, on a TypeError swallowed by
|
|
71
|
+
// the catch below. The allowlist must reject by design, not by exception.
|
|
72
|
+
if (!Object.hasOwn(EVENTS, event))
|
|
73
|
+
return null;
|
|
74
|
+
const allowed = EVENTS[event];
|
|
75
|
+
const properties = commonProps(ctx);
|
|
76
|
+
for (const [k, v] of Object.entries(props)) {
|
|
77
|
+
if (allowed.has(k) && typeof v === 'string')
|
|
78
|
+
properties[k] = v;
|
|
79
|
+
}
|
|
80
|
+
const queued = {
|
|
81
|
+
event,
|
|
82
|
+
properties,
|
|
83
|
+
timestamp: new Date().toISOString(),
|
|
84
|
+
distinct_id: installId(ctx.home).id,
|
|
85
|
+
};
|
|
86
|
+
enqueue(queued, ctx.home);
|
|
87
|
+
return queued;
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return null; // telemetry never surfaces as a user-visible failure
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The `first_run` event, fired the once. Separate from `track` because it is the
|
|
95
|
+
* only event whose trigger is "the id did not exist a moment ago", which only
|
|
96
|
+
* `installId()` can know.
|
|
97
|
+
*/
|
|
98
|
+
export function trackFirstRunIfNew(ctx = {}) {
|
|
99
|
+
try {
|
|
100
|
+
if (!telemetryOn(ctx.home, ctx.env))
|
|
101
|
+
return;
|
|
102
|
+
if (!installId(ctx.home).firstRun)
|
|
103
|
+
return;
|
|
104
|
+
track('first_run', {}, ctx);
|
|
105
|
+
}
|
|
106
|
+
catch { /* never */ }
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=track.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"track.js","sourceRoot":"","sources":["../../src/telemetry/track.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface UpkeepResult {
|
|
2
|
+
/** Lines worth showing the agent/user, already formatted. Usually empty. */
|
|
3
|
+
lines: string[];
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* @param repo the project dir
|
|
7
|
+
* @param current the running graft's version
|
|
8
|
+
* @param opts.background false in hook contexts: read the update cache, never
|
|
9
|
+
* spawn a fetch. Hooks run under a hard timeout and must stay off the network.
|
|
10
|
+
*/
|
|
11
|
+
export declare function runUpkeep(repo: string, current: string, opts?: {
|
|
12
|
+
background?: boolean;
|
|
13
|
+
home?: string;
|
|
14
|
+
}): UpkeepResult;
|
|
15
|
+
//# sourceMappingURL=upkeep-run.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upkeep-run.d.ts","sourceRoot":"","sources":["../src/upkeep-run.ts"],"names":[],"mappings":"AAmBA,MAAM,WAAW,YAAY;IAC3B,4EAA4E;IAC5E,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAyBD;;;;;GAKG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,IAAI,GAAE;IAAE,UAAU,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,GACjD,YAAY,CAad"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The wired-up version of `./upkeep.ts` — one call every entry point makes.
|
|
3
|
+
*
|
|
4
|
+
* Split from `upkeep.ts` so that module stays pure and unit-testable (no init
|
|
5
|
+
* writes, no spawning): this file is the only place that knows how to actually
|
|
6
|
+
* re-run the wiring, and it is deliberately thin.
|
|
7
|
+
*/
|
|
8
|
+
import { runInit } from './claude/init.js';
|
|
9
|
+
import { runHostsInit } from './hosts/init.js';
|
|
10
|
+
import { graftCliPath } from './claude/paths.js';
|
|
11
|
+
import { formatUpdateNudge, formatWiringRefresh, maybeRefreshInBackground, readUpdateCache, reconcileWiring, } from './upkeep.js';
|
|
12
|
+
/**
|
|
13
|
+
* Re-write the wiring for exactly the hosts a previous `init` chose here, with
|
|
14
|
+
* exactly the flags it was given.
|
|
15
|
+
*
|
|
16
|
+
* `build: false` matters: this runs at session start and at MCP boot, and a graph
|
|
17
|
+
* build there would stall the agent's first turn.
|
|
18
|
+
*
|
|
19
|
+
* The out-of-repo writes (`~/.codex/hooks.json`, `~/.codex/config.toml`) ARE
|
|
20
|
+
* included, because nothing else would ever refresh them: no skill, rule file, or
|
|
21
|
+
* MCP instruction tells an agent to run `graft init`, so leaving them out means a
|
|
22
|
+
* Codex user upgrades the binary and keeps the old hook config forever. They're
|
|
23
|
+
* safe to replay — `installCodexHooks` no-ops when `~/.codex` is absent, rewrites
|
|
24
|
+
* only its own entry (matched on `graft-hooks.cjs`), and reports `unchanged` when
|
|
25
|
+
* the bytes match. A user who declined them at init time is honoured via
|
|
26
|
+
* `opts.global`/`opts.hooks`, replayed from the stamp.
|
|
27
|
+
*/
|
|
28
|
+
function rewriteWiring(repo, hosts, opts) {
|
|
29
|
+
if (hosts.includes('claude'))
|
|
30
|
+
runInit(repo, { build: false, cliPath: graftCliPath() });
|
|
31
|
+
const others = hosts.filter((h) => h !== 'claude');
|
|
32
|
+
if (others.length)
|
|
33
|
+
runHostsInit(repo, { agents: others, global: opts.global, mcp: opts.mcp, hooks: opts.hooks });
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* @param repo the project dir
|
|
37
|
+
* @param current the running graft's version
|
|
38
|
+
* @param opts.background false in hook contexts: read the update cache, never
|
|
39
|
+
* spawn a fetch. Hooks run under a hard timeout and must stay off the network.
|
|
40
|
+
*/
|
|
41
|
+
export function runUpkeep(repo, current, opts = {}) {
|
|
42
|
+
const lines = [];
|
|
43
|
+
try {
|
|
44
|
+
const refreshed = reconcileWiring(repo, current, { rewrite: rewriteWiring });
|
|
45
|
+
const refreshLine = formatWiringRefresh(refreshed);
|
|
46
|
+
if (refreshLine)
|
|
47
|
+
lines.push(refreshLine);
|
|
48
|
+
}
|
|
49
|
+
catch { /* fail-soft: wiring refresh is never worth breaking a session for */ }
|
|
50
|
+
try {
|
|
51
|
+
if (opts.background !== false)
|
|
52
|
+
maybeRefreshInBackground(opts.home);
|
|
53
|
+
const nudge = formatUpdateNudge(current, readUpdateCache(opts.home)?.latest);
|
|
54
|
+
if (nudge)
|
|
55
|
+
lines.push(nudge);
|
|
56
|
+
}
|
|
57
|
+
catch { /* same */ }
|
|
58
|
+
return { lines };
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=upkeep-run.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upkeep-run.js","sourceRoot":"","sources":["../src/upkeep-run.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,wBAAwB,EACxB,eAAe,EACf,eAAe,GAEhB,MAAM,aAAa,CAAC;AAOrB;;;;;;;;;;;;;;;GAeG;AACH,SAAS,aAAa,CAAC,IAAY,EAAE,KAAe,EAAE,IAAgB;IACpE,IAAI,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAAE,OAAO,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,EAAE,CAAC,CAAC;IACvF,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC;IACnD,IAAI,MAAM,CAAC,MAAM;QACf,YAAY,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;AAClG,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CACvB,IAAY,EACZ,OAAe,EACf,OAAgD,EAAE;IAElD,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,eAAe,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC,CAAC;QAC7E,MAAM,WAAW,GAAG,mBAAmB,CAAC,SAAS,CAAC,CAAC;QACnD,IAAI,WAAW;YAAE,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC3C,CAAC;IAAC,MAAM,CAAC,CAAC,qEAAqE,CAAC,CAAC;IACjF,IAAI,CAAC;QACH,IAAI,IAAI,CAAC,UAAU,KAAK,KAAK;YAAE,wBAAwB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnE,MAAM,KAAK,GAAG,iBAAiB,CAAC,OAAO,EAAE,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;QAC7E,IAAI,KAAK;YAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC,CAAC,UAAU,CAAC,CAAC;IACtB,OAAO,EAAE,KAAK,EAAE,CAAC;AACnB,CAAC"}
|
package/dist/upkeep.d.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The version of the graft package this code was loaded from.
|
|
3
|
+
*
|
|
4
|
+
* Resolved from *this* module rather than the caller's: `readCurrentVersion`
|
|
5
|
+
* looks one level up from the module URL it's given, which lands on the package
|
|
6
|
+
* root for `dist/upkeep.js` but misses entirely for `dist/claude/hooks.js` and
|
|
7
|
+
* `dist/mcp/server.js`. Every caller asking here instead of passing its own
|
|
8
|
+
* `import.meta.url` is what keeps the hook and the MCP server from reading
|
|
9
|
+
* `0.0.0` and re-initing on every single session.
|
|
10
|
+
*/
|
|
11
|
+
export declare function runningVersion(): string;
|
|
12
|
+
/** How long a registry answer is considered current. A day: graft ships far less
|
|
13
|
+
* often than that, and this is a nudge, not a security update. */
|
|
14
|
+
export declare const UPDATE_TTL_MS: number;
|
|
15
|
+
/** Numeric-dotted compare of the release part only (`1.2.3-beta.1` → `1.2.3`).
|
|
16
|
+
* Prerelease ordering doesn't matter here: the only question either caller asks
|
|
17
|
+
* is "is the thing on npm ahead of what's on disk", and a prerelease that
|
|
18
|
+
* compares equal simply produces no nudge. */
|
|
19
|
+
export declare function compareVersions(a: string, b: string): number;
|
|
20
|
+
export declare function isNewer(candidate: string | null | undefined, current: string): boolean;
|
|
21
|
+
export interface UpdateCache {
|
|
22
|
+
/** Latest version seen on npm, or null when the last fetch failed. */
|
|
23
|
+
latest: string | null;
|
|
24
|
+
/** Epoch ms of the last *attempt* — written by the parent before it spawns the
|
|
25
|
+
* fetch, so a repeatedly-failing fetch can't make every command spawn a child. */
|
|
26
|
+
checkedAt: number;
|
|
27
|
+
}
|
|
28
|
+
/** Machine-global, not per-repo: "what's the latest graft" is one fact, and a
|
|
29
|
+
* dev with twelve repos should cost the registry one request a day, not twelve. */
|
|
30
|
+
export declare function updateCachePath(home?: string): string;
|
|
31
|
+
export declare function readUpdateCache(home?: string): UpdateCache | null;
|
|
32
|
+
/**
|
|
33
|
+
* The `graft _update-check` command body: hit the registry, store the answer.
|
|
34
|
+
* Runs in a detached child so nothing user-facing ever waits on the network.
|
|
35
|
+
*/
|
|
36
|
+
export declare function refreshUpdateCache(home?: string, now?: number): UpdateCache;
|
|
37
|
+
/** True when the cached answer is missing or older than the TTL. */
|
|
38
|
+
export declare function needsRefresh(cache: UpdateCache | null, now?: number): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Kick off a background registry check if the cache has gone stale. Touches
|
|
41
|
+
* `checkedAt` first so concurrent callers (and a child that dies) don't spawn a
|
|
42
|
+
* fetch per invocation. Never waits, never throws.
|
|
43
|
+
*
|
|
44
|
+
* Only called from long-lived or already-slow contexts (a CLI command, MCP
|
|
45
|
+
* boot) — never from a hook, which reads the cache and nothing else.
|
|
46
|
+
*/
|
|
47
|
+
export declare function maybeRefreshInBackground(home?: string, now?: number): boolean;
|
|
48
|
+
/** One line, or nothing. Nothing is the common case — don't spend context on
|
|
49
|
+
* "you're up to date". */
|
|
50
|
+
export declare function formatUpdateNudge(current: string, latest: string | null | undefined): string | null;
|
|
51
|
+
/**
|
|
52
|
+
* The subset of `graft init`'s flags a refresh has to replay.
|
|
53
|
+
*
|
|
54
|
+
* Without these, an auto-refresh would install things the user explicitly
|
|
55
|
+
* declined: someone who ran `graft init --no-global` (or `--no-hooks`) said "keep
|
|
56
|
+
* out of `~/.codex`", and a later session silently writing there would be graft
|
|
57
|
+
* overriding a decision rather than maintaining one. Absent from an older stamp →
|
|
58
|
+
* all true, which is what plain `graft init` does.
|
|
59
|
+
*/
|
|
60
|
+
export interface WiringOpts {
|
|
61
|
+
/** false → never write outside the repo (`--no-global`). */
|
|
62
|
+
global: boolean;
|
|
63
|
+
/** false → skip MCP server registration (`--no-mcp`). */
|
|
64
|
+
mcp: boolean;
|
|
65
|
+
/** false → skip hook installation (`--no-hooks`). */
|
|
66
|
+
hooks: boolean;
|
|
67
|
+
}
|
|
68
|
+
export declare const DEFAULT_WIRING_OPTS: WiringOpts;
|
|
69
|
+
/** An older stamp has no `opts`; a plain `graft init` wired everything. */
|
|
70
|
+
export declare function wiringOpts(stamp: WiringStamp | null): WiringOpts;
|
|
71
|
+
export interface WiringStamp {
|
|
72
|
+
/** The graft version whose `init` wrote this repo's agent files. */
|
|
73
|
+
version: string;
|
|
74
|
+
/** Host ids that were wired, so a refresh re-writes exactly those and never
|
|
75
|
+
* silently adopts an agent the user declined in the picker. */
|
|
76
|
+
hosts: string[];
|
|
77
|
+
/** The init flags to replay — see {@link WiringOpts}. */
|
|
78
|
+
opts?: Partial<WiringOpts>;
|
|
79
|
+
at: string;
|
|
80
|
+
}
|
|
81
|
+
/** Under `graft/.cache/`, beside the other derived state: git-ignored, per-clone,
|
|
82
|
+
* and cheap to lose — a missing stamp just means one idempotent refresh. */
|
|
83
|
+
export declare function stampPath(repo: string): string;
|
|
84
|
+
export declare function readStamp(repo: string): WiringStamp | null;
|
|
85
|
+
export declare function writeStamp(repo: string, version: string, hosts: string[], opts?: Partial<WiringOpts>, at?: string): void;
|
|
86
|
+
/**
|
|
87
|
+
* Which agents this repo is *already* wired for, read off disk rather than
|
|
88
|
+
* re-detected. Detection answers "which editors does this machine have"; for a
|
|
89
|
+
* refresh we need "which files did a previous init actually write" — otherwise
|
|
90
|
+
* installing Windsurf once would silently add graft rules to every repo.
|
|
91
|
+
*/
|
|
92
|
+
export declare function wiredHostIds(repo: string): string[];
|
|
93
|
+
export interface WiringRefresh {
|
|
94
|
+
from: string;
|
|
95
|
+
to: string;
|
|
96
|
+
hosts: string[];
|
|
97
|
+
/** True when the refresh included writes outside the repo (`~/.codex/`), so the
|
|
98
|
+
* caller can say so — a session changing machine-wide config should be visible. */
|
|
99
|
+
global: boolean;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Re-run init's writes when the stamp and the running binary disagree.
|
|
103
|
+
*
|
|
104
|
+
* Deliberately narrow: it refreshes the hosts already wired, replays the flags
|
|
105
|
+
* that init was given, never builds the graph (this runs at session start — a
|
|
106
|
+
* rebuild there would stall the agent's first turn), and no-ops when the repo has
|
|
107
|
+
* no graft wiring at all.
|
|
108
|
+
*/
|
|
109
|
+
export declare function reconcileWiring(repo: string, current: string, deps: {
|
|
110
|
+
wired?: (repo: string) => string[];
|
|
111
|
+
rewrite: (repo: string, hosts: string[], opts: WiringOpts) => void;
|
|
112
|
+
}): WiringRefresh | null;
|
|
113
|
+
export declare function formatWiringRefresh(r: WiringRefresh | null): string | null;
|
|
114
|
+
//# sourceMappingURL=upkeep.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upkeep.d.ts","sourceRoot":"","sources":["../src/upkeep.ts"],"names":[],"mappings":"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"}
|