@nanonets/graft 0.16.0 → 0.18.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 +9 -2
- package/dist/app/brain-build.d.ts +66 -0
- package/dist/app/brain-build.d.ts.map +1 -0
- package/dist/app/brain-build.js +169 -0
- package/dist/app/brain-build.js.map +1 -0
- package/dist/app/checkout.d.ts +53 -3
- package/dist/app/checkout.d.ts.map +1 -1
- package/dist/app/checkout.js +195 -27
- package/dist/app/checkout.js.map +1 -1
- package/dist/app/events.d.ts +7 -0
- package/dist/app/events.d.ts.map +1 -1
- package/dist/app/events.js +7 -2
- package/dist/app/events.js.map +1 -1
- package/dist/app/history.d.ts +128 -0
- package/dist/app/history.d.ts.map +1 -0
- package/dist/app/history.js +269 -0
- package/dist/app/history.js.map +1 -0
- package/dist/app/identity.d.ts +33 -0
- package/dist/app/identity.d.ts.map +1 -1
- package/dist/app/identity.js +72 -0
- package/dist/app/identity.js.map +1 -1
- package/dist/app/main.js +12 -0
- package/dist/app/main.js.map +1 -1
- package/dist/app/pages.d.ts +17 -0
- package/dist/app/pages.d.ts.map +1 -1
- package/dist/app/pages.js +112 -5
- package/dist/app/pages.js.map +1 -1
- package/dist/app/review-process.d.ts +66 -0
- package/dist/app/review-process.d.ts.map +1 -0
- package/dist/app/review-process.js +199 -0
- package/dist/app/review-process.js.map +1 -0
- package/dist/app/review-worker.d.ts +2 -0
- package/dist/app/review-worker.d.ts.map +1 -0
- package/dist/app/review-worker.js +108 -0
- package/dist/app/review-worker.js.map +1 -0
- package/dist/app/review.d.ts.map +1 -1
- package/dist/app/review.js +4 -1
- package/dist/app/review.js.map +1 -1
- package/dist/app/server.d.ts +22 -2
- package/dist/app/server.d.ts.map +1 -1
- package/dist/app/server.js +77 -3
- package/dist/app/server.js.map +1 -1
- package/dist/app/sources.d.ts +68 -0
- package/dist/app/sources.d.ts.map +1 -0
- package/dist/app/sources.js +341 -0
- package/dist/app/sources.js.map +1 -0
- package/dist/ask/ask.d.ts +6 -0
- package/dist/ask/ask.d.ts.map +1 -1
- package/dist/ask/ask.js +30 -2
- package/dist/ask/ask.js.map +1 -1
- package/dist/brain/attach.d.ts +44 -0
- package/dist/brain/attach.d.ts.map +1 -0
- package/dist/brain/attach.js +63 -0
- package/dist/brain/attach.js.map +1 -0
- package/dist/brain/connect.d.ts +59 -0
- package/dist/brain/connect.d.ts.map +1 -0
- package/dist/brain/connect.js +80 -0
- package/dist/brain/connect.js.map +1 -0
- package/dist/brain/link.d.ts +82 -0
- package/dist/brain/link.d.ts.map +1 -0
- package/dist/brain/link.js +136 -0
- package/dist/brain/link.js.map +1 -0
- package/dist/brain/push.d.ts +72 -0
- package/dist/brain/push.d.ts.map +1 -0
- package/dist/brain/push.js +191 -0
- package/dist/brain/push.js.map +1 -0
- package/dist/brain/wire.d.ts +38 -0
- package/dist/brain/wire.d.ts.map +1 -0
- package/dist/brain/wire.js +133 -0
- package/dist/brain/wire.js.map +1 -0
- package/dist/claude/format.d.ts.map +1 -1
- package/dist/claude/format.js +10 -3
- package/dist/claude/format.js.map +1 -1
- package/dist/claude/hooks.d.ts.map +1 -1
- package/dist/claude/hooks.js +32 -1
- package/dist/claude/hooks.js.map +1 -1
- package/dist/claude/init.d.ts +5 -0
- package/dist/claude/init.d.ts.map +1 -1
- package/dist/claude/init.js +9 -1
- package/dist/claude/init.js.map +1 -1
- package/dist/claude/session-metrics.d.ts +10 -0
- package/dist/claude/session-metrics.d.ts.map +1 -1
- package/dist/claude/session-metrics.js +25 -0
- package/dist/claude/session-metrics.js.map +1 -1
- package/dist/claude/settings-merge.d.ts +18 -0
- package/dist/claude/settings-merge.d.ts.map +1 -1
- package/dist/claude/settings-merge.js +42 -8
- package/dist/claude/settings-merge.js.map +1 -1
- package/dist/claude/skill-template.d.ts.map +1 -1
- package/dist/claude/skill-template.js +6 -0
- package/dist/claude/skill-template.js.map +1 -1
- package/dist/claude/state.d.ts +14 -0
- package/dist/claude/state.d.ts.map +1 -1
- package/dist/claude/state.js.map +1 -1
- package/dist/claude/tally.d.ts +27 -0
- package/dist/claude/tally.d.ts.map +1 -1
- package/dist/claude/tally.js +74 -1
- package/dist/claude/tally.js.map +1 -1
- package/dist/cli.js +210 -3
- package/dist/cli.js.map +1 -1
- package/dist/context/price.d.ts +49 -0
- package/dist/context/price.d.ts.map +1 -0
- package/dist/context/price.js +83 -0
- package/dist/context/price.js.map +1 -0
- package/dist/context/savings.d.ts +13 -3
- package/dist/context/savings.d.ts.map +1 -1
- package/dist/context/savings.js +44 -5
- package/dist/context/savings.js.map +1 -1
- package/dist/hosts/claude-global.d.ts +33 -0
- package/dist/hosts/claude-global.d.ts.map +1 -0
- package/dist/hosts/claude-global.js +118 -0
- package/dist/hosts/claude-global.js.map +1 -0
- package/dist/hosts/plan.d.ts.map +1 -1
- package/dist/hosts/plan.js +5 -1
- package/dist/hosts/plan.js.map +1 -1
- package/dist/hosts/retract.d.ts.map +1 -1
- package/dist/hosts/retract.js +33 -13
- package/dist/hosts/retract.js.map +1 -1
- package/dist/hosts/sections.d.ts +23 -2
- package/dist/hosts/sections.d.ts.map +1 -1
- package/dist/hosts/sections.js +25 -9
- package/dist/hosts/sections.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +33 -4
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +5 -1
- package/dist/mcp/tools.js.map +1 -1
- package/dist/telemetry/gate.d.ts +14 -0
- package/dist/telemetry/gate.d.ts.map +1 -1
- package/dist/telemetry/gate.js +18 -2
- package/dist/telemetry/gate.js.map +1 -1
- package/dist/upkeep-run.d.ts.map +1 -1
- package/dist/upkeep-run.js +12 -2
- package/dist/upkeep-run.js.map +1 -1
- package/dist/upkeep.d.ts +18 -0
- package/dist/upkeep.d.ts.map +1 -1
- package/dist/upkeep.js +38 -0
- package/dist/upkeep.js.map +1 -1
- package/dist/util/state.d.ts +9 -0
- package/dist/util/state.d.ts.map +1 -1
- package/dist/util/state.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a saved token was actually worth, in dollars.
|
|
3
|
+
*
|
|
4
|
+
* `savings.ts` answers "how many input tokens did graft keep out of the
|
|
5
|
+
* context"; this answers "what does an input token cost here". The two are
|
|
6
|
+
* deliberately separate: the token count is arithmetic over file sizes and is
|
|
7
|
+
* true everywhere, while the price depends on which model the session ran and
|
|
8
|
+
* how much of its context was served from cache — facts only the host's
|
|
9
|
+
* transcript knows.
|
|
10
|
+
*
|
|
11
|
+
* Nothing here guesses. There is no default rate and no env override: a model
|
|
12
|
+
* we don't have a price for yields null, and the callers then render the token
|
|
13
|
+
* count alone rather than a dollar figure we made up. A wrong number on the
|
|
14
|
+
* statusline is worse than no number.
|
|
15
|
+
*/
|
|
16
|
+
/** List input price for a model id, or null when we don't know it — a model
|
|
17
|
+
* released after this table was written, or a host reporting something else
|
|
18
|
+
* entirely. Null propagates all the way to "render tokens only". */
|
|
19
|
+
export declare function inputUsdPerMtok(model: unknown): number | null;
|
|
20
|
+
/** One turn's input-token usage, as the host's transcript reports it. */
|
|
21
|
+
export interface TurnUsage {
|
|
22
|
+
model: string;
|
|
23
|
+
/** Fresh tokens, billed at the list rate. */
|
|
24
|
+
input: number;
|
|
25
|
+
/** Written to the cache this turn — a 25% premium over list. */
|
|
26
|
+
cacheCreate: number;
|
|
27
|
+
/** Served from cache — a tenth of list, and usually the bulk of a long turn. */
|
|
28
|
+
cacheRead: number;
|
|
29
|
+
}
|
|
30
|
+
/** What this turn's input tokens cost, in micro-dollars, or null on a model we
|
|
31
|
+
* have no price for. */
|
|
32
|
+
export declare function turnInputCostMicros(usage: TurnUsage): number | null;
|
|
33
|
+
/** Every input token this turn was billed for, cached or not — the denominator
|
|
34
|
+
* of the blended rate. */
|
|
35
|
+
export declare function turnInputTokens(usage: TurnUsage): number;
|
|
36
|
+
/**
|
|
37
|
+
* Dollars saved, at the rate the session has actually been paying.
|
|
38
|
+
*
|
|
39
|
+
* `costMicros / tokensBilled` is the blended price of one input token here —
|
|
40
|
+
* model and cache-hit ratio already folded in — so this re-blends as the
|
|
41
|
+
* session goes rather than freezing turn one's rate. Returns null when nothing
|
|
42
|
+
* has been sampled yet (turn one, or a host whose hooks name no transcript).
|
|
43
|
+
*/
|
|
44
|
+
export declare function dollarsSaved(savedTokens: number, costMicros: number | undefined, tokensBilled: number | undefined): number | null;
|
|
45
|
+
/** `$1.23`, or `<$0.01` for a real but sub-cent saving. Never `$0.00`: a
|
|
46
|
+
* rounded-to-nothing number reads as "graft saved you nothing", which is a
|
|
47
|
+
* different claim from "graft saved you less than a cent". */
|
|
48
|
+
export declare function formatDollars(usd: number): string;
|
|
49
|
+
//# sourceMappingURL=price.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"price.d.ts","sourceRoot":"","sources":["../../src/context/price.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAYH;;oEAEoE;AACpE,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAI7D;AAED,yEAAyE;AACzE,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,6CAA6C;IAC7C,KAAK,EAAE,MAAM,CAAC;IACd,gEAAgE;IAChE,WAAW,EAAE,MAAM,CAAC;IACpB,gFAAgF;IAChF,SAAS,EAAE,MAAM,CAAC;CACnB;AAWD;wBACwB;AACxB,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,GAAG,IAAI,CAQnE;AAED;0BAC0B;AAC1B,wBAAgB,eAAe,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAExD;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAC1B,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,YAAY,EAAE,MAAM,GAAG,SAAS,GAC/B,MAAM,GAAG,IAAI,CAMf;AAED;;8DAE8D;AAC9D,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAGjD"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a saved token was actually worth, in dollars.
|
|
3
|
+
*
|
|
4
|
+
* `savings.ts` answers "how many input tokens did graft keep out of the
|
|
5
|
+
* context"; this answers "what does an input token cost here". The two are
|
|
6
|
+
* deliberately separate: the token count is arithmetic over file sizes and is
|
|
7
|
+
* true everywhere, while the price depends on which model the session ran and
|
|
8
|
+
* how much of its context was served from cache — facts only the host's
|
|
9
|
+
* transcript knows.
|
|
10
|
+
*
|
|
11
|
+
* Nothing here guesses. There is no default rate and no env override: a model
|
|
12
|
+
* we don't have a price for yields null, and the callers then render the token
|
|
13
|
+
* count alone rather than a dollar figure we made up. A wrong number on the
|
|
14
|
+
* statusline is worse than no number.
|
|
15
|
+
*/
|
|
16
|
+
/** Input $/Mtok, list price, per model family. Output tokens are irrelevant —
|
|
17
|
+
* what graft saves is context the agent would otherwise have read IN. */
|
|
18
|
+
const INPUT_USD_PER_MTOK = [
|
|
19
|
+
[/^claude-(fable|mythos)-5/, 10],
|
|
20
|
+
[/^claude-opus-(5|4-[678])/, 5],
|
|
21
|
+
[/^claude-sonnet-5/, 2],
|
|
22
|
+
[/^claude-sonnet-4-6/, 3],
|
|
23
|
+
[/^claude-haiku-4-5/, 1],
|
|
24
|
+
];
|
|
25
|
+
/** List input price for a model id, or null when we don't know it — a model
|
|
26
|
+
* released after this table was written, or a host reporting something else
|
|
27
|
+
* entirely. Null propagates all the way to "render tokens only". */
|
|
28
|
+
export function inputUsdPerMtok(model) {
|
|
29
|
+
if (typeof model !== 'string')
|
|
30
|
+
return null;
|
|
31
|
+
for (const [pattern, usd] of INPUT_USD_PER_MTOK)
|
|
32
|
+
if (pattern.test(model))
|
|
33
|
+
return usd;
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
/** Cache-write costs 1.25x list, a cache read a tenth of it. The multipliers are
|
|
37
|
+
* why a measured rate beats an assumed one: a session deep into a long
|
|
38
|
+
* conversation pays nearer $0.50/Mtok than the $5.00 its model lists at. */
|
|
39
|
+
const CACHE_CREATE_MULTIPLIER = 1.25;
|
|
40
|
+
const CACHE_READ_MULTIPLIER = 0.1;
|
|
41
|
+
/** Micro-dollars, so a running total stays an integer and never drifts. */
|
|
42
|
+
const MICROS_PER_USD = 1_000_000;
|
|
43
|
+
/** What this turn's input tokens cost, in micro-dollars, or null on a model we
|
|
44
|
+
* have no price for. */
|
|
45
|
+
export function turnInputCostMicros(usage) {
|
|
46
|
+
const list = inputUsdPerMtok(usage.model);
|
|
47
|
+
if (list === null)
|
|
48
|
+
return null;
|
|
49
|
+
const weighted = usage.input +
|
|
50
|
+
usage.cacheCreate * CACHE_CREATE_MULTIPLIER +
|
|
51
|
+
usage.cacheRead * CACHE_READ_MULTIPLIER;
|
|
52
|
+
return Math.round((weighted * list * MICROS_PER_USD) / 1_000_000);
|
|
53
|
+
}
|
|
54
|
+
/** Every input token this turn was billed for, cached or not — the denominator
|
|
55
|
+
* of the blended rate. */
|
|
56
|
+
export function turnInputTokens(usage) {
|
|
57
|
+
return usage.input + usage.cacheCreate + usage.cacheRead;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Dollars saved, at the rate the session has actually been paying.
|
|
61
|
+
*
|
|
62
|
+
* `costMicros / tokensBilled` is the blended price of one input token here —
|
|
63
|
+
* model and cache-hit ratio already folded in — so this re-blends as the
|
|
64
|
+
* session goes rather than freezing turn one's rate. Returns null when nothing
|
|
65
|
+
* has been sampled yet (turn one, or a host whose hooks name no transcript).
|
|
66
|
+
*/
|
|
67
|
+
export function dollarsSaved(savedTokens, costMicros, tokensBilled) {
|
|
68
|
+
if (!costMicros || !tokensBilled || savedTokens <= 0)
|
|
69
|
+
return null;
|
|
70
|
+
const usd = (savedTokens * (costMicros / tokensBilled)) / MICROS_PER_USD;
|
|
71
|
+
// Belt and braces against a non-finite accumulator reaching a rendered
|
|
72
|
+
// surface: "$NaN" on the statusline is worse than no dollar figure at all.
|
|
73
|
+
return Number.isFinite(usd) ? usd : null;
|
|
74
|
+
}
|
|
75
|
+
/** `$1.23`, or `<$0.01` for a real but sub-cent saving. Never `$0.00`: a
|
|
76
|
+
* rounded-to-nothing number reads as "graft saved you nothing", which is a
|
|
77
|
+
* different claim from "graft saved you less than a cent". */
|
|
78
|
+
export function formatDollars(usd) {
|
|
79
|
+
if (usd < 0.01)
|
|
80
|
+
return '<$0.01';
|
|
81
|
+
return `$${usd.toFixed(2)}`;
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=price.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"price.js","sourceRoot":"","sources":["../../src/context/price.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH;yEACyE;AACzE,MAAM,kBAAkB,GAA6C;IACnE,CAAC,0BAA0B,EAAE,EAAE,CAAC;IAChC,CAAC,0BAA0B,EAAE,CAAC,CAAC;IAC/B,CAAC,kBAAkB,EAAE,CAAC,CAAC;IACvB,CAAC,oBAAoB,EAAE,CAAC,CAAC;IACzB,CAAC,mBAAmB,EAAE,CAAC,CAAC;CACzB,CAAC;AAEF;;oEAEoE;AACpE,MAAM,UAAU,eAAe,CAAC,KAAc;IAC5C,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC3C,KAAK,MAAM,CAAC,OAAO,EAAE,GAAG,CAAC,IAAI,kBAAkB;QAAE,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC;YAAE,OAAO,GAAG,CAAC;IACrF,OAAO,IAAI,CAAC;AACd,CAAC;AAaD;;4EAE4E;AAC5E,MAAM,uBAAuB,GAAG,IAAI,CAAC;AACrC,MAAM,qBAAqB,GAAG,GAAG,CAAC;AAElC,2EAA2E;AAC3E,MAAM,cAAc,GAAG,SAAS,CAAC;AAEjC;wBACwB;AACxB,MAAM,UAAU,mBAAmB,CAAC,KAAgB;IAClD,MAAM,IAAI,GAAG,eAAe,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC1C,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC/B,MAAM,QAAQ,GACZ,KAAK,CAAC,KAAK;QACX,KAAK,CAAC,WAAW,GAAG,uBAAuB;QAC3C,KAAK,CAAC,SAAS,GAAG,qBAAqB,CAAC;IAC1C,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,QAAQ,GAAG,IAAI,GAAG,cAAc,CAAC,GAAG,SAAS,CAAC,CAAC;AACpE,CAAC;AAED;0BAC0B;AAC1B,MAAM,UAAU,eAAe,CAAC,KAAgB;IAC9C,OAAO,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,WAAW,GAAG,KAAK,CAAC,SAAS,CAAC;AAC3D,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAC1B,WAAmB,EACnB,UAA8B,EAC9B,YAAgC;IAEhC,IAAI,CAAC,UAAU,IAAI,CAAC,YAAY,IAAI,WAAW,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAClE,MAAM,GAAG,GAAG,CAAC,WAAW,GAAG,CAAC,UAAU,GAAG,YAAY,CAAC,CAAC,GAAG,cAAc,CAAC;IACzE,uEAAuE;IACvE,2EAA2E;IAC3E,OAAO,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;AAC3C,CAAC;AAED;;8DAE8D;AAC9D,MAAM,UAAU,aAAa,CAAC,GAAW;IACvC,IAAI,GAAG,GAAG,IAAI;QAAE,OAAO,QAAQ,CAAC;IAChC,OAAO,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;AAC9B,CAAC"}
|
|
@@ -25,12 +25,22 @@ export declare function toTokens(chars: number): number;
|
|
|
25
25
|
* Returns undefined when not a single path has a known size — the caller then
|
|
26
26
|
* omits the estimate instead of claiming a bogus one. */
|
|
27
27
|
export declare function savingsFor(graph: GraphV1, paths: Iterable<string>): Savings | undefined;
|
|
28
|
+
/** Tell this module what an input token costs here. Null clears it, and so does
|
|
29
|
+
* anything that isn't a positive finite number — a NaN from a zero-denominator
|
|
30
|
+
* rate must render as "no dollars known", never as `$NaN` in the agent's face. */
|
|
31
|
+
export declare function setInputRate(usdPerMtok: number | null): void;
|
|
28
32
|
/** Appended to every retrieval footer so the agent reports the turn's running
|
|
29
33
|
* total even when SKILL.md isn't loaded — the instruction rides along in the
|
|
30
34
|
* tool output itself. Deliberately free of the `[graft] tokens saved ≈ <n>`
|
|
31
35
|
* pattern so the PostToolUse accumulator that parses these footers never
|
|
32
|
-
* mistakes the nudge (or its example) for a second number to count.
|
|
33
|
-
|
|
36
|
+
* mistakes the nudge (or its example) for a second number to count.
|
|
37
|
+
*
|
|
38
|
+
* Carries this call's dollar value once a rate has been measured, because the
|
|
39
|
+
* agent has no way to price a token itself — the ask it's given has to contain
|
|
40
|
+
* the number, not just request one. The example phrasing stays inside what
|
|
41
|
+
* `hasSavingsTally` (claude/tally.ts) recognises, so adding money here does not
|
|
42
|
+
* quietly zero the reported-turns metric. */
|
|
43
|
+
export declare function savingsTurnNudge(savedTokens: number): string;
|
|
34
44
|
/** The one-line savings estimate for a command's text output, so the agent gets
|
|
35
45
|
* the number for free — no extra tool call. `body` is the exact rendered output
|
|
36
46
|
* the agent reads (the pack). Returns "" when there's nothing honest to claim:
|
|
@@ -41,7 +51,7 @@ export declare function savingsLine(body: string, saved: Savings | undefined): s
|
|
|
41
51
|
* Sum every `[graft] tokens saved ≈ N` footer in a blob of text — the reader
|
|
42
52
|
* half of {@link savingsLine}, kept next to the writer so the two never drift.
|
|
43
53
|
* A single blob can carry several (an agent that made two graft calls in one
|
|
44
|
-
* turn); the `
|
|
54
|
+
* turn); the nudge from `savingsTurnNudge` deliberately omits the pattern, so its example
|
|
45
55
|
* text is not miscounted here.
|
|
46
56
|
*/
|
|
47
57
|
export declare function sumSavingsFooters(text: string): number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"savings.d.ts","sourceRoot":"","sources":["../../src/context/savings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"savings.d.ts","sourceRoot":"","sources":["../../src/context/savings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAGjD,MAAM,WAAW,OAAO;IACtB,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAC;IACd,+DAA+D;IAC/D,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,qFAAqF;AACrF,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE9C;AAYD;;yDAEyD;AACzD,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,OAAO,GAAG,SAAS,CAWvF;AAgBD;;kFAEkF;AAClF,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAK5D;AAED;;;;;;;;;;6CAU6C;AAC7C,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAe5D;AAED;;;;iEAIiE;AACjE,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,CAa5E;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMtD;AAED;;;;;;;uCAOuC;AACvC,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,CAG5E"}
|
package/dist/context/savings.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { formatDollars } from './price.js';
|
|
1
2
|
/** Rough tokens for a byte length (≈ 4 chars/token; good enough for an estimate). */
|
|
2
3
|
export function toTokens(chars) {
|
|
3
4
|
return Math.round(chars / 4);
|
|
@@ -28,13 +29,51 @@ export function savingsFor(graph, paths) {
|
|
|
28
29
|
}
|
|
29
30
|
return files > 0 ? { files, baselineChars } : undefined;
|
|
30
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* The blended input rate this session is paying, in $/Mtok, or null when nobody
|
|
34
|
+
* has measured one.
|
|
35
|
+
*
|
|
36
|
+
* Process-level because it is a process-level fact: one CLI invocation answers
|
|
37
|
+
* one query for one session, and the alternative — threading a rate through
|
|
38
|
+
* `withSavings` and all seven of its callers — would put a billing parameter in
|
|
39
|
+
* the signature of every retrieval formatter for no gain. Set by the CLI's
|
|
40
|
+
* `noteQuery` and by the MCP dispatch, both of which already resolve the repo
|
|
41
|
+
* root; unset everywhere else, which is why every consumer treats null as
|
|
42
|
+
* "report tokens only" rather than as zero.
|
|
43
|
+
*/
|
|
44
|
+
let inputRateUsdPerMtok = null;
|
|
45
|
+
/** Tell this module what an input token costs here. Null clears it, and so does
|
|
46
|
+
* anything that isn't a positive finite number — a NaN from a zero-denominator
|
|
47
|
+
* rate must render as "no dollars known", never as `$NaN` in the agent's face. */
|
|
48
|
+
export function setInputRate(usdPerMtok) {
|
|
49
|
+
inputRateUsdPerMtok =
|
|
50
|
+
typeof usdPerMtok === 'number' && Number.isFinite(usdPerMtok) && usdPerMtok > 0
|
|
51
|
+
? usdPerMtok
|
|
52
|
+
: null;
|
|
53
|
+
}
|
|
31
54
|
/** Appended to every retrieval footer so the agent reports the turn's running
|
|
32
55
|
* total even when SKILL.md isn't loaded — the instruction rides along in the
|
|
33
56
|
* tool output itself. Deliberately free of the `[graft] tokens saved ≈ <n>`
|
|
34
57
|
* pattern so the PostToolUse accumulator that parses these footers never
|
|
35
|
-
* mistakes the nudge (or its example) for a second number to count.
|
|
36
|
-
|
|
37
|
-
|
|
58
|
+
* mistakes the nudge (or its example) for a second number to count.
|
|
59
|
+
*
|
|
60
|
+
* Carries this call's dollar value once a rate has been measured, because the
|
|
61
|
+
* agent has no way to price a token itself — the ask it's given has to contain
|
|
62
|
+
* the number, not just request one. The example phrasing stays inside what
|
|
63
|
+
* `hasSavingsTally` (claude/tally.ts) recognises, so adding money here does not
|
|
64
|
+
* quietly zero the reported-turns metric. */
|
|
65
|
+
export function savingsTurnNudge(savedTokens) {
|
|
66
|
+
const sum = ' — sum each such line across your graft calls — e.g. ';
|
|
67
|
+
if (inputRateUsdPerMtok === null || savedTokens <= 0) {
|
|
68
|
+
return (' At the end of your reply, tell the user the total graft tokens saved ' +
|
|
69
|
+
`this turn${sum}"🌱 graft saved ~N tokens this turn".`);
|
|
70
|
+
}
|
|
71
|
+
const usd = (savedTokens * inputRateUsdPerMtok) / 1_000_000;
|
|
72
|
+
return (` This call is worth ${formatDollars(usd)} at the rate this session is ` +
|
|
73
|
+
'actually paying for input tokens. At the end of your reply, tell the user ' +
|
|
74
|
+
`the total graft tokens saved this turn and what they were worth${sum}` +
|
|
75
|
+
'"🌱 graft saved ~N tokens (~$X) this turn".');
|
|
76
|
+
}
|
|
38
77
|
/** The one-line savings estimate for a command's text output, so the agent gets
|
|
39
78
|
* the number for free — no extra tool call. `body` is the exact rendered output
|
|
40
79
|
* the agent reads (the pack). Returns "" when there's nothing honest to claim:
|
|
@@ -52,13 +91,13 @@ export function savingsLine(body, saved) {
|
|
|
52
91
|
return (`[graft] tokens saved ≈ ${delta.toLocaleString()} (${pct}%) — this output ≈ ` +
|
|
53
92
|
`${pack.toLocaleString()} tok vs reading the ${saved.files} file(s) it covers whole ≈ ` +
|
|
54
93
|
`${base.toLocaleString()} tok (estimate).` +
|
|
55
|
-
|
|
94
|
+
savingsTurnNudge(delta));
|
|
56
95
|
}
|
|
57
96
|
/**
|
|
58
97
|
* Sum every `[graft] tokens saved ≈ N` footer in a blob of text — the reader
|
|
59
98
|
* half of {@link savingsLine}, kept next to the writer so the two never drift.
|
|
60
99
|
* A single blob can carry several (an agent that made two graft calls in one
|
|
61
|
-
* turn); the `
|
|
100
|
+
* turn); the nudge from `savingsTurnNudge` deliberately omits the pattern, so its example
|
|
62
101
|
* text is not miscounted here.
|
|
63
102
|
*/
|
|
64
103
|
export function sumSavingsFooters(text) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"savings.js","sourceRoot":"","sources":["../../src/context/savings.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"savings.js","sourceRoot":"","sources":["../../src/context/savings.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type ConfigWrite } from './config-write.js';
|
|
2
|
+
import type { PlannedWrite } from './plan.js';
|
|
3
|
+
/** Same `{ id, path, action }` contract every other writer in this layer reports. */
|
|
4
|
+
export type GlobalWrite = ConfigWrite;
|
|
5
|
+
/** The directory the user-level shim lives in — the base every hook command names. */
|
|
6
|
+
export declare function globalHelpersDir(home: string): string;
|
|
7
|
+
/**
|
|
8
|
+
* The files a user-level install would touch — pure, no writes, so `--dry-run` and
|
|
9
|
+
* the picker can report them up front. All three are scoped 'global': they apply to
|
|
10
|
+
* every project opened with Claude Code, not just this one.
|
|
11
|
+
*
|
|
12
|
+
* `~/.claude.json` holds the *user* MCP scope — the top-level `mcpServers` map, the
|
|
13
|
+
* one `claude mcp add --scope user` writes. Not to be confused with the same file's
|
|
14
|
+
* `projects["<abs path>"].mcpServers`, which is `--scope local` and per-directory:
|
|
15
|
+
* a worktree is its own project entry there, so a local registration would miss it
|
|
16
|
+
* for precisely the same reason the repo file does.
|
|
17
|
+
*/
|
|
18
|
+
export declare function claudeGlobalTargets(home: string): PlannedWrite[];
|
|
19
|
+
/**
|
|
20
|
+
* Install the user-level copy: the shim, the hook entries that call it, and the
|
|
21
|
+
* user-scope MCP registration.
|
|
22
|
+
*
|
|
23
|
+
* The shim is written to `home` and the hook commands name it by absolute path, for
|
|
24
|
+
* the reason the repo form can't be reused: `${CLAUDE_PROJECT_DIR}/.claude/helpers/`
|
|
25
|
+
* resolves inside whatever project is open, and the whole point is to work in one
|
|
26
|
+
* that has no such file. `hooksShim(claudeDistDir())` bakes in the installed
|
|
27
|
+
* package's `dist/`, exactly as the Codex install does.
|
|
28
|
+
*
|
|
29
|
+
* Best-effort by contract, like every other writer here: a failure is reported as an
|
|
30
|
+
* action, never raised, so a bad `~/.claude.json` can't fail a `graft init`.
|
|
31
|
+
*/
|
|
32
|
+
export declare function installClaudeGlobal(home: string): GlobalWrite[];
|
|
33
|
+
//# sourceMappingURL=claude-global.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-global.d.ts","sourceRoot":"","sources":["../../src/hosts/claude-global.ts"],"names":[],"mappings":"AAiCA,OAAO,EAA8B,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEjF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAE9C,qFAAqF;AACrF,MAAM,MAAM,WAAW,GAAG,WAAW,CAAC;AAEtC,sFAAsF;AACtF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAErD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,EAAE,CAQhE;AAeD;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,EAAE,CAgC/D"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User-level install for Claude Code: the copy of graft's wiring that lives
|
|
3
|
+
* outside every repo.
|
|
4
|
+
*
|
|
5
|
+
* Why this exists. Everything `graft init` writes for Claude Code lands *in* the
|
|
6
|
+
* repo — `.mcp.json` and `.claude/settings.json`. A `.gitignore` is free to ignore
|
|
7
|
+
* both, and `git worktree add` checks out tracked files only, so a worktree of such
|
|
8
|
+
* a repo starts with graft's shims present and neither of the two files that *point
|
|
9
|
+
* at* them. No settings.json means no SessionStart hook; no `.mcp.json` means no
|
|
10
|
+
* tool server. graft is absent, silently, in a tree that looks correctly wired.
|
|
11
|
+
*
|
|
12
|
+
* graft already carries the repair for exactly that — `reconcileWiring` rewrites
|
|
13
|
+
* both files — and it is unreachable here: `runUpkeep` is called only from the hook
|
|
14
|
+
* and from the MCP server, which are the two things that are missing. Seeding can't
|
|
15
|
+
* help either, since it runs on the query path, which needs the server.
|
|
16
|
+
*
|
|
17
|
+
* So the fix cannot live in the repo. `~/.claude/` is not in anyone's working tree,
|
|
18
|
+
* no `.gitignore` reaches it, and Claude Code reads it for every project — worktrees
|
|
19
|
+
* included. Codex has been installed this way from the start (see ./codex-hooks.ts,
|
|
20
|
+
* whose own note says the entries "fire in every repo opened with Codex, not just
|
|
21
|
+
* this one"); Claude Code was the one host graft wired repo-only. This module closes
|
|
22
|
+
* that gap, and is a deliberate mirror of that file.
|
|
23
|
+
*
|
|
24
|
+
* The repo-level writes stay exactly as they were. A project that has its own
|
|
25
|
+
* `.mcp.json` and settings keeps using them; this is the floor underneath, not a
|
|
26
|
+
* replacement.
|
|
27
|
+
*/
|
|
28
|
+
import { writeFileSync, mkdirSync } from 'node:fs';
|
|
29
|
+
import { dirname, join } from 'node:path';
|
|
30
|
+
import { hooksShim } from '../claude/shim-template.js';
|
|
31
|
+
import { claudeDistDir } from '../claude/paths.js';
|
|
32
|
+
import { mergeGraftHooks } from '../claude/settings-merge.js';
|
|
33
|
+
import { toPosixPath } from '../util/paths.js';
|
|
34
|
+
import { readJsonObject, writeOwned } from './config-write.js';
|
|
35
|
+
import { mergeJsonKey, serverEntry } from './mcp-config.js';
|
|
36
|
+
/** The directory the user-level shim lives in — the base every hook command names. */
|
|
37
|
+
export function globalHelpersDir(home) {
|
|
38
|
+
return join(home, '.claude', 'helpers');
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The files a user-level install would touch — pure, no writes, so `--dry-run` and
|
|
42
|
+
* the picker can report them up front. All three are scoped 'global': they apply to
|
|
43
|
+
* every project opened with Claude Code, not just this one.
|
|
44
|
+
*
|
|
45
|
+
* `~/.claude.json` holds the *user* MCP scope — the top-level `mcpServers` map, the
|
|
46
|
+
* one `claude mcp add --scope user` writes. Not to be confused with the same file's
|
|
47
|
+
* `projects["<abs path>"].mcpServers`, which is `--scope local` and per-directory:
|
|
48
|
+
* a worktree is its own project entry there, so a local registration would miss it
|
|
49
|
+
* for precisely the same reason the repo file does.
|
|
50
|
+
*/
|
|
51
|
+
export function claudeGlobalTargets(home) {
|
|
52
|
+
const g = (id, path, kind, what) => ({ hostId: 'claude', id, path, scope: 'global', kind, what });
|
|
53
|
+
return [
|
|
54
|
+
g('claude-global-shim', join(globalHelpersDir(home), 'graft-hooks.cjs'), 'hook', 'hooks shim (user level)'),
|
|
55
|
+
g('claude-global-hooks', join(home, '.claude', 'settings.json'), 'hook', 'SessionStart / UserPromptSubmit / PostToolUse / Stop'),
|
|
56
|
+
g('claude-global-mcp', join(home, '.claude.json'), 'mcp', 'mcpServers.graft'),
|
|
57
|
+
];
|
|
58
|
+
}
|
|
59
|
+
/** Merge graft's hook blocks into a settings file, preserving everything else. */
|
|
60
|
+
function upsertGlobalHooks(id, path, helpers) {
|
|
61
|
+
const loaded = readJsonObject(path);
|
|
62
|
+
if (loaded === 'unparseable')
|
|
63
|
+
return { id, path, action: 'skipped-unparseable' };
|
|
64
|
+
const { root: existing, existed } = loaded;
|
|
65
|
+
const before = JSON.stringify(existing);
|
|
66
|
+
const { merged } = mergeGraftHooks(existing, helpers);
|
|
67
|
+
if (JSON.stringify(merged) === before)
|
|
68
|
+
return { id, path, action: 'unchanged' };
|
|
69
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
70
|
+
writeFileSync(path, `${JSON.stringify(merged, null, 2)}\n`);
|
|
71
|
+
return { id, path, action: existed ? 'updated' : 'created' };
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Install the user-level copy: the shim, the hook entries that call it, and the
|
|
75
|
+
* user-scope MCP registration.
|
|
76
|
+
*
|
|
77
|
+
* The shim is written to `home` and the hook commands name it by absolute path, for
|
|
78
|
+
* the reason the repo form can't be reused: `${CLAUDE_PROJECT_DIR}/.claude/helpers/`
|
|
79
|
+
* resolves inside whatever project is open, and the whole point is to work in one
|
|
80
|
+
* that has no such file. `hooksShim(claudeDistDir())` bakes in the installed
|
|
81
|
+
* package's `dist/`, exactly as the Codex install does.
|
|
82
|
+
*
|
|
83
|
+
* Best-effort by contract, like every other writer here: a failure is reported as an
|
|
84
|
+
* action, never raised, so a bad `~/.claude.json` can't fail a `graft init`.
|
|
85
|
+
*/
|
|
86
|
+
export function installClaudeGlobal(home) {
|
|
87
|
+
const [shim, settings, mcp] = claudeGlobalTargets(home);
|
|
88
|
+
const out = [];
|
|
89
|
+
try {
|
|
90
|
+
out.push(writeOwned(shim.id, shim.path, hooksShim(claudeDistDir()), 0o755));
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
out.push({ id: shim.id, path: shim.path, action: 'skipped-unparseable' });
|
|
94
|
+
}
|
|
95
|
+
// Only wire the hooks once the shim they call is actually on disk — a hook
|
|
96
|
+
// entry pointing at a missing file is an error in every session, which is a
|
|
97
|
+
// worse failure than not installing.
|
|
98
|
+
if (out[0].action !== 'skipped-unparseable') {
|
|
99
|
+
try {
|
|
100
|
+
// Posix form in the command string: `join` gives backslashes on Windows and
|
|
101
|
+
// the template appends `/graft-hooks.cjs`, so the raw path produces a mixed
|
|
102
|
+
// `C:\Users\…\helpers/graft-hooks.cjs`. Node accepts forward slashes on
|
|
103
|
+
// Windows, so one separator throughout is both correct and readable.
|
|
104
|
+
out.push(upsertGlobalHooks(settings.id, settings.path, toPosixPath(globalHelpersDir(home))));
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
out.push({ id: settings.id, path: settings.path, action: 'skipped-unparseable' });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
out.push(mergeJsonKey(mcp.id, mcp.path, 'mcpServers', serverEntry()));
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
out.push({ id: mcp.id, path: mcp.path, action: 'skipped-unparseable' });
|
|
115
|
+
}
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=claude-global.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-global.js","sourceRoot":"","sources":["../../src/hosts/claude-global.ts"],"names":[],"mappings":"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"}
|
package/dist/hosts/plan.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../src/hosts/plan.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../src/hosts/plan.ts"],"names":[],"mappings":"AAoBA,+EAA+E;AAC/E,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,QAAQ,CAAC;AAE3C,MAAM,WAAW,YAAY;IAC3B,mFAAmF;IACnF,MAAM,EAAE,MAAM,CAAC;IACf;kEAC8D;IAC9D,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,UAAU,CAAC;IAClB,IAAI,EAAE,aAAa,GAAG,KAAK,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;IAC1D,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,YAAY,EAAE,CAAC;CACxB;AAqBD;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,EAAE,CAAA;CAAO,GAAG,QAAQ,EAAE,CAyB/F;AAED,mEAAmE;AACnE,wBAAgB,cAAc,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,YAAY,EAAE,CAE9E"}
|
package/dist/hosts/plan.js
CHANGED
|
@@ -16,6 +16,7 @@ import { hookTargets } from './codex-hooks.js';
|
|
|
16
16
|
import { cursorHookTargets } from './cursor-hooks.js';
|
|
17
17
|
import { antigravitySkillTargets } from './antigravity.js';
|
|
18
18
|
import { claudeTargets } from '../claude/init.js';
|
|
19
|
+
import { claudeGlobalTargets } from './claude-global.js';
|
|
19
20
|
function probeFor(home, repo) {
|
|
20
21
|
return {
|
|
21
22
|
home, repo,
|
|
@@ -48,7 +49,10 @@ export function planInit(repo, opts = {}) {
|
|
|
48
49
|
const probe = probeFor(home, repo);
|
|
49
50
|
const detected = new Set(detectHosts(probe).map((h) => h.id));
|
|
50
51
|
const plans = [
|
|
51
|
-
|
|
52
|
+
// Repo writes plus the user-level copy under `~/.claude` — the picker and
|
|
53
|
+
// `--dry-run` render 'global' writes in their own section, so a user sees
|
|
54
|
+
// what lands outside the repo before agreeing to it.
|
|
55
|
+
{ id: 'claude', name: 'Claude Code', detected: true, writes: [...claudeTargets(repo), ...claudeGlobalTargets(home)] },
|
|
52
56
|
...HOSTS.map((host) => ({
|
|
53
57
|
id: host.id,
|
|
54
58
|
name: host.name,
|
package/dist/hosts/plan.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plan.js","sourceRoot":"","sources":["../../src/hosts/plan.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,KAAK,EAAE,WAAW,EAAqC,MAAM,eAAe,CAAC;AACtF,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"plan.js","sourceRoot":"","sources":["../../src/hosts/plan.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"retract.d.ts","sourceRoot":"","sources":["../../src/hosts/retract.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"retract.d.ts","sourceRoot":"","sources":["../../src/hosts/retract.ts"],"names":[],"mappings":"AAoCA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAE5C,2CAA2C;AAC3C,MAAM,MAAM,aAAa;AACvB,sDAAsD;AACpD,SAAS;AACX,8DAA8D;GAC5D,SAAS;AACX,+BAA+B;GAC7B,QAAQ;AACV,4EAA4E;GAC1E,qBAAqB,CAAC;AAE1B,MAAM,WAAW,UAAU;IACzB,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,UAAU,CAAC;IAClB,MAAM,EAAE,aAAa,CAAC;CACvB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;sDACkD;IAClD,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,iFAAiF;IACjF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;kBAEc;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;kEAE8D;IAC9D,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAsZD,gEAAgE;AAChE,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,WAAgB,GAAG,UAAU,EAAE,CAE9E;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,WAAgB,GAAG,UAAU,EAAE,CAG7E;AAED,6EAA6E;AAC7E,wBAAgB,OAAO,CAAC,WAAW,EAAE,UAAU,EAAE,GAAG,UAAU,EAAE,CAE/D"}
|
package/dist/hosts/retract.js
CHANGED
|
@@ -27,10 +27,11 @@ import { readFileSync, writeFileSync, existsSync, rmSync, rmdirSync, statSync, r
|
|
|
27
27
|
import { join, dirname } from 'node:path';
|
|
28
28
|
import { homedir } from 'node:os';
|
|
29
29
|
import { HOSTS } from './registry.js';
|
|
30
|
-
import {
|
|
30
|
+
import { ALL_MARKERS } from './sections.js';
|
|
31
31
|
import { mcpTargets, stripTomlSection } from './mcp-config.js';
|
|
32
32
|
import { hookTargets } from './codex-hooks.js';
|
|
33
33
|
import { antigravitySkillTargets } from './antigravity.js';
|
|
34
|
+
import { claudeGlobalTargets } from './claude-global.js';
|
|
34
35
|
import { claudeTargets } from '../claude/init.js';
|
|
35
36
|
import { isGraftAllowEntry, isGraftFooterRegex } from '../claude/settings-merge.js';
|
|
36
37
|
/**
|
|
@@ -88,27 +89,33 @@ function pruneEmptyDirs(dir) {
|
|
|
88
89
|
* blank line, so a file that had prose either side of the block reads exactly
|
|
89
90
|
* as it did before graft appended to it.
|
|
90
91
|
*/
|
|
91
|
-
function stripSection(path, apply) {
|
|
92
|
+
function stripSection(path, apply, markers = ALL_MARKERS) {
|
|
92
93
|
if (!existsSync(path))
|
|
93
94
|
return 'absent';
|
|
94
95
|
const text = readFileSync(path, 'utf8');
|
|
95
|
-
|
|
96
|
+
// Every region graft may own, not just the instruction block. A file can hold
|
|
97
|
+
// both the instructions and a brain's rules, and leaving one behind would
|
|
98
|
+
// strand rules in a repo the user has uninstalled graft from.
|
|
99
|
+
if (!markers.some((m) => text.includes(m.start)))
|
|
96
100
|
return 'absent';
|
|
97
101
|
const eol = text.includes('\r\n') ? '\r\n' : '\n';
|
|
98
102
|
const lines = text.split(/\r\n|\n/);
|
|
99
103
|
const out = [];
|
|
100
|
-
let
|
|
104
|
+
let closing = null;
|
|
101
105
|
let found = false;
|
|
102
106
|
for (const line of lines) {
|
|
103
107
|
const t = line.trim();
|
|
104
|
-
if (
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
+
if (closing === null) {
|
|
109
|
+
const open = markers.find((m) => m.start === t);
|
|
110
|
+
if (open) {
|
|
111
|
+
closing = open.end;
|
|
112
|
+
found = true;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
108
115
|
}
|
|
109
|
-
|
|
110
|
-
if (t ===
|
|
111
|
-
|
|
116
|
+
else {
|
|
117
|
+
if (t === closing)
|
|
118
|
+
closing = null;
|
|
112
119
|
continue;
|
|
113
120
|
}
|
|
114
121
|
out.push(line);
|
|
@@ -361,9 +368,12 @@ function targets(repo, opts) {
|
|
|
361
368
|
}
|
|
362
369
|
for (const t of mcpTargets(repo, [...exclude], { home }))
|
|
363
370
|
keptPaths.add(t.path);
|
|
364
|
-
if (exclude.has('claude'))
|
|
371
|
+
if (exclude.has('claude')) {
|
|
365
372
|
for (const t of claudeTargets(repo))
|
|
366
373
|
keptPaths.add(t.path);
|
|
374
|
+
for (const t of claudeGlobalTargets(home))
|
|
375
|
+
keptPaths.add(t.path);
|
|
376
|
+
}
|
|
367
377
|
if (exclude.has('agents'))
|
|
368
378
|
for (const t of hookTargets(home))
|
|
369
379
|
keptPaths.add(t.path);
|
|
@@ -419,8 +429,18 @@ function targets(repo, opts) {
|
|
|
419
429
|
])
|
|
420
430
|
add(t);
|
|
421
431
|
}
|
|
422
|
-
// 4. Global: Codex's hook shim + entries, and
|
|
432
|
+
// 4. Global: Claude Code's user-level copy, Codex's hook shim + entries, and
|
|
433
|
+
// Antigravity's shared skill.
|
|
423
434
|
if (opts.global !== false) {
|
|
435
|
+
if (!exclude.has('claude')) {
|
|
436
|
+
const [shim, settings, mcp] = claudeGlobalTargets(home);
|
|
437
|
+
for (const t of [
|
|
438
|
+
{ hostId: 'claude', path: shim.path, what: shim.what, scope: 'global', run: (a) => removeFile(shim.path, a) },
|
|
439
|
+
{ hostId: 'claude', path: settings.path, what: settings.what, scope: 'global', run: (a) => stripClaudeSettings(settings.path, a) },
|
|
440
|
+
{ hostId: 'claude', path: mcp.path, what: mcp.what, scope: 'global', run: (a) => removeJsonKey(mcp.path, 'mcpServers', a) },
|
|
441
|
+
])
|
|
442
|
+
add(t);
|
|
443
|
+
}
|
|
424
444
|
if (!exclude.has('agents')) {
|
|
425
445
|
for (const t of hookTargets(home)) {
|
|
426
446
|
add({
|