@portel/photon-core 2.3.0 → 2.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/dist/asset-discovery.d.ts +25 -0
- package/dist/asset-discovery.d.ts.map +1 -0
- package/dist/asset-discovery.js +145 -0
- package/dist/asset-discovery.js.map +1 -0
- package/dist/base.d.ts +6 -0
- package/dist/base.d.ts.map +1 -1
- package/dist/base.js +11 -1
- package/dist/base.js.map +1 -1
- package/dist/class-detection.d.ts +32 -0
- package/dist/class-detection.d.ts.map +1 -0
- package/dist/class-detection.js +86 -0
- package/dist/class-detection.js.map +1 -0
- package/dist/collections/ReactiveArray.d.ts +97 -0
- package/dist/collections/ReactiveArray.d.ts.map +1 -0
- package/dist/collections/ReactiveArray.js +158 -0
- package/dist/collections/ReactiveArray.js.map +1 -0
- package/dist/collections/ReactiveMap.d.ts +50 -0
- package/dist/collections/ReactiveMap.d.ts.map +1 -0
- package/dist/collections/ReactiveMap.js +71 -0
- package/dist/collections/ReactiveMap.js.map +1 -0
- package/dist/collections/ReactiveSet.d.ts +50 -0
- package/dist/collections/ReactiveSet.d.ts.map +1 -0
- package/dist/collections/ReactiveSet.js +71 -0
- package/dist/collections/ReactiveSet.js.map +1 -0
- package/dist/collections/index.d.ts +44 -0
- package/dist/collections/index.d.ts.map +1 -0
- package/dist/collections/index.js +44 -0
- package/dist/collections/index.js.map +1 -0
- package/dist/compiler.d.ts +22 -0
- package/dist/compiler.d.ts.map +1 -0
- package/dist/compiler.js +48 -0
- package/dist/compiler.js.map +1 -0
- package/dist/env-utils.d.ts +61 -0
- package/dist/env-utils.d.ts.map +1 -0
- package/dist/env-utils.js +171 -0
- package/dist/env-utils.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -1
- package/dist/mime-types.d.ts +13 -0
- package/dist/mime-types.d.ts.map +1 -0
- package/dist/mime-types.js +47 -0
- package/dist/mime-types.js.map +1 -0
- package/dist/rendering/index.d.ts +49 -0
- package/dist/rendering/index.d.ts.map +1 -1
- package/dist/rendering/index.js +153 -0
- package/dist/rendering/index.js.map +1 -1
- package/dist/schema-extractor.d.ts.map +1 -1
- package/dist/schema-extractor.js +3 -0
- package/dist/schema-extractor.js.map +1 -1
- package/dist/types.d.ts +4 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/ui-types/Cards.d.ts +139 -0
- package/dist/ui-types/Cards.d.ts.map +1 -0
- package/dist/ui-types/Cards.js +235 -0
- package/dist/ui-types/Cards.js.map +1 -0
- package/dist/ui-types/Chart.d.ts +136 -0
- package/dist/ui-types/Chart.d.ts.map +1 -0
- package/dist/ui-types/Chart.js +188 -0
- package/dist/ui-types/Chart.js.map +1 -0
- package/dist/ui-types/Field.d.ts +342 -0
- package/dist/ui-types/Field.d.ts.map +1 -0
- package/dist/ui-types/Field.js +200 -0
- package/dist/ui-types/Field.js.map +1 -0
- package/dist/ui-types/FieldRenderer.d.ts +32 -0
- package/dist/ui-types/FieldRenderer.d.ts.map +1 -0
- package/dist/ui-types/FieldRenderer.js +277 -0
- package/dist/ui-types/FieldRenderer.js.map +1 -0
- package/dist/ui-types/Form.d.ts +212 -0
- package/dist/ui-types/Form.d.ts.map +1 -0
- package/dist/ui-types/Form.js +278 -0
- package/dist/ui-types/Form.js.map +1 -0
- package/dist/ui-types/Progress.d.ts +130 -0
- package/dist/ui-types/Progress.d.ts.map +1 -0
- package/dist/ui-types/Progress.js +191 -0
- package/dist/ui-types/Progress.js.map +1 -0
- package/dist/ui-types/Stats.d.ts +108 -0
- package/dist/ui-types/Stats.d.ts.map +1 -0
- package/dist/ui-types/Stats.js +162 -0
- package/dist/ui-types/Stats.js.map +1 -0
- package/dist/ui-types/Table.d.ts +206 -0
- package/dist/ui-types/Table.d.ts.map +1 -0
- package/dist/ui-types/Table.js +367 -0
- package/dist/ui-types/Table.js.map +1 -0
- package/dist/ui-types/base.d.ts +17 -0
- package/dist/ui-types/base.d.ts.map +1 -0
- package/dist/ui-types/base.js +18 -0
- package/dist/ui-types/base.js.map +1 -0
- package/dist/ui-types/index.d.ts +42 -0
- package/dist/ui-types/index.d.ts.map +1 -0
- package/dist/ui-types/index.js +50 -0
- package/dist/ui-types/index.js.map +1 -0
- package/dist/validation.d.ts +51 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +249 -0
- package/dist/validation.js.map +1 -0
- package/dist/version-check.d.ts +22 -0
- package/dist/version-check.d.ts.map +1 -0
- package/dist/version-check.js +91 -0
- package/dist/version-check.js.map +1 -0
- package/package.json +2 -2
- package/src/asset-discovery.ts +161 -0
- package/src/base.ts +13 -1
- package/src/class-detection.ts +94 -0
- package/src/collections/ReactiveArray.ts +179 -0
- package/src/collections/ReactiveMap.ts +81 -0
- package/src/collections/ReactiveSet.ts +81 -0
- package/src/collections/index.ts +44 -0
- package/src/compiler.ts +57 -0
- package/src/env-utils.ts +216 -0
- package/src/index.ts +155 -0
- package/src/mime-types.ts +49 -0
- package/src/rendering/index.ts +197 -0
- package/src/schema-extractor.ts +4 -0
- package/src/types.ts +4 -0
- package/src/ui-types/Cards.ts +286 -0
- package/src/ui-types/Chart.ts +239 -0
- package/src/ui-types/Field.ts +594 -0
- package/src/ui-types/FieldRenderer.ts +364 -0
- package/src/ui-types/Form.ts +363 -0
- package/src/ui-types/Progress.ts +237 -0
- package/src/ui-types/Stats.ts +204 -0
- package/src/ui-types/Table.ts +438 -0
- package/src/ui-types/base.ts +25 -0
- package/src/ui-types/index.ts +96 -0
- package/src/ui-types/ui-types.test.ts +444 -0
- package/src/validation.ts +363 -0
- package/src/version-check.ts +92 -0
package/src/env-utils.ts
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment Variable Utilities
|
|
3
|
+
*
|
|
4
|
+
* Shared logic for resolving constructor parameters from environment variables.
|
|
5
|
+
* Extracted from photon's config-docs.ts and lumina's photon-loader.ts.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Minimal constructor parameter info needed for env resolution.
|
|
10
|
+
* Compatible with both photon-core's full ConstructorParam and
|
|
11
|
+
* lumina's simplified version (which omits isPrimitive).
|
|
12
|
+
*/
|
|
13
|
+
export interface EnvConstructorParam {
|
|
14
|
+
name: string;
|
|
15
|
+
type: string;
|
|
16
|
+
isOptional: boolean;
|
|
17
|
+
hasDefault: boolean;
|
|
18
|
+
defaultValue?: any;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Info about a missing required parameter
|
|
23
|
+
*/
|
|
24
|
+
export interface MissingParamInfo {
|
|
25
|
+
paramName: string;
|
|
26
|
+
envVarName: string;
|
|
27
|
+
type: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Convert a photon name and parameter name to an environment variable name
|
|
32
|
+
*
|
|
33
|
+
* @example toEnvVarName('my-mcp', 'apiKey') → 'MY_MCP_API_KEY'
|
|
34
|
+
*/
|
|
35
|
+
export function toEnvVarName(photonName: string, paramName: string): string {
|
|
36
|
+
const prefix = photonName.toUpperCase().replace(/-/g, '_');
|
|
37
|
+
const suffix = paramName
|
|
38
|
+
.replace(/([A-Z])/g, '_$1')
|
|
39
|
+
.toUpperCase()
|
|
40
|
+
.replace(/^_/, '');
|
|
41
|
+
return `${prefix}_${suffix}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Parse an environment variable string value to the appropriate type
|
|
46
|
+
*/
|
|
47
|
+
export function parseEnvValue(value: string, type: string): any {
|
|
48
|
+
switch (type) {
|
|
49
|
+
case 'number':
|
|
50
|
+
return parseFloat(value);
|
|
51
|
+
case 'boolean':
|
|
52
|
+
return value.toLowerCase() === 'true';
|
|
53
|
+
case 'string':
|
|
54
|
+
default:
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Generate a sensible example value for a parameter based on its name and type
|
|
61
|
+
*/
|
|
62
|
+
export function generateExampleValue(paramName: string, paramType: string): string | null {
|
|
63
|
+
const lowerName = paramName.toLowerCase();
|
|
64
|
+
|
|
65
|
+
if (lowerName.includes('apikey') || lowerName.includes('api_key')) {
|
|
66
|
+
return 'sk_your_api_key_here';
|
|
67
|
+
}
|
|
68
|
+
if (lowerName.includes('token') || lowerName.includes('secret')) {
|
|
69
|
+
return 'your_secret_token';
|
|
70
|
+
}
|
|
71
|
+
if (lowerName.includes('url') || lowerName.includes('endpoint')) {
|
|
72
|
+
return 'https://api.example.com';
|
|
73
|
+
}
|
|
74
|
+
if (lowerName.includes('host') || lowerName.includes('server')) {
|
|
75
|
+
return 'localhost';
|
|
76
|
+
}
|
|
77
|
+
if (lowerName.includes('port')) {
|
|
78
|
+
return '5432';
|
|
79
|
+
}
|
|
80
|
+
if (lowerName.includes('database') || lowerName.includes('db')) {
|
|
81
|
+
return 'my_database';
|
|
82
|
+
}
|
|
83
|
+
if (lowerName.includes('user') || lowerName.includes('username')) {
|
|
84
|
+
return 'admin';
|
|
85
|
+
}
|
|
86
|
+
if (lowerName.includes('password')) {
|
|
87
|
+
return 'your_secure_password';
|
|
88
|
+
}
|
|
89
|
+
if (lowerName.includes('path') || lowerName.includes('dir')) {
|
|
90
|
+
return '/path/to/directory';
|
|
91
|
+
}
|
|
92
|
+
if (lowerName.includes('name')) {
|
|
93
|
+
return 'my-service';
|
|
94
|
+
}
|
|
95
|
+
if (lowerName.includes('region')) {
|
|
96
|
+
return 'us-east-1';
|
|
97
|
+
}
|
|
98
|
+
if (paramType === 'boolean') {
|
|
99
|
+
return 'true';
|
|
100
|
+
}
|
|
101
|
+
if (paramType === 'number') {
|
|
102
|
+
return '3000';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Generate documentation and example env vars for constructor parameters
|
|
110
|
+
*/
|
|
111
|
+
export function summarizeConstructorParams(
|
|
112
|
+
params: EnvConstructorParam[],
|
|
113
|
+
photonName: string,
|
|
114
|
+
): { docs: string; exampleEnv: Record<string, string> } {
|
|
115
|
+
const docs = params
|
|
116
|
+
.map((param) => {
|
|
117
|
+
const envVarName = toEnvVarName(photonName, param.name);
|
|
118
|
+
const required = !param.isOptional && !param.hasDefault;
|
|
119
|
+
const status = required ? '[REQUIRED]' : '[OPTIONAL]';
|
|
120
|
+
const defaultInfo = param.hasDefault
|
|
121
|
+
? ` (default: ${JSON.stringify(param.defaultValue)})`
|
|
122
|
+
: '';
|
|
123
|
+
const exampleValue = generateExampleValue(param.name, param.type);
|
|
124
|
+
|
|
125
|
+
let line = ` • ${envVarName} ${status}`;
|
|
126
|
+
line += `\n Type: ${param.type}${defaultInfo}`;
|
|
127
|
+
if (exampleValue) {
|
|
128
|
+
line += `\n Example: ${envVarName}="${exampleValue}"`;
|
|
129
|
+
}
|
|
130
|
+
return line;
|
|
131
|
+
})
|
|
132
|
+
.join('\n\n');
|
|
133
|
+
|
|
134
|
+
const exampleEnv: Record<string, string> = {};
|
|
135
|
+
params.forEach((param) => {
|
|
136
|
+
const envVarName = toEnvVarName(photonName, param.name);
|
|
137
|
+
if (!param.isOptional && !param.hasDefault) {
|
|
138
|
+
exampleEnv[envVarName] =
|
|
139
|
+
generateExampleValue(param.name, param.type) || `your-${param.name}`;
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
return { docs, exampleEnv };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Generate a user-friendly error message for missing configuration
|
|
148
|
+
*/
|
|
149
|
+
export function generateConfigErrorMessage(
|
|
150
|
+
photonName: string,
|
|
151
|
+
missing: MissingParamInfo[],
|
|
152
|
+
): string {
|
|
153
|
+
const envVarList = missing
|
|
154
|
+
.map((m) => ` • ${m.envVarName} (${m.paramName}: ${m.type})`)
|
|
155
|
+
.join('\n');
|
|
156
|
+
const exampleEnv = Object.fromEntries(
|
|
157
|
+
missing.map((m) => [m.envVarName, `<your-${m.paramName}>`]),
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
return `
|
|
161
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
162
|
+
⚠️ Configuration Warning: ${photonName} MCP
|
|
163
|
+
|
|
164
|
+
Missing required environment variables:
|
|
165
|
+
${envVarList}
|
|
166
|
+
|
|
167
|
+
Tools will fail until configuration is fixed.
|
|
168
|
+
|
|
169
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
170
|
+
|
|
171
|
+
To fix, add environment variables to your MCP client config:
|
|
172
|
+
|
|
173
|
+
{
|
|
174
|
+
"mcpServers": {
|
|
175
|
+
"${photonName}": {
|
|
176
|
+
"command": "npx",
|
|
177
|
+
"args": ["@portel/photon", "${photonName}"],
|
|
178
|
+
"env": ${JSON.stringify(exampleEnv, null, 8).replace(/\n/g, '\n ')}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
Or run: photon ${photonName} --config
|
|
184
|
+
|
|
185
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
186
|
+
`.trim();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Resolve constructor arguments from environment variables
|
|
191
|
+
*
|
|
192
|
+
* @returns values array (aligned with params) and configError string if any required params are missing
|
|
193
|
+
*/
|
|
194
|
+
export function resolveEnvArgs(
|
|
195
|
+
params: EnvConstructorParam[],
|
|
196
|
+
photonName: string,
|
|
197
|
+
): { values: any[]; missing: MissingParamInfo[] } {
|
|
198
|
+
const values: any[] = [];
|
|
199
|
+
const missing: MissingParamInfo[] = [];
|
|
200
|
+
|
|
201
|
+
for (const param of params) {
|
|
202
|
+
const envVarName = toEnvVarName(photonName, param.name);
|
|
203
|
+
const envValue = process.env[envVarName];
|
|
204
|
+
|
|
205
|
+
if (envValue !== undefined) {
|
|
206
|
+
values.push(parseEnvValue(envValue, param.type));
|
|
207
|
+
} else if (param.hasDefault || param.isOptional) {
|
|
208
|
+
values.push(undefined);
|
|
209
|
+
} else {
|
|
210
|
+
missing.push({ paramName: param.name, envVarName, type: param.type });
|
|
211
|
+
values.push(undefined);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return { values, missing };
|
|
216
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -360,3 +360,158 @@ export {
|
|
|
360
360
|
deletePhotonConfig,
|
|
361
361
|
listConfiguredPhotons,
|
|
362
362
|
} from './config.js';
|
|
363
|
+
|
|
364
|
+
// ===== CLASS DETECTION =====
|
|
365
|
+
// Shared Photon class detection for loaders
|
|
366
|
+
export {
|
|
367
|
+
isClass,
|
|
368
|
+
hasAsyncMethods,
|
|
369
|
+
findPhotonClass,
|
|
370
|
+
findPhotonClasses,
|
|
371
|
+
} from './class-detection.js';
|
|
372
|
+
|
|
373
|
+
// ===== ENVIRONMENT UTILITIES =====
|
|
374
|
+
// Env var resolution for constructor injection
|
|
375
|
+
export {
|
|
376
|
+
toEnvVarName,
|
|
377
|
+
parseEnvValue,
|
|
378
|
+
generateExampleValue,
|
|
379
|
+
summarizeConstructorParams,
|
|
380
|
+
generateConfigErrorMessage,
|
|
381
|
+
resolveEnvArgs,
|
|
382
|
+
type MissingParamInfo,
|
|
383
|
+
type EnvConstructorParam,
|
|
384
|
+
} from './env-utils.js';
|
|
385
|
+
|
|
386
|
+
// ===== TYPESCRIPT COMPILER =====
|
|
387
|
+
// esbuild-based .photon.ts compilation with caching
|
|
388
|
+
export { compilePhotonTS } from './compiler.js';
|
|
389
|
+
|
|
390
|
+
// ===== MIME TYPES =====
|
|
391
|
+
// Extension-to-MIME mapping for assets
|
|
392
|
+
export { getMimeType } from './mime-types.js';
|
|
393
|
+
|
|
394
|
+
// ===== VERSION CHECK =====
|
|
395
|
+
// @runtime tag parsing and semver-lite compatibility check
|
|
396
|
+
export {
|
|
397
|
+
parseRuntimeRequirement,
|
|
398
|
+
checkRuntimeCompatibility,
|
|
399
|
+
} from './version-check.js';
|
|
400
|
+
|
|
401
|
+
// ===== VALIDATION =====
|
|
402
|
+
// Input validation utilities and error base classes
|
|
403
|
+
export {
|
|
404
|
+
PhotonError,
|
|
405
|
+
ValidationError,
|
|
406
|
+
type ValidationResult,
|
|
407
|
+
type Validator,
|
|
408
|
+
combineResults,
|
|
409
|
+
isString,
|
|
410
|
+
isNumber,
|
|
411
|
+
isBoolean,
|
|
412
|
+
isObject,
|
|
413
|
+
isArray,
|
|
414
|
+
notEmpty,
|
|
415
|
+
hasLength,
|
|
416
|
+
matchesPattern,
|
|
417
|
+
isEmail,
|
|
418
|
+
isUrl,
|
|
419
|
+
inRange,
|
|
420
|
+
isPositive,
|
|
421
|
+
isInteger,
|
|
422
|
+
hasArrayLength,
|
|
423
|
+
arrayOf,
|
|
424
|
+
hasFields,
|
|
425
|
+
oneOf,
|
|
426
|
+
validate,
|
|
427
|
+
validateOrThrow,
|
|
428
|
+
pathExists,
|
|
429
|
+
hasExtension,
|
|
430
|
+
assertDefined,
|
|
431
|
+
assertString,
|
|
432
|
+
assertNumber,
|
|
433
|
+
assertObject,
|
|
434
|
+
assertArray,
|
|
435
|
+
} from './validation.js';
|
|
436
|
+
|
|
437
|
+
// ===== ASSET DISCOVERY =====
|
|
438
|
+
// Discover UI, prompt, and resource assets from Photon files
|
|
439
|
+
export {
|
|
440
|
+
discoverAssets,
|
|
441
|
+
autoDiscoverAssets,
|
|
442
|
+
} from './asset-discovery.js';
|
|
443
|
+
|
|
444
|
+
// ===== MANAGED COLLECTIONS =====
|
|
445
|
+
// Auto-emit events on mutations for seamless real-time sync
|
|
446
|
+
export {
|
|
447
|
+
ReactiveArray,
|
|
448
|
+
ReactiveMap,
|
|
449
|
+
ReactiveSet,
|
|
450
|
+
type Emitter,
|
|
451
|
+
} from './collections/index.js';
|
|
452
|
+
|
|
453
|
+
// ===== PURPOSE-DRIVEN UI TYPES =====
|
|
454
|
+
// Polymorphic return types that auto-render with appropriate UI
|
|
455
|
+
export {
|
|
456
|
+
// Base
|
|
457
|
+
PhotonUIType,
|
|
458
|
+
isPhotonUIType,
|
|
459
|
+
|
|
460
|
+
// Field System (React Admin-style)
|
|
461
|
+
Field,
|
|
462
|
+
type FieldDefinition,
|
|
463
|
+
type FieldType,
|
|
464
|
+
type FieldAlignment,
|
|
465
|
+
type BaseFieldOptions,
|
|
466
|
+
type TextFieldOptions,
|
|
467
|
+
type NumberFieldOptions,
|
|
468
|
+
type CurrencyFieldOptions,
|
|
469
|
+
type DateFieldOptions,
|
|
470
|
+
type DateFormat,
|
|
471
|
+
type ImageFieldOptions,
|
|
472
|
+
type BadgeFieldOptions,
|
|
473
|
+
type RatingFieldOptions,
|
|
474
|
+
type PriceFieldOptions,
|
|
475
|
+
type StockFieldOptions,
|
|
476
|
+
type ActionItem,
|
|
477
|
+
type ActionsFieldOptions,
|
|
478
|
+
type RenderFunction,
|
|
479
|
+
getFieldValue,
|
|
480
|
+
formatFieldLabel,
|
|
481
|
+
interpolateTemplate,
|
|
482
|
+
renderFieldToText,
|
|
483
|
+
renderFieldToStructured,
|
|
484
|
+
type RenderedField,
|
|
485
|
+
|
|
486
|
+
// Data Display
|
|
487
|
+
Table,
|
|
488
|
+
type TableColumn,
|
|
489
|
+
type TableOptions,
|
|
490
|
+
type ColumnType,
|
|
491
|
+
Cards,
|
|
492
|
+
type CardFieldMapping,
|
|
493
|
+
type CardsOptions,
|
|
494
|
+
|
|
495
|
+
// Visualization
|
|
496
|
+
Chart,
|
|
497
|
+
type ChartType,
|
|
498
|
+
type ChartSeries,
|
|
499
|
+
type ChartDataPoint,
|
|
500
|
+
type ChartOptions,
|
|
501
|
+
Stats,
|
|
502
|
+
type StatItem,
|
|
503
|
+
type StatFormat,
|
|
504
|
+
type StatsOptions,
|
|
505
|
+
Progress,
|
|
506
|
+
type ProgressBar,
|
|
507
|
+
type ProgressStep,
|
|
508
|
+
type ProgressStyle,
|
|
509
|
+
type StepStatus,
|
|
510
|
+
type ProgressOptions,
|
|
511
|
+
|
|
512
|
+
// Interactive
|
|
513
|
+
Form,
|
|
514
|
+
type FormField,
|
|
515
|
+
type FieldType as FormFieldType,
|
|
516
|
+
type FormOptions,
|
|
517
|
+
} from './ui-types/index.js';
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MIME Type Utilities
|
|
3
|
+
*
|
|
4
|
+
* Extension-to-MIME mapping for Photon assets.
|
|
5
|
+
* Extracted from photon's loader.ts.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
|
|
10
|
+
const MIME_TYPES: Record<string, string> = {
|
|
11
|
+
html: 'text/html',
|
|
12
|
+
htm: 'text/html',
|
|
13
|
+
css: 'text/css',
|
|
14
|
+
js: 'application/javascript',
|
|
15
|
+
mjs: 'application/javascript',
|
|
16
|
+
jsx: 'text/jsx',
|
|
17
|
+
ts: 'text/typescript',
|
|
18
|
+
tsx: 'text/tsx',
|
|
19
|
+
json: 'application/json',
|
|
20
|
+
yaml: 'application/yaml',
|
|
21
|
+
yml: 'application/yaml',
|
|
22
|
+
xml: 'application/xml',
|
|
23
|
+
md: 'text/markdown',
|
|
24
|
+
txt: 'text/plain',
|
|
25
|
+
png: 'image/png',
|
|
26
|
+
jpg: 'image/jpeg',
|
|
27
|
+
jpeg: 'image/jpeg',
|
|
28
|
+
gif: 'image/gif',
|
|
29
|
+
svg: 'image/svg+xml',
|
|
30
|
+
webp: 'image/webp',
|
|
31
|
+
ico: 'image/x-icon',
|
|
32
|
+
woff: 'font/woff',
|
|
33
|
+
woff2: 'font/woff2',
|
|
34
|
+
ttf: 'font/ttf',
|
|
35
|
+
otf: 'font/otf',
|
|
36
|
+
pdf: 'application/pdf',
|
|
37
|
+
zip: 'application/zip',
|
|
38
|
+
csv: 'text/csv',
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get MIME type from a filename based on its extension
|
|
43
|
+
*
|
|
44
|
+
* @returns The MIME type string, or 'application/octet-stream' for unknown extensions
|
|
45
|
+
*/
|
|
46
|
+
export function getMimeType(filename: string): string {
|
|
47
|
+
const ext = path.extname(filename).toLowerCase().slice(1);
|
|
48
|
+
return MIME_TYPES[ext] || 'application/octet-stream';
|
|
49
|
+
}
|
package/src/rendering/index.ts
CHANGED
|
@@ -19,6 +19,8 @@ export * from './field-renderers.js';
|
|
|
19
19
|
export * from './template-engine.js';
|
|
20
20
|
export * from '../design-system/index.js';
|
|
21
21
|
|
|
22
|
+
import { analyzeFields, type FieldMapping } from './field-analyzer.js';
|
|
23
|
+
import { selectLayout, type LayoutType } from './layout-selector.js';
|
|
22
24
|
import { generateFieldAnalyzerJS } from './field-analyzer.js';
|
|
23
25
|
import { generateLayoutSelectorJS } from './layout-selector.js';
|
|
24
26
|
import { generateComponentsJS, generateComponentCSS } from './components.js';
|
|
@@ -61,3 +63,198 @@ export function generateSmartRenderingCSS(): string {
|
|
|
61
63
|
generateTemplateEngineCSS(),
|
|
62
64
|
].join('\n');
|
|
63
65
|
}
|
|
66
|
+
|
|
67
|
+
// ===== Smart Rendering Utilities =====
|
|
68
|
+
// Shared by NCP, Lumina, and other runtimes
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Check if data would benefit from rich HTML rendering
|
|
72
|
+
* (arrays of objects, nested structures, etc.)
|
|
73
|
+
*/
|
|
74
|
+
export function shouldUseRichRendering(data: any): boolean {
|
|
75
|
+
if (!data) return false;
|
|
76
|
+
|
|
77
|
+
// Arrays of objects benefit from list/grid rendering
|
|
78
|
+
if (Array.isArray(data) && data.length > 0 && typeof data[0] === 'object') {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Nested objects benefit from tree/card rendering
|
|
83
|
+
if (typeof data === 'object' && !Array.isArray(data)) {
|
|
84
|
+
const values = Object.values(data);
|
|
85
|
+
if (values.some(v => typeof v === 'object' && v !== null)) {
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Generate an HTML content block for MCP tool responses.
|
|
95
|
+
* Analyses data, selects layout, and embeds CSS/JS for rich rendering.
|
|
96
|
+
*/
|
|
97
|
+
export function generateHTMLContent(
|
|
98
|
+
data: any,
|
|
99
|
+
options: {
|
|
100
|
+
title?: string;
|
|
101
|
+
format?: LayoutType;
|
|
102
|
+
standalone?: boolean;
|
|
103
|
+
theme?: 'light' | 'dark';
|
|
104
|
+
} = {}
|
|
105
|
+
): string {
|
|
106
|
+
const { title, format, standalone = false, theme } = options;
|
|
107
|
+
|
|
108
|
+
let fieldMapping: FieldMapping | undefined;
|
|
109
|
+
if (data && typeof data === 'object') {
|
|
110
|
+
fieldMapping = analyzeFields(data);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const layout = format || selectLayout(data);
|
|
114
|
+
|
|
115
|
+
const renderScript = `
|
|
116
|
+
<script>
|
|
117
|
+
${generateSmartRenderingJS()}
|
|
118
|
+
|
|
119
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
120
|
+
const container = document.getElementById('smart-render-container');
|
|
121
|
+
const data = ${JSON.stringify(data)};
|
|
122
|
+
const layout = '${layout}';
|
|
123
|
+
const fieldMapping = ${JSON.stringify(fieldMapping || {})};
|
|
124
|
+
|
|
125
|
+
if (window.TemplateEngine && window.TemplateEngine.render) {
|
|
126
|
+
container.innerHTML = window.TemplateEngine.render(data, {
|
|
127
|
+
layout,
|
|
128
|
+
fieldMapping,
|
|
129
|
+
title: ${JSON.stringify(title || '')}
|
|
130
|
+
});
|
|
131
|
+
} else {
|
|
132
|
+
container.innerHTML = '<pre>' + JSON.stringify(data, null, 2) + '</pre>';
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
</script>
|
|
136
|
+
`;
|
|
137
|
+
|
|
138
|
+
const themeVars = theme ? `
|
|
139
|
+
:root {
|
|
140
|
+
--bg-primary: ${theme === 'dark' ? '#0a0a0a' : '#ffffff'};
|
|
141
|
+
--text-primary: ${theme === 'dark' ? '#f5f5f5' : '#1a1a1a'};
|
|
142
|
+
}
|
|
143
|
+
` : '';
|
|
144
|
+
|
|
145
|
+
const styles = `
|
|
146
|
+
<style>
|
|
147
|
+
${generateSmartRenderingCSS()}
|
|
148
|
+
${themeVars}
|
|
149
|
+
</style>
|
|
150
|
+
`;
|
|
151
|
+
|
|
152
|
+
const content = `
|
|
153
|
+
${styles}
|
|
154
|
+
<div id="smart-render-container" class="smart-render">
|
|
155
|
+
${title ? `<h2 class="smart-render-title">${title}</h2>` : ''}
|
|
156
|
+
<div class="loading">Loading...</div>
|
|
157
|
+
</div>
|
|
158
|
+
${renderScript}
|
|
159
|
+
`;
|
|
160
|
+
|
|
161
|
+
if (standalone) {
|
|
162
|
+
return `<!DOCTYPE html>
|
|
163
|
+
<html${theme ? ` data-theme="${theme}"` : ''}>
|
|
164
|
+
<head>
|
|
165
|
+
<meta charset="UTF-8">
|
|
166
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
167
|
+
<title>${title || 'Result'}</title>
|
|
168
|
+
</head>
|
|
169
|
+
<body>
|
|
170
|
+
${content}
|
|
171
|
+
</body>
|
|
172
|
+
</html>`;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return content;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Create an MCP content block with HTML type
|
|
180
|
+
*/
|
|
181
|
+
export function createHTMLContentBlock(
|
|
182
|
+
data: any,
|
|
183
|
+
options: {
|
|
184
|
+
title?: string;
|
|
185
|
+
format?: LayoutType;
|
|
186
|
+
} = {}
|
|
187
|
+
): { type: 'text'; text: string; mimeType?: string } {
|
|
188
|
+
return {
|
|
189
|
+
type: 'text',
|
|
190
|
+
text: generateHTMLContent(data, { ...options, standalone: false }),
|
|
191
|
+
mimeType: 'text/html'
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Generate an HTML fragment for embedding, returning separate html/css/js parts
|
|
197
|
+
*/
|
|
198
|
+
export function generateSmartRenderFragment(
|
|
199
|
+
data: any,
|
|
200
|
+
options: { format?: LayoutType } = {}
|
|
201
|
+
): { html: string; css: string; js: string } {
|
|
202
|
+
const { format } = options;
|
|
203
|
+
|
|
204
|
+
let fieldMapping: FieldMapping | undefined;
|
|
205
|
+
if (data && typeof data === 'object') {
|
|
206
|
+
fieldMapping = analyzeFields(data);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const layout = format || selectLayout(data);
|
|
210
|
+
|
|
211
|
+
const html = `<div id="smart-render-${Date.now()}" class="smart-render-container"></div>`;
|
|
212
|
+
const css = generateSmartRenderingCSS();
|
|
213
|
+
const js = `
|
|
214
|
+
${generateSmartRenderingJS()}
|
|
215
|
+
(function() {
|
|
216
|
+
const containers = document.querySelectorAll('.smart-render-container');
|
|
217
|
+
const container = containers[containers.length - 1];
|
|
218
|
+
const data = ${JSON.stringify(data)};
|
|
219
|
+
if (window.TemplateEngine && window.TemplateEngine.render) {
|
|
220
|
+
container.innerHTML = window.TemplateEngine.render(data, {
|
|
221
|
+
layout: '${layout}',
|
|
222
|
+
fieldMapping: ${JSON.stringify(fieldMapping || {})}
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
})();
|
|
226
|
+
`;
|
|
227
|
+
|
|
228
|
+
return { html, css, js };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Generate MCP content blocks with smart rendering.
|
|
233
|
+
* Returns JSON text block + optional HTML block for rich clients.
|
|
234
|
+
*/
|
|
235
|
+
export function generateMCPSmartContent(
|
|
236
|
+
data: any,
|
|
237
|
+
options: { includeHtml?: boolean; format?: LayoutType } = {}
|
|
238
|
+
): Array<{ type: string; text?: string; mimeType?: string }> {
|
|
239
|
+
const { includeHtml = true, format } = options;
|
|
240
|
+
|
|
241
|
+
const content: Array<{ type: string; text?: string; mimeType?: string }> = [];
|
|
242
|
+
|
|
243
|
+
// Always include JSON for compatibility
|
|
244
|
+
content.push({
|
|
245
|
+
type: 'text',
|
|
246
|
+
text: JSON.stringify(data, null, 2)
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// Optionally include HTML for rich rendering
|
|
250
|
+
if (includeHtml && shouldUseRichRendering(data)) {
|
|
251
|
+
const { html, css, js } = generateSmartRenderFragment(data, { format });
|
|
252
|
+
content.push({
|
|
253
|
+
type: 'text',
|
|
254
|
+
text: `<style>${css}</style>${html}<script>${js}</script>`,
|
|
255
|
+
mimeType: 'text/html'
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return content;
|
|
260
|
+
}
|
package/src/schema-extractor.ts
CHANGED
|
@@ -184,6 +184,9 @@ export class SchemaExtractor {
|
|
|
184
184
|
const scheduled = this.extractScheduled(jsdoc, methodName);
|
|
185
185
|
const locked = this.extractLocked(jsdoc, methodName);
|
|
186
186
|
|
|
187
|
+
// Check for static keyword on the method
|
|
188
|
+
const isStaticMethod = member.modifiers?.some(m => m.kind === ts.SyntaxKind.StaticKeyword) || false;
|
|
189
|
+
|
|
187
190
|
tools.push({
|
|
188
191
|
name: methodName,
|
|
189
192
|
description,
|
|
@@ -196,6 +199,7 @@ export class SchemaExtractor {
|
|
|
196
199
|
...(yields && yields.length > 0 ? { yields } : {}),
|
|
197
200
|
...(isStateful ? { isStateful: true } : {}),
|
|
198
201
|
...(autorun ? { autorun: true } : {}),
|
|
202
|
+
...(isStaticMethod ? { isStatic: true } : {}),
|
|
199
203
|
// Daemon features
|
|
200
204
|
...(webhook !== undefined ? { webhook } : {}),
|
|
201
205
|
...(scheduled ? { scheduled } : {}),
|
package/src/types.ts
CHANGED
|
@@ -63,6 +63,8 @@ export interface ExtractedSchema {
|
|
|
63
63
|
isStateful?: boolean;
|
|
64
64
|
/** True if this method should auto-execute when selected (idempotent, no required params) */
|
|
65
65
|
autorun?: boolean;
|
|
66
|
+
/** True if this is a static method (class-level, no instance needed) */
|
|
67
|
+
isStatic?: boolean;
|
|
66
68
|
|
|
67
69
|
// ═══ DAEMON FEATURES ═══
|
|
68
70
|
|
|
@@ -371,6 +373,8 @@ export interface PhotonMCPClassExtended extends PhotonMCPClass {
|
|
|
371
373
|
statics: StaticInfo[];
|
|
372
374
|
/** Assets from the Photon's asset folder (UI, prompts, resources) */
|
|
373
375
|
assets?: PhotonAssets;
|
|
376
|
+
/** Names of injected @photon dependencies (for client-side event routing) */
|
|
377
|
+
injectedPhotons?: string[];
|
|
374
378
|
}
|
|
375
379
|
|
|
376
380
|
// ══════════════════════════════════════════════════════════════════════════════
|