@hazeljs/inspector 0.2.0-rc.5

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 (75) hide show
  1. package/LICENSE +192 -0
  2. package/README.md +97 -0
  3. package/dist/config/inspector.config.d.ts +8 -0
  4. package/dist/config/inspector.config.d.ts.map +1 -0
  5. package/dist/config/inspector.config.js +38 -0
  6. package/dist/contracts/types.d.ts +253 -0
  7. package/dist/contracts/types.d.ts.map +1 -0
  8. package/dist/contracts/types.js +5 -0
  9. package/dist/index.d.ts +11 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +14 -0
  12. package/dist/inspector.module.d.ts +25 -0
  13. package/dist/inspector.module.d.ts.map +1 -0
  14. package/dist/inspector.module.js +489 -0
  15. package/dist/plugins/agent.inspector.d.ts +8 -0
  16. package/dist/plugins/agent.inspector.d.ts.map +1 -0
  17. package/dist/plugins/agent.inspector.js +51 -0
  18. package/dist/plugins/ai.inspector.d.ts +8 -0
  19. package/dist/plugins/ai.inspector.d.ts.map +1 -0
  20. package/dist/plugins/ai.inspector.js +87 -0
  21. package/dist/plugins/core.inspector.d.ts +8 -0
  22. package/dist/plugins/core.inspector.d.ts.map +1 -0
  23. package/dist/plugins/core.inspector.js +203 -0
  24. package/dist/plugins/cron.inspector.d.ts +8 -0
  25. package/dist/plugins/cron.inspector.d.ts.map +1 -0
  26. package/dist/plugins/cron.inspector.js +74 -0
  27. package/dist/plugins/data.inspector.d.ts +8 -0
  28. package/dist/plugins/data.inspector.d.ts.map +1 -0
  29. package/dist/plugins/data.inspector.js +50 -0
  30. package/dist/plugins/event-emitter.inspector.d.ts +8 -0
  31. package/dist/plugins/event-emitter.inspector.d.ts.map +1 -0
  32. package/dist/plugins/event-emitter.inspector.js +53 -0
  33. package/dist/plugins/flow.inspector.d.ts +8 -0
  34. package/dist/plugins/flow.inspector.d.ts.map +1 -0
  35. package/dist/plugins/flow.inspector.js +112 -0
  36. package/dist/plugins/graphql.inspector.d.ts +8 -0
  37. package/dist/plugins/graphql.inspector.d.ts.map +1 -0
  38. package/dist/plugins/graphql.inspector.js +75 -0
  39. package/dist/plugins/grpc.inspector.d.ts +8 -0
  40. package/dist/plugins/grpc.inspector.d.ts.map +1 -0
  41. package/dist/plugins/grpc.inspector.js +53 -0
  42. package/dist/plugins/kafka.inspector.d.ts +8 -0
  43. package/dist/plugins/kafka.inspector.d.ts.map +1 -0
  44. package/dist/plugins/kafka.inspector.js +59 -0
  45. package/dist/plugins/ml.inspector.d.ts +8 -0
  46. package/dist/plugins/ml.inspector.d.ts.map +1 -0
  47. package/dist/plugins/ml.inspector.js +78 -0
  48. package/dist/plugins/prompts.inspector.d.ts +7 -0
  49. package/dist/plugins/prompts.inspector.d.ts.map +1 -0
  50. package/dist/plugins/prompts.inspector.js +41 -0
  51. package/dist/plugins/queue.inspector.d.ts +8 -0
  52. package/dist/plugins/queue.inspector.d.ts.map +1 -0
  53. package/dist/plugins/queue.inspector.js +55 -0
  54. package/dist/plugins/rag.inspector.d.ts +8 -0
  55. package/dist/plugins/rag.inspector.d.ts.map +1 -0
  56. package/dist/plugins/rag.inspector.js +72 -0
  57. package/dist/plugins/serverless.inspector.d.ts +8 -0
  58. package/dist/plugins/serverless.inspector.d.ts.map +1 -0
  59. package/dist/plugins/serverless.inspector.js +53 -0
  60. package/dist/plugins/websocket.inspector.d.ts +8 -0
  61. package/dist/plugins/websocket.inspector.d.ts.map +1 -0
  62. package/dist/plugins/websocket.inspector.js +73 -0
  63. package/dist/registry/registry.d.ts +12 -0
  64. package/dist/registry/registry.d.ts.map +1 -0
  65. package/dist/registry/registry.js +43 -0
  66. package/dist/runtime/inspector-runtime.d.ts +66 -0
  67. package/dist/runtime/inspector-runtime.d.ts.map +1 -0
  68. package/dist/runtime/inspector-runtime.js +97 -0
  69. package/dist/service/inspector.service.d.ts +37 -0
  70. package/dist/service/inspector.service.d.ts.map +1 -0
  71. package/dist/service/inspector.service.js +146 -0
  72. package/package.json +124 -0
  73. package/ui-dist/assets/index-BN8Zr7QX.css +1 -0
  74. package/ui-dist/assets/index-DLS5TpZI.js +80 -0
  75. package/ui-dist/index.html +316 -0
