claude-accountant 1.0.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.
Files changed (83) hide show
  1. package/.claude-plugin/plugin.json +14 -0
  2. package/.mcp.json +9 -0
  3. package/README.md +148 -0
  4. package/dist/admin-api.d.ts +3 -0
  5. package/dist/admin-api.d.ts.map +1 -0
  6. package/dist/admin-api.js +39 -0
  7. package/dist/admin-api.js.map +1 -0
  8. package/dist/budget.d.ts +4 -0
  9. package/dist/budget.d.ts.map +1 -0
  10. package/dist/budget.js +33 -0
  11. package/dist/budget.js.map +1 -0
  12. package/dist/config.d.ts +5 -0
  13. package/dist/config.d.ts.map +1 -0
  14. package/dist/config.js +42 -0
  15. package/dist/config.js.map +1 -0
  16. package/dist/db.d.ts +10 -0
  17. package/dist/db.d.ts.map +1 -0
  18. package/dist/db.js +111 -0
  19. package/dist/db.js.map +1 -0
  20. package/dist/estimator.d.ts +4 -0
  21. package/dist/estimator.d.ts.map +1 -0
  22. package/dist/estimator.js +90 -0
  23. package/dist/estimator.js.map +1 -0
  24. package/dist/index.d.ts +3 -0
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/index.js +13 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/offload.d.ts +3 -0
  29. package/dist/offload.d.ts.map +1 -0
  30. package/dist/offload.js +68 -0
  31. package/dist/offload.js.map +1 -0
  32. package/dist/pricing.d.ts +5 -0
  33. package/dist/pricing.d.ts.map +1 -0
  34. package/dist/pricing.js +49 -0
  35. package/dist/pricing.js.map +1 -0
  36. package/dist/resources/budget-status.d.ts +7 -0
  37. package/dist/resources/budget-status.d.ts.map +1 -0
  38. package/dist/resources/budget-status.js +10 -0
  39. package/dist/resources/budget-status.js.map +1 -0
  40. package/dist/resources/pricing-table.d.ts +6 -0
  41. package/dist/resources/pricing-table.d.ts.map +1 -0
  42. package/dist/resources/pricing-table.js +9 -0
  43. package/dist/resources/pricing-table.js.map +1 -0
  44. package/dist/resources/usage-summary.d.ts +7 -0
  45. package/dist/resources/usage-summary.d.ts.map +1 -0
  46. package/dist/resources/usage-summary.js +10 -0
  47. package/dist/resources/usage-summary.js.map +1 -0
  48. package/dist/server.d.ts +3 -0
  49. package/dist/server.d.ts.map +1 -0
  50. package/dist/server.js +234 -0
  51. package/dist/server.js.map +1 -0
  52. package/dist/tools/check-budget.d.ts +11 -0
  53. package/dist/tools/check-budget.d.ts.map +1 -0
  54. package/dist/tools/check-budget.js +23 -0
  55. package/dist/tools/check-budget.js.map +1 -0
  56. package/dist/tools/configure-budget.d.ts +27 -0
  57. package/dist/tools/configure-budget.d.ts.map +1 -0
  58. package/dist/tools/configure-budget.js +54 -0
  59. package/dist/tools/configure-budget.js.map +1 -0
  60. package/dist/tools/estimate-task-cost.d.ts +30 -0
  61. package/dist/tools/estimate-task-cost.d.ts.map +1 -0
  62. package/dist/tools/estimate-task-cost.js +37 -0
  63. package/dist/tools/estimate-task-cost.js.map +1 -0
  64. package/dist/tools/get-recommendations.d.ts +27 -0
  65. package/dist/tools/get-recommendations.d.ts.map +1 -0
  66. package/dist/tools/get-recommendations.js +47 -0
  67. package/dist/tools/get-recommendations.js.map +1 -0
  68. package/dist/tools/get-usage-history.d.ts +34 -0
  69. package/dist/tools/get-usage-history.d.ts.map +1 -0
  70. package/dist/tools/get-usage-history.js +42 -0
  71. package/dist/tools/get-usage-history.js.map +1 -0
  72. package/dist/tools/log-usage.d.ts +42 -0
  73. package/dist/tools/log-usage.d.ts.map +1 -0
  74. package/dist/tools/log-usage.js +42 -0
  75. package/dist/tools/log-usage.js.map +1 -0
  76. package/dist/types.d.ts +73 -0
  77. package/dist/types.d.ts.map +1 -0
  78. package/dist/types.js +2 -0
  79. package/dist/types.js.map +1 -0
  80. package/hooks/hooks.json +10 -0
  81. package/package.json +51 -0
  82. package/scripts/post-tool-hook.sh +14 -0
  83. package/skills/usage-awareness/SKILL.md +29 -0
