@agile-vibe-coding/avc 0.1.1 → 0.3.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/cli/agent-loader.js +21 -0
- package/cli/agents/agent-selector.md +152 -0
- package/cli/agents/architecture-recommender.md +418 -0
- package/cli/agents/code-implementer.md +117 -0
- package/cli/agents/code-validator.md +80 -0
- package/cli/agents/context-reviewer-epic.md +101 -0
- package/cli/agents/context-reviewer-story.md +92 -0
- package/cli/agents/context-writer-epic.md +145 -0
- package/cli/agents/context-writer-story.md +111 -0
- package/cli/agents/database-deep-dive.md +470 -0
- package/cli/agents/database-recommender.md +634 -0
- package/cli/agents/doc-distributor.md +176 -0
- package/cli/agents/doc-writer-epic.md +42 -0
- package/cli/agents/doc-writer-story.md +43 -0
- package/cli/agents/documentation-updater.md +203 -0
- package/cli/agents/duplicate-detector.md +110 -0
- package/cli/agents/epic-story-decomposer.md +559 -0
- package/cli/agents/feature-context-generator.md +91 -0
- package/cli/agents/gap-checker-epic.md +52 -0
- package/cli/agents/impact-checker-story.md +51 -0
- package/cli/agents/migration-guide-generator.md +305 -0
- package/cli/agents/mission-scope-generator.md +143 -0
- package/cli/agents/mission-scope-validator.md +146 -0
- package/cli/agents/project-context-extractor.md +122 -0
- package/cli/agents/project-documentation-creator.json +226 -0
- package/cli/agents/project-documentation-creator.md +595 -0
- package/cli/agents/question-prefiller.md +269 -0
- package/cli/agents/refiner-epic.md +39 -0
- package/cli/agents/refiner-story.md +42 -0
- package/cli/agents/scaffolding-generator.md +99 -0
- package/cli/agents/seed-validator.md +71 -0
- package/cli/agents/story-doc-enricher.md +133 -0
- package/cli/agents/story-scope-reviewer.md +147 -0
- package/cli/agents/story-splitter.md +83 -0
- package/cli/agents/suggestion-business-analyst.md +88 -0
- package/cli/agents/suggestion-deployment-architect.md +263 -0
- package/cli/agents/suggestion-product-manager.md +129 -0
- package/cli/agents/suggestion-security-specialist.md +156 -0
- package/cli/agents/suggestion-technical-architect.md +269 -0
- package/cli/agents/suggestion-ux-researcher.md +93 -0
- package/cli/agents/task-subtask-decomposer.md +188 -0
- package/cli/agents/validator-documentation.json +183 -0
- package/cli/agents/validator-documentation.md +455 -0
- package/cli/agents/validator-selector.md +211 -0
- package/cli/ansi-colors.js +21 -0
- package/cli/api-reference-tool.js +368 -0
- package/cli/build-docs.js +29 -8
- package/cli/ceremony-history.js +369 -0
- package/cli/checks/catalog.json +76 -0
- package/cli/checks/code/quality.json +26 -0
- package/cli/checks/code/testing.json +14 -0
- package/cli/checks/code/traceability.json +26 -0
- package/cli/checks/cross-refs/epic.json +171 -0
- package/cli/checks/cross-refs/story.json +149 -0
- package/cli/checks/epic/api.json +114 -0
- package/cli/checks/epic/backend.json +126 -0
- package/cli/checks/epic/cloud.json +126 -0
- package/cli/checks/epic/data.json +102 -0
- package/cli/checks/epic/database.json +114 -0
- package/cli/checks/epic/developer.json +182 -0
- package/cli/checks/epic/devops.json +174 -0
- package/cli/checks/epic/frontend.json +162 -0
- package/cli/checks/epic/mobile.json +102 -0
- package/cli/checks/epic/qa.json +90 -0
- package/cli/checks/epic/security.json +184 -0
- package/cli/checks/epic/solution-architect.json +192 -0
- package/cli/checks/epic/test-architect.json +90 -0
- package/cli/checks/epic/ui.json +102 -0
- package/cli/checks/epic/ux.json +90 -0
- package/cli/checks/fixes/epic-fix-template.md +10 -0
- package/cli/checks/fixes/story-fix-template.md +10 -0
- package/cli/checks/story/api.json +186 -0
- package/cli/checks/story/backend.json +102 -0
- package/cli/checks/story/cloud.json +102 -0
- package/cli/checks/story/data.json +210 -0
- package/cli/checks/story/database.json +102 -0
- package/cli/checks/story/developer.json +168 -0
- package/cli/checks/story/devops.json +102 -0
- package/cli/checks/story/frontend.json +174 -0
- package/cli/checks/story/mobile.json +102 -0
- package/cli/checks/story/qa.json +210 -0
- package/cli/checks/story/security.json +198 -0
- package/cli/checks/story/solution-architect.json +230 -0
- package/cli/checks/story/test-architect.json +210 -0
- package/cli/checks/story/ui.json +102 -0
- package/cli/checks/story/ux.json +102 -0
- package/cli/coding-order.js +401 -0
- package/cli/command-logger.js +49 -12
- package/cli/components/static-output.js +63 -0
- package/cli/console-output-manager.js +94 -0
- package/cli/dependency-checker.js +72 -0
- package/cli/docs-sync.js +306 -0
- package/cli/epic-story-validator.js +659 -0
- package/cli/evaluation-prompts.js +1008 -0
- package/cli/execution-context.js +195 -0
- package/cli/generate-summary-table.js +340 -0
- package/cli/init-model-config.js +704 -0
- package/cli/init.js +1737 -278
- package/cli/kanban-server-manager.js +227 -0
- package/cli/llm-claude.js +150 -1
- package/cli/llm-gemini.js +109 -0
- package/cli/llm-local.js +493 -0
- package/cli/llm-mock.js +233 -0
- package/cli/llm-openai.js +454 -0
- package/cli/llm-provider.js +379 -3
- package/cli/llm-token-limits.js +211 -0
- package/cli/llm-verifier.js +662 -0
- package/cli/llm-xiaomi.js +143 -0
- package/cli/message-constants.js +49 -0
- package/cli/message-manager.js +334 -0
- package/cli/message-types.js +96 -0
- package/cli/messaging-api.js +291 -0
- package/cli/micro-check-fixer.js +335 -0
- package/cli/micro-check-runner.js +449 -0
- package/cli/micro-check-scorer.js +148 -0
- package/cli/micro-check-validator.js +538 -0
- package/cli/model-pricing.js +192 -0
- package/cli/model-query-engine.js +468 -0
- package/cli/model-recommendation-analyzer.js +495 -0
- package/cli/model-selector.js +270 -0
- package/cli/output-buffer.js +107 -0
- package/cli/process-manager.js +73 -2
- package/cli/prompt-logger.js +57 -0
- package/cli/repl-ink.js +4625 -1094
- package/cli/repl-old.js +3 -4
- package/cli/seed-processor.js +962 -0
- package/cli/sprint-planning-processor.js +4162 -0
- package/cli/template-processor.js +2149 -105
- package/cli/templates/project.md +25 -8
- package/cli/templates/vitepress-config.mts.template +5 -4
- package/cli/token-tracker.js +547 -0
- package/cli/tools/generate-story-validators.js +317 -0
- package/cli/tools/generate-validators.js +669 -0
- package/cli/update-checker.js +19 -17
- package/cli/update-notifier.js +4 -4
- package/cli/validation-router.js +667 -0
- package/cli/verification-tracker.js +563 -0
- package/cli/worktree-runner.js +654 -0
- package/kanban/README.md +386 -0
- package/kanban/client/README.md +205 -0
- package/kanban/client/components.json +20 -0
- package/kanban/client/dist/assets/index-D_KC5EQT.css +1 -0
- package/kanban/client/dist/assets/index-DjY5zqW7.js +351 -0
- package/kanban/client/dist/index.html +16 -0
- package/kanban/client/dist/vite.svg +1 -0
- package/kanban/client/index.html +15 -0
- package/kanban/client/package-lock.json +9442 -0
- package/kanban/client/package.json +44 -0
- package/kanban/client/postcss.config.js +6 -0
- package/kanban/client/public/vite.svg +1 -0
- package/kanban/client/src/App.jsx +651 -0
- package/kanban/client/src/components/ProjectFileEditorPopup.jsx +117 -0
- package/kanban/client/src/components/ceremony/AskArchPopup.jsx +420 -0
- package/kanban/client/src/components/ceremony/AskModelPopup.jsx +629 -0
- package/kanban/client/src/components/ceremony/CeremonyWorkflowModal.jsx +1133 -0
- package/kanban/client/src/components/ceremony/EpicStorySelectionModal.jsx +254 -0
- package/kanban/client/src/components/ceremony/ProviderSwitcherButton.jsx +290 -0
- package/kanban/client/src/components/ceremony/SponsorCallModal.jsx +686 -0
- package/kanban/client/src/components/ceremony/SprintPlanningModal.jsx +838 -0
- package/kanban/client/src/components/ceremony/steps/ArchitectureStep.jsx +150 -0
- package/kanban/client/src/components/ceremony/steps/CompleteStep.jsx +136 -0
- package/kanban/client/src/components/ceremony/steps/DatabaseStep.jsx +202 -0
- package/kanban/client/src/components/ceremony/steps/DeploymentStep.jsx +123 -0
- package/kanban/client/src/components/ceremony/steps/MissionStep.jsx +106 -0
- package/kanban/client/src/components/ceremony/steps/ReviewAnswersStep.jsx +329 -0
- package/kanban/client/src/components/ceremony/steps/RunningStep.jsx +249 -0
- package/kanban/client/src/components/kanban/CardDetailModal.jsx +646 -0
- package/kanban/client/src/components/kanban/EpicSection.jsx +146 -0
- package/kanban/client/src/components/kanban/FilterToolbar.jsx +222 -0
- package/kanban/client/src/components/kanban/GroupingSelector.jsx +63 -0
- package/kanban/client/src/components/kanban/KanbanBoard.jsx +211 -0
- package/kanban/client/src/components/kanban/KanbanCard.jsx +147 -0
- package/kanban/client/src/components/kanban/KanbanColumn.jsx +90 -0
- package/kanban/client/src/components/kanban/RefineWorkItemPopup.jsx +784 -0
- package/kanban/client/src/components/kanban/RunButton.jsx +162 -0
- package/kanban/client/src/components/kanban/SeedButton.jsx +176 -0
- package/kanban/client/src/components/layout/LoadingScreen.jsx +82 -0
- package/kanban/client/src/components/process/ProcessMonitorBar.jsx +80 -0
- package/kanban/client/src/components/settings/AgentEditorPopup.jsx +171 -0
- package/kanban/client/src/components/settings/AgentsTab.jsx +381 -0
- package/kanban/client/src/components/settings/ApiKeysTab.jsx +142 -0
- package/kanban/client/src/components/settings/CeremonyModelsTab.jsx +105 -0
- package/kanban/client/src/components/settings/CheckEditorPopup.jsx +507 -0
- package/kanban/client/src/components/settings/CostThresholdsTab.jsx +95 -0
- package/kanban/client/src/components/settings/ModelPricingTab.jsx +269 -0
- package/kanban/client/src/components/settings/OpenAIAuthSection.jsx +412 -0
- package/kanban/client/src/components/settings/ServersTab.jsx +121 -0
- package/kanban/client/src/components/settings/SettingsModal.jsx +84 -0
- package/kanban/client/src/components/stats/CostModal.jsx +384 -0
- package/kanban/client/src/components/ui/badge.jsx +27 -0
- package/kanban/client/src/components/ui/dialog.jsx +121 -0
- package/kanban/client/src/components/ui/tabs.jsx +85 -0
- package/kanban/client/src/hooks/__tests__/useGrouping.test.js +232 -0
- package/kanban/client/src/hooks/useGrouping.js +177 -0
- package/kanban/client/src/hooks/useWebSocket.js +120 -0
- package/kanban/client/src/lib/__tests__/api.test.js +196 -0
- package/kanban/client/src/lib/__tests__/status-grouping.test.js +94 -0
- package/kanban/client/src/lib/api.js +515 -0
- package/kanban/client/src/lib/status-grouping.js +154 -0
- package/kanban/client/src/lib/utils.js +11 -0
- package/kanban/client/src/main.jsx +10 -0
- package/kanban/client/src/store/__tests__/kanbanStore.test.js +164 -0
- package/kanban/client/src/store/ceremonyStore.js +172 -0
- package/kanban/client/src/store/filterStore.js +201 -0
- package/kanban/client/src/store/kanbanStore.js +123 -0
- package/kanban/client/src/store/processStore.js +65 -0
- package/kanban/client/src/store/sprintPlanningStore.js +33 -0
- package/kanban/client/src/styles/globals.css +59 -0
- package/kanban/client/tailwind.config.js +77 -0
- package/kanban/client/vite.config.js +28 -0
- package/kanban/client/vitest.config.js +28 -0
- package/kanban/dev-start.sh +47 -0
- package/kanban/package.json +12 -0
- package/kanban/server/index.js +537 -0
- package/kanban/server/routes/ceremony.js +454 -0
- package/kanban/server/routes/costs.js +163 -0
- package/kanban/server/routes/openai-oauth.js +366 -0
- package/kanban/server/routes/processes.js +50 -0
- package/kanban/server/routes/settings.js +736 -0
- package/kanban/server/routes/websocket.js +281 -0
- package/kanban/server/routes/work-items.js +487 -0
- package/kanban/server/services/CeremonyService.js +1441 -0
- package/kanban/server/services/FileSystemScanner.js +95 -0
- package/kanban/server/services/FileWatcher.js +144 -0
- package/kanban/server/services/HierarchyBuilder.js +196 -0
- package/kanban/server/services/ProcessRegistry.js +122 -0
- package/kanban/server/services/TaskRunnerService.js +261 -0
- package/kanban/server/services/WorkItemReader.js +123 -0
- package/kanban/server/services/WorkItemRefineService.js +510 -0
- package/kanban/server/start.js +49 -0
- package/kanban/server/utils/kanban-logger.js +132 -0
- package/kanban/server/utils/markdown.js +91 -0
- package/kanban/server/utils/status-grouping.js +107 -0
- package/kanban/server/workers/run-task-worker.js +121 -0
- package/kanban/server/workers/seed-worker.js +94 -0
- package/kanban/server/workers/sponsor-call-worker.js +92 -0
- package/kanban/server/workers/sprint-planning-worker.js +212 -0
- package/package.json +19 -7
- package/cli/agents/documentation.md +0 -302
package/cli/templates/project.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
You will be helping to create a comprehensive
|
|
1
|
+
You will be helping to create a comprehensive Project Brief. This document will serve as a foundational specification that can later be broken down into domain-specific details and related prompts for development agents.
|
|
2
2
|
|
|
3
3
|
## Project Information
|
|
4
4
|
|
|
@@ -14,10 +14,18 @@ You will be helping to create a comprehensive software application definition do
|
|
|
14
14
|
|
|
15
15
|
{{INITIAL_SCOPE}}
|
|
16
16
|
|
|
17
|
+
### Deployment Target
|
|
18
|
+
|
|
19
|
+
{{DEPLOYMENT_TARGET}}
|
|
20
|
+
|
|
17
21
|
### Technical Considerations
|
|
18
22
|
|
|
19
23
|
{{TECHNICAL_CONSIDERATIONS}}
|
|
20
24
|
|
|
25
|
+
### Technology Exclusions
|
|
26
|
+
|
|
27
|
+
{{TECHNICAL_EXCLUSIONS}}
|
|
28
|
+
|
|
21
29
|
### Security and Compliance
|
|
22
30
|
|
|
23
31
|
{{SECURITY_AND_COMPLIANCE_REQUIREMENTS}}
|
|
@@ -39,24 +47,33 @@ In your scratchpad, consider:
|
|
|
39
47
|
- What are the primary user journeys through the application?
|
|
40
48
|
</scratchpad>
|
|
41
49
|
|
|
42
|
-
Now, create a comprehensive
|
|
50
|
+
Now, create a comprehensive Project Brief that includes the following sections:
|
|
43
51
|
|
|
44
52
|
1. **Application Overview**: A clear, concise summary of what the application does and its primary purpose
|
|
45
53
|
|
|
46
54
|
2. **Target Users and Stakeholders**: Identify the different types of users who will interact with this application, their roles, and their key needs
|
|
47
55
|
|
|
48
|
-
3. **
|
|
56
|
+
3. **Initial Scope**: Describe the essential features organized by functional area or domain (e.g., user management, data processing, reporting, etc.)
|
|
49
57
|
|
|
50
58
|
4. **User Workflows**: Outline the primary user journeys or workflows through the application with step-by-step descriptions
|
|
51
59
|
|
|
52
|
-
5. **
|
|
60
|
+
5. **UI/UX Design**: Describe the frontend technology approach, user interface requirements, and user experience considerations
|
|
61
|
+
- Frontend framework/technology selection (React, Vue, Angular, VitePress, Next.js, etc.)
|
|
62
|
+
- UI component library or design system approach (Material-UI, Tailwind, custom design system)
|
|
63
|
+
- Responsive design strategy (mobile-first, desktop-first, adaptive)
|
|
64
|
+
- Accessibility requirements (WCAG compliance level, screen reader support, keyboard navigation)
|
|
65
|
+
- User experience patterns (navigation structure, form design, loading states, error handling)
|
|
66
|
+
- Visual design considerations (branding, color scheme, typography)
|
|
67
|
+
- Internationalization needs (multi-language support, RTL layouts)
|
|
68
|
+
|
|
69
|
+
6. **Technical Architecture**: Note any important backend and infrastructure requirements, constraints, or preferences (e.g., backend technology stack, database, hosting platform, scalability needs, performance requirements)
|
|
53
70
|
|
|
54
|
-
|
|
71
|
+
7. **Integration Requirements**: Identify any external systems, APIs, or data sources the application needs to connect with
|
|
55
72
|
|
|
56
|
-
|
|
73
|
+
8. **Security and Compliance**: Highlight any security, privacy, or regulatory compliance requirements
|
|
57
74
|
|
|
58
|
-
|
|
75
|
+
9. **Success Criteria**: Define what success looks like for this application
|
|
59
76
|
|
|
60
|
-
Your final output should be a complete, well-structured
|
|
77
|
+
Your final output should be a complete, well-structured Project Brief that provides enough detail to serve as a foundation for creating domain-specific specifications and work items for AI agents, while remaining at a high enough level to give a comprehensive view of the entire project.
|
|
61
78
|
|
|
62
79
|
Use clear markdown formatting with headers, bullet points, and emphasis where appropriate. Do not include the scratchpad in your final output - only include the sections listed above.
|
|
@@ -4,6 +4,7 @@ export default defineConfig({
|
|
|
4
4
|
title: '{{PROJECT_NAME}}',
|
|
5
5
|
description: 'Project documentation powered by Agile Vibe Coding',
|
|
6
6
|
base: '/',
|
|
7
|
+
ignoreDeadLinks: true,
|
|
7
8
|
|
|
8
9
|
// Custom head tags for AVC documentation identification
|
|
9
10
|
head: [
|
|
@@ -13,17 +14,17 @@ export default defineConfig({
|
|
|
13
14
|
|
|
14
15
|
themeConfig: {
|
|
15
16
|
nav: [
|
|
16
|
-
{ text: 'Home', link: '/' }
|
|
17
|
-
{ text: 'Epics', link: '/epics' }
|
|
17
|
+
{ text: 'Home', link: '/' }
|
|
18
18
|
],
|
|
19
19
|
|
|
20
20
|
sidebar: [
|
|
21
21
|
{
|
|
22
|
-
text: 'Project',
|
|
23
22
|
items: [
|
|
24
|
-
{ text: '
|
|
23
|
+
{ text: 'Project Brief', link: '/' }
|
|
25
24
|
]
|
|
26
25
|
}
|
|
26
|
+
// @@AVC-WORK-ITEMS-START@@
|
|
27
|
+
// @@AVC-WORK-ITEMS-END@@
|
|
27
28
|
],
|
|
28
29
|
|
|
29
30
|
socialLinks: [
|
|
@@ -0,0 +1,547 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
export class TokenTracker {
|
|
5
|
+
constructor(avcPath = path.join(process.cwd(), '.avc')) {
|
|
6
|
+
this.avcPath = avcPath;
|
|
7
|
+
this.tokenHistoryPath = path.join(avcPath, 'token-history.json');
|
|
8
|
+
this.data = null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Initialize token history file if it doesn't exist
|
|
13
|
+
*/
|
|
14
|
+
init() {
|
|
15
|
+
// Ensure .avc directory exists
|
|
16
|
+
if (!fs.existsSync(this.avcPath)) {
|
|
17
|
+
fs.mkdirSync(this.avcPath, { recursive: true });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (!fs.existsSync(this.tokenHistoryPath)) {
|
|
21
|
+
const initialData = {
|
|
22
|
+
version: "1.0",
|
|
23
|
+
lastUpdated: new Date().toISOString(),
|
|
24
|
+
totals: {
|
|
25
|
+
daily: {},
|
|
26
|
+
weekly: {},
|
|
27
|
+
monthly: {},
|
|
28
|
+
allTime: {
|
|
29
|
+
input: 0,
|
|
30
|
+
output: 0,
|
|
31
|
+
total: 0,
|
|
32
|
+
executions: 0
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
this._writeData(initialData);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Load token history from disk
|
|
42
|
+
*/
|
|
43
|
+
load() {
|
|
44
|
+
if (fs.existsSync(this.tokenHistoryPath)) {
|
|
45
|
+
this.data = JSON.parse(fs.readFileSync(this.tokenHistoryPath, 'utf8'));
|
|
46
|
+
} else {
|
|
47
|
+
this.init();
|
|
48
|
+
this.data = JSON.parse(fs.readFileSync(this.tokenHistoryPath, 'utf8'));
|
|
49
|
+
}
|
|
50
|
+
return this.data;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Write data to disk atomically
|
|
55
|
+
*/
|
|
56
|
+
_writeData(data) {
|
|
57
|
+
// Ensure directory exists before writing (handles race conditions in tests)
|
|
58
|
+
if (!fs.existsSync(this.avcPath)) {
|
|
59
|
+
fs.mkdirSync(this.avcPath, { recursive: true });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const tempPath = this.tokenHistoryPath + '.tmp';
|
|
64
|
+
fs.writeFileSync(tempPath, JSON.stringify(data, null, 2), 'utf8');
|
|
65
|
+
fs.renameSync(tempPath, this.tokenHistoryPath);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
// Fallback to direct write if atomic write fails
|
|
68
|
+
try {
|
|
69
|
+
fs.writeFileSync(this.tokenHistoryPath, JSON.stringify(data, null, 2), 'utf8');
|
|
70
|
+
} catch (fallbackError) {
|
|
71
|
+
console.error(`Failed to write token history: ${fallbackError.message}`);
|
|
72
|
+
console.error(`Path: ${this.tokenHistoryPath}`);
|
|
73
|
+
throw fallbackError;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Read avc.json configuration
|
|
80
|
+
*/
|
|
81
|
+
readConfig() {
|
|
82
|
+
try {
|
|
83
|
+
const configPath = path.join(this.avcPath, 'avc.json');
|
|
84
|
+
if (fs.existsSync(configPath)) {
|
|
85
|
+
return JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
86
|
+
}
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.warn(`Could not read avc.json: ${error.message}`);
|
|
89
|
+
}
|
|
90
|
+
return { settings: {} };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Calculate cost for token usage based on model pricing
|
|
95
|
+
* @param {number} inputTokens - Input tokens consumed (includes cached tokens)
|
|
96
|
+
* @param {number} outputTokens - Output tokens consumed
|
|
97
|
+
* @param {string} modelId - Model identifier (e.g., 'claude-sonnet-4-5-20250929')
|
|
98
|
+
* @param {number} cachedTokens - Subset of inputTokens served from cache (billed at inputCached rate)
|
|
99
|
+
* @returns {Object} Cost breakdown { input, output, total, saved }
|
|
100
|
+
*/
|
|
101
|
+
calculateCost(inputTokens, outputTokens, modelId, cachedTokens = 0) {
|
|
102
|
+
const config = this.readConfig();
|
|
103
|
+
const modelConfig = config.settings?.models?.[modelId];
|
|
104
|
+
|
|
105
|
+
if (!modelConfig || !modelConfig.pricing) {
|
|
106
|
+
// Model not found or no pricing - return zero cost
|
|
107
|
+
return { input: 0, output: 0, total: 0, saved: 0 };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const pricing = modelConfig.pricing;
|
|
111
|
+
const divisor = pricing.unit === 'million' ? 1_000_000 : 1_000;
|
|
112
|
+
|
|
113
|
+
// Cached tokens billed at reduced rate; fall back to full input rate if not configured
|
|
114
|
+
const cachedRate = pricing.inputCached ?? pricing.input;
|
|
115
|
+
const nonCachedInput = Math.max(0, inputTokens - cachedTokens);
|
|
116
|
+
|
|
117
|
+
const inputCost = (pricing.input * nonCachedInput + cachedRate * cachedTokens) / divisor;
|
|
118
|
+
const outputCost = (pricing.output * outputTokens) / divisor;
|
|
119
|
+
// Savings vs. paying full rate for cached tokens
|
|
120
|
+
const saved = ((pricing.input - cachedRate) * cachedTokens) / divisor;
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
input: inputCost,
|
|
124
|
+
output: outputCost,
|
|
125
|
+
total: inputCost + outputCost,
|
|
126
|
+
saved,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Add tokens from a completed ceremony execution
|
|
132
|
+
* @param {string} ceremonyType - e.g., 'sponsor-call'
|
|
133
|
+
* @param {Object} tokens - { input, output }
|
|
134
|
+
* @param {string} modelId - Model identifier (optional)
|
|
135
|
+
*/
|
|
136
|
+
addExecution(ceremonyType, tokens, modelId = null) {
|
|
137
|
+
try {
|
|
138
|
+
this.load();
|
|
139
|
+
|
|
140
|
+
const now = new Date();
|
|
141
|
+
const dateKey = now.toISOString().split('T')[0]; // YYYY-MM-DD
|
|
142
|
+
const weekKey = this._getWeekKey(now); // YYYY-Www
|
|
143
|
+
const monthKey = dateKey.substring(0, 7); // YYYY-MM
|
|
144
|
+
const timestamp = now.toISOString();
|
|
145
|
+
|
|
146
|
+
const tokenData = {
|
|
147
|
+
input: tokens.input || 0,
|
|
148
|
+
output: tokens.output || 0,
|
|
149
|
+
cached: tokens.cached || 0,
|
|
150
|
+
cacheWrite: tokens.cacheWrite || 0,
|
|
151
|
+
total: (tokens.input || 0) + (tokens.output || 0),
|
|
152
|
+
provider: tokens.provider || 'unknown',
|
|
153
|
+
model: tokens.model || modelId || 'unknown'
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// Calculate cost: prefer per-model pricing from avc.json, fall back to provider estimate
|
|
157
|
+
// Skip cost calculation for OAuth calls (flat-rate subscription — no per-token billing)
|
|
158
|
+
let costData = null;
|
|
159
|
+
if (!tokens.skipCost) {
|
|
160
|
+
const effectiveModelId = tokens.model || modelId;
|
|
161
|
+
if (effectiveModelId) {
|
|
162
|
+
costData = this.calculateCost(tokenData.input, tokenData.output, effectiveModelId, tokenData.cached);
|
|
163
|
+
}
|
|
164
|
+
// If no per-model pricing configured but provider sent a pre-computed estimate, use it
|
|
165
|
+
if ((!costData || costData.total === 0) && tokens.estimatedCost) {
|
|
166
|
+
costData = { input: 0, output: 0, total: tokens.estimatedCost };
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
console.log(` → Tracking tokens for ${ceremonyType}: ${tokenData.input} input, ${tokenData.output} output (${tokenData.provider})`);
|
|
171
|
+
if (costData && costData.total > 0) {
|
|
172
|
+
console.log(` → Estimated cost: $${costData.total.toFixed(4)} (${effectiveModelId})`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Update totals (global)
|
|
176
|
+
this._updateAggregation(this.data.totals, dateKey, weekKey, monthKey, tokenData, timestamp, costData);
|
|
177
|
+
|
|
178
|
+
// Update ceremony-specific aggregations
|
|
179
|
+
if (!this.data[ceremonyType]) {
|
|
180
|
+
this.data[ceremonyType] = {
|
|
181
|
+
daily: {},
|
|
182
|
+
weekly: {},
|
|
183
|
+
monthly: {},
|
|
184
|
+
allTime: {
|
|
185
|
+
input: 0,
|
|
186
|
+
output: 0,
|
|
187
|
+
total: 0,
|
|
188
|
+
executions: 0,
|
|
189
|
+
cost: { input: 0, output: 0, total: 0 }
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
this._updateAggregation(this.data[ceremonyType], dateKey, weekKey, monthKey, tokenData, timestamp, costData);
|
|
194
|
+
|
|
195
|
+
// Update lastUpdated
|
|
196
|
+
this.data.lastUpdated = timestamp;
|
|
197
|
+
|
|
198
|
+
// Write to disk
|
|
199
|
+
this._writeData(this.data);
|
|
200
|
+
console.log(` → Token history saved to ${this.tokenHistoryPath}`);
|
|
201
|
+
} catch (error) {
|
|
202
|
+
console.error(`Failed to add token execution: ${error.message}`);
|
|
203
|
+
throw error;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Record tokens from a single LLM API call without incrementing the executions counter.
|
|
209
|
+
* Called after every individual LLM call so tokens are persisted crash-safely.
|
|
210
|
+
* @param {string} ceremonyType - e.g., 'sprint-planning'
|
|
211
|
+
* @param {Object} delta - { input, output, provider, model, estimatedCost? }
|
|
212
|
+
*/
|
|
213
|
+
addIncremental(ceremonyType, delta) {
|
|
214
|
+
if (!delta.input && !delta.output) return;
|
|
215
|
+
try {
|
|
216
|
+
this.load();
|
|
217
|
+
const now = new Date();
|
|
218
|
+
const dateKey = now.toISOString().split('T')[0];
|
|
219
|
+
const weekKey = this._getWeekKey(now);
|
|
220
|
+
const monthKey = dateKey.substring(0, 7);
|
|
221
|
+
const timestamp = now.toISOString();
|
|
222
|
+
|
|
223
|
+
const tokenData = {
|
|
224
|
+
input: delta.input || 0,
|
|
225
|
+
output: delta.output || 0,
|
|
226
|
+
cached: delta.cached || 0,
|
|
227
|
+
cacheWrite: delta.cacheWrite || 0,
|
|
228
|
+
total: (delta.input || 0) + (delta.output || 0),
|
|
229
|
+
provider: delta.provider || 'unknown',
|
|
230
|
+
model: delta.model || 'unknown'
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
let costData = null;
|
|
234
|
+
if (!delta.skipCost) {
|
|
235
|
+
if (delta.model) {
|
|
236
|
+
costData = this.calculateCost(tokenData.input, tokenData.output, delta.model, tokenData.cached);
|
|
237
|
+
}
|
|
238
|
+
if ((!costData || costData.total === 0) && delta.estimatedCost) {
|
|
239
|
+
costData = { input: 0, output: 0, total: delta.estimatedCost };
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (!this.data[ceremonyType]) {
|
|
244
|
+
this.data[ceremonyType] = {
|
|
245
|
+
daily: {}, weekly: {}, monthly: {},
|
|
246
|
+
allTime: { input: 0, output: 0, total: 0, executions: 0, cost: { input: 0, output: 0, total: 0 } }
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
this._updateAggregation(this.data.totals, dateKey, weekKey, monthKey, tokenData, timestamp, costData, false);
|
|
251
|
+
this._updateAggregation(this.data[ceremonyType], dateKey, weekKey, monthKey, tokenData, timestamp, costData, false);
|
|
252
|
+
this.data.lastUpdated = timestamp;
|
|
253
|
+
this._writeData(this.data);
|
|
254
|
+
} catch (error) {
|
|
255
|
+
console.error(`addIncremental failed: ${error.message}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Increment executions counter to mark a completed run.
|
|
261
|
+
* Call this once per ceremony run after all LLM calls are done.
|
|
262
|
+
* Tokens are already persisted by addIncremental() — this only bumps the count.
|
|
263
|
+
* @param {string} ceremonyType - e.g., 'sprint-planning'
|
|
264
|
+
*/
|
|
265
|
+
finalizeRun(ceremonyType) {
|
|
266
|
+
try {
|
|
267
|
+
this.load();
|
|
268
|
+
const now = new Date();
|
|
269
|
+
const dateKey = now.toISOString().split('T')[0];
|
|
270
|
+
const weekKey = this._getWeekKey(now);
|
|
271
|
+
const monthKey = dateKey.substring(0, 7);
|
|
272
|
+
const timestamp = now.toISOString();
|
|
273
|
+
|
|
274
|
+
if (!this.data[ceremonyType]) {
|
|
275
|
+
this.data[ceremonyType] = {
|
|
276
|
+
daily: {}, weekly: {}, monthly: {},
|
|
277
|
+
allTime: { input: 0, output: 0, total: 0, executions: 0, cost: { input: 0, output: 0, total: 0 } }
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Zero-token update — only increments executions counters
|
|
282
|
+
const zero = { input: 0, output: 0, total: 0, provider: 'unknown', model: 'unknown' };
|
|
283
|
+
this._updateAggregation(this.data.totals, dateKey, weekKey, monthKey, zero, timestamp, null, true);
|
|
284
|
+
this._updateAggregation(this.data[ceremonyType], dateKey, weekKey, monthKey, zero, timestamp, null, true);
|
|
285
|
+
this.data.lastUpdated = timestamp;
|
|
286
|
+
this._writeData(this.data);
|
|
287
|
+
console.log(` → Ceremony run finalized for ${ceremonyType}`);
|
|
288
|
+
} catch (error) {
|
|
289
|
+
console.error(`finalizeRun failed: ${error.message}`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Update aggregations for a given scope (totals or ceremony-type)
|
|
295
|
+
* @param {boolean} incExec - Whether to increment executions counters (default true)
|
|
296
|
+
*/
|
|
297
|
+
_updateAggregation(scope, dateKey, weekKey, monthKey, tokenData, timestamp, costData = null, incExec = true) {
|
|
298
|
+
// Update daily
|
|
299
|
+
if (!scope.daily[dateKey]) {
|
|
300
|
+
scope.daily[dateKey] = {
|
|
301
|
+
date: dateKey,
|
|
302
|
+
input: 0,
|
|
303
|
+
output: 0,
|
|
304
|
+
cached: 0,
|
|
305
|
+
total: 0,
|
|
306
|
+
executions: 0,
|
|
307
|
+
cost: { input: 0, output: 0, total: 0, saved: 0 }
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
scope.daily[dateKey].input += tokenData.input;
|
|
311
|
+
scope.daily[dateKey].output += tokenData.output;
|
|
312
|
+
scope.daily[dateKey].cached = (scope.daily[dateKey].cached || 0) + (tokenData.cached || 0);
|
|
313
|
+
scope.daily[dateKey].total += tokenData.total;
|
|
314
|
+
if (incExec) scope.daily[dateKey].executions++;
|
|
315
|
+
if (costData) {
|
|
316
|
+
scope.daily[dateKey].cost.input += costData.input;
|
|
317
|
+
scope.daily[dateKey].cost.output += costData.output;
|
|
318
|
+
scope.daily[dateKey].cost.total += costData.total;
|
|
319
|
+
scope.daily[dateKey].cost.saved = (scope.daily[dateKey].cost.saved || 0) + (costData.saved || 0);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Update weekly
|
|
323
|
+
if (!scope.weekly[weekKey]) {
|
|
324
|
+
scope.weekly[weekKey] = {
|
|
325
|
+
week: weekKey,
|
|
326
|
+
input: 0,
|
|
327
|
+
output: 0,
|
|
328
|
+
cached: 0,
|
|
329
|
+
total: 0,
|
|
330
|
+
executions: 0,
|
|
331
|
+
cost: { input: 0, output: 0, total: 0, saved: 0 }
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
scope.weekly[weekKey].input += tokenData.input;
|
|
335
|
+
scope.weekly[weekKey].output += tokenData.output;
|
|
336
|
+
scope.weekly[weekKey].cached = (scope.weekly[weekKey].cached || 0) + (tokenData.cached || 0);
|
|
337
|
+
scope.weekly[weekKey].total += tokenData.total;
|
|
338
|
+
if (incExec) scope.weekly[weekKey].executions++;
|
|
339
|
+
if (costData) {
|
|
340
|
+
scope.weekly[weekKey].cost.input += costData.input;
|
|
341
|
+
scope.weekly[weekKey].cost.output += costData.output;
|
|
342
|
+
scope.weekly[weekKey].cost.total += costData.total;
|
|
343
|
+
scope.weekly[weekKey].cost.saved = (scope.weekly[weekKey].cost.saved || 0) + (costData.saved || 0);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Update monthly
|
|
347
|
+
if (!scope.monthly[monthKey]) {
|
|
348
|
+
scope.monthly[monthKey] = {
|
|
349
|
+
month: monthKey,
|
|
350
|
+
input: 0,
|
|
351
|
+
output: 0,
|
|
352
|
+
cached: 0,
|
|
353
|
+
total: 0,
|
|
354
|
+
executions: 0,
|
|
355
|
+
cost: { input: 0, output: 0, total: 0, saved: 0 }
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
scope.monthly[monthKey].input += tokenData.input;
|
|
359
|
+
scope.monthly[monthKey].output += tokenData.output;
|
|
360
|
+
scope.monthly[monthKey].cached = (scope.monthly[monthKey].cached || 0) + (tokenData.cached || 0);
|
|
361
|
+
scope.monthly[monthKey].total += tokenData.total;
|
|
362
|
+
if (incExec) scope.monthly[monthKey].executions++;
|
|
363
|
+
if (costData) {
|
|
364
|
+
scope.monthly[monthKey].cost.input += costData.input;
|
|
365
|
+
scope.monthly[monthKey].cost.output += costData.output;
|
|
366
|
+
scope.monthly[monthKey].cost.total += costData.total;
|
|
367
|
+
scope.monthly[monthKey].cost.saved = (scope.monthly[monthKey].cost.saved || 0) + (costData.saved || 0);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Update all-time
|
|
371
|
+
scope.allTime.input += tokenData.input;
|
|
372
|
+
scope.allTime.output += tokenData.output;
|
|
373
|
+
scope.allTime.cached = (scope.allTime.cached || 0) + (tokenData.cached || 0);
|
|
374
|
+
scope.allTime.total += tokenData.total;
|
|
375
|
+
if (incExec) scope.allTime.executions++;
|
|
376
|
+
|
|
377
|
+
// Initialize cost tracking if not present
|
|
378
|
+
if (!scope.allTime.cost) {
|
|
379
|
+
scope.allTime.cost = { input: 0, output: 0, total: 0, saved: 0 };
|
|
380
|
+
}
|
|
381
|
+
if (costData) {
|
|
382
|
+
scope.allTime.cost.input += costData.input;
|
|
383
|
+
scope.allTime.cost.output += costData.output;
|
|
384
|
+
scope.allTime.cost.total += costData.total;
|
|
385
|
+
scope.allTime.cost.saved = (scope.allTime.cost.saved || 0) + (costData.saved || 0);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (!scope.allTime.firstExecution) {
|
|
389
|
+
scope.allTime.firstExecution = timestamp;
|
|
390
|
+
}
|
|
391
|
+
scope.allTime.lastExecution = timestamp;
|
|
392
|
+
|
|
393
|
+
// Cleanup old rolling windows
|
|
394
|
+
this._cleanupRollingWindows();
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Get ISO week key (YYYY-Www)
|
|
399
|
+
*/
|
|
400
|
+
_getWeekKey(date) {
|
|
401
|
+
const tempDate = new Date(date.getTime());
|
|
402
|
+
tempDate.setHours(0, 0, 0, 0);
|
|
403
|
+
tempDate.setDate(tempDate.getDate() + 3 - (tempDate.getDay() + 6) % 7);
|
|
404
|
+
const week1 = new Date(tempDate.getFullYear(), 0, 4);
|
|
405
|
+
const weekNum = 1 + Math.round(((tempDate - week1) / 86400000 - 3 + (week1.getDay() + 6) % 7) / 7);
|
|
406
|
+
return `${tempDate.getFullYear()}-W${String(weekNum).padStart(2, '0')}`;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Cleanup old entries from rolling windows
|
|
411
|
+
*/
|
|
412
|
+
_cleanupRollingWindows() {
|
|
413
|
+
const now = new Date();
|
|
414
|
+
|
|
415
|
+
// Keep last 31 days
|
|
416
|
+
const cutoffDaily = new Date(now);
|
|
417
|
+
cutoffDaily.setDate(cutoffDaily.getDate() - 31);
|
|
418
|
+
|
|
419
|
+
// Clean totals.daily
|
|
420
|
+
Object.keys(this.data.totals.daily).forEach(key => {
|
|
421
|
+
if (new Date(key) < cutoffDaily) {
|
|
422
|
+
delete this.data.totals.daily[key];
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
// Clean ceremony-type daily
|
|
427
|
+
Object.keys(this.data).forEach(key => {
|
|
428
|
+
if (key !== 'version' && key !== 'lastUpdated' && key !== 'totals' && this.data[key].daily) {
|
|
429
|
+
Object.keys(this.data[key].daily).forEach(dateKey => {
|
|
430
|
+
if (new Date(dateKey) < cutoffDaily) {
|
|
431
|
+
delete this.data[key].daily[dateKey];
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
// Keep last 12 weeks
|
|
438
|
+
const cutoffWeekly = new Date(now);
|
|
439
|
+
cutoffWeekly.setDate(cutoffWeekly.getDate() - 84);
|
|
440
|
+
|
|
441
|
+
Object.keys(this.data.totals.weekly).forEach(key => {
|
|
442
|
+
const [year, week] = key.split('-W');
|
|
443
|
+
const weekDate = this._getDateFromWeek(parseInt(year), parseInt(week));
|
|
444
|
+
if (weekDate < cutoffWeekly) {
|
|
445
|
+
delete this.data.totals.weekly[key];
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
Object.keys(this.data).forEach(key => {
|
|
450
|
+
if (key !== 'version' && key !== 'lastUpdated' && key !== 'totals' && this.data[key].weekly) {
|
|
451
|
+
Object.keys(this.data[key].weekly).forEach(weekKey => {
|
|
452
|
+
const [year, week] = weekKey.split('-W');
|
|
453
|
+
const weekDate = this._getDateFromWeek(parseInt(year), parseInt(week));
|
|
454
|
+
if (weekDate < cutoffWeekly) {
|
|
455
|
+
delete this.data[key].weekly[weekKey];
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
// Keep last 12 months
|
|
462
|
+
const cutoffMonthly = new Date(now);
|
|
463
|
+
cutoffMonthly.setMonth(cutoffMonthly.getMonth() - 12);
|
|
464
|
+
|
|
465
|
+
Object.keys(this.data.totals.monthly).forEach(key => {
|
|
466
|
+
if (new Date(key + '-01') < cutoffMonthly) {
|
|
467
|
+
delete this.data.totals.monthly[key];
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
Object.keys(this.data).forEach(key => {
|
|
472
|
+
if (key !== 'version' && key !== 'lastUpdated' && key !== 'totals' && this.data[key].monthly) {
|
|
473
|
+
Object.keys(this.data[key].monthly).forEach(monthKey => {
|
|
474
|
+
if (new Date(monthKey + '-01') < cutoffMonthly) {
|
|
475
|
+
delete this.data[key].monthly[monthKey];
|
|
476
|
+
}
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Get date from ISO week
|
|
484
|
+
*/
|
|
485
|
+
_getDateFromWeek(year, week) {
|
|
486
|
+
const simple = new Date(year, 0, 1 + (week - 1) * 7);
|
|
487
|
+
const dow = simple.getDay();
|
|
488
|
+
const ISOweekStart = simple;
|
|
489
|
+
if (dow <= 4) ISOweekStart.setDate(simple.getDate() - simple.getDay() + 1);
|
|
490
|
+
else ISOweekStart.setDate(simple.getDate() + 8 - simple.getDay());
|
|
491
|
+
return ISOweekStart;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Query methods
|
|
495
|
+
getTotalsToday() {
|
|
496
|
+
this.load();
|
|
497
|
+
const today = new Date().toISOString().split('T')[0];
|
|
498
|
+
return this.data.totals.daily[today] || { date: today, input: 0, output: 0, total: 0, executions: 0 };
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
getTotalsThisWeek() {
|
|
502
|
+
this.load();
|
|
503
|
+
const week = this._getWeekKey(new Date());
|
|
504
|
+
return this.data.totals.weekly[week] || { week, input: 0, output: 0, total: 0, executions: 0 };
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
getTotalsThisMonth() {
|
|
508
|
+
this.load();
|
|
509
|
+
const month = new Date().toISOString().substring(0, 7);
|
|
510
|
+
return this.data.totals.monthly[month] || { month, input: 0, output: 0, total: 0, executions: 0 };
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
getTotalsAllTime() {
|
|
514
|
+
this.load();
|
|
515
|
+
return this.data.totals.allTime;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
getCeremonyToday(ceremonyType) {
|
|
519
|
+
this.load();
|
|
520
|
+
const today = new Date().toISOString().split('T')[0];
|
|
521
|
+
return this.data[ceremonyType]?.daily[today] || { date: today, input: 0, output: 0, total: 0, executions: 0 };
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
getCeremonyThisWeek(ceremonyType) {
|
|
525
|
+
this.load();
|
|
526
|
+
const week = this._getWeekKey(new Date());
|
|
527
|
+
return this.data[ceremonyType]?.weekly[week] || { week, input: 0, output: 0, total: 0, executions: 0 };
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
getCeremonyThisMonth(ceremonyType) {
|
|
531
|
+
this.load();
|
|
532
|
+
const month = new Date().toISOString().substring(0, 7);
|
|
533
|
+
return this.data[ceremonyType]?.monthly[month] || { month, input: 0, output: 0, total: 0, executions: 0 };
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
getCeremonyAllTime(ceremonyType) {
|
|
537
|
+
this.load();
|
|
538
|
+
return this.data[ceremonyType]?.allTime || { input: 0, output: 0, total: 0, executions: 0 };
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
getAllCeremonyTypes() {
|
|
542
|
+
this.load();
|
|
543
|
+
return Object.keys(this.data).filter(key =>
|
|
544
|
+
key !== 'version' && key !== 'lastUpdated' && key !== 'totals'
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
}
|