@tokentop/ttop 0.2.2

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 (159) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +273 -0
  3. package/bin/ttop.js +49 -0
  4. package/package.json +68 -0
  5. package/src/agents/aggregator.ts +176 -0
  6. package/src/agents/costing.ts +74 -0
  7. package/src/agents/index.ts +12 -0
  8. package/src/agents/types.ts +71 -0
  9. package/src/cli.ts +167 -0
  10. package/src/config/schema.test.ts +166 -0
  11. package/src/config/schema.ts +165 -0
  12. package/src/demo/simulator.ts +745 -0
  13. package/src/plugins/agents/index.ts +2 -0
  14. package/src/plugins/auth-sources.ts +186 -0
  15. package/src/plugins/lifecycle.ts +177 -0
  16. package/src/plugins/loader.test.ts +195 -0
  17. package/src/plugins/loader.ts +282 -0
  18. package/src/plugins/notification-bus.ts +153 -0
  19. package/src/plugins/notifications/index.ts +2 -0
  20. package/src/plugins/notifications/terminal-bell.ts +66 -0
  21. package/src/plugins/notifications/visual-flash.ts +57 -0
  22. package/src/plugins/npm-installer.ts +164 -0
  23. package/src/plugins/plugin-context-factory.ts +67 -0
  24. package/src/plugins/plugin-host.ts +205 -0
  25. package/src/plugins/providers/anthropic.ts +325 -0
  26. package/src/plugins/providers/antigravity.ts +309 -0
  27. package/src/plugins/providers/codex.ts +250 -0
  28. package/src/plugins/providers/gemini.ts +391 -0
  29. package/src/plugins/providers/github-copilot.ts +194 -0
  30. package/src/plugins/providers/index.ts +12 -0
  31. package/src/plugins/providers/minimax.ts +223 -0
  32. package/src/plugins/providers/openai-api.ts +250 -0
  33. package/src/plugins/providers/opencode-zen.ts +125 -0
  34. package/src/plugins/providers/perplexity.ts +177 -0
  35. package/src/plugins/providers/zai.ts +225 -0
  36. package/src/plugins/registry.ts +251 -0
  37. package/src/plugins/sandbox-guard.test.ts +191 -0
  38. package/src/plugins/sandbox-guard.ts +155 -0
  39. package/src/plugins/sandbox.ts +138 -0
  40. package/src/plugins/themes/catppuccin-latte.ts +45 -0
  41. package/src/plugins/themes/catppuccin-mocha.ts +45 -0
  42. package/src/plugins/themes/claude-code.ts +45 -0
  43. package/src/plugins/themes/dracula.ts +45 -0
  44. package/src/plugins/themes/github-light.ts +45 -0
  45. package/src/plugins/themes/gruvbox-dark.ts +45 -0
  46. package/src/plugins/themes/gruvbox-light.ts +45 -0
  47. package/src/plugins/themes/index.ts +15 -0
  48. package/src/plugins/themes/kanagawa.ts +45 -0
  49. package/src/plugins/themes/nord.ts +45 -0
  50. package/src/plugins/themes/one-dark.ts +45 -0
  51. package/src/plugins/themes/opencode.ts +47 -0
  52. package/src/plugins/themes/rose-pine-dawn.ts +45 -0
  53. package/src/plugins/themes/rose-pine.ts +45 -0
  54. package/src/plugins/themes/solarized-light.ts +45 -0
  55. package/src/plugins/themes/tokyo-night.ts +49 -0
  56. package/src/plugins/types/agent.ts +108 -0
  57. package/src/plugins/types/base.ts +267 -0
  58. package/src/plugins/types/index.ts +19 -0
  59. package/src/plugins/types/notification.ts +51 -0
  60. package/src/plugins/types/provider.ts +233 -0
  61. package/src/plugins/types/theme.ts +70 -0
  62. package/src/plugins/update-checker.ts +177 -0
  63. package/src/pricing/estimator.test.ts +181 -0
  64. package/src/pricing/estimator.ts +80 -0
  65. package/src/pricing/fallback.test.ts +71 -0
  66. package/src/pricing/fallback.ts +62 -0
  67. package/src/pricing/index.ts +66 -0
  68. package/src/pricing/models-dev.ts +130 -0
  69. package/src/storage/database.ts +613 -0
  70. package/src/storage/db.ts +64 -0
  71. package/src/storage/index.ts +66 -0
  72. package/src/storage/migrations/index.ts +189 -0
  73. package/src/storage/paths.ts +24 -0
  74. package/src/storage/repos/agentSessions.ts +210 -0
  75. package/src/storage/repos/providerSnapshots.ts +116 -0
  76. package/src/storage/repos/usageEvents.ts +334 -0
  77. package/src/storage/types.ts +288 -0
  78. package/src/tui/App.tsx +381 -0
  79. package/src/tui/components/CommandPalette.tsx +166 -0
  80. package/src/tui/components/DebugConsole.tsx +161 -0
  81. package/src/tui/components/DebugPanel.tsx +660 -0
  82. package/src/tui/components/FooterHints.tsx +22 -0
  83. package/src/tui/components/GhostProviderCard.tsx +45 -0
  84. package/src/tui/components/Header.tsx +125 -0
  85. package/src/tui/components/HelpOverlay.tsx +45 -0
  86. package/src/tui/components/InlineGauge.tsx +34 -0
  87. package/src/tui/components/InlineSparkline.tsx +78 -0
  88. package/src/tui/components/KpiStrip.tsx +246 -0
  89. package/src/tui/components/LimitGauge.tsx +186 -0
  90. package/src/tui/components/ModalBackdrop.tsx +42 -0
  91. package/src/tui/components/ProviderAggregateStrip.tsx +200 -0
  92. package/src/tui/components/ProviderCard.tsx +309 -0
  93. package/src/tui/components/ProviderDetailPanel.tsx +237 -0
  94. package/src/tui/components/ProviderLimitsPanel.tsx +308 -0
  95. package/src/tui/components/ProvidersList.tsx +380 -0
  96. package/src/tui/components/SessionDetailsDrawer.tsx +317 -0
  97. package/src/tui/components/SessionsTable.tsx +401 -0
  98. package/src/tui/components/SettingsModal.tsx +587 -0
  99. package/src/tui/components/SidebarBreakdown.tsx +87 -0
  100. package/src/tui/components/Skeleton.tsx +102 -0
  101. package/src/tui/components/SmartSidebar.tsx +644 -0
  102. package/src/tui/components/Sparkline.tsx +180 -0
  103. package/src/tui/components/Spinner.tsx +36 -0
  104. package/src/tui/components/StatusBar.tsx +96 -0
  105. package/src/tui/components/ThemePicker.tsx +234 -0
  106. package/src/tui/components/Toast.tsx +61 -0
  107. package/src/tui/components/UsageGauge.tsx +80 -0
  108. package/src/tui/components/index.ts +5 -0
  109. package/src/tui/contexts/AgentSessionContext.tsx +307 -0
  110. package/src/tui/contexts/ConfigContext.tsx +141 -0
  111. package/src/tui/contexts/DashboardRuntimeContext.tsx +102 -0
  112. package/src/tui/contexts/DemoModeContext.tsx +60 -0
  113. package/src/tui/contexts/DrawerContext.tsx +42 -0
  114. package/src/tui/contexts/InputContext.tsx +30 -0
  115. package/src/tui/contexts/LogContext.tsx +150 -0
  116. package/src/tui/contexts/PluginContext.tsx +424 -0
  117. package/src/tui/contexts/RealTimeActivityContext.tsx +119 -0
  118. package/src/tui/contexts/StorageContext.tsx +233 -0
  119. package/src/tui/contexts/ThemeContext.tsx +95 -0
  120. package/src/tui/contexts/TimeWindowContext.tsx +158 -0
  121. package/src/tui/contexts/ToastContext.tsx +40 -0
  122. package/src/tui/contexts/index.ts +5 -0
  123. package/src/tui/createApp.tsx +47 -0
  124. package/src/tui/debug/captureFrame.ts +262 -0
  125. package/src/tui/debug/snapshot.tsx +644 -0
  126. package/src/tui/driver/assertions.ts +279 -0
  127. package/src/tui/driver/cli.ts +717 -0
  128. package/src/tui/driver/coverage.ts +204 -0
  129. package/src/tui/driver/demo-snapshot.ts +66 -0
  130. package/src/tui/driver/diff.ts +166 -0
  131. package/src/tui/driver/driver.ts +270 -0
  132. package/src/tui/driver/index.ts +53 -0
  133. package/src/tui/driver/recorder.ts +303 -0
  134. package/src/tui/driver/test-workflow.ts +115 -0
  135. package/src/tui/driver/test.ts +47 -0
  136. package/src/tui/hooks/index.ts +20 -0
  137. package/src/tui/hooks/useAnimatedValue.ts +113 -0
  138. package/src/tui/hooks/useDashboardKeyboard.ts +341 -0
  139. package/src/tui/hooks/useDashboardState.ts +116 -0
  140. package/src/tui/hooks/useEmaActivity.ts +223 -0
  141. package/src/tui/hooks/useEntranceAnimation.ts +62 -0
  142. package/src/tui/hooks/useExitAnimation.ts +137 -0
  143. package/src/tui/hooks/usePulse.ts +138 -0
  144. package/src/tui/hooks/useSafeRenderer.ts +24 -0
  145. package/src/tui/hooks/useValueFlash.ts +111 -0
  146. package/src/tui/index.tsx +50 -0
  147. package/src/tui/utils/providerColor.ts +32 -0
  148. package/src/tui/views/Dashboard.tsx +340 -0
  149. package/src/tui/views/HistoricalTrendsView.tsx +1431 -0
  150. package/src/tui/views/ProjectsView.tsx +1277 -0
  151. package/src/tui/views/RealTimeDashboard.tsx +393 -0
  152. package/src/tui/views/SettingsView.tsx +465 -0
  153. package/src/tui/views/index.ts +2 -0
  154. package/src/types/global.d.ts +5 -0
  155. package/src/types/opentui-test-utils.d.ts +18 -0
  156. package/src/utils/clipboard.ts +118 -0
  157. package/src/utils/currency.test.ts +106 -0
  158. package/src/utils/currency.ts +79 -0
  159. package/src/version.ts +1 -0
