@tokentop/ttop 0.3.0 → 0.5.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/package.json +9 -4
  2. package/src/agents/aggregator.ts +8 -1
  3. package/src/agents/types.ts +3 -0
  4. package/src/cli.ts +2 -2
  5. package/src/demo/simulator.ts +1 -1
  6. package/src/plugins/agents/index.ts +2 -0
  7. package/src/plugins/auth-sources.ts +2 -3
  8. package/src/plugins/lifecycle.ts +17 -13
  9. package/src/plugins/loader.test.ts +1 -1
  10. package/src/plugins/loader.ts +15 -7
  11. package/src/plugins/notification-bus.ts +5 -2
  12. package/src/plugins/notifications/terminal-bell.ts +9 -2
  13. package/src/plugins/notifications/visual-flash.ts +13 -3
  14. package/src/plugins/plugin-context-factory.ts +4 -19
  15. package/src/plugins/providers/anthropic.ts +2 -2
  16. package/src/plugins/providers/antigravity.ts +1 -1
  17. package/src/plugins/providers/chutes.ts +189 -0
  18. package/src/plugins/providers/codex.ts +1 -1
  19. package/src/plugins/providers/gemini.ts +1 -1
  20. package/src/plugins/providers/github-copilot.ts +1 -1
  21. package/src/plugins/providers/index.ts +1 -0
  22. package/src/plugins/providers/minimax.ts +1 -1
  23. package/src/plugins/providers/openai-api.ts +1 -1
  24. package/src/plugins/providers/opencode-zen.ts +1 -1
  25. package/src/plugins/providers/perplexity.ts +1 -1
  26. package/src/plugins/providers/zai.ts +4 -2
  27. package/src/plugins/registry.ts +51 -46
  28. package/src/plugins/sandbox-guard.test.ts +1 -1
  29. package/src/plugins/sandbox-guard.ts +1 -1
  30. package/src/plugins/sandbox.ts +1 -1
  31. package/src/plugins/themes/catppuccin-latte.ts +1 -1
  32. package/src/plugins/themes/catppuccin-mocha.ts +1 -1
  33. package/src/plugins/themes/claude-code.ts +1 -1
  34. package/src/plugins/themes/dracula.ts +1 -1
  35. package/src/plugins/themes/github-light.ts +1 -1
  36. package/src/plugins/themes/gruvbox-dark.ts +1 -1
  37. package/src/plugins/themes/gruvbox-light.ts +1 -1
  38. package/src/plugins/themes/kanagawa.ts +1 -1
  39. package/src/plugins/themes/nord.ts +1 -1
  40. package/src/plugins/themes/one-dark.ts +1 -1
  41. package/src/plugins/themes/opencode.ts +1 -1
  42. package/src/plugins/themes/rose-pine-dawn.ts +1 -1
  43. package/src/plugins/themes/rose-pine.ts +1 -1
  44. package/src/plugins/themes/solarized-light.ts +1 -1
  45. package/src/plugins/themes/tokyo-night.ts +1 -1
  46. package/src/plugins/update-checker.ts +2 -2
  47. package/src/pricing/estimator.test.ts +1 -1
  48. package/src/pricing/estimator.ts +1 -1
  49. package/src/pricing/fallback.ts +1 -1
  50. package/src/pricing/index.ts +1 -1
  51. package/src/pricing/models-dev.ts +1 -1
  52. package/src/storage/index.ts +6 -0
  53. package/src/storage/migrations/index.ts +17 -0
  54. package/src/storage/persistence-service.ts +206 -0
  55. package/src/storage/repos/pluginStorage.ts +35 -0
  56. package/src/storage/retention.ts +146 -0
  57. package/src/tui/App.tsx +1 -1
  58. package/src/tui/components/DebugPanel.tsx +23 -10
  59. package/src/tui/components/ProviderCard.tsx +1 -1
  60. package/src/tui/components/ProviderDetailPanel.tsx +1 -1
  61. package/src/tui/components/SessionDetailsDrawer.tsx +10 -0
  62. package/src/tui/components/SessionsTable.tsx +15 -4
  63. package/src/tui/components/SettingsModal.tsx +383 -76
  64. package/src/tui/components/ThemePicker.tsx +1 -1
  65. package/src/tui/contexts/AgentSessionContext.tsx +44 -80
  66. package/src/tui/contexts/PluginContext.tsx +13 -3
  67. package/src/tui/contexts/RealTimeActivityContext.tsx +33 -20
  68. package/src/tui/contexts/StorageContext.tsx +42 -130
  69. package/src/tui/contexts/ThemeContext.tsx +3 -3
  70. package/src/tui/hooks/useExitAnimation.ts +4 -0
  71. package/src/tui/hooks/useSessionEnrichedProviders.ts +1 -1
  72. package/src/tui/views/Dashboard.tsx +40 -3
  73. package/src/tui/views/ProjectsView.tsx +29 -6
  74. package/src/tui/views/RealTimeDashboard.tsx +29 -8
  75. package/src/tui/views/SettingsView.tsx +9 -4
  76. package/src/version.ts +1 -1
  77. package/src/plugins/types/agent.ts +0 -108
  78. package/src/plugins/types/base.ts +0 -278
  79. package/src/plugins/types/index.ts +0 -19
  80. package/src/plugins/types/notification.ts +0 -51
  81. package/src/plugins/types/provider.ts +0 -237
  82. package/src/plugins/types/theme.ts +0 -78
  83. package/src/storage/database.ts +0 -623