@@ -0,0 +1,12 @@
1
+ /**
2
+ * HazelInspectorRegistry - Pluggable inspector plugin registration and aggregation
3
+ */
4
+ import type { InspectorContext, InspectorEntry, HazelInspectorPlugin } from '../contracts/types';
5
+ export type { HazelInspectorPlugin };
6
+ export declare class HazelInspectorRegistry {
7
+ private plugins;
8
+ register(plugin: HazelInspectorPlugin): void;
9
+ getPlugins(): HazelInspectorPlugin[];
10
+ runAll(context: InspectorContext): Promise<InspectorEntry[]>;
11
+ }
12
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/registry/registry.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAEjG,YAAY,EAAE,oBAAoB,EAAE,CAAC;AAErC,qBAAa,sBAAsB;IACjC,OAAO,CAAC,OAAO,CAA8B;IAE7C,QAAQ,CAAC,MAAM,EAAE,oBAAoB,GAAG,IAAI;IAM5C,UAAU,IAAI,oBAAoB,EAAE;IAI9B,MAAM,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;CAwBnE"}
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ /**
3
+ * HazelInspectorRegistry - Pluggable inspector plugin registration and aggregation
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.HazelInspectorRegistry = void 0;
7
+ class HazelInspectorRegistry {
8
+ constructor() {
9
+ this.plugins = [];
10
+ }
11
+ register(plugin) {
12
+ if (!this.plugins.some((p) => p.name === plugin.name)) {
13
+ this.plugins.push(plugin);
14
+ }
15
+ }
16
+ getPlugins() {
17
+ return [...this.plugins];
18
+ }
19
+ async runAll(context) {
20
+ const results = [];
21
+ const seenIds = new Set();
22
+ for (const plugin of this.plugins) {
23
+ try {
24
+ if (!plugin.supports(context)) {
25
+ continue;
26
+ }
27
+ const entries = await plugin.inspect(context);
28
+ const list = Array.isArray(entries) ? entries : [];
29
+ for (const entry of list) {
30
+ if (!seenIds.has(entry.id)) {
31
+ seenIds.add(entry.id);
32
+ results.push(entry);
33
+ }
34
+ }
35
+ }
36
+ catch (error) {
37
+ console.error(`[HazelInspector] Plugin ${plugin.name} failed:`, error);
38
+ }
39
+ }
40
+ return results;
41
+ }
42
+ }
43
+ exports.HazelInspectorRegistry = HazelInspectorRegistry;
@@ -0,0 +1,66 @@
1
+ /**
2
+ * InspectorRuntime - Global registry for gateway, discovery, resilience
3
+ * Packages register their instances here so the Inspector can display overview data.
4
+ *
5
+ * @example
6
+ * // In your app when creating a gateway:
7
+ * import { InspectorRuntime } from '@hazeljs/inspector';
8
+ * const gateway = GatewayServer.fromConfig(config);
9
+ * InspectorRuntime.registerGateway(gateway);
10
+ */
11
+ export interface GatewayOverview {
12
+ routes: string[];
13
+ totalRoutes: number;
14
+ metrics?: {
15
+ totalCalls: number;
16
+ successCalls: number;
17
+ failureCalls: number;
18
+ failureRate: number;
19
+ averageResponseTime: number;
20
+ };
21
+ }
22
+ export interface DiscoveryOverview {
23
+ services: string[];
24
+ totalServices: number;
25
+ totalInstances: number;
26
+ instancesByService: Record<string, number>;
27
+ }
28
+ export interface ResilienceOverview {
29
+ circuitBreakers: number;
30
+ circuitBreakerStates: {
31
+ name: string;
32
+ state: string;
33
+ }[];
34
+ }
35
+ declare class InspectorRuntimeImpl {
36
+ private gateway;
37
+ private discovery;
38
+ registerGateway(gw: {
39
+ getRoutes: () => string[];
40
+ getMetrics?: () => {
41
+ getSnapshot?: () => {
42
+ aggregated?: {
43
+ totalCalls?: number;
44
+ successCalls?: number;
45
+ failureCalls?: number;
46
+ failureRate?: number;
47
+ averageResponseTime?: number;
48
+ };
49
+ };
50
+ };
51
+ }): void;
52
+ registerDiscovery(client: {
53
+ getAllServices: () => Promise<string[]>;
54
+ getInstances: (s: string) => Promise<unknown[]>;
55
+ }): void;
56
+ getGateway(): typeof this.gateway;
57
+ getDiscovery(): typeof this.discovery;
58
+ /** Reset for testing */
59
+ reset(): void;
60
+ getGatewayOverview(): Promise<GatewayOverview | null>;
61
+ getDiscoveryOverview(): Promise<DiscoveryOverview | null>;
62
+ getResilienceOverview(): Promise<ResilienceOverview | null>;
63
+ }
64
+ export declare const InspectorRuntime: InspectorRuntimeImpl;
65
+ export {};
66
+ //# sourceMappingURL=inspector-runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inspector-runtime.d.ts","sourceRoot":"","sources":["../../src/runtime/inspector-runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE;QACR,UAAU,EAAE,MAAM,CAAC;QACnB,YAAY,EAAE,MAAM,CAAC;QACrB,YAAY,EAAE,MAAM,CAAC;QACrB,WAAW,EAAE,MAAM,CAAC;QACpB,mBAAmB,EAAE,MAAM,CAAC;KAC7B,CAAC;CACH;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC5C;AAED,MAAM,WAAW,kBAAkB;IACjC,eAAe,EAAE,MAAM,CAAC;IACxB,oBAAoB,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACzD;AAED,cAAM,oBAAoB;IACxB,OAAO,CAAC,OAAO,CAaC;IAChB,OAAO,CAAC,SAAS,CAGD;IAEhB,eAAe,CAAC,EAAE,EAAE;QAClB,SAAS,EAAE,MAAM,MAAM,EAAE,CAAC;QAC1B,UAAU,CAAC,EAAE,MAAM;YACjB,WAAW,CAAC,EAAE,MAAM;gBAClB,UAAU,CAAC,EAAE;oBACX,UAAU,CAAC,EAAE,MAAM,CAAC;oBACpB,YAAY,CAAC,EAAE,MAAM,CAAC;oBACtB,YAAY,CAAC,EAAE,MAAM,CAAC;oBACtB,WAAW,CAAC,EAAE,MAAM,CAAC;oBACrB,mBAAmB,CAAC,EAAE,MAAM,CAAC;iBAC9B,CAAC;aACH,CAAC;SACH,CAAC;KACH,GAAG,IAAI;IAIR,iBAAiB,CAAC,MAAM,EAAE;QACxB,cAAc,EAAE,MAAM,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QACxC,YAAY,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;KACjD,GAAG,IAAI;IAIR,UAAU,IAAI,OAAO,IAAI,CAAC,OAAO;IAIjC,YAAY,IAAI,OAAO,IAAI,CAAC,SAAS;IAIrC,wBAAwB;IACxB,KAAK,IAAI,IAAI;IAKP,kBAAkB,IAAI,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAuBrD,oBAAoB,IAAI,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;IAkBzD,qBAAqB,IAAI,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC;CAelE;AAED,eAAO,MAAM,gBAAgB,sBAA6B,CAAC"}
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ /**
3
+ * InspectorRuntime - Global registry for gateway, discovery, resilience
4
+ * Packages register their instances here so the Inspector can display overview data.
5
+ *
6
+ * @example
7
+ * // In your app when creating a gateway:
8
+ * import { InspectorRuntime } from '@hazeljs/inspector';
9
+ * const gateway = GatewayServer.fromConfig(config);
10
+ * InspectorRuntime.registerGateway(gateway);
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.InspectorRuntime = void 0;
14
+ class InspectorRuntimeImpl {
15
+ constructor() {
16
+ this.gateway = null;
17
+ this.discovery = null;
18
+ }
19
+ registerGateway(gw) {
20
+ this.gateway = gw;
21
+ }
22
+ registerDiscovery(client) {
23
+ this.discovery = client;
24
+ }
25
+ getGateway() {
26
+ return this.gateway;
27
+ }
28
+ getDiscovery() {
29
+ return this.discovery;
30
+ }
31
+ /** Reset for testing */
32
+ reset() {
33
+ this.gateway = null;
34
+ this.discovery = null;
35
+ }
36
+ async getGatewayOverview() {
37
+ if (!this.gateway)
38
+ return null;
39
+ try {
40
+ const routes = this.gateway.getRoutes();
41
+ let metrics;
42
+ if (typeof this.gateway.getMetrics === 'function') {
43
+ const snap = this.gateway.getMetrics()?.getSnapshot?.()?.aggregated;
44
+ if (snap) {
45
+ metrics = {
46
+ totalCalls: snap.totalCalls ?? 0,
47
+ successCalls: snap.successCalls ?? 0,
48
+ failureCalls: snap.failureCalls ?? 0,
49
+ failureRate: snap.failureRate ?? 0,
50
+ averageResponseTime: snap.averageResponseTime ?? 0,
51
+ };
52
+ }
53
+ }
54
+ return { routes, totalRoutes: routes.length, metrics: metrics ?? undefined };
55
+ }
56
+ catch {
57
+ return null;
58
+ }
59
+ }
60
+ async getDiscoveryOverview() {
61
+ if (!this.discovery)
62
+ return null;
63
+ try {
64
+ const services = await this.discovery.getAllServices();
65
+ const instancesByService = {};
66
+ let totalInstances = 0;
67
+ for (const svc of services) {
68
+ const instances = await this.discovery.getInstances(svc);
69
+ const count = Array.isArray(instances) ? instances.length : 0;
70
+ instancesByService[svc] = count;
71
+ totalInstances += count;
72
+ }
73
+ return { services, totalServices: services.length, totalInstances, instancesByService };
74
+ }
75
+ catch {
76
+ return null;
77
+ }
78
+ }
79
+ async getResilienceOverview() {
80
+ try {
81
+ const { CircuitBreakerRegistry } = require('@hazeljs/resilience');
82
+ const breakers = CircuitBreakerRegistry.getAll?.();
83
+ if (!breakers || !(breakers instanceof Map))
84
+ return null;
85
+ const circuitBreakerStates = [];
86
+ for (const [name, breaker] of breakers) {
87
+ const state = breaker.getState?.() ?? 'unknown';
88
+ circuitBreakerStates.push({ name, state });
89
+ }
90
+ return { circuitBreakers: breakers.size, circuitBreakerStates };
91
+ }
92
+ catch {
93
+ return null;
94
+ }
95
+ }
96
+ }
97
+ exports.InspectorRuntime = new InspectorRuntimeImpl();
@@ -0,0 +1,37 @@
1
+ /**
2
+ * HazelInspectorService - Aggregates inspector plugin results with caching
3
+ */
4
+ import type { InspectorContext, InspectorEntry, InspectorSnapshot, InspectorEntryKind, RouteInspectorEntry, ModuleInspectorEntry, ProviderInspectorEntry, DecoratorInspectorEntry, CronInspectorEntry, QueueInspectorEntry, WebSocketInspectorEntry, AgentInspectorEntry, RagInspectorEntry, PromptInspectorEntry, AIFunctionInspectorEntry, EventInspectorEntry, GraphQLInspectorEntry, GrpcInspectorEntry, KafkaInspectorEntry, FlowInspectorEntry, DataPipelineInspectorEntry, ServerlessInspectorEntry, MLModelInspectorEntry, GroupedSnapshot } from '../contracts/types';
5
+ import type { HazelInspectorRegistry } from '../registry/registry';
6
+ import type { InspectorModuleOptions } from '../contracts/types';
7
+ export declare class HazelInspectorService {
8
+ private registry;
9
+ private config;
10
+ private cache;
11
+ private cacheTime;
12
+ constructor(registry: HazelInspectorRegistry, config: Required<InspectorModuleOptions>);
13
+ collectSnapshot(context: InspectorContext): Promise<InspectorSnapshot>;
14
+ refresh(context: InspectorContext): Promise<InspectorSnapshot>;
15
+ getRoutes(entries: InspectorEntry[]): RouteInspectorEntry[];
16
+ getModules(entries: InspectorEntry[]): ModuleInspectorEntry[];
17
+ getProviders(entries: InspectorEntry[]): ProviderInspectorEntry[];
18
+ getDecorators(entries: InspectorEntry[]): DecoratorInspectorEntry[];
19
+ getJobs(entries: InspectorEntry[]): CronInspectorEntry[];
20
+ getQueues(entries: InspectorEntry[]): QueueInspectorEntry[];
21
+ getWebSockets(entries: InspectorEntry[]): WebSocketInspectorEntry[];
22
+ getAgents(entries: InspectorEntry[]): AgentInspectorEntry[];
23
+ getRag(entries: InspectorEntry[]): RagInspectorEntry[];
24
+ getPrompts(entries: InspectorEntry[]): PromptInspectorEntry[];
25
+ getAIFunctions(entries: InspectorEntry[]): AIFunctionInspectorEntry[];
26
+ getEvents(entries: InspectorEntry[]): EventInspectorEntry[];
27
+ getGraphQL(entries: InspectorEntry[]): GraphQLInspectorEntry[];
28
+ getGrpc(entries: InspectorEntry[]): GrpcInspectorEntry[];
29
+ getKafka(entries: InspectorEntry[]): KafkaInspectorEntry[];
30
+ getFlows(entries: InspectorEntry[]): FlowInspectorEntry[];
31
+ getDataPipelines(entries: InspectorEntry[]): DataPipelineInspectorEntry[];
32
+ getServerless(entries: InspectorEntry[]): ServerlessInspectorEntry[];
33
+ getMLModels(entries: InspectorEntry[]): MLModelInspectorEntry[];
34
+ getByKind(entries: InspectorEntry[], kind: InspectorEntryKind): InspectorEntry[];
35
+ getGroupedSnapshot(entries: InspectorEntry[]): GroupedSnapshot;
36
+ }
37
+ //# sourceMappingURL=inspector.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inspector.service.d.ts","sourceRoot":"","sources":["../../src/service/inspector.service.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACV,gBAAgB,EAChB,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACnB,oBAAoB,EACpB,sBAAsB,EACtB,uBAAuB,EACvB,kBAAkB,EAClB,mBAAmB,EACnB,uBAAuB,EACvB,mBAAmB,EACnB,iBAAiB,EACjB,oBAAoB,EACpB,wBAAwB,EACxB,mBAAmB,EACnB,qBAAqB,EACrB,kBAAkB,EAClB,mBAAmB,EACnB,kBAAkB,EAClB,0BAA0B,EAC1B,wBAAwB,EACxB,qBAAqB,EACrB,eAAe,EAChB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAEnE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAEjE,qBAAa,qBAAqB;IAK9B,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,MAAM;IALhB,OAAO,CAAC,KAAK,CAAkC;IAC/C,OAAO,CAAC,SAAS,CAAK;gBAGZ,QAAQ,EAAE,sBAAsB,EAChC,MAAM,EAAE,QAAQ,CAAC,sBAAsB,CAAC;IAK5C,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAuBtE,OAAO,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAKpE,SAAS,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,mBAAmB,EAAE;IAI3D,UAAU,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,oBAAoB,EAAE;IAI7D,YAAY,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,sBAAsB,EAAE;IAIjE,aAAa,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,uBAAuB,EAAE;IAInE,OAAO,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,kBAAkB,EAAE;IAIxD,SAAS,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,mBAAmB,EAAE;IAI3D,aAAa,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,uBAAuB,EAAE;IAInE,SAAS,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,mBAAmB,EAAE;IAI3D,MAAM,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,iBAAiB,EAAE;IAItD,UAAU,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,oBAAoB,EAAE;IAI7D,cAAc,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,wBAAwB,EAAE;IAIrE,SAAS,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,mBAAmB,EAAE;IAI3D,UAAU,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,qBAAqB,EAAE;IAI9D,OAAO,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,kBAAkB,EAAE;IAIxD,QAAQ,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,mBAAmB,EAAE;IAI1D,QAAQ,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,kBAAkB,EAAE;IAIzD,gBAAgB,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,0BAA0B,EAAE;IAIzE,aAAa,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,wBAAwB,EAAE;IAIpE,WAAW,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,qBAAqB,EAAE;IAI/D,SAAS,CAAC,OAAO,EAAE,cAAc,EAAE,EAAE,IAAI,EAAE,kBAAkB,GAAG,cAAc,EAAE;IAIhF,kBAAkB,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,eAAe;CA6C/D"}
@@ -0,0 +1,146 @@
1
+ "use strict";
2
+ /**
3
+ * HazelInspectorService - Aggregates inspector plugin results with caching
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.HazelInspectorService = void 0;
7
+ const inspector_config_1 = require("../config/inspector.config");
8
+ class HazelInspectorService {
9
+ constructor(registry, config) {
10
+ this.registry = registry;
11
+ this.config = config;
12
+ this.cache = null;
13
+ this.cacheTime = 0;
14
+ this.config = (0, inspector_config_1.mergeInspectorConfig)(config);
15
+ }
16
+ async collectSnapshot(context) {
17
+ const now = Date.now();
18
+ const maxAge = this.config.maxSnapshotCacheAgeMs ?? 5000;
19
+ if (this.cache && now - this.cacheTime < maxAge) {
20
+ return this.cache;
21
+ }
22
+ const rawEntries = await this.registry.runAll(context);
23
+ const entries = Array.isArray(rawEntries) ? rawEntries : [];
24
+ const summary = {};
25
+ for (const e of entries) {
26
+ summary[e.kind] = (summary[e.kind] ?? 0) + 1;
27
+ }
28
+ this.cache = {
29
+ collectedAt: new Date().toISOString(),
30
+ entries,
31
+ summary,
32
+ };
33
+ this.cacheTime = now;
34
+ return this.cache;
35
+ }
36
+ async refresh(context) {
37
+ this.cache = null;
38
+ return this.collectSnapshot(context);
39
+ }
40
+ getRoutes(entries) {
41
+ return entries.filter((e) => e.kind === 'route');
42
+ }
43
+ getModules(entries) {
44
+ return entries.filter((e) => e.kind === 'module');
45
+ }
46
+ getProviders(entries) {
47
+ return entries.filter((e) => e.kind === 'provider');
48
+ }
49
+ getDecorators(entries) {
50
+ return entries.filter((e) => e.kind === 'decorator');
51
+ }
52
+ getJobs(entries) {
53
+ return entries.filter((e) => e.kind === 'cron');
54
+ }
55
+ getQueues(entries) {
56
+ return entries.filter((e) => e.kind === 'queue');
57
+ }
58
+ getWebSockets(entries) {
59
+ return entries.filter((e) => e.kind === 'websocket');
60
+ }
61
+ getAgents(entries) {
62
+ return entries.filter((e) => e.kind === 'agent');
63
+ }
64
+ getRag(entries) {
65
+ return entries.filter((e) => e.kind === 'rag');
66
+ }
67
+ getPrompts(entries) {
68
+ return entries.filter((e) => e.kind === 'prompt');
69
+ }
70
+ getAIFunctions(entries) {
71
+ return entries.filter((e) => e.kind === 'aifunction');
72
+ }
73
+ getEvents(entries) {
74
+ return entries.filter((e) => e.kind === 'event');
75
+ }
76
+ getGraphQL(entries) {
77
+ return entries.filter((e) => e.kind === 'graphql');
78
+ }
79
+ getGrpc(entries) {
80
+ return entries.filter((e) => e.kind === 'grpc');
81
+ }
82
+ getKafka(entries) {
83
+ return entries.filter((e) => e.kind === 'kafka');
84
+ }
85
+ getFlows(entries) {
86
+ return entries.filter((e) => e.kind === 'flow');
87
+ }
88
+ getDataPipelines(entries) {
89
+ return entries.filter((e) => e.kind === 'data');
90
+ }
91
+ getServerless(entries) {
92
+ return entries.filter((e) => e.kind === 'serverless');
93
+ }
94
+ getMLModels(entries) {
95
+ return entries.filter((e) => e.kind === 'ml');
96
+ }
97
+ getByKind(entries, kind) {
98
+ return entries.filter((e) => e.kind === kind);
99
+ }
100
+ getGroupedSnapshot(entries) {
101
+ const knownKinds = [
102
+ 'route',
103
+ 'decorator',
104
+ 'module',
105
+ 'provider',
106
+ 'websocket',
107
+ 'cron',
108
+ 'queue',
109
+ 'agent',
110
+ 'rag',
111
+ 'prompt',
112
+ 'aifunction',
113
+ 'event',
114
+ 'graphql',
115
+ 'grpc',
116
+ 'kafka',
117
+ 'flow',
118
+ 'data',
119
+ 'serverless',
120
+ 'ml',
121
+ ];
122
+ return {
123
+ routes: this.getRoutes(entries),
124
+ decorators: this.getDecorators(entries),
125
+ modules: this.getModules(entries),
126
+ providers: this.getProviders(entries),
127
+ websockets: this.getWebSockets(entries),
128
+ jobs: this.getJobs(entries),
129
+ queues: this.getQueues(entries),
130
+ agents: this.getAgents(entries),
131
+ rag: this.getRag(entries),
132
+ prompts: this.getPrompts(entries),
133
+ aifunctions: this.getAIFunctions(entries),
134
+ events: this.getEvents(entries),
135
+ graphql: this.getGraphQL(entries),
136
+ grpc: this.getGrpc(entries),
137
+ kafka: this.getKafka(entries),
138
+ flows: this.getFlows(entries),
139
+ dataPipelines: this.getDataPipelines(entries),
140
+ serverless: this.getServerless(entries),
141
+ mlModels: this.getMLModels(entries),
142
+ other: entries.filter((e) => !knownKinds.includes(e.kind)),
143
+ };
144
+ }
145
+ }
146
+ exports.HazelInspectorService = HazelInspectorService;
package/package.json ADDED
@@ -0,0 +1,124 @@
1
+ {
2
+ "name": "@hazeljs/inspector",
3
+ "version": "0.2.0-rc.5",
4
+ "description": "Framework-aware runtime inspector for HazelJS - metadata explorer and DevTools UI",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist",
9
+ "ui-dist"
10
+ ],
11
+ "scripts": {
12
+ "build": "tsc && npm run build:ui",
13
+ "build:ui": "vite build",
14
+ "test": "jest --coverage --passWithNoTests",
15
+ "test:ci": "jest --coverage --coverageReporters=text --coverageReporters=lcov --no-coverage-threshold",
16
+ "lint": "eslint \"src/**/*.{ts,tsx}\"",
17
+ "lint:fix": "eslint \"src/**/*.{ts,tsx}\" --fix",
18
+ "clean": "rm -rf dist ui-dist"
19
+ },
20
+ "dependencies": {
21
+ "cron-parser": "^4.9.0",
22
+ "reflect-metadata": "^0.2.2"
23
+ },
24
+ "peerDependencies": {
25
+ "@hazeljs/agent": ">=0.2.0-beta.0",
26
+ "@hazeljs/ai": ">=0.2.0-beta.0",
27
+ "@hazeljs/core": ">=0.2.0-beta.0",
28
+ "@hazeljs/cron": ">=0.2.0-beta.0",
29
+ "@hazeljs/data": ">=0.2.0-beta.0",
30
+ "@hazeljs/event-emitter": ">=0.2.0-beta.0",
31
+ "@hazeljs/flow": ">=0.2.0-beta.0",
32
+ "@hazeljs/graphql": ">=0.2.0-beta.0",
33
+ "@hazeljs/grpc": ">=0.2.0-beta.0",
34
+ "@hazeljs/kafka": ">=0.2.0-beta.0",
35
+ "@hazeljs/ml": ">=0.2.0-beta.0",
36
+ "@hazeljs/prompts": ">=0.2.0-beta.0",
37
+ "@hazeljs/queue": ">=0.2.0-beta.0",
38
+ "@hazeljs/rag": ">=0.2.0-beta.0",
39
+ "@hazeljs/serverless": ">=0.2.0-beta.0",
40
+ "@hazeljs/websocket": ">=0.2.0-beta.0"
41
+ },
42
+ "peerDependenciesMeta": {
43
+ "@hazeljs/cron": {
44
+ "optional": true
45
+ },
46
+ "@hazeljs/queue": {
47
+ "optional": true
48
+ },
49
+ "@hazeljs/websocket": {
50
+ "optional": true
51
+ },
52
+ "@hazeljs/agent": {
53
+ "optional": true
54
+ },
55
+ "@hazeljs/ai": {
56
+ "optional": true
57
+ },
58
+ "@hazeljs/rag": {
59
+ "optional": true
60
+ },
61
+ "@hazeljs/prompts": {
62
+ "optional": true
63
+ },
64
+ "@hazeljs/event-emitter": {
65
+ "optional": true
66
+ },
67
+ "@hazeljs/graphql": {
68
+ "optional": true
69
+ },
70
+ "@hazeljs/grpc": {
71
+ "optional": true
72
+ },
73
+ "@hazeljs/kafka": {
74
+ "optional": true
75
+ },
76
+ "@hazeljs/flow": {
77
+ "optional": true
78
+ },
79
+ "@hazeljs/data": {
80
+ "optional": true
81
+ },
82
+ "@hazeljs/serverless": {
83
+ "optional": true
84
+ },
85
+ "@hazeljs/ml": {
86
+ "optional": true
87
+ }
88
+ },
89
+ "devDependencies": {
90
+ "@hazeljs/core": "^0.2.0-rc.5",
91
+ "@types/jest": "^29.5.14",
92
+ "@types/node": "^20.17.50",
93
+ "@typescript-eslint/eslint-plugin": "^8.18.2",
94
+ "@typescript-eslint/parser": "^8.18.2",
95
+ "eslint": "^8.56.0",
96
+ "jest": "^29.7.0",
97
+ "preact": "^10.19.0",
98
+ "ts-jest": "^29.1.2",
99
+ "typescript": "^5.3.3",
100
+ "vite": "^5.0.0"
101
+ },
102
+ "publishConfig": {
103
+ "access": "public"
104
+ },
105
+ "repository": {
106
+ "type": "git",
107
+ "url": "git+https://github.com/hazel-js/hazeljs.git",
108
+ "directory": "packages/inspector"
109
+ },
110
+ "keywords": [
111
+ "hazeljs",
112
+ "inspector",
113
+ "devtools",
114
+ "metadata",
115
+ "debugging"
116
+ ],
117
+ "author": "Muhammad Arslan <muhammad.arslan@hazeljs.ai>",
118
+ "license": "Apache-2.0",
119
+ "bugs": {
120
+ "url": "https://github.com/hazeljs/hazel-js/issues"
121
+ },
122
+ "homepage": "https://hazeljs.com",
123
+ "gitHead": "84ad3db0f24f75a9d55c6eec5997849fed3aa00e"
124
+ }
@@ -0,0 +1 @@
1
+ @import"https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=DM+Sans:wght@400;500;600;700&display=swap";:root{--hazel-primary: #1a365d;--hazel-primary-hover: #2c5282;--hazel-accent: #1a365d;--hazel-accent-hover: #2c5282;--hazel-primary-muted: rgba(26, 54, 93, .25);--hazel-accent-muted: rgba(26, 54, 93, .25);--bg-primary: #0c0e12;--bg-secondary: #14171d;--bg-tertiary: #1a1e26;--bg-elevated: #1f242b;--bg-hover: #252a33;--border: #2d333b;--border-subtle: #21262d;--text-primary: #e6edf3;--text-secondary: #8b949e;--text-muted: #6e7681;--accent: var(--hazel-accent);--accent-hover: var(--hazel-accent-hover);--accent-muted: var(--hazel-accent-muted);--success: #3fb950;--warning: #d29922;--error: #f85149;--radius-sm: 6px;--radius-md: 8px;--radius-lg: 12px;--radius-xl: 16px;--shadow-sm: 0 1px 2px rgba(0, 0, 0, .3);--shadow-md: 0 4px 12px rgba(0, 0, 0, .4);--shadow-lg: 0 8px 24px rgba(0, 0, 0, .5);--shadow-glow: 0 0 24px rgba(26, 54, 93, .2);--transition: .2s cubic-bezier(.4, 0, .2, 1)}*{box-sizing:border-box}body{margin:0;font-family:DM Sans,-apple-system,BlinkMacSystemFont,sans-serif;background:var(--bg-primary);color:var(--text-primary);font-size:14px;line-height:1.5;-webkit-font-smoothing:antialiased}#app{display:flex;min-height:100vh}.sidebar{width:240px;min-width:240px;background:var(--bg-secondary);border-right:1px solid var(--border-subtle);display:flex;flex-direction:column;padding:0}.logo{font-size:13px;font-weight:600;letter-spacing:.02em;padding:1.25rem 1.25rem 1rem;margin:0;color:var(--text-primary);border-bottom:1px solid var(--border-subtle);display:flex;align-items:center;gap:.625rem}.logo-img{width:24px;height:24px;object-fit:contain;flex-shrink:0}.logo-svg{width:24px;height:24px;color:var(--accent);flex-shrink:0}.logo-fallback{display:flex;align-items:center}.logo-text{color:var(--text-primary);font-weight:700}.sidebar nav{flex:1;padding:.75rem 0;overflow-y:auto;min-height:0;display:flex;flex-direction:column;gap:.25rem}.nav-section{display:flex;flex-direction:column;gap:.125rem}.nav-section-title{font-size:10px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:.08em;padding:.5rem 1.25rem .25rem;margin:.25rem .5rem 0 0;border-top:1px solid var(--border-subtle)}.sidebar nav a{display:flex;align-items:center;gap:.625rem;padding:.5rem 1.25rem;margin:0 .5rem;color:var(--text-secondary);text-decoration:none;border-radius:var(--radius-sm);font-weight:500;transition:color var(--transition),background var(--transition)}.sidebar nav a:hover{color:var(--text-primary);background:var(--bg-hover)}.sidebar nav a.active{color:#60a5fa;background:#3b82f633}.sidebar nav a .nav-icon{width:3px;height:14px;border-radius:2px;background:var(--text-muted);flex-shrink:0}.sidebar nav a:hover .nav-icon{background:var(--text-secondary)}.sidebar nav a.active .nav-icon{background:#60a5fa}.sidebar-footer{padding:1rem 1.25rem;border-top:1px solid var(--border-subtle)}.btn{width:100%;padding:.5rem 1rem;font-family:inherit;font-size:13px;font-weight:500;background:var(--bg-tertiary);color:var(--text-primary);border:1px solid var(--border);border-radius:var(--radius-sm);cursor:pointer;transition:background var(--transition),border-color var(--transition)}.btn:hover{background:var(--bg-hover);border-color:var(--text-muted)}.btn-primary{background:var(--accent);border-color:var(--accent);color:#fff}.btn-primary:hover{background:var(--accent-hover);border-color:var(--accent-hover);color:#fff}.content{flex:1;padding:1rem 1.5rem;overflow:auto;position:relative;background:var(--bg-primary);isolation:isolate}.content:before{content:"";position:absolute;top:0;right:0;bottom:0;left:0;background:radial-gradient(ellipse 80% 50% at 50% -20%,rgba(26,54,93,.35),transparent),radial-gradient(ellipse 60% 40% at 100% 50%,rgba(26,54,93,.08),transparent),radial-gradient(ellipse 40% 30% at 0% 80%,rgba(26,54,93,.15),transparent);pointer-events:none;z-index:0}.content>*{position:relative;z-index:1}.toolbar{margin-bottom:1rem;display:flex;align-items:center;gap:.75rem;flex-wrap:wrap;padding:.5rem .75rem;background:var(--bg-secondary);border:1px solid var(--border-subtle);border-radius:var(--radius-md)}.toolbar-actions{display:flex;align-items:center;gap:.75rem}.toolbar-toggle{display:flex;align-items:center;gap:.5rem;font-size:13px;color:var(--text-secondary);cursor:pointer}.toolbar-toggle input{accent-color:var(--accent)}.btn-sm{width:auto;padding:.4rem .75rem}.overview-status-bar{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:.75rem;padding:.75rem 1rem;margin-bottom:1rem;background:var(--bg-secondary);border:1px solid var(--border-subtle);border-radius:var(--radius-md)}.status-bar-left{display:flex;align-items:center;gap:.75rem}.status-dot{width:8px;height:8px;border-radius:50%;background:var(--text-muted);animation:pulse-dot 2s ease-in-out infinite}.status-dot.healthy{background:var(--success);box-shadow:0 0 8px #3fb95080}.status-dot.unhealthy{background:var(--error);box-shadow:0 0 8px #f8514980}.status-text{font-weight:600;font-size:14px;color:var(--text-primary)}.status-uptime{font-size:12px;font-family:JetBrains Mono,monospace}.quick-stats{display:flex;align-items:center;gap:1.5rem;font-size:12px;color:var(--text-secondary)}.quick-stat{display:flex;align-items:center;gap:.375rem}.quick-stat strong{color:var(--accent);font-weight:600}.quick-stat.muted{color:var(--text-muted)}@keyframes pulse-dot{0%,to{opacity:1}50%{opacity:.6}}.loading-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background:#0c0e12e6;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:1.25rem;z-index:1000;color:var(--text-secondary)}.loading-spinner{width:44px;height:44px;border:3px solid var(--border-subtle);border-top-color:var(--accent);border-radius:50%;animation:spin .7s cubic-bezier(.5,0,.5,1) infinite}@keyframes spin{to{transform:rotate(360deg)}}.health-container{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:1rem;margin-bottom:1.5rem}.health-card{background:var(--bg-secondary);border:1px solid var(--border-subtle);border-radius:var(--radius-md);padding:1rem 1.25rem}.health-card.healthy{border-left:4px solid var(--success);background:linear-gradient(90deg,rgba(63,185,80,.06) 0%,transparent 100%)}.health-card.unhealthy{border-left:4px solid var(--error);background:linear-gradient(90deg,rgba(248,81,73,.06) 0%,transparent 100%)}.health-card.unknown{border-left:4px solid var(--warning);background:linear-gradient(90deg,rgba(210,153,34,.06) 0%,transparent 100%)}.health-card h3{margin:0 0 .5rem;font-size:14px}.health-card .status{font-weight:600}.health-card.healthy .status{color:var(--success)}.health-card.unhealthy .status{color:var(--error)}.health-card.unknown .status{color:var(--warning)}.health-card .endpoint{font-size:10px;color:var(--text-muted);margin-top:.375rem;font-family:JetBrains Mono,monospace}.overview-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));grid-template-rows:repeat(2,minmax(0,1fr));gap:1rem;margin-bottom:1.5rem;min-height:340px;align-items:stretch}@media (max-width: 900px){.overview-grid{grid-template-columns:repeat(2,minmax(0,1fr));grid-template-rows:repeat(3,minmax(0,1fr));min-height:510px}}@media (max-width: 600px){.overview-grid{grid-template-columns:1fr;grid-template-rows:repeat(6,minmax(0,1fr));min-height:720px}}.overview-block{position:relative;min-width:0;min-height:0;display:flex;flex-direction:column;background:var(--bg-secondary);border:1px solid var(--border-subtle);border-radius:var(--radius-md);padding:1rem 1.25rem;overflow-y:auto;transition:border-color var(--transition),box-shadow var(--transition)}.overview-block:before{content:"";position:absolute;top:0;left:0;right:0;height:2px;background:linear-gradient(90deg,var(--accent) 0%,transparent 70%);opacity:.5}.overview-block:hover{border-color:var(--border);box-shadow:var(--shadow-sm)}.overview-block-title{margin:0 0 .75rem;font-size:10px;font-weight:700;color:var(--text-muted);text-transform:uppercase;letter-spacing:.1em}.overview-env .overview-block-title,.overview-health .overview-block-title,.overview-runtime .overview-block-title,.overview-gateway .overview-block-title,.overview-discovery .overview-block-title,.overview-resilience .overview-block-title{color:var(--text-secondary)}.env-info{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:1rem;margin-bottom:1.5rem}.env-info-compact{margin-bottom:0;gap:.5rem}.env-info .env-item{font-size:12px;color:var(--text-secondary);padding:.35rem 0;border-bottom:1px solid var(--border-subtle)}.env-info .env-item:last-child{border-bottom:none}.env-info .env-item strong{color:var(--text-primary);font-weight:600;display:block;margin-bottom:.125rem}.health-cards-inline{display:flex;flex-wrap:wrap;gap:.5rem}.health-cards-inline .health-card{flex:1;min-width:0;padding:.75rem 1rem;border-radius:var(--radius-sm);transition:box-shadow var(--transition)}.health-cards-inline .health-card:hover{box-shadow:var(--shadow-sm)}.health-cards-inline .health-card h3{font-size:11px;font-weight:600;margin-bottom:.375rem;color:var(--text-secondary)}.health-cards-inline .health-card .status{font-size:13px;font-weight:700}.runtime-cards-inline{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:.75rem}.runtime-cards-inline .card{min-width:0;padding:.75rem 1rem;background:var(--bg-tertiary);border-radius:var(--radius-sm);border:1px solid var(--border-subtle);transition:border-color var(--transition),background var(--transition)}.runtime-cards-inline .card:hover{background:var(--bg-elevated);border-color:var(--border)}.runtime-cards-inline .card .card-icon{width:22px;height:22px;color:var(--accent);margin-bottom:.35rem}.runtime-cards-inline .card .card-label{font-size:9px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--text-muted);margin-bottom:.2rem}.runtime-cards-inline .card .card-value{font-size:1.1rem;font-weight:600;font-family:JetBrains Mono,monospace}.runtime-cards-inline .card .card-value.highlight{color:var(--accent)}.chart-inline{height:120px;margin-bottom:1rem}.overview-gateway-stats,.overview-discovery-stats,.overview-resilience-stats{display:flex;flex-wrap:wrap;gap:.75rem;margin-bottom:.75rem}.overview-stat{display:flex;flex-direction:column;gap:.125rem;padding:.4rem .6rem;background:var(--bg-tertiary);border-radius:var(--radius-sm);border:1px solid var(--border-subtle);min-width:0}.overview-stat .stat-value{font-size:1.1rem;font-weight:700;font-family:JetBrains Mono,monospace;color:var(--accent)}.overview-stat .stat-label{font-size:9px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:.05em}.overview-gateway-routes,.overview-discovery-services,.overview-resilience-states{font-size:11px;color:var(--text-secondary);line-height:1.5;min-width:0;overflow-wrap:break-word;word-break:break-word}.overview-resilience-states{display:flex;flex-wrap:wrap;gap:.375rem}.overview-gateway-routes code,.overview-discovery-services code,.overview-resilience-states code{font-size:10px;padding:.15rem .35rem;background:var(--bg-tertiary);border-radius:4px;border:1px solid var(--border-subtle);font-family:JetBrains Mono,monospace}.discovery-service{margin-right:.5rem}.cb-state{display:inline-block;padding:.15rem .4rem;border-radius:4px;font-size:11px;font-weight:600}.cb-state.closed{color:var(--success);background:#3fb9501f}.cb-state.open{color:var(--error);background:#f851491f}.cb-state.half-open{color:var(--warning);background:#d299221f}.muted{color:var(--text-muted);font-size:11px}.detail-actions{display:flex;gap:.5rem}.modal{position:fixed;top:0;right:0;bottom:0;left:0;background:#0009;display:flex;align-items:center;justify-content:center;z-index:200}.modal-content{background:var(--bg-secondary);border:1px solid var(--border);border-radius:var(--radius-lg);padding:1.5rem;max-width:480px;width:90%}.modal-content.modal-wide{max-width:720px}.playground-output{background:var(--bg-tertiary);border:1px solid var(--border);border-radius:var(--radius-sm);padding:1rem;font-size:12px;max-height:200px;overflow:auto;white-space:pre-wrap;word-break:break-word}.playground-meta{font-size:12px;color:var(--text-muted);margin-top:.5rem}.playground-actions{margin:.5rem 0}.diff-sources{display:grid;grid-template-columns:1fr 1fr;gap:1rem;margin-bottom:1rem}.diff-hint{font-size:12px;color:var(--text-muted);margin:.25rem 0}.diff-result{margin-top:1rem;padding:1rem;background:var(--bg-tertiary);border-radius:var(--radius-sm);max-height:300px;overflow:auto}.diff-result .added{color:var(--success)}.diff-result .removed{color:var(--error)}.diff-result .changed{color:var(--warning)}.module-graph-container{margin-bottom:1rem;padding:1rem;background:var(--bg-secondary);border:1px solid var(--border-subtle);border-radius:var(--radius-md);min-height:200px}.module-graph-svg{width:100%;height:300px}.rag-explorer{margin-bottom:1rem;padding:1rem;background:var(--bg-secondary);border:1px solid var(--border-subtle);border-radius:var(--radius-md)}.rag-search-row{display:flex;gap:.5rem;margin-top:.5rem}.rag-search-row input{flex:1}.rag-results{margin-top:1rem;max-height:300px;overflow:auto}.rag-result-item{padding:.75rem;border-bottom:1px solid var(--border-subtle);font-size:12px}.rag-result-item .score{color:var(--accent);font-weight:600}.modal-content h3{margin:0 0 1rem;font-size:16px}.modal-body,.form-group{margin-bottom:1rem}.form-group label{display:block;font-size:12px;font-weight:600;color:var(--text-muted);margin-bottom:.5rem}.form-group input,.form-group select,.form-group textarea{width:100%;padding:.5rem;border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--bg-tertiary);color:var(--text-primary);font-family:JetBrains Mono,monospace}.modal-footer{display:flex;gap:.5rem;justify-content:flex-end}.test-result{margin-top:1rem;padding:1rem;border-radius:var(--radius-sm);font-family:JetBrains Mono,monospace;font-size:12px;max-height:200px;overflow:auto}.test-result.success{background:#3fb95026;color:var(--success)}.test-result.error{background:#f8514926;color:var(--error)}.chart-container{margin-bottom:1.5rem;height:200px;background:var(--bg-secondary);border:1px solid var(--border-subtle);border-radius:var(--radius-md);padding:1rem}.flow-diagram{--flow-edge: var(--text-muted);display:flex;flex-direction:column;gap:.5rem;padding:.5rem;background:var(--bg-secondary);border:1px solid var(--border-subtle);border-radius:var(--radius-md);margin-bottom:.5rem}.flow-diagram-card{display:flex;flex-direction:column;gap:.35rem;background:var(--bg-tertiary);border:1px solid var(--border-subtle);border-radius:var(--radius-sm);padding:.35rem .5rem;overflow:hidden}.flow-diagram-badge{display:inline-flex;align-items:center;font-size:.65rem;font-weight:600;color:var(--text-secondary);text-transform:uppercase;letter-spacing:.04em}.flow-diagram-svg-wrap{height:64px;overflow:hidden;border-radius:var(--radius-sm);background:var(--bg-primary)}.flow-diagram-svg{display:block;width:100%;height:100%;object-fit:contain;object-position:left center}.flow-runtime-hint{display:flex;flex-wrap:wrap;align-items:center;gap:.35rem .5rem;font-size:11px;color:var(--text-muted);margin-bottom:.5rem;padding:.4rem .6rem;background:var(--bg-secondary);border:1px solid var(--border-subtle);border-radius:var(--radius-sm)}.flow-runtime-label{font-weight:500;color:var(--text-secondary)}.flow-runtime-hint code{font-size:10px;padding:.1rem .3rem;background:var(--bg-tertiary);border-radius:4px;font-family:JetBrains Mono,monospace}.flow-runtime-sep{color:var(--border);font-weight:300}#flows .section-title{margin-bottom:.5rem}.flow-diagram-card .flow-node-group{cursor:default}.flow-diagram-card .flow-node-group:hover .flow-diagram-node{filter:brightness(1.08)}.flow-diagram-node{fill:var(--bg-elevated);stroke:var(--border-subtle);stroke-width:1;transition:filter .15s ease}.flow-diagram-node.entry{fill:var(--accent-muted);stroke:var(--accent)}.flow-group{display:flex;flex-wrap:wrap;align-items:center;gap:.5rem}.flow-node{padding:.5rem 1rem;background:var(--bg-tertiary);border:1px solid var(--border);border-radius:var(--radius-sm);font-size:12px;font-family:JetBrains Mono,monospace}.flow-node.entry{border-color:var(--accent);background:var(--accent-muted)}.offline-banner{background:var(--error);color:#fff;padding:1rem 1.5rem;border-radius:var(--radius-md);margin-bottom:1rem;display:flex;align-items:center;justify-content:space-between;gap:1rem}.offline-banner button{padding:.5rem 1rem;background:#fff3;border:none;border-radius:var(--radius-sm);color:#fff;cursor:pointer;font-weight:600}.offline-banner button:hover{background:#ffffff4d}th.sortable{cursor:pointer;-webkit-user-select:none;user-select:none}th.sortable:hover{color:var(--accent)}th.sortable:after{content:" ▾";opacity:.5;font-size:10px}th.sortable.asc:after{content:" ▴";opacity:1}th.sortable.desc:after{content:" ▾";opacity:1}.search{flex:1;max-width:360px;padding:.5rem 1rem .5rem 2.5rem;font-family:JetBrains Mono,monospace;font-size:13px;border:1px solid var(--border);border-radius:var(--radius-md);background:var(--bg-secondary);color:var(--text-primary);transition:border-color var(--transition),box-shadow var(--transition)}.search::placeholder{color:var(--text-muted)}.search:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 2px var(--accent-muted)}.search-wrapper{position:relative}.search-wrapper:before{content:"⌕";position:absolute;left:.875rem;top:50%;transform:translateY(-50%);color:var(--text-muted);font-size:14px;pointer-events:none}.search-wrapper .search{padding-left:2.25rem}.tab-panel{display:none;animation:fadeIn .2s ease}.tab-panel.active{display:block}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}#summary-cards{display:flex;flex-direction:column;gap:2rem;margin-top:.5rem}#summary-cards:before{content:"Application Summary";display:block;font-size:10px;font-weight:700;color:var(--text-muted);text-transform:uppercase;letter-spacing:.12em;margin-bottom:1rem;padding-bottom:.75rem;border-bottom:1px solid var(--border-subtle)}.overview-section{display:flex;flex-direction:column;gap:1rem}.overview-section-title{margin:0;font-size:12px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:.08em;padding-bottom:.5rem;border-bottom:1px solid var(--border-subtle)}.overview-section-cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:1rem}.cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:1rem}.overview-section .card{cursor:pointer;background:#14171d99;transition:border-color var(--transition),box-shadow var(--transition),transform var(--transition),background var(--transition)}.overview-section .card:hover{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent-muted);background:#1f242bcc;transform:translate(4px)}.overview-section .card .card-icon{color:var(--text-muted);transition:color var(--transition)}.overview-section .card:hover .card-icon{color:var(--accent)}.card{background:var(--bg-secondary);border:1px solid var(--border-subtle);border-radius:var(--radius-md);padding:1.25rem;transition:border-color var(--transition),box-shadow var(--transition);display:flex;flex-direction:column;gap:.5rem;cursor:default}.card:hover{border-color:var(--border);box-shadow:var(--shadow-sm)}.card .card-icon{width:36px;height:36px;display:flex;align-items:center;justify-content:center;background:var(--accent-muted);color:var(--accent);border-radius:var(--radius-sm);flex-shrink:0}.card .card-icon svg{flex-shrink:0}.card .card-label{font-size:11px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:0}.card .card-value{font-size:1.75rem;font-weight:600;font-family:JetBrains Mono,monospace;color:var(--text-primary)}.card .card-value.highlight{color:var(--accent)}.table-container{overflow-x:auto;border:1px solid var(--border-subtle);border-radius:var(--radius-md);background:var(--bg-secondary)}table{width:100%;border-collapse:collapse;font-size:13px}th,td{padding:.75rem 1rem;text-align:left;border-bottom:1px solid var(--border-subtle)}th{background:var(--bg-tertiary);color:var(--text-muted);font-weight:600;font-size:11px;text-transform:uppercase;letter-spacing:.05em}tr{transition:background var(--transition)}tr:hover{background:var(--bg-hover)}tr:last-child td{border-bottom:none}tr[data-id]{cursor:pointer}td{font-family:JetBrains Mono,monospace;font-size:12px}.badge{display:inline-block;padding:.2rem .5rem;font-size:10px;font-weight:600;text-transform:uppercase;border-radius:4px}.badge-get{background:#3fb95033;color:var(--success)}.badge-post{background:#d2992233;color:var(--warning)}.badge-put{background:#388bfd33;color:#389bfd}.badge-patch{background:#a371f733;color:#a371f7}.badge-delete{background:#f8514933;color:var(--error)}.badge-default{background:var(--bg-tertiary);color:var(--text-muted)}.detail-panel{position:fixed;top:0;right:0;width:420px;max-width:90vw;height:100%;background:var(--bg-secondary);border-left:1px solid var(--border);box-shadow:-8px 0 24px #0006;padding:0;overflow:hidden;z-index:100;display:flex;flex-direction:column;animation:slideIn .2s ease}.hidden{display:none!important}.info-card{background:var(--bg-secondary);border:1px solid var(--border);border-radius:var(--radius-md);padding:1rem 1.25rem;margin-bottom:1rem}.info-card-title{margin:0 0 .5rem;font-size:14px;font-weight:600;color:var(--text-primary)}.info-card-text{margin:.25rem 0;font-size:13px;color:var(--text-secondary);line-height:1.5}.info-card code{background:var(--bg-tertiary);padding:.15rem .4rem;border-radius:4px;font-family:JetBrains Mono,monospace;font-size:12px}.overview-error{background:var(--bg-secondary);border:1px solid var(--error);border-radius:var(--radius-md);padding:1.5rem;color:var(--text-secondary)}.overview-error a{color:var(--accent)}@keyframes slideIn{0%{transform:translate(100%)}to{transform:translate(0)}}.detail-header{display:flex;justify-content:space-between;align-items:center;padding:1rem 1.25rem;border-bottom:1px solid var(--border-subtle);background:var(--bg-tertiary)}.detail-header h2{margin:0;font-size:14px;font-weight:600;color:var(--text-primary)}#detail-content{flex:1;padding:1.25rem;margin:0;font-family:JetBrains Mono,monospace;font-size:12px;line-height:1.6;white-space:pre-wrap;word-break:break-word;overflow:auto;color:var(--text-secondary)}#detail-content .json-key{color:#7ee787}#detail-content .json-string{color:#a5d6ff}#detail-content .json-number{color:#79c0ff}#detail-content .json-boolean{color:#ff7b72}#detail-content .json-null{color:var(--text-muted)}td.empty-state{text-align:center;padding:3rem 2rem!important;color:var(--text-muted)}.empty-state p{margin:.5rem 0;font-size:14px}.content a{color:var(--accent);text-decoration:none}.content a:hover{text-decoration:underline}.section-title{font-size:16px;font-weight:600;margin:0 0 1rem;color:var(--text-primary)}