@knpkv/herdr-usage 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +11 -2
- package/dist/app.d.ts +18 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +77 -0
- package/dist/app.js.map +1 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +24 -0
- package/dist/bin.js.map +1 -0
- package/dist/client/client.css +2 -0
- package/dist/client/client.css.map +7 -0
- package/dist/client/client.js +180 -0
- package/dist/client/client.js.map +7 -0
- package/dist/client.d.ts +3 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +88 -0
- package/dist/client.js.map +1 -0
- package/dist/host/failure.d.ts +15 -0
- package/dist/host/failure.d.ts.map +1 -0
- package/dist/host/failure.js +44 -0
- package/dist/host/failure.js.map +1 -0
- package/dist/host/limits.d.ts +59 -0
- package/dist/host/limits.d.ts.map +1 -0
- package/dist/host/limits.js +96 -0
- package/dist/host/limits.js.map +1 -0
- package/dist/host/usage.d.ts +93 -0
- package/dist/host/usage.d.ts.map +1 -0
- package/dist/host/usage.js +95 -0
- package/dist/host/usage.js.map +1 -0
- package/dist/host.d.ts +12 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +12 -0
- package/dist/host.js.map +1 -0
- package/dist/options.d.ts +24 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +28 -0
- package/dist/options.js.map +1 -0
- package/dist/selection.d.ts +8 -0
- package/dist/selection.d.ts.map +1 -0
- package/dist/selection.js +8 -0
- package/dist/selection.js.map +1 -0
- package/dist/views/usage-empty-art.d.ts +4 -0
- package/dist/views/usage-empty-art.d.ts.map +1 -0
- package/dist/views/usage-empty-art.js +8 -0
- package/dist/views/usage-empty-art.js.map +1 -0
- package/dist/views/usage-limits-chart-view.d.ts +12 -0
- package/dist/views/usage-limits-chart-view.d.ts.map +1 -0
- package/dist/views/usage-limits-chart-view.js +8 -0
- package/dist/views/usage-limits-chart-view.js.map +1 -0
- package/dist/views/usage-limits-chart.d.ts +28 -0
- package/dist/views/usage-limits-chart.d.ts.map +1 -0
- package/dist/views/usage-limits-chart.js +50 -0
- package/dist/views/usage-limits-chart.js.map +1 -0
- package/dist/views/usage-limits-page.d.ts +61 -0
- package/dist/views/usage-limits-page.d.ts.map +1 -0
- package/dist/views/usage-limits-page.js +275 -0
- package/dist/views/usage-limits-page.js.map +1 -0
- package/dist/views/usage-limits-view.d.ts +20 -0
- package/dist/views/usage-limits-view.d.ts.map +1 -0
- package/dist/views/usage-limits-view.js +36 -0
- package/dist/views/usage-limits-view.js.map +1 -0
- package/dist/views/usage-page.d.ts +53 -0
- package/dist/views/usage-page.d.ts.map +1 -0
- package/dist/views/usage-page.js +242 -0
- package/dist/views/usage-page.js.map +1 -0
- package/dist/views/usage-selection.d.ts +24 -0
- package/dist/views/usage-selection.d.ts.map +1 -0
- package/dist/views/usage-selection.js +51 -0
- package/dist/views/usage-selection.js.map +1 -0
- package/dist/views/views.css +1034 -0
- package/dist/views.d.ts +13 -0
- package/dist/views.d.ts.map +1 -0
- package/dist/views.js +13 -0
- package/dist/views.js.map +1 -0
- package/package.json +90 -3
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.tsx"],"names":[],"mappings":""}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* The Usage app's client: the same {@link UsagePage} the hub renders, in the app's own React root, given a
|
|
4
|
+
* {@link UsageShell} made from the {@link ShellContext} its shell passes. Bundled to `dist/client/client.js`.
|
|
5
|
+
*
|
|
6
|
+
* Reads go to the app's own routes (`ctx.api("/limits")`, `ctx.api("/usage")`), with the shell's request headers.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import { FleetLimits, FleetReadLoadError, FleetUsage } from "@knpkv/herdr-connect/usage";
|
|
11
|
+
import { defineAppClient } from "@knpkv/hub-app";
|
|
12
|
+
import { Icon } from "@knpkv/rly/foundations";
|
|
13
|
+
import { Clock, Effect, Exit, ManagedRuntime, Option, Schema } from "effect";
|
|
14
|
+
import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/http";
|
|
15
|
+
import { useMemo } from "react";
|
|
16
|
+
import { createRoot } from "react-dom/client";
|
|
17
|
+
import { UsagePage, UsagePageId } from "./views.js";
|
|
18
|
+
const runtime = ManagedRuntime.make(FetchHttpClient.layer);
|
|
19
|
+
/** One read of an app route, stamped with when this page received it. */
|
|
20
|
+
const load = Effect.fn("UsageClient.load")(function* (context, path, schema) {
|
|
21
|
+
const url = context.api(path);
|
|
22
|
+
const client = yield* HttpClient.HttpClient;
|
|
23
|
+
const response = yield* client
|
|
24
|
+
.execute(HttpClientRequest.get(new URL(url, window.location.href)).pipe(HttpClientRequest.setHeaders(context.requestHeaders())))
|
|
25
|
+
.pipe(Effect.mapError((cause) => new FleetReadLoadError({ path: url, reason: "network", cause })));
|
|
26
|
+
if (response.status < 200 || response.status >= 300) {
|
|
27
|
+
return yield* new FleetReadLoadError({ path: url, reason: "status", cause: response.status });
|
|
28
|
+
}
|
|
29
|
+
const fleet = yield* response.json.pipe(Effect.flatMap(Schema.decodeUnknownEffect(schema)), Effect.mapError((cause) => new FleetReadLoadError({ path: url, reason: "decode", cause })));
|
|
30
|
+
return { fleet, receivedAt: yield* Clock.currentTimeMillis };
|
|
31
|
+
});
|
|
32
|
+
/** Run a read on this page's runtime; unmounting or leaving the page aborts its request. */
|
|
33
|
+
const onRuntime = (effect) => Effect.callback((resume, signal) => {
|
|
34
|
+
runtime.runCallback(effect, {
|
|
35
|
+
signal,
|
|
36
|
+
onExit: (exit) => resume(Exit.isSuccess(exit) ? Effect.succeed(exit.value) : Effect.failCause(exit.cause))
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
/** The header chrome the shell lends, when it lends an outlet and the means to group around it. */
|
|
40
|
+
const chromeOf = (context) => {
|
|
41
|
+
const { headerOutlet, setHeaderMode } = context;
|
|
42
|
+
return headerOutlet === undefined || headerOutlet === null || setHeaderMode === undefined
|
|
43
|
+
? null
|
|
44
|
+
: {
|
|
45
|
+
host: context.caller.login,
|
|
46
|
+
headerOutlet,
|
|
47
|
+
setEmpty: context.setEmpty,
|
|
48
|
+
setGrouped: (grouped) => setHeaderMode(grouped ? "grouped" : "shell")
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
const UsageApp = ({ context }) => {
|
|
52
|
+
const reads = useMemo(() => ({
|
|
53
|
+
limits: onRuntime(load(context, "/limits", FleetLimits)),
|
|
54
|
+
usage: (query) => onRuntime(load(context, `/usage?${new URLSearchParams({ range: query.range, timeZone: query.timeZone }).toString()}`, FleetUsage))
|
|
55
|
+
}), [context.api, context.requestHeaders]);
|
|
56
|
+
const shell = useMemo(() => ({
|
|
57
|
+
active: context.active,
|
|
58
|
+
navigate: (href) => context.navigate(href, "replace", null),
|
|
59
|
+
href: (page, search) => {
|
|
60
|
+
const query = new URLSearchParams(search);
|
|
61
|
+
query.delete("tab");
|
|
62
|
+
return `?${query.toString()}#usage/${page}`;
|
|
63
|
+
},
|
|
64
|
+
// The shell's header outlet, when it lends one; standalone it has no grouped header, so no chrome.
|
|
65
|
+
chrome: chromeOf(context),
|
|
66
|
+
nextIcon: _jsx(Icon, { name: "chevron-right", decorative: true })
|
|
67
|
+
}), [context]);
|
|
68
|
+
const page = Option.getOrElse(Schema.decodeUnknownOption(UsagePageId)(context.page), () => "limits");
|
|
69
|
+
return (_jsx(UsagePage, { content: {
|
|
70
|
+
host: context.caller.login,
|
|
71
|
+
reads,
|
|
72
|
+
viewerTimeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
73
|
+
now: Date.now(),
|
|
74
|
+
onHubMachine: false
|
|
75
|
+
}, shell: shell, page: page, search: context.search }));
|
|
76
|
+
};
|
|
77
|
+
export default defineAppClient({
|
|
78
|
+
contract: 1,
|
|
79
|
+
mount: (root, context) => {
|
|
80
|
+
const react = createRoot(root);
|
|
81
|
+
react.render(_jsx(UsageApp, { context: context }));
|
|
82
|
+
return {
|
|
83
|
+
update: (next) => react.render(_jsx(UsageApp, { context: next })),
|
|
84
|
+
unmount: () => react.unmount()
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
|
+
declare const CommandFailed_base: Schema.Class<CommandFailed, Schema.TaggedStruct<"CommandFailed", {
|
|
3
|
+
readonly kind: Schema.Literals<readonly ["exit", "spawn", "too_large"]>;
|
|
4
|
+
/** Host-local text: stderr, or the spawn error. Logged, never sent. */
|
|
5
|
+
readonly detail: Schema.String;
|
|
6
|
+
}>, import("effect/Cause").YieldableError>;
|
|
7
|
+
/** agent-usage could not answer: it exited nonzero (with its stderr), could not be started, or printed too much. */
|
|
8
|
+
export declare class CommandFailed extends CommandFailed_base {
|
|
9
|
+
}
|
|
10
|
+
/** The fixed sentence a failure is reported by off this host. Never contains the failure's own text. */
|
|
11
|
+
export declare const failureSentence: (failure: CommandFailed) => string;
|
|
12
|
+
/** Logs the failure's host-local text here and answers the sentence that may leave the host. */
|
|
13
|
+
export declare const reportFailure: (what: string, failure: CommandFailed) => Effect.Effect<string>;
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=failure.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"failure.d.ts","sourceRoot":"","sources":["../../src/host/failure.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;;;IAKrC,uEAAuE;;;AAHzE,oHAAoH;AACpH,qBAAa,aAAc,SAAQ,kBAIjC;CAAG;AAeL,wGAAwG;AACxG,eAAO,MAAM,eAAe,YAAa,aAAa,KAAG,MASxD,CAAA;AAED,gGAAgG;AAChG,eAAO,MAAM,aAAa,SAAU,MAAM,WAAW,aAAa,KAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAGtF,CAAA"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a failed agent-usage command may say off this host. agent-usage explains itself on stderr,
|
|
3
|
+
* and its sentences can name host paths (the control socket under the user's home, the store
|
|
4
|
+
* directory, the executable); a peer's reading crosses the tailnet to the hub and reaches the
|
|
5
|
+
* browser. So the raw text is logged here, on this host, and the reading carries only one of a fixed
|
|
6
|
+
* set of sentences, chosen by recognising agent-usage's own wording.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import { failureSentences } from "@knpkv/herdr-connect";
|
|
11
|
+
import { Effect, Schema } from "effect";
|
|
12
|
+
/** agent-usage could not answer: it exited nonzero (with its stderr), could not be started, or printed too much. */
|
|
13
|
+
export class CommandFailed extends Schema.TaggedError()("CommandFailed", {
|
|
14
|
+
kind: Schema.Literals(["exit", "spawn", "too_large"]),
|
|
15
|
+
/** Host-local text: stderr, or the spawn error. Logged, never sent. */
|
|
16
|
+
detail: Schema.String
|
|
17
|
+
}) {
|
|
18
|
+
}
|
|
19
|
+
/** agent-usage's own sentences, recognised by wording that holds no path, and what is said instead. */
|
|
20
|
+
const knownFailures = [
|
|
21
|
+
[/is not running on this store/u, failureSentences.notRunning],
|
|
22
|
+
[/older version without (?:limits|usage)/u, failureSentences.olderVersion],
|
|
23
|
+
[/could not read its store/u, failureSentences.storeUnreadable],
|
|
24
|
+
[/control socket/u, failureSentences.controlSocket],
|
|
25
|
+
[/does not know the time zone/u, failureSentences.unknownTimeZone],
|
|
26
|
+
[/does not offer the range/u, failureSentences.unknownRange],
|
|
27
|
+
[/request was malformed/u, failureSentences.malformed],
|
|
28
|
+
[/configuration could not be read/u, failureSentences.configuration],
|
|
29
|
+
[/answered with something that is not/u, failureSentences.unexpectedAnswer]
|
|
30
|
+
];
|
|
31
|
+
/** The fixed sentence a failure is reported by off this host. Never contains the failure's own text. */
|
|
32
|
+
export const failureSentence = (failure) => {
|
|
33
|
+
switch (failure.kind) {
|
|
34
|
+
case "spawn":
|
|
35
|
+
return failureSentences.spawn;
|
|
36
|
+
case "too_large":
|
|
37
|
+
return failureSentences.tooLarge;
|
|
38
|
+
case "exit":
|
|
39
|
+
return knownFailures.find(([pattern]) => pattern.test(failure.detail))?.[1] ?? failureSentences.other;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
/** Logs the failure's host-local text here and answers the sentence that may leave the host. */
|
|
43
|
+
export const reportFailure = (what, failure) => Effect.logWarning(`${what}: agent-usage failed (${failure.kind})`, failure.detail).pipe(Effect.as(failureSentence(failure)));
|
|
44
|
+
//# sourceMappingURL=failure.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"failure.js","sourceRoot":"","sources":["../../src/host/failure.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAEvC,oHAAoH;AACpH,MAAM,OAAO,aAAc,SAAQ,MAAM,CAAC,WAAW,EAAiB,CAAC,eAAe,EAAE;IACtF,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC;IACrD,uEAAuE;IACvE,MAAM,EAAE,MAAM,CAAC,MAAM;CACtB,CAAC;CAAG;AAEL,uGAAuG;AACvG,MAAM,aAAa,GAA6C;IAC9D,CAAC,+BAA+B,EAAE,gBAAgB,CAAC,UAAU,CAAC;IAC9D,CAAC,yCAAyC,EAAE,gBAAgB,CAAC,YAAY,CAAC;IAC1E,CAAC,2BAA2B,EAAE,gBAAgB,CAAC,eAAe,CAAC;IAC/D,CAAC,iBAAiB,EAAE,gBAAgB,CAAC,aAAa,CAAC;IACnD,CAAC,8BAA8B,EAAE,gBAAgB,CAAC,eAAe,CAAC;IAClE,CAAC,2BAA2B,EAAE,gBAAgB,CAAC,YAAY,CAAC;IAC5D,CAAC,wBAAwB,EAAE,gBAAgB,CAAC,SAAS,CAAC;IACtD,CAAC,kCAAkC,EAAE,gBAAgB,CAAC,aAAa,CAAC;IACpE,CAAC,sCAAsC,EAAE,gBAAgB,CAAC,gBAAgB,CAAC;CAC5E,CAAA;AAED,wGAAwG;AACxG,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,OAAsB,EAAU,EAAE;IAChE,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;QACrB,KAAK,OAAO;YACV,OAAO,gBAAgB,CAAC,KAAK,CAAA;QAC/B,KAAK,WAAW;YACd,OAAO,gBAAgB,CAAC,QAAQ,CAAA;QAClC,KAAK,MAAM;YACT,OAAO,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,gBAAgB,CAAC,KAAK,CAAA;IACzG,CAAC;AACH,CAAC,CAAA;AAED,gGAAgG;AAChG,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,IAAY,EAAE,OAAsB,EAAyB,EAAE,CAC3F,MAAM,CAAC,UAAU,CAAC,GAAG,IAAI,yBAAyB,OAAO,CAAC,IAAI,GAAG,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,CACrF,MAAM,CAAC,EAAE,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CACpC,CAAA"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Duration, Effect, type Scope } from "effect";
|
|
2
|
+
import { ChildProcessSpawner } from "effect/process";
|
|
3
|
+
import { CommandFailed } from "./failure.js";
|
|
4
|
+
/** agent-usage's answer is a few KiB; anything near this is not it. */
|
|
5
|
+
export declare const hostLimitsOutputMaxBytes: number;
|
|
6
|
+
export declare const hostLimitsTimeout = "10 seconds";
|
|
7
|
+
/** Runs an agent-usage command and answers its stdout, failing with its stderr when it exits nonzero. */
|
|
8
|
+
export declare const runCommand: (command: readonly [string, ...string[]], maxBytes?: number | undefined) => Effect.Effect<string, CommandFailed, ChildProcessSpawner.ChildProcessSpawner>;
|
|
9
|
+
/**
|
|
10
|
+
* Runs the configured command once and reports what it said. Never fails: no command, a crash, a
|
|
11
|
+
* timeout, a newer format (`unsupported_version`) or output that is not agent-usage's each become
|
|
12
|
+
* `Unavailable` with its own reason, so one broken host cannot blank the fleet view.
|
|
13
|
+
*/
|
|
14
|
+
export declare const readHostLimits: (host: string, command: readonly string[] | undefined) => Effect.Effect<{
|
|
15
|
+
host: string;
|
|
16
|
+
readAt: number;
|
|
17
|
+
reading: {
|
|
18
|
+
readonly _tag: "Read";
|
|
19
|
+
readonly limits: {
|
|
20
|
+
readonly v: 1;
|
|
21
|
+
readonly machine: string;
|
|
22
|
+
readonly observedAt: number;
|
|
23
|
+
readonly latest: readonly {
|
|
24
|
+
readonly agent: "claude" | "codex";
|
|
25
|
+
readonly machine: string;
|
|
26
|
+
readonly source: "claude-oauth-usage" | "claude-statusline" | "codex-rollout";
|
|
27
|
+
readonly label: string;
|
|
28
|
+
readonly windowMinutes: number | null;
|
|
29
|
+
readonly observedAt: number;
|
|
30
|
+
readonly reading: {
|
|
31
|
+
readonly _tag: "Known";
|
|
32
|
+
readonly usedPercent: number;
|
|
33
|
+
readonly resetsAt: number | null;
|
|
34
|
+
} | {
|
|
35
|
+
readonly _tag: "Unknown";
|
|
36
|
+
readonly reason: "AuthExpired" | "Fetch" | "KeychainDenied" | "NoAuth" | "NoData" | "NotSupported" | "Parse";
|
|
37
|
+
readonly detail?: string;
|
|
38
|
+
};
|
|
39
|
+
}[];
|
|
40
|
+
};
|
|
41
|
+
readonly skipped: number;
|
|
42
|
+
} | {
|
|
43
|
+
readonly _tag: "Unavailable";
|
|
44
|
+
readonly reason: "failed" | "invalid_output" | "not_configured" | "timeout" | "unsupported_version";
|
|
45
|
+
readonly detail: string;
|
|
46
|
+
};
|
|
47
|
+
}, never, ChildProcessSpawner.ChildProcessSpawner>;
|
|
48
|
+
/** A value read through a cache: `read` answers at once after the first call. */
|
|
49
|
+
export interface StaleWhileRevalidate<A> {
|
|
50
|
+
readonly read: Effect.Effect<A>;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Serves `read`'s last value and refreshes it in the background once it is older than `ttl`, so a
|
|
54
|
+
* caller never waits on a slow read after the first. Only the first call blocks, and concurrent
|
|
55
|
+
* first calls share that one read. At most one refresh runs at a time; it lives in the caller's
|
|
56
|
+
* scope, so closing the scope stops it.
|
|
57
|
+
*/
|
|
58
|
+
export declare const staleWhileRevalidate: <A>(read: Effect.Effect<A, never, never>, ttl: Duration.Duration) => Effect.Effect<StaleWhileRevalidate<A>, never, Scope.Scope>;
|
|
59
|
+
//# sourceMappingURL=limits.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"limits.d.ts","sourceRoot":"","sources":["../../src/host/limits.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAS,QAAQ,EAAE,MAAM,EAAuB,KAAK,KAAK,EAAE,MAAM,QAAQ,CAAA;AACjF,OAAO,EAAgB,mBAAmB,EAAE,MAAM,gBAAgB,CAAA;AAClE,OAAO,EAAE,aAAa,EAAiB,MAAM,cAAc,CAAA;AAE3D,uEAAuE;AACvE,eAAO,MAAM,wBAAwB,QAAa,CAAA;AAClD,eAAO,MAAM,iBAAiB,eAAe,CAAA;AAI7C,yGAAyG;AACzG,eAAO,MAAM,UAAU,2JA8BrB,CAAA;AA+BF;;;;GAIG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kDAMzB,CAAA;AAEF,iFAAiF;AACjF,MAAM,WAAW,oBAAoB,CAAC,CAAC;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;CAChC;AAED;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,GAA0D,CAAC,6HAyB1F,CAAA"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This host's Claude and Codex limits for Connect: hostd runs the configured
|
|
3
|
+
* `agent-usage limits` command and serves its answer, refreshed in the background. The command
|
|
4
|
+
* reads agent-usage's own store over its owner-only control socket, so no provider credential and
|
|
5
|
+
* no session cookie passes through hostd.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
import { collectBoundedText } from "@knpkv/bounded-io";
|
|
10
|
+
import { decodeLimitsTolerantly, limitsUnavailable, notConfiguredSentences, RawLimits, readingOf, reasonSentences } from "@knpkv/herdr-connect";
|
|
11
|
+
import { Clock, Duration, Effect, Ref, Result, Schema } from "effect";
|
|
12
|
+
import { ChildProcess, ChildProcessSpawner } from "effect/process";
|
|
13
|
+
import { CommandFailed, reportFailure } from "./failure.js";
|
|
14
|
+
/** agent-usage's answer is a few KiB; anything near this is not it. */
|
|
15
|
+
export const hostLimitsOutputMaxBytes = 256 * 1024;
|
|
16
|
+
export const hostLimitsTimeout = "10 seconds";
|
|
17
|
+
const decodeRawLimits = Schema.decodeUnknownResult(Schema.fromJsonString(RawLimits));
|
|
18
|
+
/** Runs an agent-usage command and answers its stdout, failing with its stderr when it exits nonzero. */
|
|
19
|
+
export const runCommand = Effect.fn("HostLimits.run")(function* (command, maxBytes = hostLimitsOutputMaxBytes) {
|
|
20
|
+
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
|
|
21
|
+
return yield* Effect.scoped(Effect.gen(function* () {
|
|
22
|
+
const handle = yield* spawner.spawn(ChildProcess.make(command[0], command.slice(1)));
|
|
23
|
+
const { exitCode, stderr, stdout } = yield* Effect.all({
|
|
24
|
+
exitCode: handle.exitCode,
|
|
25
|
+
stderr: collectBoundedText(handle.stderr, hostLimitsOutputMaxBytes),
|
|
26
|
+
stdout: collectBoundedText(handle.stdout, maxBytes)
|
|
27
|
+
}, { concurrency: "unbounded" });
|
|
28
|
+
if (Number(exitCode) !== 0) {
|
|
29
|
+
return yield* new CommandFailed({
|
|
30
|
+
kind: "exit",
|
|
31
|
+
detail: stderr.trim() === "" ? `exited with code ${String(exitCode)}` : stderr
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return stdout;
|
|
35
|
+
})).pipe(Effect.catchTags({
|
|
36
|
+
ByteLimitExceeded: () => Effect.fail(new CommandFailed({ kind: "too_large", detail: "output over the size limit" }))
|
|
37
|
+
}), Effect.mapError((error) => error._tag === "CommandFailed" ? error : new CommandFailed({ kind: "spawn", detail: String(error) })));
|
|
38
|
+
});
|
|
39
|
+
const readReading = (command) => {
|
|
40
|
+
const executable = command?.[0];
|
|
41
|
+
if (command === undefined || executable === undefined) {
|
|
42
|
+
return Effect.succeed(limitsUnavailable("not_configured", notConfiguredSentences.noCommand));
|
|
43
|
+
}
|
|
44
|
+
return runCommand([executable, ...command.slice(1)]).pipe(Effect.timeoutOrElse({ duration: hostLimitsTimeout, orElse: () => Effect.succeed(null) }), Effect.map((stdout) => {
|
|
45
|
+
if (stdout === null)
|
|
46
|
+
return limitsUnavailable("timeout", reasonSentences.timeout);
|
|
47
|
+
const json = decodeRawLimits(stdout.trim());
|
|
48
|
+
if (Result.isFailure(json)) {
|
|
49
|
+
return limitsUnavailable("invalid_output", reasonSentences.invalid_output);
|
|
50
|
+
}
|
|
51
|
+
// A newer agent-usage may add sources or reasons: what this hostd can't read is skipped and counted.
|
|
52
|
+
return readingOf(decodeLimitsTolerantly(json.success));
|
|
53
|
+
}),
|
|
54
|
+
// agent-usage's own sentence can name host paths: it is logged here, and a fixed sentence leaves.
|
|
55
|
+
Effect.catchTag("CommandFailed", (failure) => Effect.map(reportFailure("Connect limits", failure), (sentence) => limitsUnavailable("failed", sentence))));
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Runs the configured command once and reports what it said. Never fails: no command, a crash, a
|
|
59
|
+
* timeout, a newer format (`unsupported_version`) or output that is not agent-usage's each become
|
|
60
|
+
* `Unavailable` with its own reason, so one broken host cannot blank the fleet view.
|
|
61
|
+
*/
|
|
62
|
+
export const readHostLimits = Effect.fn("HostLimits.read")(function* (host, command) {
|
|
63
|
+
const reading = yield* readReading(command);
|
|
64
|
+
return { host, readAt: yield* Clock.currentTimeMillis, reading };
|
|
65
|
+
});
|
|
66
|
+
/**
|
|
67
|
+
* Serves `read`'s last value and refreshes it in the background once it is older than `ttl`, so a
|
|
68
|
+
* caller never waits on a slow read after the first. Only the first call blocks, and concurrent
|
|
69
|
+
* first calls share that one read. At most one refresh runs at a time; it lives in the caller's
|
|
70
|
+
* scope, so closing the scope stops it.
|
|
71
|
+
*/
|
|
72
|
+
export const staleWhileRevalidate = Effect.fn("HostLimits.staleWhileRevalidate")(function* (read, ttl) {
|
|
73
|
+
const scope = yield* Effect.scope;
|
|
74
|
+
const ttlMillis = Duration.toMillis(ttl);
|
|
75
|
+
const latest = yield* Ref.make(null);
|
|
76
|
+
const refreshing = yield* Ref.make(false);
|
|
77
|
+
const refresh = Effect.gen(function* () {
|
|
78
|
+
const value = yield* read;
|
|
79
|
+
yield* Ref.set(latest, { value, at: yield* Clock.currentTimeMillis });
|
|
80
|
+
return value;
|
|
81
|
+
});
|
|
82
|
+
const first = yield* Effect.cached(refresh);
|
|
83
|
+
const cached = {
|
|
84
|
+
read: Effect.gen(function* () {
|
|
85
|
+
const current = yield* Ref.get(latest);
|
|
86
|
+
if (current === null)
|
|
87
|
+
return yield* first;
|
|
88
|
+
if ((yield* Clock.currentTimeMillis) - current.at >= ttlMillis && !(yield* Ref.getAndSet(refreshing, true))) {
|
|
89
|
+
yield* Effect.forkIn(refresh.pipe(Effect.ensuring(Ref.set(refreshing, false))), scope);
|
|
90
|
+
}
|
|
91
|
+
return current.value;
|
|
92
|
+
})
|
|
93
|
+
};
|
|
94
|
+
return cached;
|
|
95
|
+
});
|
|
96
|
+
//# sourceMappingURL=limits.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"limits.js","sourceRoot":"","sources":["../../src/host/limits.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This host's usage for the hub's Usage tab: hostd runs `agent-usage usage` for the asked range and
|
|
3
|
+
* zone and serves its answer, refreshed in the background. Like limits, the command reads
|
|
4
|
+
* agent-usage's own store over its owner-only control socket, and its answer holds tokens per agent
|
|
5
|
+
* and model and the limit series only: agent-usage leaves cost, balances, Bookings and paths out at
|
|
6
|
+
* the source.
|
|
7
|
+
*
|
|
8
|
+
* **The command.** It is the configured `agentUsageLimitsCommand` with its final `limits` word
|
|
9
|
+
* replaced by `usage`, so a host set up for Connect's limits needs nothing new. A command that does
|
|
10
|
+
* not end in `limits` (a wrapper script) has no usage, and says so.
|
|
11
|
+
*
|
|
12
|
+
* @module
|
|
13
|
+
*/
|
|
14
|
+
import { type HostUsage, type UsageQuery } from "@knpkv/herdr-connect";
|
|
15
|
+
import { Duration, Effect, Scope } from "effect";
|
|
16
|
+
import type { ChildProcessSpawner } from "effect/process";
|
|
17
|
+
export declare const hostUsageTimeout = "20 seconds";
|
|
18
|
+
/** How long a read is served before it is refreshed in the background: usage is history, not a live gauge. */
|
|
19
|
+
export declare const hostUsageTtl: Duration.Duration;
|
|
20
|
+
/** At most this many range and zone pairs are cached; the oldest is dropped for a new one. */
|
|
21
|
+
export declare const hostUsageCacheEntries = 16;
|
|
22
|
+
/** The `agent-usage usage` command for a configured limits command, or undefined when it has none. */
|
|
23
|
+
export declare const usageCommandFor: (limitsCommand: ReadonlyArray<string> | undefined) => readonly [string, ...Array<string>] | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* Runs the usage command once for `query` and reports what it said. Never fails: no command, a
|
|
26
|
+
* crash, a timeout, a newer format or output that is not agent-usage's each become `Unavailable`
|
|
27
|
+
* with its own reason.
|
|
28
|
+
*/
|
|
29
|
+
export declare const readHostUsage: (host: string, limitsCommand: readonly string[] | undefined, query: {
|
|
30
|
+
readonly range: "24h" | "30d" | "7d";
|
|
31
|
+
readonly timeZone: string;
|
|
32
|
+
}) => Effect.Effect<{
|
|
33
|
+
host: string;
|
|
34
|
+
readAt: number;
|
|
35
|
+
reading: {
|
|
36
|
+
readonly _tag: "Read";
|
|
37
|
+
readonly usage: {
|
|
38
|
+
readonly v: 1;
|
|
39
|
+
readonly machine: string;
|
|
40
|
+
readonly observedAt: number;
|
|
41
|
+
readonly range: {
|
|
42
|
+
readonly preset: "24h" | "30d" | "7d";
|
|
43
|
+
readonly timeZone: string;
|
|
44
|
+
readonly from: number;
|
|
45
|
+
readonly to: number;
|
|
46
|
+
readonly bucket: "day" | "hour" | "week";
|
|
47
|
+
};
|
|
48
|
+
readonly periods: readonly {
|
|
49
|
+
readonly key: string;
|
|
50
|
+
readonly start: number;
|
|
51
|
+
}[];
|
|
52
|
+
readonly tokens: readonly {
|
|
53
|
+
readonly period: number;
|
|
54
|
+
readonly agent: "claude" | "codex";
|
|
55
|
+
readonly model: string;
|
|
56
|
+
readonly tokens: number;
|
|
57
|
+
}[];
|
|
58
|
+
readonly limits: readonly {
|
|
59
|
+
readonly agent: "claude" | "codex";
|
|
60
|
+
readonly label: string;
|
|
61
|
+
readonly windowMinutes: number | null;
|
|
62
|
+
readonly points: readonly {
|
|
63
|
+
readonly at: number;
|
|
64
|
+
readonly reading: {
|
|
65
|
+
readonly _tag: "Known";
|
|
66
|
+
readonly usedPercent: number;
|
|
67
|
+
readonly resetsAt: number | null;
|
|
68
|
+
} | {
|
|
69
|
+
readonly _tag: "Unknown";
|
|
70
|
+
readonly reason: "AuthExpired" | "Fetch" | "KeychainDenied" | "NoAuth" | "NoData" | "NotSupported" | "Parse";
|
|
71
|
+
readonly detail?: string;
|
|
72
|
+
};
|
|
73
|
+
}[];
|
|
74
|
+
}[];
|
|
75
|
+
};
|
|
76
|
+
readonly skipped: number;
|
|
77
|
+
} | {
|
|
78
|
+
readonly _tag: "Unavailable";
|
|
79
|
+
readonly reason: "failed" | "invalid_output" | "not_configured" | "timeout" | "unsupported_version";
|
|
80
|
+
readonly detail: string;
|
|
81
|
+
};
|
|
82
|
+
}, never, ChildProcessSpawner.ChildProcessSpawner>;
|
|
83
|
+
/** At most this many agent-usage reads run at once across every range and zone. */
|
|
84
|
+
export declare const hostUsageConcurrentReads = 2;
|
|
85
|
+
/**
|
|
86
|
+
* One {@link staleWhileRevalidate} per range and zone, at most {@link hostUsageCacheEntries} of
|
|
87
|
+
* them: a new pair beyond that drops the least recently asked one. Finding or creating a pair's
|
|
88
|
+
* entry is atomic, so concurrent first asks share one read; and at most
|
|
89
|
+
* {@link hostUsageConcurrentReads} reads run at once, so a burst of new pairs queues rather than
|
|
90
|
+
* starting a command each. Refreshes live in `scope`.
|
|
91
|
+
*/
|
|
92
|
+
export declare const usageCache: (read: (query: UsageQuery) => Effect.Effect<HostUsage>, ttl?: Duration.Duration | undefined, entries?: number | undefined) => Effect.Effect<(query: UsageQuery) => Effect.Effect<HostUsage>, never, Scope.Scope>;
|
|
93
|
+
//# sourceMappingURL=usage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../../src/host/usage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAEL,KAAK,SAAS,EAId,KAAK,UAAU,EAIhB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAS,QAAQ,EAAE,MAAM,EAAkB,KAAK,EAA8B,MAAM,QAAQ,CAAA;AACnG,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAA;AAKzD,eAAO,MAAM,gBAAgB,eAAe,CAAA;AAE5C,8GAA8G;AAC9G,eAAO,MAAM,YAAY,mBAAsB,CAAA;AAE/C,8FAA8F;AAC9F,eAAO,MAAM,qBAAqB,KAAK,CAAA;AAIvC,sGAAsG;AACtG,eAAO,MAAM,eAAe,kBACX,aAAa,CAAC,MAAM,CAAC,GAAG,SAAS,KAC/C,SAAS,CAAC,MAAM,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,SAKxC,CAAA;AAiCD;;;;GAIG;AACH,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kDAOxB,CAAA;AAEF,mFAAmF;AACnF,eAAO,MAAM,wBAAwB,IAAI,CAAA;AAEzC;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,iBACP,UAAU,KAAK,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,8FA8BtC,UAAU,KAAG,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,qBACpD,CAAA"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This host's usage for the hub's Usage tab: hostd runs `agent-usage usage` for the asked range and
|
|
3
|
+
* zone and serves its answer, refreshed in the background. Like limits, the command reads
|
|
4
|
+
* agent-usage's own store over its owner-only control socket, and its answer holds tokens per agent
|
|
5
|
+
* and model and the limit series only: agent-usage leaves cost, balances, Bookings and paths out at
|
|
6
|
+
* the source.
|
|
7
|
+
*
|
|
8
|
+
* **The command.** It is the configured `agentUsageLimitsCommand` with its final `limits` word
|
|
9
|
+
* replaced by `usage`, so a host set up for Connect's limits needs nothing new. A command that does
|
|
10
|
+
* not end in `limits` (a wrapper script) has no usage, and says so.
|
|
11
|
+
*
|
|
12
|
+
* @module
|
|
13
|
+
*/
|
|
14
|
+
import { decodeUsageTolerantly, notConfiguredSentences, RawUsage, reasonSentences, usageReadingOf, usageResponseMaxBytes, usageUnavailable } from "@knpkv/herdr-connect";
|
|
15
|
+
import { Clock, Duration, Effect, Result, Schema, Scope, Semaphore, SynchronizedRef } from "effect";
|
|
16
|
+
import { reportFailure } from "./failure.js";
|
|
17
|
+
import { runCommand, staleWhileRevalidate } from "./limits.js";
|
|
18
|
+
// A 30-day read scans a month of the store; give it longer than limits' 10 seconds.
|
|
19
|
+
export const hostUsageTimeout = "20 seconds";
|
|
20
|
+
/** How long a read is served before it is refreshed in the background: usage is history, not a live gauge. */
|
|
21
|
+
export const hostUsageTtl = Duration.minutes(5);
|
|
22
|
+
/** At most this many range and zone pairs are cached; the oldest is dropped for a new one. */
|
|
23
|
+
export const hostUsageCacheEntries = 16;
|
|
24
|
+
const decodeRawUsage = Schema.decodeUnknownResult(Schema.fromJsonString(RawUsage));
|
|
25
|
+
/** The `agent-usage usage` command for a configured limits command, or undefined when it has none. */
|
|
26
|
+
export const usageCommandFor = (limitsCommand) => {
|
|
27
|
+
const executable = limitsCommand?.[0];
|
|
28
|
+
if (limitsCommand === undefined || executable === undefined || limitsCommand.length < 2)
|
|
29
|
+
return undefined;
|
|
30
|
+
if (limitsCommand.at(-1) !== "limits")
|
|
31
|
+
return undefined;
|
|
32
|
+
return [executable, ...limitsCommand.slice(1, -1), "usage"];
|
|
33
|
+
};
|
|
34
|
+
const readReading = (limitsCommand, query) => {
|
|
35
|
+
const command = usageCommandFor(limitsCommand);
|
|
36
|
+
if (command === undefined) {
|
|
37
|
+
return Effect.succeed(usageUnavailable("not_configured", limitsCommand === undefined ? notConfiguredSentences.noCommand : notConfiguredSentences.noUsage));
|
|
38
|
+
}
|
|
39
|
+
return runCommand([...command, "--range", query.range, "--time-zone", query.timeZone], usageResponseMaxBytes).pipe(Effect.timeoutOrElse({ duration: hostUsageTimeout, orElse: () => Effect.succeed(null) }), Effect.map((stdout) => {
|
|
40
|
+
if (stdout === null)
|
|
41
|
+
return usageUnavailable("timeout", reasonSentences.timeout);
|
|
42
|
+
const json = decodeRawUsage(stdout.trim());
|
|
43
|
+
if (Result.isFailure(json)) {
|
|
44
|
+
return usageUnavailable("invalid_output", reasonSentences.invalid_output);
|
|
45
|
+
}
|
|
46
|
+
return usageReadingOf(decodeUsageTolerantly(json.success));
|
|
47
|
+
}),
|
|
48
|
+
// agent-usage's own sentence can name host paths: it is logged here, and a fixed sentence leaves.
|
|
49
|
+
Effect.catchTag("CommandFailed", (failure) => Effect.map(reportFailure("Usage tab", failure), (sentence) => usageUnavailable("failed", sentence))));
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Runs the usage command once for `query` and reports what it said. Never fails: no command, a
|
|
53
|
+
* crash, a timeout, a newer format or output that is not agent-usage's each become `Unavailable`
|
|
54
|
+
* with its own reason.
|
|
55
|
+
*/
|
|
56
|
+
export const readHostUsage = Effect.fn("HostUsage.read")(function* (host, limitsCommand, query) {
|
|
57
|
+
const reading = yield* readReading(limitsCommand, query);
|
|
58
|
+
return { host, readAt: yield* Clock.currentTimeMillis, reading };
|
|
59
|
+
});
|
|
60
|
+
/** At most this many agent-usage reads run at once across every range and zone. */
|
|
61
|
+
export const hostUsageConcurrentReads = 2;
|
|
62
|
+
/**
|
|
63
|
+
* One {@link staleWhileRevalidate} per range and zone, at most {@link hostUsageCacheEntries} of
|
|
64
|
+
* them: a new pair beyond that drops the least recently asked one. Finding or creating a pair's
|
|
65
|
+
* entry is atomic, so concurrent first asks share one read; and at most
|
|
66
|
+
* {@link hostUsageConcurrentReads} reads run at once, so a burst of new pairs queues rather than
|
|
67
|
+
* starting a command each. Refreshes live in `scope`.
|
|
68
|
+
*/
|
|
69
|
+
export const usageCache = Effect.fn("HostUsage.cache")(function* (read, ttl = hostUsageTtl, entries = hostUsageCacheEntries) {
|
|
70
|
+
const scope = yield* Effect.scope;
|
|
71
|
+
const reads = yield* Semaphore.make(hostUsageConcurrentReads);
|
|
72
|
+
const caches = yield* SynchronizedRef.make(new Map());
|
|
73
|
+
const entryFor = (query) => SynchronizedRef.modifyEffect(caches, (current) => Effect.gen(function* () {
|
|
74
|
+
const key = `${query.range} ${query.timeZone}`;
|
|
75
|
+
const next = new Map(current);
|
|
76
|
+
const known = next.get(key);
|
|
77
|
+
// Re-inserted on every ask, so the Map's order is least recently asked first.
|
|
78
|
+
next.delete(key);
|
|
79
|
+
const cache = known ?? (yield* staleWhileRevalidate(reads.withPermits(1)(read(query)), ttl).pipe(Effect.provideService(Scope.Scope, scope)));
|
|
80
|
+
next.set(key, cache);
|
|
81
|
+
while (next.size > entries) {
|
|
82
|
+
const oldest = next.keys().next().value;
|
|
83
|
+
if (oldest === undefined)
|
|
84
|
+
break;
|
|
85
|
+
next.delete(oldest);
|
|
86
|
+
}
|
|
87
|
+
const updated = [
|
|
88
|
+
cache,
|
|
89
|
+
next
|
|
90
|
+
];
|
|
91
|
+
return updated;
|
|
92
|
+
}));
|
|
93
|
+
return (query) => Effect.flatMap(entryFor(query), (cache) => cache.read);
|
|
94
|
+
});
|
|
95
|
+
//# sourceMappingURL=usage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usage.js","sourceRoot":"","sources":["../../src/host/usage.ts"],"names":[],"mappings":"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"}
|
package/dist/host.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@knpkv/herdr-usage/host` — this machine's agent-usage readings: `readHostLimits` and `readHostUsage` run the
|
|
3
|
+
* configured `agent-usage` command, bounded and cached, and say why in plain sentences when it can't answer.
|
|
4
|
+
* hostd serves them to the hub as each peer's `/v1/connect/{limits,usage}/local`; the standalone Usage app
|
|
5
|
+
* reads them directly.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
export { CommandFailed, failureSentence, reportFailure } from "./host/failure.js";
|
|
10
|
+
export { hostLimitsOutputMaxBytes, hostLimitsTimeout, readHostLimits, runCommand, type StaleWhileRevalidate, staleWhileRevalidate } from "./host/limits.js";
|
|
11
|
+
export { hostUsageCacheEntries, hostUsageConcurrentReads, hostUsageTimeout, hostUsageTtl, readHostUsage, usageCache, usageCommandFor } from "./host/usage.js";
|
|
12
|
+
//# sourceMappingURL=host.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AACjF,OAAO,EACL,wBAAwB,EACxB,iBAAiB,EACjB,cAAc,EACd,UAAU,EACV,KAAK,oBAAoB,EACzB,oBAAoB,EACrB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EACL,qBAAqB,EACrB,wBAAwB,EACxB,gBAAgB,EAChB,YAAY,EACZ,aAAa,EACb,UAAU,EACV,eAAe,EAChB,MAAM,iBAAiB,CAAA"}
|
package/dist/host.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@knpkv/herdr-usage/host` — this machine's agent-usage readings: `readHostLimits` and `readHostUsage` run the
|
|
3
|
+
* configured `agent-usage` command, bounded and cached, and say why in plain sentences when it can't answer.
|
|
4
|
+
* hostd serves them to the hub as each peer's `/v1/connect/{limits,usage}/local`; the standalone Usage app
|
|
5
|
+
* reads them directly.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
export { CommandFailed, failureSentence, reportFailure } from "./host/failure.js";
|
|
10
|
+
export { hostLimitsOutputMaxBytes, hostLimitsTimeout, readHostLimits, runCommand, staleWhileRevalidate } from "./host/limits.js";
|
|
11
|
+
export { hostUsageCacheEntries, hostUsageConcurrentReads, hostUsageTimeout, hostUsageTtl, readHostUsage, usageCache, usageCommandFor } from "./host/usage.js";
|
|
12
|
+
//# sourceMappingURL=host.js.map
|
package/dist/host.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host.js","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AACjF,OAAO,EACL,wBAAwB,EACxB,iBAAiB,EACjB,cAAc,EACd,UAAU,EAEV,oBAAoB,EACrB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EACL,qBAAqB,EACrB,wBAAwB,EACxB,gBAAgB,EAChB,YAAY,EACZ,aAAa,EACb,UAAU,EACV,eAAe,EAChB,MAAM,iBAAiB,CAAA"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `herdr-usage` executable's options, read from its environment. `HERDR_USAGE_LIMITS_COMMAND` (a JSON array)
|
|
3
|
+
* overrides the agent-usage command a standalone run reads this machine with. Unset, it is the installed
|
|
4
|
+
* `@knpkv/agent-usage` run by the same node: no PATH lookup and no `.cmd` shim, which Windows can't spawn without a
|
|
5
|
+
* shell. Set but malformed, it is refused rather than quietly replaced by the default.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
import { Result, Schema } from "effect";
|
|
10
|
+
import type { UsageAppOptions } from "./app.js";
|
|
11
|
+
declare const UsageLimitsCommandInvalid_base: Schema.Class<UsageLimitsCommandInvalid, Schema.TaggedStruct<"UsageLimitsCommandInvalid", {
|
|
12
|
+
readonly message: Schema.String;
|
|
13
|
+
}>, import("effect/Cause").YieldableError>;
|
|
14
|
+
/** `HERDR_USAGE_LIMITS_COMMAND` is set but isn't a non-empty JSON array of strings. */
|
|
15
|
+
export declare class UsageLimitsCommandInvalid extends UsageLimitsCommandInvalid_base {
|
|
16
|
+
}
|
|
17
|
+
/** Where the default command comes from: the running node and a module resolver next to this package. */
|
|
18
|
+
export interface UsageRuntime {
|
|
19
|
+
readonly execPath: string;
|
|
20
|
+
readonly resolve: (specifier: string) => string;
|
|
21
|
+
}
|
|
22
|
+
export declare const readUsageOptions: (env: Readonly<Record<string, string | undefined>>, runtime: UsageRuntime) => Result.Result<UsageAppOptions, UsageLimitsCommandInvalid>;
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=options.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AACvC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;;;;AAE/C,uFAAuF;AACvF,qBAAa,yBAA0B,SAAQ,8BAG9C;CAAG;AAEJ,yGAAyG;AACzG,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,OAAO,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAA;CAChD;AAID,eAAO,MAAM,gBAAgB,QACtB,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,WACxC,YAAY,KACpB,MAAM,CAAC,MAAM,CAAC,eAAe,EAAE,yBAAyB,CAc1D,CAAA"}
|