@tokentop/ttop 0.2.2 → 0.3.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/README.md +2 -5
- package/package.json +8 -5
- package/src/agents/aggregator.ts +39 -19
- package/src/agents/costing.ts +11 -9
- package/src/agents/index.ts +7 -9
- package/src/agents/types.ts +2 -2
- package/src/cli.ts +28 -23
- package/src/config/schema.test.ts +42 -40
- package/src/config/schema.ts +26 -26
- package/src/demo/simulator.ts +161 -122
- package/src/plugins/agents/index.ts +2 -2
- package/src/plugins/auth-sources.ts +42 -38
- package/src/plugins/lifecycle.ts +32 -32
- package/src/plugins/loader.test.ts +49 -49
- package/src/plugins/loader.ts +67 -63
- package/src/plugins/notification-bus.ts +21 -16
- package/src/plugins/notifications/index.ts +2 -2
- package/src/plugins/notifications/terminal-bell.ts +29 -23
- package/src/plugins/notifications/visual-flash.ts +21 -19
- package/src/plugins/npm-installer.ts +23 -25
- package/src/plugins/plugin-context-factory.ts +10 -10
- package/src/plugins/plugin-host.ts +12 -5
- package/src/plugins/providers/anthropic.ts +64 -57
- package/src/plugins/providers/antigravity.ts +81 -69
- package/src/plugins/providers/codex.ts +42 -42
- package/src/plugins/providers/gemini.ts +106 -87
- package/src/plugins/providers/github-copilot.ts +49 -45
- package/src/plugins/providers/index.ts +10 -12
- package/src/plugins/providers/minimax.ts +50 -44
- package/src/plugins/providers/openai-api.ts +82 -62
- package/src/plugins/providers/opencode-zen.ts +46 -39
- package/src/plugins/providers/perplexity.ts +55 -46
- package/src/plugins/providers/zai.ts +66 -57
- package/src/plugins/registry.ts +47 -43
- package/src/plugins/sandbox-guard.test.ts +45 -48
- package/src/plugins/sandbox-guard.ts +20 -25
- package/src/plugins/sandbox.ts +26 -48
- package/src/plugins/themes/catppuccin-latte.ts +29 -29
- package/src/plugins/themes/catppuccin-mocha.ts +29 -29
- package/src/plugins/themes/claude-code.ts +29 -29
- package/src/plugins/themes/dracula.ts +29 -29
- package/src/plugins/themes/github-light.ts +29 -29
- package/src/plugins/themes/gruvbox-dark.ts +29 -29
- package/src/plugins/themes/gruvbox-light.ts +29 -29
- package/src/plugins/themes/index.ts +15 -15
- package/src/plugins/themes/kanagawa.ts +29 -29
- package/src/plugins/themes/nord.ts +29 -29
- package/src/plugins/themes/one-dark.ts +29 -29
- package/src/plugins/themes/opencode.ts +31 -31
- package/src/plugins/themes/rose-pine-dawn.ts +29 -29
- package/src/plugins/themes/rose-pine.ts +29 -29
- package/src/plugins/themes/solarized-light.ts +29 -29
- package/src/plugins/themes/tokyo-night.ts +29 -29
- package/src/plugins/types/agent.ts +5 -5
- package/src/plugins/types/base.ts +35 -24
- package/src/plugins/types/index.ts +9 -9
- package/src/plugins/types/notification.ts +12 -12
- package/src/plugins/types/provider.ts +12 -8
- package/src/plugins/types/theme.ts +31 -23
- package/src/plugins/update-checker.ts +6 -6
- package/src/pricing/estimator.test.ts +39 -44
- package/src/pricing/estimator.ts +7 -13
- package/src/pricing/fallback.test.ts +30 -34
- package/src/pricing/fallback.ts +54 -27
- package/src/pricing/index.ts +18 -12
- package/src/pricing/models-dev.ts +6 -6
- package/src/storage/database.ts +56 -46
- package/src/storage/db.ts +12 -12
- package/src/storage/index.ts +44 -45
- package/src/storage/migrations/index.ts +2 -2
- package/src/storage/paths.ts +13 -13
- package/src/storage/repos/agentSessions.ts +33 -23
- package/src/storage/repos/providerSnapshots.ts +5 -5
- package/src/storage/repos/usageEvents.ts +40 -28
- package/src/storage/types.ts +5 -5
- package/src/tui/App.tsx +180 -137
- package/src/tui/components/CommandPalette.tsx +46 -34
- package/src/tui/components/DebugConsole.tsx +39 -33
- package/src/tui/components/DebugPanel.tsx +295 -177
- package/src/tui/components/FooterHints.tsx +2 -2
- package/src/tui/components/GhostProviderCard.tsx +37 -35
- package/src/tui/components/Header.tsx +68 -35
- package/src/tui/components/HelpOverlay.tsx +99 -29
- package/src/tui/components/InlineGauge.tsx +10 -7
- package/src/tui/components/InlineSparkline.tsx +17 -11
- package/src/tui/components/KpiStrip.tsx +128 -98
- package/src/tui/components/LimitGauge.tsx +96 -59
- package/src/tui/components/ModalBackdrop.tsx +4 -4
- package/src/tui/components/ProviderAggregateStrip.tsx +69 -38
- package/src/tui/components/ProviderCard.tsx +232 -226
- package/src/tui/components/ProviderDetailPanel.tsx +133 -58
- package/src/tui/components/ProviderLimitsPanel.tsx +126 -89
- package/src/tui/components/ProvidersList.tsx +215 -126
- package/src/tui/components/SessionDetailsDrawer.tsx +191 -94
- package/src/tui/components/SessionsTable.tsx +313 -166
- package/src/tui/components/SettingsModal.tsx +231 -214
- package/src/tui/components/SidebarBreakdown.tsx +30 -16
- package/src/tui/components/Skeleton.tsx +24 -17
- package/src/tui/components/SmartSidebar.tsx +360 -258
- package/src/tui/components/Sparkline.tsx +64 -52
- package/src/tui/components/Spinner.tsx +3 -3
- package/src/tui/components/StatusBar.tsx +27 -27
- package/src/tui/components/ThemePicker.tsx +67 -66
- package/src/tui/components/Toast.tsx +9 -6
- package/src/tui/components/UsageGauge.tsx +10 -13
- package/src/tui/components/index.ts +5 -5
- package/src/tui/contexts/AgentSessionContext.tsx +193 -144
- package/src/tui/contexts/ConfigContext.tsx +15 -13
- package/src/tui/contexts/DashboardRuntimeContext.tsx +26 -26
- package/src/tui/contexts/DemoModeContext.tsx +22 -15
- package/src/tui/contexts/DrawerContext.tsx +11 -9
- package/src/tui/contexts/InputContext.tsx +2 -2
- package/src/tui/contexts/LogContext.tsx +62 -47
- package/src/tui/contexts/PluginContext.tsx +232 -183
- package/src/tui/contexts/RealTimeActivityContext.tsx +70 -36
- package/src/tui/contexts/StorageContext.tsx +151 -117
- package/src/tui/contexts/ThemeContext.tsx +21 -16
- package/src/tui/contexts/TimeWindowContext.tsx +72 -61
- package/src/tui/contexts/ToastContext.tsx +10 -7
- package/src/tui/contexts/index.ts +9 -5
- package/src/tui/createApp.tsx +8 -12
- package/src/tui/debug/captureFrame.ts +25 -28
- package/src/tui/debug/snapshot.tsx +179 -162
- package/src/tui/driver/assertions.ts +30 -22
- package/src/tui/driver/cli.ts +231 -221
- package/src/tui/driver/coverage.ts +27 -44
- package/src/tui/driver/demo-snapshot.ts +33 -33
- package/src/tui/driver/diff.ts +26 -32
- package/src/tui/driver/driver.ts +43 -48
- package/src/tui/driver/index.ts +36 -40
- package/src/tui/driver/recorder.ts +37 -48
- package/src/tui/driver/test-workflow.ts +51 -51
- package/src/tui/driver/test.ts +25 -25
- package/src/tui/hooks/index.ts +24 -20
- package/src/tui/hooks/useAnimatedValue.ts +13 -18
- package/src/tui/hooks/useDashboardKeyboard.ts +118 -98
- package/src/tui/hooks/useDashboardState.ts +26 -24
- package/src/tui/hooks/useEmaActivity.ts +82 -74
- package/src/tui/hooks/useEntranceAnimation.ts +11 -11
- package/src/tui/hooks/useExitAnimation.ts +3 -3
- package/src/tui/hooks/usePulse.ts +4 -4
- package/src/tui/hooks/useSafeRenderer.ts +5 -5
- package/src/tui/hooks/useSessionEnrichedProviders.ts +150 -0
- package/src/tui/hooks/useValueFlash.ts +10 -14
- package/src/tui/index.tsx +29 -6
- package/src/tui/shutdown.ts +19 -0
- package/src/tui/utils/providerColor.ts +1 -1
- package/src/tui/views/Dashboard.tsx +127 -102
- package/src/tui/views/HistoricalTrendsView.tsx +579 -250
- package/src/tui/views/ProjectsView.tsx +466 -237
- package/src/tui/views/RealTimeDashboard.tsx +150 -111
- package/src/tui/views/SettingsView.tsx +222 -191
- package/src/tui/views/index.ts +2 -2
- package/src/types/opentui-test-utils.d.ts +7 -7
- package/src/utils/clipboard.ts +29 -29
- package/src/utils/currency.test.ts +53 -53
- package/src/utils/currency.ts +35 -32
- package/src/version.ts +1 -1
|
@@ -1,37 +1,34 @@
|
|
|
1
|
-
import { afterAll, beforeAll, describe, expect, test } from
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
2
2
|
import {
|
|
3
3
|
deepFreeze,
|
|
4
4
|
getActivePluginGuard,
|
|
5
5
|
installGlobalFetchGuard,
|
|
6
6
|
runInPluginGuard,
|
|
7
|
-
} from
|
|
8
|
-
import { PluginPermissionError } from
|
|
7
|
+
} from "./sandbox-guard.ts";
|
|
8
|
+
import { PluginPermissionError } from "./types/base.ts";
|
|
9
9
|
|
|
10
10
|
const originalFetch = globalThis.fetch;
|
|
11
11
|
const fetchCalls: string[] = [];
|
|
12
12
|
|
|
13
13
|
const mockFetch: typeof fetch = Object.assign(
|
|
14
14
|
(input: string | URL | Request, _init?: RequestInit): Promise<Response> => {
|
|
15
|
-
const url =
|
|
16
|
-
? input
|
|
17
|
-
: input instanceof URL
|
|
18
|
-
? input.toString()
|
|
19
|
-
: input.url;
|
|
15
|
+
const url =
|
|
16
|
+
typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
|
20
17
|
fetchCalls.push(url);
|
|
21
|
-
return Promise.resolve(new Response(
|
|
18
|
+
return Promise.resolve(new Response("ok", { status: 200 }));
|
|
22
19
|
},
|
|
23
20
|
{ preconnect: originalFetch.preconnect },
|
|
24
21
|
);
|
|
25
22
|
|
|
26
|
-
describe(
|
|
27
|
-
test(
|
|
23
|
+
describe("deepFreeze", () => {
|
|
24
|
+
test("freezes a plain object", () => {
|
|
28
25
|
const obj = { a: 1 };
|
|
29
26
|
deepFreeze(obj);
|
|
30
27
|
|
|
31
28
|
expect(Object.isFrozen(obj)).toBe(true);
|
|
32
29
|
});
|
|
33
30
|
|
|
34
|
-
test(
|
|
31
|
+
test("freezes nested objects recursively", () => {
|
|
35
32
|
const obj = {
|
|
36
33
|
top: {
|
|
37
34
|
child: {
|
|
@@ -47,19 +44,19 @@ describe('deepFreeze', () => {
|
|
|
47
44
|
expect(Object.isFrozen(obj.top.child)).toBe(true);
|
|
48
45
|
});
|
|
49
46
|
|
|
50
|
-
test(
|
|
47
|
+
test("returns the same reference", () => {
|
|
51
48
|
const obj = { nested: { value: 1 } };
|
|
52
49
|
const frozen = deepFreeze(obj);
|
|
53
50
|
|
|
54
51
|
expect(frozen).toBe(obj);
|
|
55
52
|
});
|
|
56
53
|
|
|
57
|
-
test(
|
|
54
|
+
test("handles null and undefined gracefully", () => {
|
|
58
55
|
expect(deepFreeze(null)).toBeNull();
|
|
59
56
|
expect(deepFreeze(undefined)).toBeUndefined();
|
|
60
57
|
});
|
|
61
58
|
|
|
62
|
-
test(
|
|
59
|
+
test("handles circular references without infinite loop", () => {
|
|
63
60
|
const obj: { self?: unknown; nested: { parent?: unknown } } = {
|
|
64
61
|
nested: {},
|
|
65
62
|
};
|
|
@@ -73,7 +70,7 @@ describe('deepFreeze', () => {
|
|
|
73
70
|
expect(Object.isFrozen(obj.nested)).toBe(true);
|
|
74
71
|
});
|
|
75
72
|
|
|
76
|
-
test(
|
|
73
|
+
test("handles already-frozen objects", () => {
|
|
77
74
|
const obj = Object.freeze({ already: true });
|
|
78
75
|
|
|
79
76
|
const frozen = deepFreeze(obj);
|
|
@@ -83,44 +80,44 @@ describe('deepFreeze', () => {
|
|
|
83
80
|
});
|
|
84
81
|
});
|
|
85
82
|
|
|
86
|
-
describe(
|
|
87
|
-
test(
|
|
83
|
+
describe("runInPluginGuard and getActivePluginGuard", () => {
|
|
84
|
+
test("returns undefined outside guard", () => {
|
|
88
85
|
expect(getActivePluginGuard()).toBeUndefined();
|
|
89
86
|
});
|
|
90
87
|
|
|
91
|
-
test(
|
|
92
|
-
const context = runInPluginGuard(
|
|
88
|
+
test("returns plugin context inside guard", () => {
|
|
89
|
+
const context = runInPluginGuard("plugin-a", {}, () => getActivePluginGuard());
|
|
93
90
|
|
|
94
91
|
expect(context).toBeDefined();
|
|
95
|
-
expect(context?.pluginId).toBe(
|
|
92
|
+
expect(context?.pluginId).toBe("plugin-a");
|
|
96
93
|
});
|
|
97
94
|
|
|
98
|
-
test(
|
|
99
|
-
const context = await runInPluginGuard(
|
|
95
|
+
test("preserves context across async/await", async () => {
|
|
96
|
+
const context = await runInPluginGuard("plugin-async", {}, async () => {
|
|
100
97
|
await Promise.resolve();
|
|
101
98
|
return getActivePluginGuard();
|
|
102
99
|
});
|
|
103
100
|
|
|
104
101
|
expect(context).toBeDefined();
|
|
105
|
-
expect(context?.pluginId).toBe(
|
|
102
|
+
expect(context?.pluginId).toBe("plugin-async");
|
|
106
103
|
});
|
|
107
104
|
|
|
108
|
-
test(
|
|
109
|
-
const contexts = runInPluginGuard(
|
|
105
|
+
test("nested guards use the innermost context", () => {
|
|
106
|
+
const contexts = runInPluginGuard("outer-plugin", {}, () => {
|
|
110
107
|
const outerBefore = getActivePluginGuard();
|
|
111
|
-
const inner = runInPluginGuard(
|
|
108
|
+
const inner = runInPluginGuard("inner-plugin", {}, () => getActivePluginGuard());
|
|
112
109
|
const outerAfter = getActivePluginGuard();
|
|
113
110
|
|
|
114
111
|
return { outerBefore, inner, outerAfter };
|
|
115
112
|
});
|
|
116
113
|
|
|
117
|
-
expect(contexts.outerBefore?.pluginId).toBe(
|
|
118
|
-
expect(contexts.inner?.pluginId).toBe(
|
|
119
|
-
expect(contexts.outerAfter?.pluginId).toBe(
|
|
114
|
+
expect(contexts.outerBefore?.pluginId).toBe("outer-plugin");
|
|
115
|
+
expect(contexts.inner?.pluginId).toBe("inner-plugin");
|
|
116
|
+
expect(contexts.outerAfter?.pluginId).toBe("outer-plugin");
|
|
120
117
|
});
|
|
121
118
|
});
|
|
122
119
|
|
|
123
|
-
describe(
|
|
120
|
+
describe("installGlobalFetchGuard", () => {
|
|
124
121
|
beforeAll(() => {
|
|
125
122
|
globalThis.fetch = mockFetch;
|
|
126
123
|
installGlobalFetchGuard();
|
|
@@ -130,62 +127,62 @@ describe('installGlobalFetchGuard', () => {
|
|
|
130
127
|
globalThis.fetch = originalFetch;
|
|
131
128
|
});
|
|
132
129
|
|
|
133
|
-
test(
|
|
130
|
+
test("outside guard, fetch calls pass through", async () => {
|
|
134
131
|
fetchCalls.length = 0;
|
|
135
132
|
|
|
136
|
-
const response = await fetch(
|
|
133
|
+
const response = await fetch("https://outside-guard.example");
|
|
137
134
|
|
|
138
135
|
expect(response.status).toBe(200);
|
|
139
136
|
expect(fetchCalls).toHaveLength(1);
|
|
140
|
-
expect(fetchCalls[0]).toContain(
|
|
137
|
+
expect(fetchCalls[0]).toContain("outside-guard.example");
|
|
141
138
|
});
|
|
142
139
|
|
|
143
|
-
test(
|
|
140
|
+
test("inside guard with no network permission throws PluginPermissionError", () => {
|
|
144
141
|
expect(() => {
|
|
145
|
-
runInPluginGuard(
|
|
146
|
-
void fetch(
|
|
142
|
+
runInPluginGuard("plugin-no-network", {}, () => {
|
|
143
|
+
void fetch("https://blocked.example");
|
|
147
144
|
});
|
|
148
145
|
}).toThrow(PluginPermissionError);
|
|
149
146
|
});
|
|
150
147
|
|
|
151
|
-
test(
|
|
148
|
+
test("inside guard with allowedDomains blocks unlisted domains", () => {
|
|
152
149
|
expect(() => {
|
|
153
150
|
runInPluginGuard(
|
|
154
|
-
|
|
151
|
+
"plugin-allowlist",
|
|
155
152
|
{
|
|
156
153
|
network: {
|
|
157
154
|
enabled: true,
|
|
158
|
-
allowedDomains: [
|
|
155
|
+
allowedDomains: ["allowed.example"],
|
|
159
156
|
},
|
|
160
157
|
},
|
|
161
158
|
() => {
|
|
162
|
-
void fetch(
|
|
159
|
+
void fetch("https://not-allowed.example/path");
|
|
163
160
|
},
|
|
164
161
|
);
|
|
165
162
|
}).toThrow(PluginPermissionError);
|
|
166
163
|
});
|
|
167
164
|
|
|
168
|
-
test(
|
|
165
|
+
test("inside guard with allowedDomains allows listed domains and subdomains", async () => {
|
|
169
166
|
fetchCalls.length = 0;
|
|
170
167
|
|
|
171
168
|
const response = await runInPluginGuard(
|
|
172
|
-
|
|
169
|
+
"plugin-allowlist",
|
|
173
170
|
{
|
|
174
171
|
network: {
|
|
175
172
|
enabled: true,
|
|
176
|
-
allowedDomains: [
|
|
173
|
+
allowedDomains: ["allowed.example"],
|
|
177
174
|
},
|
|
178
175
|
},
|
|
179
176
|
async () => {
|
|
180
|
-
const rootResponse = await fetch(
|
|
181
|
-
await fetch(
|
|
177
|
+
const rootResponse = await fetch("https://allowed.example/path");
|
|
178
|
+
await fetch("https://api.allowed.example/path");
|
|
182
179
|
return rootResponse;
|
|
183
180
|
},
|
|
184
181
|
);
|
|
185
182
|
|
|
186
183
|
expect(response.status).toBe(200);
|
|
187
184
|
expect(fetchCalls).toHaveLength(2);
|
|
188
|
-
expect(fetchCalls[0]).toContain(
|
|
189
|
-
expect(fetchCalls[1]).toContain(
|
|
185
|
+
expect(fetchCalls[0]).toContain("allowed.example/path");
|
|
186
|
+
expect(fetchCalls[1]).toContain("api.allowed.example/path");
|
|
190
187
|
});
|
|
191
188
|
});
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
* 2. **Deep-freeze** — prevents plugins from mutating the PluginContext.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import { AsyncLocalStorage } from
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
14
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
15
|
+
import { createPluginLogger } from "./sandbox.ts";
|
|
16
|
+
import { PluginPermissionError, type PluginPermissions } from "./types/base.ts";
|
|
17
17
|
|
|
18
18
|
// ---------------------------------------------------------------------------
|
|
19
19
|
// Plugin execution context (AsyncLocalStorage)
|
|
@@ -68,10 +68,7 @@ export function installGlobalFetchGuard(): void {
|
|
|
68
68
|
const originalFetch = globalThis.fetch;
|
|
69
69
|
|
|
70
70
|
const guardedFetch: typeof fetch = Object.assign(
|
|
71
|
-
function guardedFetch(
|
|
72
|
-
input: string | URL | Request,
|
|
73
|
-
init?: RequestInit,
|
|
74
|
-
): Promise<Response> {
|
|
71
|
+
function guardedFetch(input: string | URL | Request, init?: RequestInit): Promise<Response> {
|
|
75
72
|
const guard = pluginGuardStorage.getStore();
|
|
76
73
|
|
|
77
74
|
if (!guard) {
|
|
@@ -82,34 +79,32 @@ export function installGlobalFetchGuard(): void {
|
|
|
82
79
|
|
|
83
80
|
if (!permissions.network?.enabled) {
|
|
84
81
|
const log = createPluginLogger(pluginId);
|
|
85
|
-
log.error(
|
|
86
|
-
throw new PluginPermissionError(
|
|
87
|
-
pluginId,
|
|
88
|
-
'network',
|
|
89
|
-
'Network access not permitted',
|
|
90
|
-
);
|
|
82
|
+
log.error("Network access blocked — plugin has not declared network permissions");
|
|
83
|
+
throw new PluginPermissionError(pluginId, "network", "Network access not permitted");
|
|
91
84
|
}
|
|
92
85
|
|
|
93
86
|
const allowedDomains = permissions.network.allowedDomains;
|
|
94
87
|
if (allowedDomains && allowedDomains.length > 0) {
|
|
95
|
-
const url =
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
88
|
+
const url =
|
|
89
|
+
typeof input === "string"
|
|
90
|
+
? new URL(input)
|
|
91
|
+
: input instanceof URL
|
|
92
|
+
? input
|
|
93
|
+
: new URL(input.url);
|
|
100
94
|
|
|
101
95
|
const isAllowed = allowedDomains.some(
|
|
102
|
-
(domain) =>
|
|
103
|
-
url.hostname === domain || url.hostname.endsWith(`.${domain}`),
|
|
96
|
+
(domain) => url.hostname === domain || url.hostname.endsWith(`.${domain}`),
|
|
104
97
|
);
|
|
105
98
|
|
|
106
99
|
if (!isAllowed) {
|
|
107
100
|
const log = createPluginLogger(pluginId);
|
|
108
|
-
log.error(
|
|
101
|
+
log.error(
|
|
102
|
+
`Network blocked: domain "${url.hostname}" not in allowlist [${allowedDomains.join(", ")}]`,
|
|
103
|
+
);
|
|
109
104
|
throw new PluginPermissionError(
|
|
110
105
|
pluginId,
|
|
111
|
-
|
|
112
|
-
`Domain "${url.hostname}" not in allowlist: ${allowedDomains.join(
|
|
106
|
+
"network",
|
|
107
|
+
`Domain "${url.hostname}" not in allowlist: ${allowedDomains.join(", ")}`,
|
|
113
108
|
);
|
|
114
109
|
}
|
|
115
110
|
}
|
|
@@ -136,7 +131,7 @@ const frozenObjects = new WeakSet<object>();
|
|
|
136
131
|
*/
|
|
137
132
|
export function deepFreeze<T>(obj: T): T {
|
|
138
133
|
if (obj === null || obj === undefined) return obj;
|
|
139
|
-
if (typeof obj !==
|
|
134
|
+
if (typeof obj !== "object") return obj;
|
|
140
135
|
|
|
141
136
|
const o = obj as object;
|
|
142
137
|
|
|
@@ -146,7 +141,7 @@ export function deepFreeze<T>(obj: T): T {
|
|
|
146
141
|
Object.freeze(o);
|
|
147
142
|
|
|
148
143
|
for (const value of Object.values(o)) {
|
|
149
|
-
if (value !== null && typeof value ===
|
|
144
|
+
if (value !== null && typeof value === "object") {
|
|
150
145
|
deepFreeze(value);
|
|
151
146
|
}
|
|
152
147
|
}
|
package/src/plugins/sandbox.ts
CHANGED
|
@@ -1,22 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
|
-
PluginPermissionError,
|
|
3
|
-
type PluginPermissions,
|
|
4
2
|
type PluginHttpClient,
|
|
5
3
|
type PluginLogger,
|
|
6
|
-
|
|
4
|
+
PluginPermissionError,
|
|
5
|
+
type PluginPermissions,
|
|
6
|
+
} from "./types/index.ts";
|
|
7
7
|
|
|
8
8
|
export function createSandboxedHttpClient(
|
|
9
9
|
pluginId: string,
|
|
10
|
-
permissions: PluginPermissions
|
|
10
|
+
permissions: PluginPermissions,
|
|
11
11
|
): PluginHttpClient {
|
|
12
12
|
return {
|
|
13
13
|
async fetch(url: string, init?: RequestInit): Promise<Response> {
|
|
14
14
|
if (!permissions.network?.enabled) {
|
|
15
|
-
throw new PluginPermissionError(
|
|
16
|
-
pluginId,
|
|
17
|
-
'network',
|
|
18
|
-
'Network access not permitted'
|
|
19
|
-
);
|
|
15
|
+
throw new PluginPermissionError(pluginId, "network", "Network access not permitted");
|
|
20
16
|
}
|
|
21
17
|
|
|
22
18
|
const urlObj = new URL(url);
|
|
@@ -24,16 +20,14 @@ export function createSandboxedHttpClient(
|
|
|
24
20
|
|
|
25
21
|
if (allowedDomains && allowedDomains.length > 0) {
|
|
26
22
|
const isAllowed = allowedDomains.some(
|
|
27
|
-
(domain) =>
|
|
28
|
-
urlObj.hostname === domain ||
|
|
29
|
-
urlObj.hostname.endsWith(`.${domain}`)
|
|
23
|
+
(domain) => urlObj.hostname === domain || urlObj.hostname.endsWith(`.${domain}`),
|
|
30
24
|
);
|
|
31
25
|
|
|
32
26
|
if (!isAllowed) {
|
|
33
27
|
throw new PluginPermissionError(
|
|
34
28
|
pluginId,
|
|
35
|
-
|
|
36
|
-
`Domain "${urlObj.hostname}" not in allowlist: ${allowedDomains.join(
|
|
29
|
+
"network",
|
|
30
|
+
`Domain "${urlObj.hostname}" not in allowlist: ${allowedDomains.join(", ")}`,
|
|
37
31
|
);
|
|
38
32
|
}
|
|
39
33
|
}
|
|
@@ -49,17 +43,17 @@ export function createPluginLogger(pluginId: string): PluginLogger {
|
|
|
49
43
|
return {
|
|
50
44
|
debug(message: string, data?: Record<string, unknown>) {
|
|
51
45
|
if (process.env.DEBUG) {
|
|
52
|
-
console.debug(prefix, message, data ??
|
|
46
|
+
console.debug(prefix, message, data ?? "");
|
|
53
47
|
}
|
|
54
48
|
},
|
|
55
49
|
info(message: string, data?: Record<string, unknown>) {
|
|
56
|
-
console.info(prefix, message, data ??
|
|
50
|
+
console.info(prefix, message, data ?? "");
|
|
57
51
|
},
|
|
58
52
|
warn(message: string, data?: Record<string, unknown>) {
|
|
59
|
-
console.warn(prefix, message, data ??
|
|
53
|
+
console.warn(prefix, message, data ?? "");
|
|
60
54
|
},
|
|
61
55
|
error(message: string, data?: Record<string, unknown>) {
|
|
62
|
-
console.error(prefix, message, data ??
|
|
56
|
+
console.error(prefix, message, data ?? "");
|
|
63
57
|
},
|
|
64
58
|
};
|
|
65
59
|
}
|
|
@@ -67,22 +61,18 @@ export function createPluginLogger(pluginId: string): PluginLogger {
|
|
|
67
61
|
export function validateEnvAccess(
|
|
68
62
|
pluginId: string,
|
|
69
63
|
permissions: PluginPermissions,
|
|
70
|
-
varName: string
|
|
64
|
+
varName: string,
|
|
71
65
|
): void {
|
|
72
66
|
if (!permissions.env?.read) {
|
|
73
|
-
throw new PluginPermissionError(
|
|
74
|
-
pluginId,
|
|
75
|
-
'env',
|
|
76
|
-
'Environment variable access not permitted'
|
|
77
|
-
);
|
|
67
|
+
throw new PluginPermissionError(pluginId, "env", "Environment variable access not permitted");
|
|
78
68
|
}
|
|
79
69
|
|
|
80
70
|
const allowedVars = permissions.env.vars;
|
|
81
71
|
if (allowedVars && allowedVars.length > 0 && !allowedVars.includes(varName)) {
|
|
82
72
|
throw new PluginPermissionError(
|
|
83
73
|
pluginId,
|
|
84
|
-
|
|
85
|
-
`Environment variable "${varName}" not in allowlist
|
|
74
|
+
"env",
|
|
75
|
+
`Environment variable "${varName}" not in allowlist`,
|
|
86
76
|
);
|
|
87
77
|
}
|
|
88
78
|
}
|
|
@@ -91,48 +81,36 @@ export function validateFilesystemAccess(
|
|
|
91
81
|
pluginId: string,
|
|
92
82
|
permissions: PluginPermissions,
|
|
93
83
|
path: string,
|
|
94
|
-
mode:
|
|
84
|
+
mode: "read" | "write",
|
|
95
85
|
): void {
|
|
96
86
|
const fsPerms = permissions.filesystem;
|
|
97
87
|
|
|
98
88
|
if (!fsPerms) {
|
|
99
|
-
throw new PluginPermissionError(
|
|
100
|
-
pluginId,
|
|
101
|
-
'filesystem',
|
|
102
|
-
'Filesystem access not permitted'
|
|
103
|
-
);
|
|
89
|
+
throw new PluginPermissionError(pluginId, "filesystem", "Filesystem access not permitted");
|
|
104
90
|
}
|
|
105
91
|
|
|
106
|
-
if (mode ===
|
|
107
|
-
throw new PluginPermissionError(
|
|
108
|
-
pluginId,
|
|
109
|
-
'filesystem',
|
|
110
|
-
'Filesystem read access not permitted'
|
|
111
|
-
);
|
|
92
|
+
if (mode === "read" && !fsPerms.read) {
|
|
93
|
+
throw new PluginPermissionError(pluginId, "filesystem", "Filesystem read access not permitted");
|
|
112
94
|
}
|
|
113
95
|
|
|
114
|
-
if (mode ===
|
|
96
|
+
if (mode === "write" && !fsPerms.write) {
|
|
115
97
|
throw new PluginPermissionError(
|
|
116
98
|
pluginId,
|
|
117
|
-
|
|
118
|
-
|
|
99
|
+
"filesystem",
|
|
100
|
+
"Filesystem write access not permitted",
|
|
119
101
|
);
|
|
120
102
|
}
|
|
121
103
|
|
|
122
104
|
const allowedPaths = fsPerms.paths;
|
|
123
105
|
if (allowedPaths && allowedPaths.length > 0) {
|
|
124
|
-
const normalizedPath = path.replace(/^~/, process.env.HOME ??
|
|
106
|
+
const normalizedPath = path.replace(/^~/, process.env.HOME ?? "");
|
|
125
107
|
const isAllowed = allowedPaths.some((allowed) => {
|
|
126
|
-
const normalizedAllowed = allowed.replace(/^~/, process.env.HOME ??
|
|
108
|
+
const normalizedAllowed = allowed.replace(/^~/, process.env.HOME ?? "");
|
|
127
109
|
return normalizedPath.startsWith(normalizedAllowed);
|
|
128
110
|
});
|
|
129
111
|
|
|
130
112
|
if (!isAllowed) {
|
|
131
|
-
throw new PluginPermissionError(
|
|
132
|
-
pluginId,
|
|
133
|
-
'filesystem',
|
|
134
|
-
`Path "${path}" not in allowlist`
|
|
135
|
-
);
|
|
113
|
+
throw new PluginPermissionError(pluginId, "filesystem", `Path "${path}" not in allowlist`);
|
|
136
114
|
}
|
|
137
115
|
}
|
|
138
116
|
}
|
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
import type { ThemePlugin } from
|
|
1
|
+
import type { ThemePlugin } from "../types/theme.ts";
|
|
2
2
|
|
|
3
3
|
export const catppuccinLatteTheme: ThemePlugin = {
|
|
4
4
|
apiVersion: 2,
|
|
5
|
-
id:
|
|
6
|
-
type:
|
|
7
|
-
name:
|
|
8
|
-
family:
|
|
9
|
-
version:
|
|
5
|
+
id: "catppuccin-latte",
|
|
6
|
+
type: "theme",
|
|
7
|
+
name: "Catppuccin Latte",
|
|
8
|
+
family: "catppuccin",
|
|
9
|
+
version: "1.0.0",
|
|
10
10
|
permissions: {},
|
|
11
11
|
|
|
12
|
-
colorScheme:
|
|
12
|
+
colorScheme: "light",
|
|
13
13
|
|
|
14
14
|
colors: {
|
|
15
|
-
background:
|
|
16
|
-
foreground:
|
|
17
|
-
text:
|
|
18
|
-
textMuted:
|
|
19
|
-
textSubtle:
|
|
20
|
-
primary:
|
|
21
|
-
secondary:
|
|
22
|
-
accent:
|
|
23
|
-
success:
|
|
24
|
-
warning:
|
|
25
|
-
error:
|
|
26
|
-
info:
|
|
27
|
-
border:
|
|
28
|
-
borderMuted:
|
|
29
|
-
selection:
|
|
30
|
-
highlight:
|
|
31
|
-
gaugeBackground:
|
|
32
|
-
gaugeFill:
|
|
33
|
-
gaugeWarning:
|
|
34
|
-
gaugeDanger:
|
|
15
|
+
background: "#eff1f5",
|
|
16
|
+
foreground: "#e6e9ef",
|
|
17
|
+
text: "#4c4f69",
|
|
18
|
+
textMuted: "#6c6f85",
|
|
19
|
+
textSubtle: "#9ca0b0",
|
|
20
|
+
primary: "#1e66f5",
|
|
21
|
+
secondary: "#8839ef",
|
|
22
|
+
accent: "#179299",
|
|
23
|
+
success: "#40a02b",
|
|
24
|
+
warning: "#df8e1d",
|
|
25
|
+
error: "#d20f39",
|
|
26
|
+
info: "#04a5e5",
|
|
27
|
+
border: "#bcc0cc",
|
|
28
|
+
borderMuted: "#ccd0da",
|
|
29
|
+
selection: "#dce0e8",
|
|
30
|
+
highlight: "#e6e9ef",
|
|
31
|
+
gaugeBackground: "#ccd0da",
|
|
32
|
+
gaugeFill: "#1e66f5",
|
|
33
|
+
gaugeWarning: "#df8e1d",
|
|
34
|
+
gaugeDanger: "#d20f39",
|
|
35
35
|
},
|
|
36
36
|
|
|
37
37
|
components: {
|
|
38
38
|
header: {
|
|
39
|
-
background:
|
|
39
|
+
background: "#e6e9ef",
|
|
40
40
|
},
|
|
41
41
|
statusBar: {
|
|
42
|
-
background:
|
|
42
|
+
background: "#dce0e8",
|
|
43
43
|
},
|
|
44
44
|
},
|
|
45
45
|
};
|
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
import type { ThemePlugin } from
|
|
1
|
+
import type { ThemePlugin } from "../types/theme.ts";
|
|
2
2
|
|
|
3
3
|
export const catppuccinMochaTheme: ThemePlugin = {
|
|
4
4
|
apiVersion: 2,
|
|
5
|
-
id:
|
|
6
|
-
type:
|
|
7
|
-
name:
|
|
8
|
-
family:
|
|
9
|
-
version:
|
|
5
|
+
id: "catppuccin-mocha",
|
|
6
|
+
type: "theme",
|
|
7
|
+
name: "Catppuccin Mocha",
|
|
8
|
+
family: "catppuccin",
|
|
9
|
+
version: "1.0.0",
|
|
10
10
|
permissions: {},
|
|
11
11
|
|
|
12
|
-
colorScheme:
|
|
12
|
+
colorScheme: "dark",
|
|
13
13
|
|
|
14
14
|
colors: {
|
|
15
|
-
background:
|
|
16
|
-
foreground:
|
|
17
|
-
text:
|
|
18
|
-
textMuted:
|
|
19
|
-
textSubtle:
|
|
20
|
-
primary:
|
|
21
|
-
secondary:
|
|
22
|
-
accent:
|
|
23
|
-
success:
|
|
24
|
-
warning:
|
|
25
|
-
error:
|
|
26
|
-
info:
|
|
27
|
-
border:
|
|
28
|
-
borderMuted:
|
|
29
|
-
selection:
|
|
30
|
-
highlight:
|
|
31
|
-
gaugeBackground:
|
|
32
|
-
gaugeFill:
|
|
33
|
-
gaugeWarning:
|
|
34
|
-
gaugeDanger:
|
|
15
|
+
background: "#1e1e2e",
|
|
16
|
+
foreground: "#313244",
|
|
17
|
+
text: "#cdd6f4",
|
|
18
|
+
textMuted: "#a6adc8",
|
|
19
|
+
textSubtle: "#6c7086",
|
|
20
|
+
primary: "#89b4fa",
|
|
21
|
+
secondary: "#cba6f7",
|
|
22
|
+
accent: "#94e2d5",
|
|
23
|
+
success: "#a6e3a1",
|
|
24
|
+
warning: "#f9e2af",
|
|
25
|
+
error: "#f38ba8",
|
|
26
|
+
info: "#89dceb",
|
|
27
|
+
border: "#45475a",
|
|
28
|
+
borderMuted: "#313244",
|
|
29
|
+
selection: "#45475a",
|
|
30
|
+
highlight: "#313244",
|
|
31
|
+
gaugeBackground: "#313244",
|
|
32
|
+
gaugeFill: "#89b4fa",
|
|
33
|
+
gaugeWarning: "#f9e2af",
|
|
34
|
+
gaugeDanger: "#f38ba8",
|
|
35
35
|
},
|
|
36
36
|
|
|
37
37
|
components: {
|
|
38
38
|
header: {
|
|
39
|
-
background:
|
|
39
|
+
background: "#181825",
|
|
40
40
|
},
|
|
41
41
|
statusBar: {
|
|
42
|
-
background:
|
|
42
|
+
background: "#11111b",
|
|
43
43
|
},
|
|
44
44
|
},
|
|
45
45
|
};
|