@@ -1202,6 +1202,7 @@ export function ProjectsView() {
1202
1202
  const [isFiltering, setIsFiltering] = useState(false);
1203
1203
  const scrollOffsetRef = useRef(0);
1204
1204
  const scrollboxRef = useRef<ScrollBoxRenderable>(null);
1205
+ const selectedProjectPathRef = useRef<string | null>(null);
1205
1206
 
1206
1207
  // Aggregate projects
1207
1208
  const projectStats = useMemo(
@@ -1252,16 +1253,38 @@ export function ProjectsView() {
1252
1253
  // Visible columns
1253
1254
  const visibleColumns: ColumnSpec[] = useMemo(() => getVisibleColumns(termWidth), [termWidth]);
1254
1255
 
1255
- // Clamp selection
1256
- useEffect(() => {
1256
+ // Stabilize selection across re-sorts: track selected project by path
1257
+ // so the highlight follows the same project when live data causes reordering.
1258
+ useLayoutEffect(() => {
1257
1259
  if (sortedProjects.length === 0) {
1258
- if (selectedIndex !== 0) setSelectedIndex(0);
1260
+ setSelectedIndex(0);
1259
1261
  scrollOffsetRef.current = 0;
1260
1262
  scrollboxRef.current?.scrollTo(0);
1261
- } else if (selectedIndex >= sortedProjects.length) {
1262
- setSelectedIndex(sortedProjects.length - 1);
1263
+ selectedProjectPathRef.current = null;
1264
+ return;
1265
+ }
1266
+
1267
+ if (selectedProjectPathRef.current) {
1268
+ const newIndex = sortedProjects.findIndex((p) => p.path === selectedProjectPathRef.current);
1269
+ if (newIndex !== -1) {
1270
+ setSelectedIndex(newIndex);
1271
+ return;
1272
+ }
1273
+ // Tracked project was filtered out — fall through to clamp
1274
+ selectedProjectPathRef.current = null;
1275
+ }
1276
+
1277
+ // Clamp to valid range (functional update avoids selectedIndex in deps)
1278
+ setSelectedIndex((prev) => Math.min(prev, sortedProjects.length - 1));
1279
+ }, [sortedProjects]);
1280
+
1281
+ // Record which project the user selected
1282
+ useEffect(() => {
1283
+ const project = sortedProjects[selectedIndex];
1284
+ if (project) {
1285
+ selectedProjectPathRef.current = project.path;
1263
1286
  }
1264
- }, [sortedProjects.length, selectedIndex]);
1287
+ }, [selectedIndex, sortedProjects]);
1265
1288
 
1266
1289
  // Scroll to keep selected row visible
1267
1290
  const visibleRows = useMemo(() => {
@@ -64,6 +64,7 @@ export function RealTimeDashboard() {
64
64
  const [sortField, setSortField] = useState<"cost" | "tokens" | "time">("cost");
65
65
  const [pendingG, setPendingG] = useState(false);
66
66
  const scrollOffsetRef = useRef(0);
67
+ const selectedSessionIdRef = useRef<string | null>(null);
67
68
  const setScrollOffset = useCallback((val: number) => {
68
69
  scrollOffsetRef.current = val;
69
70
  sessionsScrollboxRef.current?.scrollTo(val);
@@ -192,19 +193,39 @@ export function RealTimeDashboard() {
192
193
  return result;
193
194
  }, [baseFilteredSessions, activeDriverFilter, driverDimension, sortField]);
194
195
 
195
- useEffect(() => {
196
+ // Stabilize selection across re-sorts: track selected session by ID
197
+ // so the highlight follows the same session when live data causes reordering.
198
+ useLayoutEffect(() => {
196
199
  if (processedSessions.length === 0) {
197
- if (selectedRow !== 0) setSelectedRow(0);
198
- if (scrollOffsetRef.current !== 0) setScrollOffset(0);
200
+ setSelectedRow(0);
201
+ setScrollOffset(0);
202
+ selectedSessionIdRef.current = null;
199
203
  return;
200
204
  }
201
205
 
202
- const maxRow = processedSessions.length - 1;
203
- if (selectedRow > maxRow) {
204
- setSelectedRow(maxRow);
205
- return;
206
+ if (selectedSessionIdRef.current) {
207
+ const newIndex = processedSessions.findIndex(
208
+ (s) => s.sessionId === selectedSessionIdRef.current,
209
+ );
210
+ if (newIndex !== -1) {
211
+ setSelectedRow(newIndex);
212
+ return;
213
+ }
214
+ // Tracked session was filtered out — fall through to clamp
215
+ selectedSessionIdRef.current = null;
216
+ }
217
+
218
+ // Clamp to valid range (functional update avoids selectedRow in deps)
219
+ setSelectedRow((prev) => Math.min(prev, processedSessions.length - 1));
220
+ }, [processedSessions, setScrollOffset]);
221
+
222
+ // Record which session the user selected (runs after navigation and stabilization)
223
+ useEffect(() => {
224
+ const session = processedSessions[selectedRow];
225
+ if (session) {
226
+ selectedSessionIdRef.current = session.sessionId;
206
227
  }
207
- }, [processedSessions.length, selectedRow, setScrollOffset]);
228
+ }, [selectedRow, processedSessions]);
208
229
 
209
230
  useLayoutEffect(() => {
210
231
  if (!sessionsScrollboxRef.current || processedSessions.length === 0) return;
@@ -1,7 +1,7 @@
1
1
  import { useKeyboard, useTerminalDimensions } from "@opentui/react";
2
+ import type { ConfigField } from "@tokentop/plugin-sdk";
2
3
  import { useCallback, useMemo, useState } from "react";
3
4
  import type { AppConfig } from "@/config/schema.ts";
4
- import type { ConfigField } from "@/plugins/types/base.ts";
5
5
  import { useConfig } from "../contexts/ConfigContext.tsx";
6
6
  import { useDemoMode } from "../contexts/DemoModeContext.tsx";
7
7
  import { usePlugins } from "../contexts/PluginContext.tsx";
@@ -284,7 +284,7 @@ export function SettingsView() {
284
284
  const colors = useColors();
285
285
  const { showToast } = useToastContext();
286
286
  const { config, isLoading, updateConfig, resetToDefaults, saveNow } = useConfig();
287
- const { providers, notifications } = usePlugins();
287
+ const { providers, agents, themes, notifications } = usePlugins();
288
288
  const { demoMode, seed, preset } = useDemoMode();
289
289
  const { width: terminalWidth } = useTerminalDimensions();
290
290
 
@@ -293,9 +293,14 @@ export function SettingsView() {
293
293
  const [focusedPane, setFocusedPane] = useState<"categories" | "settings">("settings");
294
294
 
295
295
  const pluginSettings = useMemo(() => {
296
- const allPlugins = [...Array.from(providers.values()).map((p) => p.plugin), ...notifications];
296
+ const allPlugins = [
297
+ ...Array.from(providers.values()).map((p) => p.plugin),
298
+ ...agents,
299
+ ...themes,
300
+ ...notifications,
301
+ ];
297
302
  return buildPluginSettings(allPlugins);
298
- }, [providers, notifications, config]);
303
+ }, [providers, agents, themes, notifications]);
299
304
 
300
305
  const allSettings = useMemo(() => [...SETTINGS, ...pluginSettings], [pluginSettings]);
301
306
  const categorySettings = allSettings.filter((s) => s.category === selectedCategory);
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const VERSION = "0.3.0"; // x-release-please-version
1
+ export const VERSION = "0.5.0"; // x-release-please-version
@@ -1,108 +0,0 @@
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
- }
@@ -1,278 +0,0 @@
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
34
- .object({
35
- enabled: z.boolean(),
36
- allowedDomains: z.array(z.string()).optional(),
37
- })
38
- .optional(),
39
- filesystem: z
40
- .object({
41
- read: z.boolean().optional(),
42
- write: z.boolean().optional(),
43
- paths: z.array(z.string()).optional(),
44
- })
45
- .optional(),
46
- env: z
47
- .object({
48
- read: z.boolean().optional(),
49
- vars: z.array(z.string()).optional(),
50
- })
51
- .optional(),
52
- system: z
53
- .object({
54
- notifications: z.boolean().optional(),
55
- clipboard: z.boolean().optional(),
56
- })
57
- .optional(),
58
- });
59
-
60
- export type PluginPermissions = z.infer<typeof PluginPermissionsSchema>;
61
-
62
- // ---------------------------------------------------------------------------
63
- // Metadata
64
- // ---------------------------------------------------------------------------
65
-
66
- /**
67
- * Plugin metadata
68
- */
69
- export const PluginMetaSchema = z.object({
70
- author: z.string().optional(),
71
- description: z.string().optional(),
72
- homepage: z.string().url().optional(),
73
- repository: z.string().url().optional(),
74
- license: z.string().optional(),
75
- /**
76
- * Brand color as a hex string (e.g. `"#d97757"`).
77
- * Used by the TUI for provider cards, charts, and status indicators.
78
- */
79
- brandColor: z.string().optional(),
80
- /**
81
- * Single-character icon or short glyph for compact displays.
82
- * Example: `"◆"`, `"▲"`, `"⚡"`
83
- */
84
- icon: z.string().optional(),
85
- /**
86
- * Additional provider IDs that should resolve to this plugin.
87
- * Coding agents may tag sessions with provider IDs that differ from
88
- * the plugin's `id` (e.g. OpenCode uses `"openai"` but the plugin
89
- * registers as `"openai-api"`). List those alternate IDs here so the
90
- * TUI can resolve brand colors and other metadata correctly.
91
- */
92
- providerAliases: z.array(z.string()).optional(),
93
- });
94
-
95
- export type PluginMeta = z.infer<typeof PluginMetaSchema>;
96
-
97
- // ---------------------------------------------------------------------------
98
- // Configuration
99
- // ---------------------------------------------------------------------------
100
-
101
- /**
102
- * Plugin configuration field definition (for plugin settings UI)
103
- */
104
- export interface ConfigField {
105
- /** Data type of the setting value. */
106
- type: "string" | "number" | "boolean" | "select";
107
- /** Label shown in the settings UI. */
108
- label?: string;
109
- /** Help text shown below the field. */
110
- description?: string;
111
- /** Whether the field must have a value. */
112
- required?: boolean;
113
- /** Default value when no user config exists. */
114
- default?: unknown;
115
- /** Available options (for `select` type only). */
116
- options?: Array<{ value: string; label: string }>;
117
- /** Minimum value (for `number` type only). */
118
- min?: number;
119
- /** Maximum value (for `number` type only). */
120
- max?: number;
121
- }
122
-
123
- // ---------------------------------------------------------------------------
124
- // Lifecycle Context
125
- // ---------------------------------------------------------------------------
126
-
127
- /**
128
- * Minimal context provided to lifecycle hooks.
129
- */
130
- export interface PluginLifecycleContext {
131
- /** Plugin's validated configuration values. */
132
- readonly config: Record<string, unknown>;
133
- /** Scoped logger that prefixes all output with the plugin ID. */
134
- readonly logger: PluginLogger;
135
- }
136
-
137
- // ---------------------------------------------------------------------------
138
- // Base Plugin
139
- // ---------------------------------------------------------------------------
140
-
141
- /**
142
- * Base plugin interface - all plugins must implement this.
143
- *
144
- * Provides identity, metadata, permissions, optional configuration schema,
145
- * and lifecycle hooks.
146
- */
147
- export interface BasePlugin {
148
- /** API version this plugin targets. Must equal {@link CURRENT_API_VERSION}. */
149
- readonly apiVersion: 2;
150
-
151
- /** Unique plugin identifier (kebab-case) */
152
- readonly id: string;
153
-
154
- /** Plugin type discriminator */
155
- readonly type: PluginType;
156
-
157
- /** Human-readable display name */
158
- readonly name: string;
159
-
160
- /** Semantic version string */
161
- readonly version: string;
162
-
163
- /** Plugin metadata */
164
- readonly meta?: PluginMeta;
165
-
166
- /** Required permissions */
167
- readonly permissions: PluginPermissions;
168
-
169
- /** Plugin-declared configuration fields, rendered in Settings UI */
170
- readonly configSchema?: Record<string, ConfigField>;
171
-
172
- /**
173
- * Default config values. Used when no user configuration exists.
174
- * Keys must match those in {@link configSchema}.
175
- */
176
- readonly defaultConfig?: Record<string, unknown>;
177
-
178
- // -- Lifecycle Hooks (all optional) -------------------------------------
179
-
180
- /**
181
- * Called once after the plugin is loaded and validated.
182
- * Use for one-time setup (open connections, allocate resources).
183
- */
184
- initialize?(ctx: PluginLifecycleContext): Promise<void>;
185
-
186
- /**
187
- * Called when the plugin should begin active work (e.g. polling).
188
- * Called after `initialize()` during app startup, and after re-enable.
189
- */
190
- start?(ctx: PluginLifecycleContext): Promise<void>;
191
-
192
- /**
193
- * Called when the plugin should pause active work.
194
- * Called before `destroy()` during app shutdown, and on disable.
195
- */
196
- stop?(ctx: PluginLifecycleContext): Promise<void>;
197
-
198
- /**
199
- * Called once before the plugin is unloaded.
200
- * Use for cleanup (close connections, flush buffers).
201
- */
202
- destroy?(ctx: PluginLifecycleContext): Promise<void>;
203
-
204
- /**
205
- * Called when the user changes this plugin's configuration.
206
- * Receive the new validated config values.
207
- */
208
- onConfigChange?(
209
- config: Record<string, unknown>,
210
- ctx: PluginLifecycleContext,
211
- ): Promise<void> | void;
212
- }
213
-
214
- // ---------------------------------------------------------------------------
215
- // Logger
216
- // ---------------------------------------------------------------------------
217
-
218
- /**
219
- * Logger interface provided to plugins
220
- */
221
- export interface PluginLogger {
222
- debug(message: string, data?: Record<string, unknown>): void;
223
- info(message: string, data?: Record<string, unknown>): void;
224
- warn(message: string, data?: Record<string, unknown>): void;
225
- error(message: string, data?: Record<string, unknown>): void;
226
- }
227
-
228
- // ---------------------------------------------------------------------------
229
- // HTTP Client
230
- // ---------------------------------------------------------------------------
231
-
232
- /**
233
- * HTTP client interface provided to plugins (sandboxed)
234
- */
235
- export interface PluginHttpClient {
236
- fetch(url: string, init?: RequestInit): Promise<Response>;
237
- }
238
-
239
- // ---------------------------------------------------------------------------
240
- // Validation
241
- // ---------------------------------------------------------------------------
242
-
243
- /**
244
- * Plugin validation result
245
- */
246
- export interface PluginValidationResult {
247
- valid: boolean;
248
- errors: string[];
249
- warnings: string[];
250
- }
251
-
252
- /**
253
- * Plugin load result
254
- */
255
- export interface PluginLoadResult<T extends BasePlugin = BasePlugin> {
256
- success: boolean;
257
- plugin?: T;
258
- error?: string;
259
- source: "builtin" | "local" | "npm";
260
- }
261
-
262
- // ---------------------------------------------------------------------------
263
- // Errors
264
- // ---------------------------------------------------------------------------
265
-
266
- /**
267
- * Plugin permission error
268
- */
269
- export class PluginPermissionError extends Error {
270
- constructor(
271
- public readonly pluginId: string,
272
- public readonly permission: keyof PluginPermissions,
273
- message: string,
274
- ) {
275
- super(`Plugin "${pluginId}" permission denied: ${message}`);
276
- this.name = "PluginPermissionError";
277
- }
278
- }
@@ -1,19 +0,0 @@
1
- export * from "./agent.ts";
2
- export * from "./base.ts";
3
- export * from "./notification.ts";
4
- export * from "./provider.ts";
5
- export * from "./theme.ts";
6
-
7
- import type { AgentPlugin } from "./agent.ts";
8
- import type { NotificationPlugin } from "./notification.ts";
9
- import type { ProviderPlugin } from "./provider.ts";
10
- import type { ThemePlugin } from "./theme.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
- };
@@ -1,51 +0,0 @@
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
- }