@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.
- package/package.json +9 -4
- package/src/agents/aggregator.ts +8 -1
- package/src/agents/types.ts +3 -0
- package/src/cli.ts +2 -2
- package/src/demo/simulator.ts +1 -1
- package/src/plugins/agents/index.ts +2 -0
- package/src/plugins/auth-sources.ts +2 -3
- package/src/plugins/lifecycle.ts +17 -13
- package/src/plugins/loader.test.ts +1 -1
- package/src/plugins/loader.ts +15 -7
- package/src/plugins/notification-bus.ts +5 -2
- package/src/plugins/notifications/terminal-bell.ts +9 -2
- package/src/plugins/notifications/visual-flash.ts +13 -3
- package/src/plugins/plugin-context-factory.ts +4 -19
- package/src/plugins/providers/anthropic.ts +2 -2
- package/src/plugins/providers/antigravity.ts +1 -1
- package/src/plugins/providers/chutes.ts +189 -0
- package/src/plugins/providers/codex.ts +1 -1
- package/src/plugins/providers/gemini.ts +1 -1
- package/src/plugins/providers/github-copilot.ts +1 -1
- package/src/plugins/providers/index.ts +1 -0
- package/src/plugins/providers/minimax.ts +1 -1
- package/src/plugins/providers/openai-api.ts +1 -1
- package/src/plugins/providers/opencode-zen.ts +1 -1
- package/src/plugins/providers/perplexity.ts +1 -1
- package/src/plugins/providers/zai.ts +4 -2
- package/src/plugins/registry.ts +51 -46
- package/src/plugins/sandbox-guard.test.ts +1 -1
- package/src/plugins/sandbox-guard.ts +1 -1
- package/src/plugins/sandbox.ts +1 -1
- package/src/plugins/themes/catppuccin-latte.ts +1 -1
- package/src/plugins/themes/catppuccin-mocha.ts +1 -1
- package/src/plugins/themes/claude-code.ts +1 -1
- package/src/plugins/themes/dracula.ts +1 -1
- package/src/plugins/themes/github-light.ts +1 -1
- package/src/plugins/themes/gruvbox-dark.ts +1 -1
- package/src/plugins/themes/gruvbox-light.ts +1 -1
- package/src/plugins/themes/kanagawa.ts +1 -1
- package/src/plugins/themes/nord.ts +1 -1
- package/src/plugins/themes/one-dark.ts +1 -1
- package/src/plugins/themes/opencode.ts +1 -1
- package/src/plugins/themes/rose-pine-dawn.ts +1 -1
- package/src/plugins/themes/rose-pine.ts +1 -1
- package/src/plugins/themes/solarized-light.ts +1 -1
- package/src/plugins/themes/tokyo-night.ts +1 -1
- package/src/plugins/update-checker.ts +2 -2
- package/src/pricing/estimator.test.ts +1 -1
- package/src/pricing/estimator.ts +1 -1
- package/src/pricing/fallback.ts +1 -1
- package/src/pricing/index.ts +1 -1
- package/src/pricing/models-dev.ts +1 -1
- package/src/storage/index.ts +6 -0
- package/src/storage/migrations/index.ts +17 -0
- package/src/storage/persistence-service.ts +206 -0
- package/src/storage/repos/pluginStorage.ts +35 -0
- package/src/storage/retention.ts +146 -0
- package/src/tui/App.tsx +1 -1
- package/src/tui/components/DebugPanel.tsx +23 -10
- package/src/tui/components/ProviderCard.tsx +1 -1
- package/src/tui/components/ProviderDetailPanel.tsx +1 -1
- package/src/tui/components/SessionDetailsDrawer.tsx +10 -0
- package/src/tui/components/SessionsTable.tsx +15 -4
- package/src/tui/components/SettingsModal.tsx +383 -76
- package/src/tui/components/ThemePicker.tsx +1 -1
- package/src/tui/contexts/AgentSessionContext.tsx +44 -80
- package/src/tui/contexts/PluginContext.tsx +13 -3
- package/src/tui/contexts/RealTimeActivityContext.tsx +33 -20
- package/src/tui/contexts/StorageContext.tsx +42 -130
- package/src/tui/contexts/ThemeContext.tsx +3 -3
- package/src/tui/hooks/useExitAnimation.ts +4 -0
- package/src/tui/hooks/useSessionEnrichedProviders.ts +1 -1
- package/src/tui/views/Dashboard.tsx +40 -3
- package/src/tui/views/ProjectsView.tsx +29 -6
- package/src/tui/views/RealTimeDashboard.tsx +29 -8
- package/src/tui/views/SettingsView.tsx +9 -4
- package/src/version.ts +1 -1
- package/src/plugins/types/agent.ts +0 -108
- package/src/plugins/types/base.ts +0 -278
- package/src/plugins/types/index.ts +0 -19
- package/src/plugins/types/notification.ts +0 -51
- package/src/plugins/types/provider.ts +0 -237
- package/src/plugins/types/theme.ts +0 -78
- 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
|
-
//
|
|
1256
|
-
|
|
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
|
-
|
|
1260
|
+
setSelectedIndex(0);
|
|
1259
1261
|
scrollOffsetRef.current = 0;
|
|
1260
1262
|
scrollboxRef.current?.scrollTo(0);
|
|
1261
|
-
|
|
1262
|
-
|
|
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
|
-
}, [
|
|
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
|
-
|
|
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
|
-
|
|
198
|
-
|
|
200
|
+
setSelectedRow(0);
|
|
201
|
+
setScrollOffset(0);
|
|
202
|
+
selectedSessionIdRef.current = null;
|
|
199
203
|
return;
|
|
200
204
|
}
|
|
201
205
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
}, [
|
|
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 = [
|
|
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,
|
|
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.
|
|
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
|
-
}
|