@@ -0,0 +1,30 @@
1
+ import { z } from "zod";
2
+ import type { Config } from "../types.js";
3
+ export declare const estimateTaskCostSchema: z.ZodObject<{
4
+ task_description: z.ZodString;
5
+ complexity: z.ZodOptional<z.ZodEnum<["trivial", "simple", "moderate", "complex", "massive"]>>;
6
+ file_count: z.ZodDefault<z.ZodNumber>;
7
+ model: z.ZodOptional<z.ZodString>;
8
+ extended_thinking: z.ZodDefault<z.ZodBoolean>;
9
+ }, "strip", z.ZodTypeAny, {
10
+ task_description: string;
11
+ file_count: number;
12
+ extended_thinking: boolean;
13
+ complexity?: "trivial" | "simple" | "moderate" | "complex" | "massive" | undefined;
14
+ model?: string | undefined;
15
+ }, {
16
+ task_description: string;
17
+ complexity?: "trivial" | "simple" | "moderate" | "complex" | "massive" | undefined;
18
+ file_count?: number | undefined;
19
+ model?: string | undefined;
20
+ extended_thinking?: boolean | undefined;
21
+ }>;
22
+ export type EstimateTaskCostInput = z.infer<typeof estimateTaskCostSchema>;
23
+ export declare function estimateTaskCostTool(config: Config, input: EstimateTaskCostInput): {
24
+ content: {
25
+ type: "text";
26
+ text: string;
27
+ }[];
28
+ data: import("../types.js").CostEstimate;
29
+ };
30
+ //# sourceMappingURL=estimate-task-cost.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"estimate-task-cost.d.ts","sourceRoot":"","sources":["../../src/tools/estimate-task-cost.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAkB,MAAM,EAAE,MAAM,aAAa,CAAC;AAG1D,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;EAiBjC,CAAC;AAEH,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAE3E,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,qBAAqB;;;;;;EA0B7B"}
@@ -0,0 +1,37 @@
1
+ import { z } from "zod";
2
+ import { estimateTaskCost, inferComplexity } from "../estimator.js";
3
+ export const estimateTaskCostSchema = z.object({
4
+ task_description: z.string().describe("Brief description of the planned task"),
5
+ complexity: z
6
+ .enum(["trivial", "simple", "moderate", "complex", "massive"])
7
+ .optional()
8
+ .describe("Override complexity tier (auto-detected if omitted)"),
9
+ file_count: z
10
+ .number()
11
+ .int()
12
+ .min(0)
13
+ .default(0)
14
+ .describe("Number of files involved"),
15
+ model: z.string().optional().describe("Model to use for cost calculation"),
16
+ extended_thinking: z
17
+ .boolean()
18
+ .default(false)
19
+ .describe("Whether extended thinking will be used"),
20
+ });
21
+ export function estimateTaskCostTool(config, input) {
22
+ const complexity = input.complexity ?? inferComplexity(input.task_description);
23
+ const model = input.model ?? config.default_model;
24
+ const estimate = estimateTaskCost(model, complexity, input.file_count, config.budget.daily_limit_usd, input.extended_thinking);
25
+ const lines = [
26
+ `Task: ${input.task_description}`,
27
+ `Model: ${model}`,
28
+ estimate.breakdown,
29
+ `Estimated Cost: $${estimate.estimated_cost_usd.toFixed(4)}`,
30
+ `% of Daily Budget: ${estimate.pct_of_daily_budget.toFixed(2)}%`,
31
+ ];
32
+ return {
33
+ content: [{ type: "text", text: lines.join("\n") }],
34
+ data: estimate,
35
+ };
36
+ }
37
+ //# sourceMappingURL=estimate-task-cost.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"estimate-task-cost.js","sourceRoot":"","sources":["../../src/tools/estimate-task-cost.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEpE,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC7C,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,uCAAuC,CAAC;IAC9E,UAAU,EAAE,CAAC;SACV,IAAI,CAAC,CAAC,SAAS,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;SAC7D,QAAQ,EAAE;SACV,QAAQ,CAAC,qDAAqD,CAAC;IAClE,UAAU,EAAE,CAAC;SACV,MAAM,EAAE;SACR,GAAG,EAAE;SACL,GAAG,CAAC,CAAC,CAAC;SACN,OAAO,CAAC,CAAC,CAAC;SACV,QAAQ,CAAC,0BAA0B,CAAC;IACvC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,mCAAmC,CAAC;IAC1E,iBAAiB,EAAE,CAAC;SACjB,OAAO,EAAE;SACT,OAAO,CAAC,KAAK,CAAC;SACd,QAAQ,CAAC,wCAAwC,CAAC;CACtD,CAAC,CAAC;AAIH,MAAM,UAAU,oBAAoB,CAClC,MAAc,EACd,KAA4B;IAE5B,MAAM,UAAU,GACd,KAAK,CAAC,UAAU,IAAI,eAAe,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;IAC9D,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,MAAM,CAAC,aAAa,CAAC;IAElD,MAAM,QAAQ,GAAG,gBAAgB,CAC/B,KAAK,EACL,UAAU,EACV,KAAK,CAAC,UAAU,EAChB,MAAM,CAAC,MAAM,CAAC,eAAe,EAC7B,KAAK,CAAC,iBAAiB,CACxB,CAAC;IAEF,MAAM,KAAK,GAAG;QACZ,SAAS,KAAK,CAAC,gBAAgB,EAAE;QACjC,UAAU,KAAK,EAAE;QACjB,QAAQ,CAAC,SAAS;QAClB,oBAAoB,QAAQ,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;QAC5D,sBAAsB,QAAQ,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG;KACjE,CAAC;IAEF,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC5D,IAAI,EAAE,QAAQ;KACf,CAAC;AACJ,CAAC"}
@@ -0,0 +1,27 @@
1
+ import { z } from "zod";
2
+ import type { Config } from "../types.js";
3
+ export declare const getRecommendationsSchema: z.ZodObject<{
4
+ current_model: z.ZodOptional<z.ZodString>;
5
+ task_complexity: z.ZodOptional<z.ZodEnum<["trivial", "simple", "moderate", "complex", "massive"]>>;
6
+ is_urgent: z.ZodDefault<z.ZodBoolean>;
7
+ }, "strip", z.ZodTypeAny, {
8
+ is_urgent: boolean;
9
+ current_model?: string | undefined;
10
+ task_complexity?: "trivial" | "simple" | "moderate" | "complex" | "massive" | undefined;
11
+ }, {
12
+ current_model?: string | undefined;
13
+ task_complexity?: "trivial" | "simple" | "moderate" | "complex" | "massive" | undefined;
14
+ is_urgent?: boolean | undefined;
15
+ }>;
16
+ export type GetRecommendationsInput = z.infer<typeof getRecommendationsSchema>;
17
+ export declare function getRecommendationsTool(config: Config, input: GetRecommendationsInput): {
18
+ content: {
19
+ type: "text";
20
+ text: string;
21
+ }[];
22
+ data: {
23
+ recommendations: import("../types.js").Recommendation[];
24
+ budget_status: import("../types.js").BudgetStatus;
25
+ };
26
+ };
27
+ //# sourceMappingURL=get-recommendations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-recommendations.d.ts","sourceRoot":"","sources":["../../src/tools/get-recommendations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAI1C,eAAO,MAAM,wBAAwB;;;;;;;;;;;;EAanC,CAAC;AAEH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE/E,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,uBAAuB;;;;;;;;;EAwC/B"}
@@ -0,0 +1,47 @@
1
+ import { z } from "zod";
2
+ import { getBudgetStatus } from "../budget.js";
3
+ import { getRecommendations } from "../offload.js";
4
+ export const getRecommendationsSchema = z.object({
5
+ current_model: z
6
+ .string()
7
+ .optional()
8
+ .describe("Model currently being used"),
9
+ task_complexity: z
10
+ .enum(["trivial", "simple", "moderate", "complex", "massive"])
11
+ .optional()
12
+ .describe("Complexity of the planned task"),
13
+ is_urgent: z
14
+ .boolean()
15
+ .default(true)
16
+ .describe("Whether the current task is time-sensitive"),
17
+ });
18
+ export function getRecommendationsTool(config, input) {
19
+ const budget = getBudgetStatus(config);
20
+ const model = input.current_model ?? config.default_model;
21
+ const recs = getRecommendations(budget, model, input.task_complexity, input.is_urgent);
22
+ if (recs.length === 0) {
23
+ return {
24
+ content: [
25
+ {
26
+ type: "text",
27
+ text: "No cost-saving recommendations at this time. Budget usage is healthy.",
28
+ },
29
+ ],
30
+ data: { recommendations: [], budget_status: budget.status },
31
+ };
32
+ }
33
+ const lines = [
34
+ `Budget: ${budget.pct_used.toFixed(1)}% used ($${budget.spent_today_usd.toFixed(4)} / $${budget.daily_limit_usd.toFixed(2)})`,
35
+ "",
36
+ "Recommendations:",
37
+ ];
38
+ for (const rec of recs) {
39
+ lines.push(`[${rec.priority.toUpperCase()}] ${rec.action} (~${rec.estimated_savings_pct}% savings)`);
40
+ lines.push(` ${rec.description}`);
41
+ }
42
+ return {
43
+ content: [{ type: "text", text: lines.join("\n") }],
44
+ data: { recommendations: recs, budget_status: budget.status },
45
+ };
46
+ }
47
+ //# sourceMappingURL=get-recommendations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-recommendations.js","sourceRoot":"","sources":["../../src/tools/get-recommendations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAEnD,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC/C,aAAa,EAAE,CAAC;SACb,MAAM,EAAE;SACR,QAAQ,EAAE;SACV,QAAQ,CAAC,4BAA4B,CAAC;IACzC,eAAe,EAAE,CAAC;SACf,IAAI,CAAC,CAAC,SAAS,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;SAC7D,QAAQ,EAAE;SACV,QAAQ,CAAC,gCAAgC,CAAC;IAC7C,SAAS,EAAE,CAAC;SACT,OAAO,EAAE;SACT,OAAO,CAAC,IAAI,CAAC;SACb,QAAQ,CAAC,4CAA4C,CAAC;CAC1D,CAAC,CAAC;AAIH,MAAM,UAAU,sBAAsB,CACpC,MAAc,EACd,KAA8B;IAE9B,MAAM,MAAM,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;IACvC,MAAM,KAAK,GAAG,KAAK,CAAC,aAAa,IAAI,MAAM,CAAC,aAAa,CAAC;IAC1D,MAAM,IAAI,GAAG,kBAAkB,CAC7B,MAAM,EACN,KAAK,EACL,KAAK,CAAC,eAAe,EACrB,KAAK,CAAC,SAAS,CAChB,CAAC;IAEF,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,uEAAuE;iBAC9E;aACF;YACD,IAAI,EAAE,EAAE,eAAe,EAAE,EAAE,EAAE,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE;SAC5D,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAG;QACZ,WAAW,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,MAAM,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,MAAM,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG;QAC7H,EAAE;QACF,kBAAkB;KACnB,CAAC;IAEF,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,KAAK,CAAC,IAAI,CACR,IAAI,GAAG,CAAC,QAAQ,CAAC,WAAW,EAAE,KAAK,GAAG,CAAC,MAAM,MAAM,GAAG,CAAC,qBAAqB,YAAY,CACzF,CAAC;QACF,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC;IACrC,CAAC;IAED,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC5D,IAAI,EAAE,EAAE,eAAe,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE;KAC9D,CAAC;AACJ,CAAC"}
@@ -0,0 +1,34 @@
1
+ import { z } from "zod";
2
+ import type { Config } from "../types.js";
3
+ export declare const getUsageHistorySchema: z.ZodObject<{
4
+ days: z.ZodDefault<z.ZodNumber>;
5
+ }, "strip", z.ZodTypeAny, {
6
+ days: number;
7
+ }, {
8
+ days?: number | undefined;
9
+ }>;
10
+ export type GetUsageHistoryInput = z.infer<typeof getUsageHistorySchema>;
11
+ export declare function getUsageHistoryTool(config: Config, input: GetUsageHistoryInput): {
12
+ content: {
13
+ type: "text";
14
+ text: string;
15
+ }[];
16
+ data: {
17
+ history: never[];
18
+ trend: null;
19
+ total_cost?: undefined;
20
+ avg_daily?: undefined;
21
+ };
22
+ } | {
23
+ content: {
24
+ type: "text";
25
+ text: string;
26
+ }[];
27
+ data: {
28
+ history: import("../types.js").DailySummary[];
29
+ total_cost: number;
30
+ avg_daily: number;
31
+ trend?: undefined;
32
+ };
33
+ };
34
+ //# sourceMappingURL=get-usage-history.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-usage-history.d.ts","sourceRoot":"","sources":["../../src/tools/get-usage-history.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAG1C,eAAO,MAAM,qBAAqB;;;;;;EAQhC,CAAC;AAEH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEzE,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,oBAAoB;;;;;;;;;;;;;;;;;;;;;;EAsC5B"}
@@ -0,0 +1,42 @@
1
+ import { z } from "zod";
2
+ import { getUsageHistory } from "../db.js";
3
+ export const getUsageHistorySchema = z.object({
4
+ days: z
5
+ .number()
6
+ .int()
7
+ .min(1)
8
+ .max(90)
9
+ .default(7)
10
+ .describe("Number of days of history to retrieve"),
11
+ });
12
+ export function getUsageHistoryTool(config, input) {
13
+ const history = getUsageHistory(config, input.days);
14
+ if (history.length === 0) {
15
+ return {
16
+ content: [
17
+ {
18
+ type: "text",
19
+ text: `No usage data found for the past ${input.days} days.`,
20
+ },
21
+ ],
22
+ data: { history: [], trend: null },
23
+ };
24
+ }
25
+ const totalCost = history.reduce((sum, d) => sum + d.total_cost_usd, 0);
26
+ const avgDaily = totalCost / history.length;
27
+ const lines = [
28
+ `Usage History (past ${input.days} days, ${history.length} days with data):`,
29
+ "",
30
+ ];
31
+ for (const day of history) {
32
+ lines.push(`${day.date}: $${day.total_cost_usd.toFixed(4)} (${day.request_count} requests, ${(day.total_input_tokens + day.total_output_tokens).toLocaleString()} tokens)`);
33
+ }
34
+ lines.push("");
35
+ lines.push(`Total: $${totalCost.toFixed(4)}`);
36
+ lines.push(`Daily Average: $${avgDaily.toFixed(4)}`);
37
+ return {
38
+ content: [{ type: "text", text: lines.join("\n") }],
39
+ data: { history, total_cost: totalCost, avg_daily: avgDaily },
40
+ };
41
+ }
42
+ //# sourceMappingURL=get-usage-history.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-usage-history.js","sourceRoot":"","sources":["../../src/tools/get-usage-history.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAE3C,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC5C,IAAI,EAAE,CAAC;SACJ,MAAM,EAAE;SACR,GAAG,EAAE;SACL,GAAG,CAAC,CAAC,CAAC;SACN,GAAG,CAAC,EAAE,CAAC;SACP,OAAO,CAAC,CAAC,CAAC;SACV,QAAQ,CAAC,uCAAuC,CAAC;CACrD,CAAC,CAAC;AAIH,MAAM,UAAU,mBAAmB,CACjC,MAAc,EACd,KAA2B;IAE3B,MAAM,OAAO,GAAG,eAAe,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IAEpD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,oCAAoC,KAAK,CAAC,IAAI,QAAQ;iBAC7D;aACF;YACD,IAAI,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE;SACnC,CAAC;IACJ,CAAC;IAED,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC;IACxE,MAAM,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC;IAE5C,MAAM,KAAK,GAAG;QACZ,uBAAuB,KAAK,CAAC,IAAI,UAAU,OAAO,CAAC,MAAM,mBAAmB;QAC5E,EAAE;KACH,CAAC;IAEF,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;QAC1B,KAAK,CAAC,IAAI,CACR,GAAG,GAAG,CAAC,IAAI,MAAM,GAAG,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,aAAa,cAAc,CAAC,GAAG,CAAC,kBAAkB,GAAG,GAAG,CAAC,mBAAmB,CAAC,CAAC,cAAc,EAAE,UAAU,CAChK,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,KAAK,CAAC,IAAI,CAAC,WAAW,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC9C,KAAK,CAAC,IAAI,CAAC,mBAAmB,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAErD,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC5D,IAAI,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE;KAC9D,CAAC;AACJ,CAAC"}
@@ -0,0 +1,42 @@
1
+ import { z } from "zod";
2
+ import type { Config } from "../types.js";
3
+ export declare const logUsageSchema: z.ZodObject<{
4
+ session_id: z.ZodString;
5
+ model: z.ZodOptional<z.ZodString>;
6
+ input_tokens: z.ZodNumber;
7
+ output_tokens: z.ZodNumber;
8
+ cache_read_tokens: z.ZodDefault<z.ZodNumber>;
9
+ cache_write_tokens: z.ZodDefault<z.ZodNumber>;
10
+ task_description: z.ZodDefault<z.ZodString>;
11
+ source: z.ZodDefault<z.ZodEnum<["estimate", "admin_api", "hook", "manual"]>>;
12
+ }, "strip", z.ZodTypeAny, {
13
+ task_description: string;
14
+ session_id: string;
15
+ input_tokens: number;
16
+ output_tokens: number;
17
+ cache_read_tokens: number;
18
+ cache_write_tokens: number;
19
+ source: "estimate" | "admin_api" | "hook" | "manual";
20
+ model?: string | undefined;
21
+ }, {
22
+ session_id: string;
23
+ input_tokens: number;
24
+ output_tokens: number;
25
+ task_description?: string | undefined;
26
+ model?: string | undefined;
27
+ cache_read_tokens?: number | undefined;
28
+ cache_write_tokens?: number | undefined;
29
+ source?: "estimate" | "admin_api" | "hook" | "manual" | undefined;
30
+ }>;
31
+ export type LogUsageInput = z.infer<typeof logUsageSchema>;
32
+ export declare function logUsageTool(config: Config, input: LogUsageInput): {
33
+ content: {
34
+ type: "text";
35
+ text: string;
36
+ }[];
37
+ data: {
38
+ cost: number;
39
+ model: string;
40
+ };
41
+ };
42
+ //# sourceMappingURL=log-usage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log-usage.d.ts","sourceRoot":"","sources":["../../src/tools/log-usage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,aAAa,CAAC;AAIvD,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;EAYzB,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE3D,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa;;;;;;;;;EAgChE"}
@@ -0,0 +1,42 @@
1
+ import { z } from "zod";
2
+ import { logUsage } from "../db.js";
3
+ import { calculateCost } from "../pricing.js";
4
+ export const logUsageSchema = z.object({
5
+ session_id: z.string().describe("Current session identifier"),
6
+ model: z.string().optional().describe("Model used (defaults to config default)"),
7
+ input_tokens: z.number().int().min(0).describe("Input tokens consumed"),
8
+ output_tokens: z.number().int().min(0).describe("Output tokens consumed"),
9
+ cache_read_tokens: z.number().int().min(0).default(0),
10
+ cache_write_tokens: z.number().int().min(0).default(0),
11
+ task_description: z.string().default("").describe("What was done"),
12
+ source: z
13
+ .enum(["estimate", "admin_api", "hook", "manual"])
14
+ .default("estimate")
15
+ .describe("Source of token counts"),
16
+ });
17
+ export function logUsageTool(config, input) {
18
+ const model = input.model ?? config.default_model;
19
+ const cost = calculateCost(model, input.input_tokens, input.output_tokens, input.cache_read_tokens, input.cache_write_tokens);
20
+ logUsage(config, {
21
+ timestamp: new Date().toISOString(),
22
+ session_id: input.session_id,
23
+ model,
24
+ input_tokens: input.input_tokens,
25
+ output_tokens: input.output_tokens,
26
+ cache_read_tokens: input.cache_read_tokens,
27
+ cache_write_tokens: input.cache_write_tokens,
28
+ estimated_cost_usd: cost,
29
+ task_description: input.task_description,
30
+ source: input.source,
31
+ });
32
+ return {
33
+ content: [
34
+ {
35
+ type: "text",
36
+ text: `Logged: ${input.input_tokens} input + ${input.output_tokens} output tokens ($${cost.toFixed(4)})`,
37
+ },
38
+ ],
39
+ data: { cost, model },
40
+ };
41
+ }
42
+ //# sourceMappingURL=log-usage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log-usage.js","sourceRoot":"","sources":["../../src/tools/log-usage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE9C,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC;IACrC,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,4BAA4B,CAAC;IAC7D,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,yCAAyC,CAAC;IAChF,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,uBAAuB,CAAC;IACvE,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,wBAAwB,CAAC;IACzE,iBAAiB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IACrD,kBAAkB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IACtD,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,eAAe,CAAC;IAClE,MAAM,EAAE,CAAC;SACN,IAAI,CAAC,CAAC,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;SACjD,OAAO,CAAC,UAAU,CAAC;SACnB,QAAQ,CAAC,wBAAwB,CAAC;CACtC,CAAC,CAAC;AAIH,MAAM,UAAU,YAAY,CAAC,MAAc,EAAE,KAAoB;IAC/D,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,MAAM,CAAC,aAAa,CAAC;IAClD,MAAM,IAAI,GAAG,aAAa,CACxB,KAAK,EACL,KAAK,CAAC,YAAY,EAClB,KAAK,CAAC,aAAa,EACnB,KAAK,CAAC,iBAAiB,EACvB,KAAK,CAAC,kBAAkB,CACzB,CAAC;IAEF,QAAQ,CAAC,MAAM,EAAE;QACf,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,KAAK;QACL,YAAY,EAAE,KAAK,CAAC,YAAY;QAChC,aAAa,EAAE,KAAK,CAAC,aAAa;QAClC,iBAAiB,EAAE,KAAK,CAAC,iBAAiB;QAC1C,kBAAkB,EAAE,KAAK,CAAC,kBAAkB;QAC5C,kBAAkB,EAAE,IAAI;QACxB,gBAAgB,EAAE,KAAK,CAAC,gBAAgB;QACxC,MAAM,EAAE,KAAK,CAAC,MAAqB;KACpC,CAAC,CAAC;IAEH,OAAO;QACL,OAAO,EAAE;YACP;gBACE,IAAI,EAAE,MAAe;gBACrB,IAAI,EAAE,WAAW,KAAK,CAAC,YAAY,YAAY,KAAK,CAAC,aAAa,oBAAoB,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG;aACzG;SACF;QACD,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE;KACtB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,73 @@
1
+ export type BudgetStatus = "ok" | "warning" | "critical" | "exceeded";
2
+ export type ComplexityTier = "trivial" | "simple" | "moderate" | "complex" | "massive";
3
+ export type UsageSource = "estimate" | "admin_api" | "hook" | "manual";
4
+ export interface Config {
5
+ budget: {
6
+ daily_limit_usd: number;
7
+ monthly_limit_usd: number | null;
8
+ warning_threshold_pct: number;
9
+ critical_threshold_pct: number;
10
+ };
11
+ pricing_tier: string;
12
+ default_model: string;
13
+ data_dir: string;
14
+ admin_api?: {
15
+ api_key: string;
16
+ organization_id: string;
17
+ sync_interval_minutes: number;
18
+ };
19
+ }
20
+ export interface UsageLogEntry {
21
+ id?: number;
22
+ timestamp: string;
23
+ session_id: string;
24
+ model: string;
25
+ input_tokens: number;
26
+ output_tokens: number;
27
+ cache_read_tokens: number;
28
+ cache_write_tokens: number;
29
+ estimated_cost_usd: number;
30
+ task_description: string;
31
+ source: UsageSource;
32
+ }
33
+ export interface DailySummary {
34
+ date: string;
35
+ total_input_tokens: number;
36
+ total_output_tokens: number;
37
+ total_cache_read_tokens: number;
38
+ total_cache_write_tokens: number;
39
+ total_cost_usd: number;
40
+ request_count: number;
41
+ }
42
+ export interface BudgetSnapshot {
43
+ status: BudgetStatus;
44
+ daily_limit_usd: number;
45
+ spent_today_usd: number;
46
+ remaining_usd: number;
47
+ pct_used: number;
48
+ request_count_today: number;
49
+ monthly_limit_usd: number | null;
50
+ spent_this_month_usd: number | null;
51
+ }
52
+ export interface CostEstimate {
53
+ estimated_input_tokens: number;
54
+ estimated_output_tokens: number;
55
+ estimated_cost_usd: number;
56
+ pct_of_daily_budget: number;
57
+ complexity: ComplexityTier;
58
+ breakdown: string;
59
+ }
60
+ export interface Recommendation {
61
+ action: string;
62
+ description: string;
63
+ estimated_savings_pct: number;
64
+ priority: "low" | "medium" | "high";
65
+ }
66
+ export interface ModelPricing {
67
+ model: string;
68
+ input_per_mtok: number;
69
+ output_per_mtok: number;
70
+ cache_read_per_mtok: number;
71
+ cache_write_per_mtok: number;
72
+ }
73
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG,IAAI,GAAG,SAAS,GAAG,UAAU,GAAG,UAAU,CAAC;AAEtE,MAAM,MAAM,cAAc,GACtB,SAAS,GACT,QAAQ,GACR,UAAU,GACV,SAAS,GACT,SAAS,CAAC;AAEd,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,WAAW,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEvE,MAAM,WAAW,MAAM;IACrB,MAAM,EAAE;QACN,eAAe,EAAE,MAAM,CAAC;QACxB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;QACjC,qBAAqB,EAAE,MAAM,CAAC;QAC9B,sBAAsB,EAAE,MAAM,CAAC;KAChC,CAAC;IACF,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE;QACV,OAAO,EAAE,MAAM,CAAC;QAChB,eAAe,EAAE,MAAM,CAAC;QACxB,qBAAqB,EAAE,MAAM,CAAC;KAC/B,CAAC;CACH;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,kBAAkB,EAAE,MAAM,CAAC;IAC3B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,uBAAuB,EAAE,MAAM,CAAC;IAChC,wBAAwB,EAAE,MAAM,CAAC;IACjC,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,YAAY,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;CACrC;AAED,MAAM,WAAW,YAAY;IAC3B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,uBAAuB,EAAE,MAAM,CAAC;IAChC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,UAAU,EAAE,cAAc,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,QAAQ,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;CACrC;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,oBAAoB,EAAE,MAAM,CAAC;CAC9B"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,10 @@
1
+ {
2
+ "hooks": {
3
+ "PostToolUse": [
4
+ {
5
+ "matcher": ".*",
6
+ "command": "bash scripts/post-tool-hook.sh \"$TOOL_NAME\" \"$SESSION_ID\""
7
+ }
8
+ ]
9
+ }
10
+ }
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "claude-accountant",
3
+ "version": "1.0.0",
4
+ "description": "Claude Code plugin that tracks API usage, estimates costs, and recommends budget-aware actions",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "claude-accountant": "dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "skills",
13
+ "hooks",
14
+ "scripts",
15
+ ".claude-plugin",
16
+ ".mcp.json"
17
+ ],
18
+ "keywords": [
19
+ "claude",
20
+ "claude-code",
21
+ "mcp",
22
+ "usage-tracker",
23
+ "budget",
24
+ "anthropic",
25
+ "plugin",
26
+ "model-context-protocol"
27
+ ],
28
+ "license": "MIT",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": ""
32
+ },
33
+ "scripts": {
34
+ "build": "tsc",
35
+ "dev": "tsc --watch",
36
+ "prepublishOnly": "npm run build",
37
+ "test": "vitest run",
38
+ "test:watch": "vitest"
39
+ },
40
+ "dependencies": {
41
+ "@modelcontextprotocol/sdk": "^1.12.1",
42
+ "better-sqlite3": "^11.8.1",
43
+ "zod": "^3.24.2"
44
+ },
45
+ "devDependencies": {
46
+ "@types/better-sqlite3": "^7.6.13",
47
+ "@types/node": "^22.13.1",
48
+ "typescript": "^5.7.3",
49
+ "vitest": "^3.0.5"
50
+ }
51
+ }
@@ -0,0 +1,14 @@
1
+ #!/bin/bash
2
+ # PostToolUse hook: logs tool invocations to JSONL for usage tracking
3
+ # Called automatically after each tool use in Claude Code
4
+
5
+ TOOL_NAME="${1:-unknown}"
6
+ SESSION_ID="${2:-unknown}"
7
+ TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
8
+
9
+ DATA_DIR="${HOME}/.config/usage-plugin/data"
10
+ mkdir -p "$DATA_DIR"
11
+
12
+ LOG_FILE="${DATA_DIR}/hook_events.jsonl"
13
+
14
+ echo "{\"timestamp\":\"${TIMESTAMP}\",\"tool\":\"${TOOL_NAME}\",\"session_id\":\"${SESSION_ID}\"}" >> "$LOG_FILE"
@@ -0,0 +1,29 @@
1
+ # Usage Awareness Skill
2
+
3
+ You have access to a usage tracking plugin. Follow these rules to stay budget-aware:
4
+
5
+ ## Session Start
6
+ - Call `check_budget` at the beginning of every conversation to see how much budget remains today.
7
+ - If budget status is "warning" or "critical", mention this to the user and adjust your approach accordingly.
8
+
9
+ ## Before Expensive Operations
10
+ - Before large refactors, multi-file edits, or complex analysis, call `estimate_task_cost` with the task description and file count.
11
+ - If the estimated cost exceeds 10% of the daily budget, inform the user and suggest alternatives.
12
+ - Call `get_offload_recommendations` when budget is above 60% used.
13
+
14
+ ## After Completing Work
15
+ - Call `log_usage` with your best estimate of tokens consumed after each significant task.
16
+ - Use session_id to group related work within a conversation.
17
+ - Include a brief task_description so the user can see what each charge was for.
18
+
19
+ ## Budget Thresholds
20
+ - **OK** (<80%): Work normally.
21
+ - **Warning** (80-95%): Prioritize essential tasks. Use concise responses. Suggest deferring non-urgent work.
22
+ - **Critical** (95-100%): Only handle urgent requests. Recommend switching to a cheaper model. Strongly suggest deferring.
23
+ - **Exceeded** (>100%): Inform the user their daily budget is spent. Suggest waiting until tomorrow or increasing the limit.
24
+
25
+ ## Cost-Saving Habits
26
+ - Prefer concise answers when budget is above 60%.
27
+ - Suggest Haiku for simple lookups and formatting tasks.
28
+ - Recommend breaking large tasks into smaller, focused pieces.
29
+ - Mention the Batch API for non-time-sensitive bulk operations.