@nanonets/graft 0.10.0 → 0.12.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 +34 -13
- 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 +13 -0
- package/dist/claude/hooks.d.ts.map +1 -1
- package/dist/claude/hooks.js +44 -4
- 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 +245 -2
- 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 +8 -1
- package/dist/graph/extract.d.ts.map +1 -1
- package/dist/graph/extract.js +401 -12
- 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 +176 -6
- package/dist/graph/generic.js.map +1 -1
- package/dist/graph/invariants.d.ts +32 -0
- package/dist/graph/invariants.d.ts.map +1 -0
- package/dist/graph/invariants.js +49 -0
- package/dist/graph/invariants.js.map +1 -0
- package/dist/graph/queries/clojure.scm +58 -0
- package/dist/graph/resolve.d.ts.map +1 -1
- package/dist/graph/resolve.js +249 -23
- 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 +3 -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/codex-hooks.d.ts +3 -3
- package/dist/hosts/codex-hooks.d.ts.map +1 -1
- package/dist/hosts/codex-hooks.js +27 -21
- package/dist/hosts/codex-hooks.js.map +1 -1
- 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 +15 -5
- package/scripts/stamp-telemetry-key.mjs +53 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The PostHog project key, and the reason forks stay silent.
|
|
3
|
+
*
|
|
4
|
+
* `BAKED_KEY` is empty in the repository and is rewritten in place by
|
|
5
|
+
* `scripts/stamp-telemetry-key.mjs`, which runs from `prepublishOnly`. So the
|
|
6
|
+
* key exists only in the published tarball: a clone, a fork, and a plain
|
|
7
|
+
* `npm run build` all compile to `''`, and every send path short-circuits. A
|
|
8
|
+
* contributor cannot accidentally send us their own usage, and a fork cannot
|
|
9
|
+
* send us anything at all.
|
|
10
|
+
*
|
|
11
|
+
* `GRAFT_POSTHOG_KEY` overrides it at runtime so a maintainer can exercise the
|
|
12
|
+
* real path locally without publishing.
|
|
13
|
+
*
|
|
14
|
+
* Public project keys are write-only ingestion keys — they authorise capture and
|
|
15
|
+
* nothing else — which is why one may sit in a published artifact at all. It is
|
|
16
|
+
* still not committed here: an empty default is what makes "forks never send"
|
|
17
|
+
* true by construction rather than by policy.
|
|
18
|
+
*/
|
|
19
|
+
/** Rewritten at publish time. Do not hand-edit — see the module comment. */
|
|
20
|
+
const BAKED_KEY = '';
|
|
21
|
+
/**
|
|
22
|
+
* Nanonets' own PostHog host, not `us.i.posthog.com`.
|
|
23
|
+
*
|
|
24
|
+
* Every Nanonets product ingests through a nanonets.com proxy rather than
|
|
25
|
+
* PostHog directly, on one shared project token, so pointing graft at PostHog
|
|
26
|
+
* Cloud would put its events in a project nobody looks at.
|
|
27
|
+
*
|
|
28
|
+
* `events.` rather than `e.` because graft is server-side, and this is the host
|
|
29
|
+
* the other server-side integration already uses (the agents-platform Go
|
|
30
|
+
* backend sets `POSTHOG_HOST=https://events.nanonets.com`). The one documented
|
|
31
|
+
* objection to it — 400 `invalid_payload` — was gzip-compressed ingest from
|
|
32
|
+
* `posthog-js` in a browser, fixed there with `disable_compression: true`. We
|
|
33
|
+
* send plain uncompressed JSON from Node, so that failure mode cannot apply.
|
|
34
|
+
*
|
|
35
|
+
* Probed directly, that host answers 401 to a well-formed batch with a bad key,
|
|
36
|
+
* so it serves `/batch/` and parses the body — see the note in send.ts.
|
|
37
|
+
*
|
|
38
|
+
* The HOST is safe to commit; the KEY is not, and stays empty here. That
|
|
39
|
+
* asymmetry is the point: with no key, a fork built from this source sends
|
|
40
|
+
* nothing no matter where the host points.
|
|
41
|
+
*/
|
|
42
|
+
const BAKED_HOST = 'https://events.nanonets.com';
|
|
43
|
+
export function posthogKey() {
|
|
44
|
+
return process.env.GRAFT_POSTHOG_KEY || BAKED_KEY;
|
|
45
|
+
}
|
|
46
|
+
export function posthogHost() {
|
|
47
|
+
return (process.env.GRAFT_POSTHOG_HOST || BAKED_HOST).replace(/\/+$/, '');
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=key.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key.js","sourceRoot":"","sources":["../../src/telemetry/key.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,4EAA4E;AAC5E,MAAM,SAAS,GAAG,EAAE,CAAC;AAErB;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,GAAG,6BAA6B,CAAC;AAEjD,MAAM,UAAU,UAAU;IACxB,OAAO,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,SAAS,CAAC;AACpD,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,UAAU,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AAC5E,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare const TELEMETRY_DOC_URL = "https://github.com/NanoNets/context-graph-engine/blob/main/TELEMETRY.md";
|
|
2
|
+
export declare const NOTICE: string;
|
|
3
|
+
/**
|
|
4
|
+
* The notice text the first time it is due, then null forever after. Marks it as
|
|
5
|
+
* shown before returning, so two concurrent commands print it at most twice
|
|
6
|
+
* rather than every time.
|
|
7
|
+
*/
|
|
8
|
+
export declare function firstRunNotice(home?: string, env?: NodeJS.ProcessEnv): string | null;
|
|
9
|
+
/** `graft telemetry status`. `env` is a test seam, as elsewhere in this module —
|
|
10
|
+
* a real run must be gated on the real `DO_NOT_TRACK` and CI variables. */
|
|
11
|
+
export declare function formatStatus(home?: string, env?: NodeJS.ProcessEnv): string;
|
|
12
|
+
/**
|
|
13
|
+
* `graft telemetry debug` — Next.js's `NEXT_TELEMETRY_DEBUG` and Astro's
|
|
14
|
+
* `DEBUG=astro:telemetry` in command form. Prints the pending queue and the
|
|
15
|
+
* literal JSON body a flush would POST, and sends nothing. The point is that a
|
|
16
|
+
* user never has to take our word for the contract.
|
|
17
|
+
*/
|
|
18
|
+
export declare function formatDebug(home?: string): string;
|
|
19
|
+
//# sourceMappingURL=notice.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notice.d.ts","sourceRoot":"","sources":["../../src/telemetry/notice.ts"],"names":[],"mappings":"AAqBA,eAAO,MAAM,iBAAiB,4EAA4E,CAAC;AAE3G,eAAO,MAAM,MAAM,QAGP,CAAC;AAEb;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,GAAG,MAAM,GAAG,IAAI,CASpF;AAED;4EAC4E;AAC5E,wBAAgB,YAAY,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,GAAG,MAAM,CAoB3E;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAkBjD"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one-time disclosure, and the `graft telemetry` command's output.
|
|
3
|
+
*
|
|
4
|
+
* The notice is the whole reason this design is defensible. Every telemetry
|
|
5
|
+
* backlash in the tools we looked at — the GitHub CLI's silent 2.91.0 rollout,
|
|
6
|
+
* Gatsby's on-by-default addition, the .NET SDK's opt-out that distributors now
|
|
7
|
+
* patch out — was about a tool that started sending without saying so. Homebrew
|
|
8
|
+
* does exactly what is done here and is uncontroversial: print once, in plain
|
|
9
|
+
* language, before anything leaves, and say how to turn it off in the same
|
|
10
|
+
* breath.
|
|
11
|
+
*
|
|
12
|
+
* Once per MACHINE, not per repo: a dev with twelve checkouts should read this
|
|
13
|
+
* one time. `graft init`'s picker is the other disclosure surface, for the users
|
|
14
|
+
* who see it — this catches everyone else.
|
|
15
|
+
*/
|
|
16
|
+
import { patchState, readState } from './identity.js';
|
|
17
|
+
import { explainOff, offReason } from './gate.js';
|
|
18
|
+
import { peek } from './queue.js';
|
|
19
|
+
import { buildBatch } from './send.js';
|
|
20
|
+
import { posthogHost } from './key.js';
|
|
21
|
+
export const TELEMETRY_DOC_URL = 'https://github.com/NanoNets/context-graph-engine/blob/main/TELEMETRY.md';
|
|
22
|
+
export const NOTICE = [
|
|
23
|
+
'· graft collects anonymous usage stats (no code, no file paths, no queries).',
|
|
24
|
+
` What exactly: ${TELEMETRY_DOC_URL} — turn it off with \`graft telemetry disable\`.`,
|
|
25
|
+
].join('\n');
|
|
26
|
+
/**
|
|
27
|
+
* The notice text the first time it is due, then null forever after. Marks it as
|
|
28
|
+
* shown before returning, so two concurrent commands print it at most twice
|
|
29
|
+
* rather than every time.
|
|
30
|
+
*/
|
|
31
|
+
export function firstRunNotice(home, env) {
|
|
32
|
+
try {
|
|
33
|
+
if (offReason(home, env) !== null)
|
|
34
|
+
return null; // nothing to disclose if nothing can be sent
|
|
35
|
+
if (readState(home)?.noticeShownAt)
|
|
36
|
+
return null;
|
|
37
|
+
patchState({ noticeShownAt: new Date().toISOString() }, home);
|
|
38
|
+
return NOTICE;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** `graft telemetry status`. `env` is a test seam, as elsewhere in this module —
|
|
45
|
+
* a real run must be gated on the real `DO_NOT_TRACK` and CI variables. */
|
|
46
|
+
export function formatStatus(home, env) {
|
|
47
|
+
const reason = offReason(home, env);
|
|
48
|
+
const pending = peek(home).length;
|
|
49
|
+
const lines = [
|
|
50
|
+
reason === null
|
|
51
|
+
? 'telemetry: on — anonymous, aggregate-only'
|
|
52
|
+
: `telemetry: ${explainOff(reason)}`,
|
|
53
|
+
` contract: ${TELEMETRY_DOC_URL}`,
|
|
54
|
+
];
|
|
55
|
+
if (reason === null || reason === 'disabled') {
|
|
56
|
+
lines.push(` endpoint: ${posthogHost()}`);
|
|
57
|
+
lines.push(` queued: ${pending} event${pending === 1 ? '' : 's'} waiting for the next daily flush`);
|
|
58
|
+
}
|
|
59
|
+
lines.push(reason === 'disabled'
|
|
60
|
+
? ' enable: graft telemetry enable'
|
|
61
|
+
: ' disable: graft telemetry disable (or set DO_NOT_TRACK=1)');
|
|
62
|
+
lines.push(' inspect: graft telemetry debug (prints the exact batch, sends nothing)');
|
|
63
|
+
return lines.join('\n');
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* `graft telemetry debug` — Next.js's `NEXT_TELEMETRY_DEBUG` and Astro's
|
|
67
|
+
* `DEBUG=astro:telemetry` in command form. Prints the pending queue and the
|
|
68
|
+
* literal JSON body a flush would POST, and sends nothing. The point is that a
|
|
69
|
+
* user never has to take our word for the contract.
|
|
70
|
+
*/
|
|
71
|
+
export function formatDebug(home) {
|
|
72
|
+
const events = peek(home);
|
|
73
|
+
if (events.length === 0) {
|
|
74
|
+
return [
|
|
75
|
+
'telemetry: nothing queued.',
|
|
76
|
+
' Run a graft command first — events are written locally and flushed once a day.',
|
|
77
|
+
].join('\n');
|
|
78
|
+
}
|
|
79
|
+
// A literal placeholder, never the key itself. This output is written to be
|
|
80
|
+
// pasted into an issue; the project key is not the user's to share and is not
|
|
81
|
+
// needed to audit what graft sends.
|
|
82
|
+
const body = { api_key: '<omitted — graft\'s own ingestion key>', ...buildBatch(events) };
|
|
83
|
+
return [
|
|
84
|
+
`telemetry: ${events.length} event(s) queued. This is the exact body a flush would POST`,
|
|
85
|
+
`to ${posthogHost()}/batch/ — running this command sends nothing.`,
|
|
86
|
+
'',
|
|
87
|
+
JSON.stringify(body, null, 2),
|
|
88
|
+
].join('\n');
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=notice.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notice.js","sourceRoot":"","sources":["../../src/telemetry/notice.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ceiling on the queue. An unflushable machine — permanently offline, or a
|
|
3
|
+
* key that never works — must not grow a file in someone's home forever.
|
|
4
|
+
*
|
|
5
|
+
* Bytes are the bound, because size is the only thing cheap enough to check on
|
|
6
|
+
* every append (one `statSync`); counting lines would mean reading the whole
|
|
7
|
+
* file each time. {@link TRIM_TO_EVENTS} is not a second ceiling — it is how
|
|
8
|
+
* FAR a trim cuts back, so a queue of unusually small events sheds a worthwhile
|
|
9
|
+
* amount rather than one line at a time. Between trims the file can hold more
|
|
10
|
+
* than that many events, and can exceed the byte bound by one append.
|
|
11
|
+
*/
|
|
12
|
+
export declare const MAX_QUEUE_BYTES: number;
|
|
13
|
+
export declare const TRIM_TO_EVENTS = 500;
|
|
14
|
+
export declare function queuePath(home?: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* Append one event. Never throws — a full disk or a read-only home must not fail
|
|
17
|
+
* the command the user actually ran.
|
|
18
|
+
*/
|
|
19
|
+
export declare function enqueue(event: unknown, home?: string): void;
|
|
20
|
+
/**
|
|
21
|
+
* Take everything pending, leaving an empty queue behind.
|
|
22
|
+
*
|
|
23
|
+
* The rename is what makes this safe against a concurrent append: from the
|
|
24
|
+
* instant it returns, other processes are writing to a brand-new file. Malformed
|
|
25
|
+
* lines are skipped rather than poisoning the batch — a torn write from a crash
|
|
26
|
+
* mid-append should cost one event, not the whole queue.
|
|
27
|
+
*/
|
|
28
|
+
export declare function drain(home?: string): unknown[];
|
|
29
|
+
/** Read without draining, for `graft telemetry debug`. */
|
|
30
|
+
export declare function peek(home?: string): unknown[];
|
|
31
|
+
/** Put a failed batch back, so a flush that could not reach the network does not
|
|
32
|
+
* silently discard a week of events. Oldest-first, ahead of anything queued
|
|
33
|
+
* since the drain. */
|
|
34
|
+
export declare function requeue(events: unknown[], home?: string): void;
|
|
35
|
+
//# sourceMappingURL=queue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../../src/telemetry/queue.ts"],"names":[],"mappings":"AAsBA;;;;;;;;;;GAUG;AACH,eAAO,MAAM,eAAe,QAAa,CAAC;AAC1C,eAAO,MAAM,cAAc,MAAM,CAAC;AAElC,wBAAgB,SAAS,CAAC,IAAI,GAAE,MAAkB,GAAG,MAAM,CAE1D;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAO3D;AA6BD;;;;;;;GAOG;AACH,wBAAgB,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,EAAE,CAa9C;AAED,0DAA0D;AAC1D,wBAAgB,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,EAAE,CAS7C;AAED;;uBAEuB;AACvB,wBAAgB,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAS9D"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The local event queue: one NDJSON line per event in `~/.graft/`.
|
|
3
|
+
*
|
|
4
|
+
* The queue exists because graft is a CLI, not an app. Every other tool we
|
|
5
|
+
* studied sends from a process that is already running and can afford to wait;
|
|
6
|
+
* `graft ask` lives for two seconds and its whole value proposition is being
|
|
7
|
+
* faster than reading files. So nothing is ever sent inline — events are
|
|
8
|
+
* appended here, and a detached child (`graft _telemetry-flush`, once a day)
|
|
9
|
+
* does the network. A user who is offline for a week loses nothing; a user on a
|
|
10
|
+
* hostile network never notices, because no command ever waits on a socket.
|
|
11
|
+
*
|
|
12
|
+
* Concurrency: appends are a single `appendFileSync` of one short line to a file
|
|
13
|
+
* opened `O_APPEND`, which POSIX makes atomic below PIPE_BUF (4 KB — an event is
|
|
14
|
+
* ~300 bytes). Two graft processes interleaving cannot produce a torn line.
|
|
15
|
+
*
|
|
16
|
+
* Draining renames the file first and reads the renamed copy, so events appended
|
|
17
|
+
* during a flush land in a fresh queue instead of being dropped by the truncate.
|
|
18
|
+
*/
|
|
19
|
+
import { homedir } from 'node:os';
|
|
20
|
+
import { join, dirname } from 'node:path';
|
|
21
|
+
import { appendFileSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
22
|
+
/**
|
|
23
|
+
* The ceiling on the queue. An unflushable machine — permanently offline, or a
|
|
24
|
+
* key that never works — must not grow a file in someone's home forever.
|
|
25
|
+
*
|
|
26
|
+
* Bytes are the bound, because size is the only thing cheap enough to check on
|
|
27
|
+
* every append (one `statSync`); counting lines would mean reading the whole
|
|
28
|
+
* file each time. {@link TRIM_TO_EVENTS} is not a second ceiling — it is how
|
|
29
|
+
* FAR a trim cuts back, so a queue of unusually small events sheds a worthwhile
|
|
30
|
+
* amount rather than one line at a time. Between trims the file can hold more
|
|
31
|
+
* than that many events, and can exceed the byte bound by one append.
|
|
32
|
+
*/
|
|
33
|
+
export const MAX_QUEUE_BYTES = 256 * 1024;
|
|
34
|
+
export const TRIM_TO_EVENTS = 500;
|
|
35
|
+
export function queuePath(home = homedir()) {
|
|
36
|
+
return join(home, '.graft', 'telemetry-queue.ndjson');
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Append one event. Never throws — a full disk or a read-only home must not fail
|
|
40
|
+
* the command the user actually ran.
|
|
41
|
+
*/
|
|
42
|
+
export function enqueue(event, home) {
|
|
43
|
+
const path = queuePath(home);
|
|
44
|
+
try {
|
|
45
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
46
|
+
appendFileSync(path, JSON.stringify(event) + '\n');
|
|
47
|
+
trim(path);
|
|
48
|
+
}
|
|
49
|
+
catch { /* unwritable home, full disk — telemetry is never worth an error */ }
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Enforce the cap by dropping the OLDEST events. Newest-wins because the recent
|
|
53
|
+
* ones describe the version the user is on now; a year-old queue from 0.9
|
|
54
|
+
* answers nothing. Only rewrites when actually over, so the common path is one
|
|
55
|
+
* `statSync`.
|
|
56
|
+
*/
|
|
57
|
+
function trim(path) {
|
|
58
|
+
let size;
|
|
59
|
+
try {
|
|
60
|
+
size = statSync(path).size;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (size <= MAX_QUEUE_BYTES)
|
|
66
|
+
return;
|
|
67
|
+
try {
|
|
68
|
+
const lines = readFileSync(path, 'utf8').split('\n').filter(Boolean);
|
|
69
|
+
// Walk backwards from the newest, taking lines while both budgets hold, so
|
|
70
|
+
// the file left behind always satisfies the byte bound — a count-only trim
|
|
71
|
+
// would leave 500 large events well over it.
|
|
72
|
+
const kept = [];
|
|
73
|
+
let bytes = 0;
|
|
74
|
+
for (let i = lines.length - 1; i >= 0 && kept.length < TRIM_TO_EVENTS; i--) {
|
|
75
|
+
bytes += Buffer.byteLength(lines[i]) + 1;
|
|
76
|
+
if (bytes > MAX_QUEUE_BYTES)
|
|
77
|
+
break;
|
|
78
|
+
kept.push(lines[i]);
|
|
79
|
+
}
|
|
80
|
+
kept.reverse();
|
|
81
|
+
writeFileSync(path, kept.length ? kept.join('\n') + '\n' : '');
|
|
82
|
+
}
|
|
83
|
+
catch { /* leave it; the next drain clears it anyway */ }
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Take everything pending, leaving an empty queue behind.
|
|
87
|
+
*
|
|
88
|
+
* The rename is what makes this safe against a concurrent append: from the
|
|
89
|
+
* instant it returns, other processes are writing to a brand-new file. Malformed
|
|
90
|
+
* lines are skipped rather than poisoning the batch — a torn write from a crash
|
|
91
|
+
* mid-append should cost one event, not the whole queue.
|
|
92
|
+
*/
|
|
93
|
+
export function drain(home) {
|
|
94
|
+
const path = queuePath(home);
|
|
95
|
+
const taken = `${path}.${process.pid}.sending`;
|
|
96
|
+
try {
|
|
97
|
+
renameSync(path, taken);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return [];
|
|
101
|
+
} // nothing queued
|
|
102
|
+
let text = '';
|
|
103
|
+
try {
|
|
104
|
+
text = readFileSync(taken, 'utf8');
|
|
105
|
+
}
|
|
106
|
+
catch { /* fall through to cleanup */ }
|
|
107
|
+
try {
|
|
108
|
+
rmSync(taken, { force: true });
|
|
109
|
+
}
|
|
110
|
+
catch { /* best effort */ }
|
|
111
|
+
const out = [];
|
|
112
|
+
for (const line of text.split('\n')) {
|
|
113
|
+
if (!line.trim())
|
|
114
|
+
continue;
|
|
115
|
+
try {
|
|
116
|
+
out.push(JSON.parse(line));
|
|
117
|
+
}
|
|
118
|
+
catch { /* torn line — skip just this one */ }
|
|
119
|
+
}
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
/** Read without draining, for `graft telemetry debug`. */
|
|
123
|
+
export function peek(home) {
|
|
124
|
+
const out = [];
|
|
125
|
+
let text = '';
|
|
126
|
+
try {
|
|
127
|
+
text = readFileSync(queuePath(home), 'utf8');
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return out;
|
|
131
|
+
}
|
|
132
|
+
for (const line of text.split('\n')) {
|
|
133
|
+
if (!line.trim())
|
|
134
|
+
continue;
|
|
135
|
+
try {
|
|
136
|
+
out.push(JSON.parse(line));
|
|
137
|
+
}
|
|
138
|
+
catch { /* skip */ }
|
|
139
|
+
}
|
|
140
|
+
return out;
|
|
141
|
+
}
|
|
142
|
+
/** Put a failed batch back, so a flush that could not reach the network does not
|
|
143
|
+
* silently discard a week of events. Oldest-first, ahead of anything queued
|
|
144
|
+
* since the drain. */
|
|
145
|
+
export function requeue(events, home) {
|
|
146
|
+
if (events.length === 0)
|
|
147
|
+
return;
|
|
148
|
+
const path = queuePath(home);
|
|
149
|
+
try {
|
|
150
|
+
const pending = (() => { try {
|
|
151
|
+
return readFileSync(path, 'utf8');
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return '';
|
|
155
|
+
} })();
|
|
156
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
157
|
+
writeFileSync(path, events.map((e) => JSON.stringify(e)).join('\n') + '\n' + pending);
|
|
158
|
+
trim(path);
|
|
159
|
+
}
|
|
160
|
+
catch { /* best effort */ }
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=queue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue.js","sourceRoot":"","sources":["../../src/telemetry/queue.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface SendResult {
|
|
2
|
+
ok: boolean;
|
|
3
|
+
status?: number;
|
|
4
|
+
/** Why it failed, for `graft telemetry debug` only — never itself reported. */
|
|
5
|
+
error?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The events as PostHog wants them — everything the wire body contains EXCEPT
|
|
9
|
+
* the project key.
|
|
10
|
+
*
|
|
11
|
+
* The key is deliberately not in here. `graft telemetry debug` prints this
|
|
12
|
+
* object so a user can audit exactly what graft sends, and a command that exists
|
|
13
|
+
* to be pasted into a bug report must not also paste our ingestion key into it.
|
|
14
|
+
* `sendBatch` adds the key at the moment of the request and nowhere else, which
|
|
15
|
+
* keeps the key entirely out of every code path that can reach a terminal.
|
|
16
|
+
*/
|
|
17
|
+
export declare function buildBatch(events: unknown[]): Record<string, unknown>;
|
|
18
|
+
export declare function sendBatch(events: unknown[]): Promise<SendResult>;
|
|
19
|
+
//# sourceMappingURL=send.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"send.d.ts","sourceRoot":"","sources":["../../src/telemetry/send.ts"],"names":[],"mappings":"AAoBA,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+EAA+E;IAC/E,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAgBrE;AAkBD,wBAAsB,SAAS,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,UAAU,CAAC,CAmBtE"}
|
|
@@ -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"}
|