@@ -0,0 +1,45 @@
1
+ import type { ThemePlugin } from '../types/theme.ts';
2
+
3
+ export const rosePineDawnTheme: ThemePlugin = {
4
+ apiVersion: 2,
5
+ id: 'rose-pine-dawn',
6
+ type: 'theme',
7
+ name: 'Rosé Pine Dawn',
8
+ family: 'rose-pine',
9
+ version: '1.0.0',
10
+ permissions: {},
11
+
12
+ colorScheme: 'light',
13
+
14
+ colors: {
15
+ background: '#faf4ed',
16
+ foreground: '#fffaf3',
17
+ text: '#575279',
18
+ textMuted: '#797593',
19
+ textSubtle: '#9893a5',
20
+ primary: '#907aa9',
21
+ secondary: '#d7827e',
22
+ accent: '#56949f',
23
+ success: '#286983',
24
+ warning: '#ea9d34',
25
+ error: '#b4637a',
26
+ info: '#56949f',
27
+ border: '#cecacd',
28
+ borderMuted: '#dfdad9',
29
+ selection: '#dfdad9',
30
+ highlight: '#f2e9e1',
31
+ gaugeBackground: '#dfdad9',
32
+ gaugeFill: '#907aa9',
33
+ gaugeWarning: '#ea9d34',
34
+ gaugeDanger: '#b4637a',
35
+ },
36
+
37
+ components: {
38
+ header: {
39
+ background: '#f2e9e1',
40
+ },
41
+ statusBar: {
42
+ background: '#f2e9e1',
43
+ },
44
+ },
45
+ };
@@ -0,0 +1,45 @@
1
+ import type { ThemePlugin } from '../types/theme.ts';
2
+
3
+ export const rosePineTheme: ThemePlugin = {
4
+ apiVersion: 2,
5
+ id: 'rose-pine',
6
+ type: 'theme',
7
+ name: 'Rosé Pine',
8
+ family: 'rose-pine',
9
+ version: '1.0.0',
10
+ permissions: {},
11
+
12
+ colorScheme: 'dark',
13
+
14
+ colors: {
15
+ background: '#191724',
16
+ foreground: '#1f1d2e',
17
+ text: '#e0def4',
18
+ textMuted: '#908caa',
19
+ textSubtle: '#6e6a86',
20
+ primary: '#c4a7e7',
21
+ secondary: '#ebbcba',
22
+ accent: '#9ccfd8',
23
+ success: '#31748f',
24
+ warning: '#f6c177',
25
+ error: '#eb6f92',
26
+ info: '#9ccfd8',
27
+ border: '#403d52',
28
+ borderMuted: '#26233a',
29
+ selection: '#403d52',
30
+ highlight: '#26233a',
31
+ gaugeBackground: '#1f1d2e',
32
+ gaugeFill: '#c4a7e7',
33
+ gaugeWarning: '#f6c177',
34
+ gaugeDanger: '#eb6f92',
35
+ },
36
+
37
+ components: {
38
+ header: {
39
+ background: '#1f1d2e',
40
+ },
41
+ statusBar: {
42
+ background: '#1f1d2e',
43
+ },
44
+ },
45
+ };
@@ -0,0 +1,45 @@
1
+ import type { ThemePlugin } from '../types/theme.ts';
2
+
3
+ export const solarizedLightTheme: ThemePlugin = {
4
+ apiVersion: 2,
5
+ id: 'solarized-light',
6
+ type: 'theme',
7
+ name: 'Solarized Light',
8
+ family: 'solarized',
9
+ version: '1.0.0',
10
+ permissions: {},
11
+
12
+ colorScheme: 'light',
13
+
14
+ colors: {
15
+ background: '#fdf6e3',
16
+ foreground: '#eee8d5',
17
+ text: '#657b83',
18
+ textMuted: '#839496',
19
+ textSubtle: '#93a1a1',
20
+ primary: '#268bd2',
21
+ secondary: '#6c71c4',
22
+ accent: '#2aa198',
23
+ success: '#859900',
24
+ warning: '#b58900',
25
+ error: '#dc322f',
26
+ info: '#268bd2',
27
+ border: '#93a1a1',
28
+ borderMuted: '#eee8d5',
29
+ selection: '#eee8d5',
30
+ highlight: '#eee8d5',
31
+ gaugeBackground: '#eee8d5',
32
+ gaugeFill: '#268bd2',
33
+ gaugeWarning: '#b58900',
34
+ gaugeDanger: '#dc322f',
35
+ },
36
+
37
+ components: {
38
+ header: {
39
+ background: '#eee8d5',
40
+ },
41
+ statusBar: {
42
+ background: '#eee8d5',
43
+ },
44
+ },
45
+ };
@@ -0,0 +1,49 @@
1
+ import type { ThemePlugin } from '../types/theme.ts';
2
+
3
+ export const tokyoNightTheme: ThemePlugin = {
4
+ apiVersion: 2,
5
+ id: 'tokyo-night',
6
+ type: 'theme',
7
+ name: 'Tokyo Night',
8
+ family: 'tokyo-night',
9
+ version: '1.0.0',
10
+ permissions: {},
11
+
12
+ colorScheme: 'dark',
13
+
14
+ colors: {
15
+ background: '#1a1b26',
16
+ foreground: '#24283b',
17
+ text: '#c0caf5',
18
+ textMuted: '#737aa2',
19
+ textSubtle: '#565f89',
20
+ primary: '#7aa2f7',
21
+ secondary: '#bb9af7',
22
+ accent: '#7dcfff',
23
+ success: '#9ece6a',
24
+ warning: '#e0af68',
25
+ error: '#f7768e',
26
+ info: '#7aa2f7',
27
+ border: '#414868',
28
+ borderMuted: '#292e42',
29
+ selection: '#33467c',
30
+ highlight: '#2f3549',
31
+ gaugeBackground: '#24283b',
32
+ gaugeFill: '#7aa2f7',
33
+ gaugeWarning: '#e0af68',
34
+ gaugeDanger: '#f7768e',
35
+ },
36
+
37
+ components: {
38
+ header: {
39
+ background: '#16161e',
40
+ },
41
+ statusBar: {
42
+ background: '#1f2335',
43
+ },
44
+ gauge: {
45
+ height: 1,
46
+ borderRadius: 0,
47
+ },
48
+ },
49
+ };
@@ -0,0 +1,108 @@
1
+ import { z } from 'zod';
2
+ import type { BasePlugin, PluginHttpClient, PluginLogger } from './base.ts';
3
+ import type { Credentials, OAuthCredentials, PluginContext } from './provider.ts';
4
+
5
+ export const AgentCapabilitiesSchema = z.object({
6
+ sessionParsing: z.boolean(),
7
+ authReading: z.boolean(),
8
+ realTimeTracking: z.boolean(),
9
+ multiProvider: z.boolean(),
10
+ });
11
+
12
+ export type AgentCapabilities = z.infer<typeof AgentCapabilitiesSchema>;
13
+
14
+ export interface AgentConfig {
15
+ /** Display name for this coding agent (e.g. "OpenCode", "Cursor"). */
16
+ name: string;
17
+ /** CLI command that launches the agent (for display/detection). */
18
+ command?: string;
19
+ /** Path to the agent's config directory (for display/debugging). */
20
+ configPath?: string;
21
+ /** Path to the agent's session storage. */
22
+ sessionPath?: string;
23
+ /** Path to the agent's auth file (for credential reading). */
24
+ authPath?: string;
25
+ }
26
+
27
+ export interface AgentCredentials {
28
+ providers: Record<string, Credentials | undefined>;
29
+ oauth?: Record<string, OAuthCredentials>;
30
+ }
31
+
32
+ export interface AgentProviderConfig {
33
+ id: string;
34
+ name: string;
35
+ configured: boolean;
36
+ enabled?: boolean;
37
+ }
38
+
39
+ export interface SessionParseOptions {
40
+ sessionId?: string;
41
+ timePeriod?: 'session' | 'daily' | 'weekly' | 'monthly';
42
+ limit?: number;
43
+ /** Epoch ms — only return sessions updated after this timestamp. */
44
+ since?: number;
45
+ }
46
+
47
+ export interface SessionUsageData {
48
+ sessionId: string;
49
+ sessionName?: string;
50
+ providerId: string;
51
+ modelId: string;
52
+ tokens: {
53
+ input: number;
54
+ output: number;
55
+ cacheRead?: number;
56
+ cacheWrite?: number;
57
+ };
58
+ timestamp: number;
59
+ sessionUpdatedAt?: number;
60
+ projectPath?: string;
61
+ cost?: number;
62
+ }
63
+
64
+ export interface AgentFetchContext {
65
+ readonly http: PluginHttpClient;
66
+ readonly logger: PluginLogger;
67
+ readonly config: Record<string, unknown>;
68
+ readonly signal: AbortSignal;
69
+ }
70
+
71
+ export interface ActivityUpdate {
72
+ sessionId: string;
73
+ messageId: string;
74
+ tokens: {
75
+ input: number;
76
+ output: number;
77
+ reasoning?: number;
78
+ cacheRead?: number;
79
+ cacheWrite?: number;
80
+ };
81
+ timestamp: number;
82
+ }
83
+
84
+ export type ActivityCallback = (update: ActivityUpdate) => void;
85
+
86
+ export interface AgentPlugin extends BasePlugin {
87
+ readonly type: 'agent';
88
+ readonly agent: AgentConfig;
89
+ readonly capabilities: AgentCapabilities;
90
+
91
+ /** Check if this coding agent is installed on the user's machine. */
92
+ isInstalled(ctx: PluginContext): Promise<boolean>;
93
+
94
+ /** Read credentials that the coding agent has stored. */
95
+ readCredentials?(ctx: AgentFetchContext): Promise<AgentCredentials>;
96
+
97
+ /** Parse session usage data from the agent's local storage. */
98
+ parseSessions(options: SessionParseOptions, ctx: AgentFetchContext): Promise<SessionUsageData[]>;
99
+
100
+ /** List which model providers this agent is configured to use. */
101
+ getProviders?(ctx: AgentFetchContext): Promise<AgentProviderConfig[]>;
102
+
103
+ /** Start watching for real-time activity updates. */
104
+ startActivityWatch?(ctx: PluginContext, callback: ActivityCallback): void;
105
+
106
+ /** Stop watching for real-time activity updates. */
107
+ stopActivityWatch?(ctx: PluginContext): void;
108
+ }
@@ -0,0 +1,267 @@
1
+ /**
2
+ * Base plugin types and interfaces for the tokentop plugin system.
3
+ *
4
+ * These types mirror the Plugin SDK (@tokentop/plugin-sdk) — the SDK is the
5
+ * authoritative contract for community developers; core adapts to match.
6
+ */
7
+
8
+ import { z } from 'zod';
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // Identity
12
+ // ---------------------------------------------------------------------------
13
+
14
+ /**
15
+ * Plugin type discriminator
16
+ */
17
+ export type PluginType = 'provider' | 'agent' | 'theme' | 'notification';
18
+
19
+ /**
20
+ * Current API contract version.
21
+ * Core checks this at load time to ensure compatibility.
22
+ */
23
+ export const CURRENT_API_VERSION = 2;
24
+
25
+ // ---------------------------------------------------------------------------
26
+ // Permissions
27
+ // ---------------------------------------------------------------------------
28
+
29
+ /**
30
+ * Plugin permissions schema - defines what a plugin can access
31
+ */
32
+ export const PluginPermissionsSchema = z.object({
33
+ network: z.object({
34
+ enabled: z.boolean(),
35
+ allowedDomains: z.array(z.string()).optional(),
36
+ }).optional(),
37
+ filesystem: z.object({
38
+ read: z.boolean().optional(),
39
+ write: z.boolean().optional(),
40
+ paths: z.array(z.string()).optional(),
41
+ }).optional(),
42
+ env: z.object({
43
+ read: z.boolean().optional(),
44
+ vars: z.array(z.string()).optional(),
45
+ }).optional(),
46
+ system: z.object({
47
+ notifications: z.boolean().optional(),
48
+ clipboard: z.boolean().optional(),
49
+ }).optional(),
50
+ });
51
+
52
+ export type PluginPermissions = z.infer<typeof PluginPermissionsSchema>;
53
+
54
+ // ---------------------------------------------------------------------------
55
+ // Metadata
56
+ // ---------------------------------------------------------------------------
57
+
58
+ /**
59
+ * Plugin metadata
60
+ */
61
+ export const PluginMetaSchema = z.object({
62
+ author: z.string().optional(),
63
+ description: z.string().optional(),
64
+ homepage: z.string().url().optional(),
65
+ repository: z.string().url().optional(),
66
+ license: z.string().optional(),
67
+ /**
68
+ * Brand color as a hex string (e.g. `"#d97757"`).
69
+ * Used by the TUI for provider cards, charts, and status indicators.
70
+ */
71
+ brandColor: z.string().optional(),
72
+ /**
73
+ * Single-character icon or short glyph for compact displays.
74
+ * Example: `"◆"`, `"▲"`, `"⚡"`
75
+ */
76
+ icon: z.string().optional(),
77
+ /**
78
+ * Additional provider IDs that should resolve to this plugin.
79
+ * Coding agents may tag sessions with provider IDs that differ from
80
+ * the plugin's `id` (e.g. OpenCode uses `"openai"` but the plugin
81
+ * registers as `"openai-api"`). List those alternate IDs here so the
82
+ * TUI can resolve brand colors and other metadata correctly.
83
+ */
84
+ providerAliases: z.array(z.string()).optional(),
85
+ });
86
+
87
+ export type PluginMeta = z.infer<typeof PluginMetaSchema>;
88
+
89
+ // ---------------------------------------------------------------------------
90
+ // Configuration
91
+ // ---------------------------------------------------------------------------
92
+
93
+ /**
94
+ * Plugin configuration field definition (for plugin settings UI)
95
+ */
96
+ export interface ConfigField {
97
+ /** Data type of the setting value. */
98
+ type: 'string' | 'number' | 'boolean' | 'select';
99
+ /** Label shown in the settings UI. */
100
+ label?: string;
101
+ /** Help text shown below the field. */
102
+ description?: string;
103
+ /** Whether the field must have a value. */
104
+ required?: boolean;
105
+ /** Default value when no user config exists. */
106
+ default?: unknown;
107
+ /** Available options (for `select` type only). */
108
+ options?: Array<{ value: string; label: string }>;
109
+ /** Minimum value (for `number` type only). */
110
+ min?: number;
111
+ /** Maximum value (for `number` type only). */
112
+ max?: number;
113
+ }
114
+
115
+ // ---------------------------------------------------------------------------
116
+ // Lifecycle Context
117
+ // ---------------------------------------------------------------------------
118
+
119
+ /**
120
+ * Minimal context provided to lifecycle hooks.
121
+ */
122
+ export interface PluginLifecycleContext {
123
+ /** Plugin's validated configuration values. */
124
+ readonly config: Record<string, unknown>;
125
+ /** Scoped logger that prefixes all output with the plugin ID. */
126
+ readonly logger: PluginLogger;
127
+ }
128
+
129
+ // ---------------------------------------------------------------------------
130
+ // Base Plugin
131
+ // ---------------------------------------------------------------------------
132
+
133
+ /**
134
+ * Base plugin interface - all plugins must implement this.
135
+ *
136
+ * Provides identity, metadata, permissions, optional configuration schema,
137
+ * and lifecycle hooks.
138
+ */
139
+ export interface BasePlugin {
140
+ /** API version this plugin targets. Must equal {@link CURRENT_API_VERSION}. */
141
+ readonly apiVersion: 2;
142
+
143
+ /** Unique plugin identifier (kebab-case) */
144
+ readonly id: string;
145
+
146
+ /** Plugin type discriminator */
147
+ readonly type: PluginType;
148
+
149
+ /** Human-readable display name */
150
+ readonly name: string;
151
+
152
+ /** Semantic version string */
153
+ readonly version: string;
154
+
155
+ /** Plugin metadata */
156
+ readonly meta?: PluginMeta;
157
+
158
+ /** Required permissions */
159
+ readonly permissions: PluginPermissions;
160
+
161
+ /** Plugin-declared configuration fields, rendered in Settings UI */
162
+ readonly configSchema?: Record<string, ConfigField>;
163
+
164
+ /**
165
+ * Default config values. Used when no user configuration exists.
166
+ * Keys must match those in {@link configSchema}.
167
+ */
168
+ readonly defaultConfig?: Record<string, unknown>;
169
+
170
+ // -- Lifecycle Hooks (all optional) -------------------------------------
171
+
172
+ /**
173
+ * Called once after the plugin is loaded and validated.
174
+ * Use for one-time setup (open connections, allocate resources).
175
+ */
176
+ initialize?(ctx: PluginLifecycleContext): Promise<void>;
177
+
178
+ /**
179
+ * Called when the plugin should begin active work (e.g. polling).
180
+ * Called after `initialize()` during app startup, and after re-enable.
181
+ */
182
+ start?(ctx: PluginLifecycleContext): Promise<void>;
183
+
184
+ /**
185
+ * Called when the plugin should pause active work.
186
+ * Called before `destroy()` during app shutdown, and on disable.
187
+ */
188
+ stop?(ctx: PluginLifecycleContext): Promise<void>;
189
+
190
+ /**
191
+ * Called once before the plugin is unloaded.
192
+ * Use for cleanup (close connections, flush buffers).
193
+ */
194
+ destroy?(ctx: PluginLifecycleContext): Promise<void>;
195
+
196
+ /**
197
+ * Called when the user changes this plugin's configuration.
198
+ * Receive the new validated config values.
199
+ */
200
+ onConfigChange?(config: Record<string, unknown>, ctx: PluginLifecycleContext): Promise<void> | void;
201
+ }
202
+
203
+ // ---------------------------------------------------------------------------
204
+ // Logger
205
+ // ---------------------------------------------------------------------------
206
+
207
+ /**
208
+ * Logger interface provided to plugins
209
+ */
210
+ export interface PluginLogger {
211
+ debug(message: string, data?: Record<string, unknown>): void;
212
+ info(message: string, data?: Record<string, unknown>): void;
213
+ warn(message: string, data?: Record<string, unknown>): void;
214
+ error(message: string, data?: Record<string, unknown>): void;
215
+ }
216
+
217
+ // ---------------------------------------------------------------------------
218
+ // HTTP Client
219
+ // ---------------------------------------------------------------------------
220
+
221
+ /**
222
+ * HTTP client interface provided to plugins (sandboxed)
223
+ */
224
+ export interface PluginHttpClient {
225
+ fetch(url: string, init?: RequestInit): Promise<Response>;
226
+ }
227
+
228
+ // ---------------------------------------------------------------------------
229
+ // Validation
230
+ // ---------------------------------------------------------------------------
231
+
232
+ /**
233
+ * Plugin validation result
234
+ */
235
+ export interface PluginValidationResult {
236
+ valid: boolean;
237
+ errors: string[];
238
+ warnings: string[];
239
+ }
240
+
241
+ /**
242
+ * Plugin load result
243
+ */
244
+ export interface PluginLoadResult<T extends BasePlugin = BasePlugin> {
245
+ success: boolean;
246
+ plugin?: T;
247
+ error?: string;
248
+ source: 'builtin' | 'local' | 'npm';
249
+ }
250
+
251
+ // ---------------------------------------------------------------------------
252
+ // Errors
253
+ // ---------------------------------------------------------------------------
254
+
255
+ /**
256
+ * Plugin permission error
257
+ */
258
+ export class PluginPermissionError extends Error {
259
+ constructor(
260
+ public readonly pluginId: string,
261
+ public readonly permission: keyof PluginPermissions,
262
+ message: string
263
+ ) {
264
+ super(`Plugin "${pluginId}" permission denied: ${message}`);
265
+ this.name = 'PluginPermissionError';
266
+ }
267
+ }
@@ -0,0 +1,19 @@
1
+ export * from './base.ts';
2
+ export * from './provider.ts';
3
+ export * from './agent.ts';
4
+ export * from './theme.ts';
5
+ export * from './notification.ts';
6
+
7
+ import type { ProviderPlugin } from './provider.ts';
8
+ import type { AgentPlugin } from './agent.ts';
9
+ import type { ThemePlugin } from './theme.ts';
10
+ import type { NotificationPlugin } from './notification.ts';
11
+
12
+ export type AnyPlugin = ProviderPlugin | AgentPlugin | ThemePlugin | NotificationPlugin;
13
+
14
+ export type PluginByType = {
15
+ provider: ProviderPlugin;
16
+ agent: AgentPlugin;
17
+ theme: ThemePlugin;
18
+ notification: NotificationPlugin;
19
+ };
@@ -0,0 +1,51 @@
1
+ import type { BasePlugin, PluginLogger } from './base.ts';
2
+
3
+ // ---------------------------------------------------------------------------
4
+ // Notification Events
5
+ // ---------------------------------------------------------------------------
6
+
7
+ export type NotificationSeverity = 'info' | 'warning' | 'critical';
8
+
9
+ export type NotificationEventType =
10
+ | 'budget.thresholdCrossed'
11
+ | 'budget.limitReached'
12
+ | 'provider.fetchFailed'
13
+ | 'provider.limitReached'
14
+ | 'provider.recovered'
15
+ | 'plugin.crashed'
16
+ | 'plugin.disabled'
17
+ | 'app.started'
18
+ | 'app.updated';
19
+
20
+ export interface NotificationEvent {
21
+ type: NotificationEventType;
22
+ severity: NotificationSeverity;
23
+ title: string;
24
+ message: string;
25
+ timestamp: number;
26
+ data?: Record<string, unknown>;
27
+ }
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // Notification Context
31
+ // ---------------------------------------------------------------------------
32
+
33
+ export interface NotificationContext {
34
+ readonly logger: PluginLogger;
35
+ readonly config: Record<string, unknown>;
36
+ readonly signal: AbortSignal;
37
+ }
38
+
39
+ // ---------------------------------------------------------------------------
40
+ // Notification Plugin
41
+ // ---------------------------------------------------------------------------
42
+
43
+ export interface NotificationPlugin extends BasePlugin {
44
+ readonly type: 'notification';
45
+
46
+ initialize(ctx: NotificationContext): Promise<void>;
47
+ notify(ctx: NotificationContext, event: NotificationEvent): Promise<void>;
48
+ test?(ctx: NotificationContext): Promise<boolean>;
49
+ supports?(event: NotificationEvent): boolean;
50
+ destroy?(): Promise<void>;
51
+ }