@portel/photon 1.7.0 → 1.8.1
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 +23 -24
- package/dist/auto-ui/beam.d.ts.map +1 -1
- package/dist/auto-ui/beam.js +117 -42
- package/dist/auto-ui/beam.js.map +1 -1
- package/dist/auto-ui/design-system/tokens.d.ts +1 -1
- package/dist/auto-ui/design-system/tokens.d.ts.map +1 -1
- package/dist/auto-ui/design-system/tokens.js +1 -1
- package/dist/auto-ui/design-system/tokens.js.map +1 -1
- package/dist/auto-ui/frontend/index.html +1 -1
- package/dist/auto-ui/rendering/components.d.ts.map +1 -1
- package/dist/auto-ui/rendering/components.js +568 -0
- package/dist/auto-ui/rendering/components.js.map +1 -1
- package/dist/auto-ui/rendering/field-analyzer.d.ts +56 -0
- package/dist/auto-ui/rendering/field-analyzer.d.ts.map +1 -1
- package/dist/auto-ui/rendering/field-analyzer.js +177 -0
- package/dist/auto-ui/rendering/field-analyzer.js.map +1 -1
- package/dist/auto-ui/rendering/layout-selector.d.ts +14 -2
- package/dist/auto-ui/rendering/layout-selector.d.ts.map +1 -1
- package/dist/auto-ui/rendering/layout-selector.js +125 -1
- package/dist/auto-ui/rendering/layout-selector.js.map +1 -1
- package/dist/auto-ui/streamable-http-transport.d.ts +1 -1
- package/dist/auto-ui/streamable-http-transport.d.ts.map +1 -1
- package/dist/auto-ui/streamable-http-transport.js +353 -19
- package/dist/auto-ui/streamable-http-transport.js.map +1 -1
- package/dist/auto-ui/types.d.ts +7 -1
- package/dist/auto-ui/types.d.ts.map +1 -1
- package/dist/auto-ui/types.js.map +1 -1
- package/dist/beam.bundle.js +22441 -4216
- package/dist/beam.bundle.js.map +4 -4
- package/dist/cli/commands/info.d.ts.map +1 -1
- package/dist/cli/commands/info.js +37 -0
- package/dist/cli/commands/info.js.map +1 -1
- package/dist/cli/commands/package.d.ts.map +1 -1
- package/dist/cli/commands/package.js +16 -0
- package/dist/cli/commands/package.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +628 -14
- package/dist/cli.js.map +1 -1
- package/dist/context-store.d.ts +79 -0
- package/dist/context-store.d.ts.map +1 -0
- package/dist/context-store.js +210 -0
- package/dist/context-store.js.map +1 -0
- package/dist/daemon/client.d.ts +13 -4
- package/dist/daemon/client.d.ts.map +1 -1
- package/dist/daemon/client.js +138 -77
- package/dist/daemon/client.js.map +1 -1
- package/dist/daemon/manager.d.ts +0 -25
- package/dist/daemon/manager.d.ts.map +1 -1
- package/dist/daemon/manager.js +10 -38
- package/dist/daemon/manager.js.map +1 -1
- package/dist/daemon/protocol.d.ts +7 -2
- package/dist/daemon/protocol.d.ts.map +1 -1
- package/dist/daemon/protocol.js.map +1 -1
- package/dist/daemon/server.js +257 -35
- package/dist/daemon/server.js.map +1 -1
- package/dist/daemon/session-manager.d.ts +24 -4
- package/dist/daemon/session-manager.d.ts.map +1 -1
- package/dist/daemon/session-manager.js +62 -12
- package/dist/daemon/session-manager.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -3
- package/dist/index.js.map +1 -1
- package/dist/loader.d.ts +3 -20
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +53 -75
- package/dist/loader.js.map +1 -1
- package/dist/photon-cli-runner.d.ts.map +1 -1
- package/dist/photon-cli-runner.js +258 -218
- package/dist/photon-cli-runner.js.map +1 -1
- package/dist/photon-doc-extractor.d.ts +2 -0
- package/dist/photon-doc-extractor.d.ts.map +1 -1
- package/dist/photon-doc-extractor.js +42 -6
- package/dist/photon-doc-extractor.js.map +1 -1
- package/dist/photons/maker.photon.d.ts.map +1 -1
- package/dist/photons/maker.photon.js +3 -1
- package/dist/photons/maker.photon.js.map +1 -1
- package/dist/photons/maker.photon.ts +3 -1
- package/dist/serv/index.d.ts.map +1 -1
- package/dist/serv/index.js.map +1 -1
- package/dist/server.d.ts +32 -15
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +468 -469
- package/dist/server.js.map +1 -1
- package/dist/shared/security.d.ts.map +1 -1
- package/dist/shared/security.js +4 -8
- package/dist/shared/security.js.map +1 -1
- package/dist/shell-completions.d.ts +21 -0
- package/dist/shell-completions.d.ts.map +1 -0
- package/dist/shell-completions.js +102 -0
- package/dist/shell-completions.js.map +1 -0
- package/dist/template-manager.d.ts.map +1 -1
- package/dist/template-manager.js.map +1 -1
- package/package.json +10 -6
|
@@ -30,177 +30,52 @@ async function resolvePhotonPathWithBundled(name) {
|
|
|
30
30
|
return resolvePhotonPath(name);
|
|
31
31
|
}
|
|
32
32
|
import { PhotonDocExtractor } from './photon-doc-extractor.js';
|
|
33
|
-
import {
|
|
33
|
+
import { isGlobalDaemonRunning, startGlobalDaemon } from './daemon/manager.js';
|
|
34
34
|
import { sendCommand, pingDaemon } from './daemon/client.js';
|
|
35
35
|
import { formatOutput as baseFormatOutput, renderNone, formatKey, } from './cli-formatter.js';
|
|
36
|
-
import { getErrorMessage } from './shared/error-handler.js';
|
|
37
|
-
import { logger } from './shared/logger.js';
|
|
36
|
+
import { getErrorMessage, exitWithError, ExitCode } from './shared/error-handler.js';
|
|
38
37
|
/**
|
|
39
38
|
* Extract all public async methods from a photon file
|
|
40
39
|
*/
|
|
41
40
|
async function extractMethods(filePath) {
|
|
42
41
|
const source = await fs.readFile(filePath, 'utf-8');
|
|
43
42
|
const extractor = new SchemaExtractor();
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
// Also match async generator methods (async *methodName) and static methods
|
|
47
|
-
const methodMatches = source.matchAll(/(?:static\s+)?async\s+\*?\s*(\w+)\s*\(([^)]*)\)/g);
|
|
48
|
-
for (const match of methodMatches) {
|
|
49
|
-
const methodName = match[1];
|
|
50
|
-
const methodParams = match[2];
|
|
51
|
-
// Skip private methods and lifecycle hooks
|
|
52
|
-
if (methodName.startsWith('_') ||
|
|
53
|
-
methodName === 'onInitialize' ||
|
|
54
|
-
methodName === 'onShutdown') {
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
// Extract method signature
|
|
58
|
-
const methodIndex = match.index;
|
|
59
|
-
const precedingContent = source.substring(0, methodIndex);
|
|
60
|
-
// Find JSDoc comment
|
|
61
|
-
const lastJSDocStart = precedingContent.lastIndexOf('/**');
|
|
62
|
-
if (lastJSDocStart === -1)
|
|
63
|
-
continue;
|
|
64
|
-
const jsdocSection = precedingContent.substring(lastJSDocStart);
|
|
65
|
-
const jsdocMatch = jsdocSection.match(/\/\*\*([\s\S]*?)\*\/\s*$/);
|
|
66
|
-
if (!jsdocMatch)
|
|
67
|
-
continue;
|
|
68
|
-
const jsdoc = jsdocMatch[1];
|
|
69
|
-
// Extract description
|
|
70
|
-
const descMatch = jsdoc.match(/^\s*\*\s*(.+?)(?=\n\s*\*\s*@|\n\s*$)/s);
|
|
71
|
-
const description = descMatch
|
|
72
|
-
? descMatch[1]
|
|
73
|
-
.split('\n')
|
|
74
|
-
.map((line) => line.replace(/^\s*\*\s?/, '').trim())
|
|
75
|
-
.join(' ')
|
|
76
|
-
.trim()
|
|
77
|
-
: undefined;
|
|
78
|
-
// Parse TypeScript parameter types and optionality from method signature
|
|
79
|
-
const tsParamTypes = new Map();
|
|
80
|
-
const tsParamOptional = new Map();
|
|
81
|
-
if (methodParams.trim()) {
|
|
82
|
-
// Extract: params?: { mute?: boolean } | boolean
|
|
83
|
-
const paramTypeMatch = methodParams.match(/(\w+)(\??):\s*(.+)/);
|
|
84
|
-
if (paramTypeMatch) {
|
|
85
|
-
const tsParamName = paramTypeMatch[1];
|
|
86
|
-
const isParamOptional = paramTypeMatch[2] === '?';
|
|
87
|
-
const paramType = paramTypeMatch[3].trim();
|
|
88
|
-
// Extract base type (handle unions like "{ mute?: boolean } | boolean")
|
|
89
|
-
const baseType = extractBaseType(paramType);
|
|
90
|
-
// Store type for both the TS param name and any inner property names
|
|
91
|
-
tsParamTypes.set(tsParamName, baseType);
|
|
92
|
-
tsParamOptional.set(tsParamName, isParamOptional);
|
|
93
|
-
// Also extract inner object properties: { mute?: boolean }
|
|
94
|
-
const innerProps = extractObjectProperties(paramType);
|
|
95
|
-
for (const [propName, propInfo] of innerProps) {
|
|
96
|
-
tsParamTypes.set(propName, propInfo.type);
|
|
97
|
-
// If the parent param is optional, inner properties are also optional
|
|
98
|
-
tsParamOptional.set(propName, isParamOptional || propInfo.optional);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
// Extract format hint from @format tag (structural and content formats)
|
|
103
|
-
let format;
|
|
104
|
-
// Match structural formats (including card, tabs, accordion)
|
|
105
|
-
const structuralMatch = jsdoc.match(/@format\s+(primitive|table|tree|list|card|tabs|accordion|none)/i);
|
|
106
|
-
if (structuralMatch) {
|
|
107
|
-
format = structuralMatch[1].toLowerCase();
|
|
108
|
-
}
|
|
109
|
-
// Match content formats
|
|
110
|
-
else {
|
|
111
|
-
const contentMatch = jsdoc.match(/@format\s+(json|markdown|yaml|xml|html)/i);
|
|
112
|
-
if (contentMatch) {
|
|
113
|
-
format = contentMatch[1].toLowerCase();
|
|
114
|
-
}
|
|
115
|
-
else {
|
|
116
|
-
// Match code format (with optional language)
|
|
117
|
-
const codeMatch = jsdoc.match(/@format\s+code(?::(\w+))?/i);
|
|
118
|
-
if (codeMatch) {
|
|
119
|
-
format = codeMatch[1] ? `code:${codeMatch[1]}` : 'code';
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
// Extract parameters
|
|
43
|
+
const metadata = extractor.extractAllFromSource(source);
|
|
44
|
+
return metadata.tools.map((tool) => {
|
|
124
45
|
const params = [];
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if (exampleStart !== -1) {
|
|
137
|
-
const contentStart = exampleStart + '{@example '.length;
|
|
138
|
-
let braceDepth = 0;
|
|
139
|
-
let bracketDepth = 0;
|
|
140
|
-
let i = contentStart;
|
|
141
|
-
let inString = false;
|
|
142
|
-
while (i < paramDesc.length) {
|
|
143
|
-
const ch = paramDesc[i];
|
|
144
|
-
const prevCh = i > 0 ? paramDesc[i - 1] : '';
|
|
145
|
-
if (ch === '"' && prevCh !== '\\') {
|
|
146
|
-
inString = !inString;
|
|
147
|
-
}
|
|
148
|
-
else if (!inString) {
|
|
149
|
-
if (ch === '{')
|
|
150
|
-
braceDepth++;
|
|
151
|
-
else if (ch === '[')
|
|
152
|
-
bracketDepth++;
|
|
153
|
-
else if (ch === ']')
|
|
154
|
-
bracketDepth--;
|
|
155
|
-
else if (ch === '}') {
|
|
156
|
-
if (braceDepth === 0 && bracketDepth === 0) {
|
|
157
|
-
example = paramDesc.substring(contentStart, i).trim();
|
|
158
|
-
break;
|
|
159
|
-
}
|
|
160
|
-
braceDepth--;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
i++;
|
|
46
|
+
const schema = tool.inputSchema;
|
|
47
|
+
if (schema?.properties) {
|
|
48
|
+
for (const [name, prop] of Object.entries(schema.properties)) {
|
|
49
|
+
// Resolve type from anyOf/oneOf union schemas (e.g. number | string)
|
|
50
|
+
let type = prop.type;
|
|
51
|
+
if (!type && (prop.anyOf || prop.oneOf)) {
|
|
52
|
+
const variants = (prop.anyOf || prop.oneOf);
|
|
53
|
+
type = variants
|
|
54
|
+
.map((v) => v.type)
|
|
55
|
+
.filter(Boolean)
|
|
56
|
+
.join(' | ');
|
|
164
57
|
}
|
|
58
|
+
params.push({
|
|
59
|
+
name,
|
|
60
|
+
type: type || 'any',
|
|
61
|
+
optional: !schema.required?.includes(name),
|
|
62
|
+
description: prop.description,
|
|
63
|
+
...(prop.title ? { label: prop.title } : {}),
|
|
64
|
+
...(prop.examples?.[0] !== undefined ? { example: String(prop.examples[0]) } : {}),
|
|
65
|
+
...(prop.enum ? { enum: prop.enum.map(String) } : {}),
|
|
66
|
+
});
|
|
165
67
|
}
|
|
166
|
-
// Remove {@example ...} tag (handles nested braces/brackets)
|
|
167
|
-
let cleanedParamDesc = paramDesc;
|
|
168
|
-
if (exampleStart !== -1 && example) {
|
|
169
|
-
// Remove the full {@example ...} tag we extracted
|
|
170
|
-
const tagEnd = exampleStart + '{@example '.length + example.length + 1; // +1 for closing }
|
|
171
|
-
cleanedParamDesc = paramDesc.substring(0, exampleStart) + paramDesc.substring(tagEnd);
|
|
172
|
-
}
|
|
173
|
-
// Remove remaining JSDoc constraint tags for display
|
|
174
|
-
const cleanDesc = cleanedParamDesc
|
|
175
|
-
.replace(/\{@\w+[^}]*\}/g, '')
|
|
176
|
-
.replace(/\(optional\)/gi, '')
|
|
177
|
-
.replace(/\(default:.*?\)/gi, '')
|
|
178
|
-
.trim();
|
|
179
|
-
// Check optional from TypeScript signature first, fallback to JSDoc
|
|
180
|
-
const tsOptional = tsParamOptional.get(paramName);
|
|
181
|
-
const jsdocOptional = /\(optional\)/i.test(paramDesc) || /\(default:/i.test(paramDesc);
|
|
182
|
-
const optional = tsOptional !== undefined ? tsOptional : jsdocOptional;
|
|
183
|
-
params.push({
|
|
184
|
-
name: paramName,
|
|
185
|
-
type: tsParamTypes.get(paramName) || 'any',
|
|
186
|
-
optional,
|
|
187
|
-
description: cleanDesc,
|
|
188
|
-
...(customLabel ? { label: customLabel } : {}),
|
|
189
|
-
...(example ? { example } : {}),
|
|
190
|
-
});
|
|
191
68
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const buttonLabel = returnsMatch ? returnsMatch[1].trim() : undefined;
|
|
195
|
-
methods.push({
|
|
196
|
-
name: methodName,
|
|
69
|
+
return {
|
|
70
|
+
name: tool.name,
|
|
197
71
|
params,
|
|
198
|
-
description,
|
|
199
|
-
...(
|
|
200
|
-
...(buttonLabel ? { buttonLabel } : {}),
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
72
|
+
description: tool.description !== 'No description' ? tool.description : undefined,
|
|
73
|
+
...(tool.outputFormat ? { format: tool.outputFormat } : {}),
|
|
74
|
+
...(tool.buttonLabel ? { buttonLabel: tool.buttonLabel } : {}),
|
|
75
|
+
...(tool.scheduled ? { scheduled: tool.scheduled } : {}),
|
|
76
|
+
...(tool.webhook !== undefined ? { webhook: true } : {}),
|
|
77
|
+
};
|
|
78
|
+
});
|
|
204
79
|
}
|
|
205
80
|
/**
|
|
206
81
|
* Extract base type from TypeScript type annotation
|
|
@@ -252,6 +127,45 @@ function extractObjectProperties(typeStr) {
|
|
|
252
127
|
*/
|
|
253
128
|
function formatOutput(result, formatHint) {
|
|
254
129
|
let hint = formatHint;
|
|
130
|
+
// Handle _photonType structured data (e.g., table, collection)
|
|
131
|
+
if (result && typeof result === 'object' && result._photonType) {
|
|
132
|
+
// If the object has toJSON(), call it to get the plain data (e.g., Table instances have private fields)
|
|
133
|
+
const data = typeof result.toJSON === 'function' ? result.toJSON() : result;
|
|
134
|
+
const photonType = data._photonType;
|
|
135
|
+
if (photonType === 'table' && data.rows && Array.isArray(data.rows)) {
|
|
136
|
+
// Convert structured table to array of objects for renderTable
|
|
137
|
+
const columns = data.columns || [];
|
|
138
|
+
const rows = data.rows.map((row) => {
|
|
139
|
+
if (Array.isArray(row)) {
|
|
140
|
+
// Row is an array of values, map to column names
|
|
141
|
+
const obj = {};
|
|
142
|
+
columns.forEach((col, i) => {
|
|
143
|
+
const key = typeof col === 'string' ? col : col.label || col.field || `col${i}`;
|
|
144
|
+
obj[key] = row[i] ?? '';
|
|
145
|
+
});
|
|
146
|
+
return obj;
|
|
147
|
+
}
|
|
148
|
+
// Row is already an object, extract display values using column fields
|
|
149
|
+
if (columns.length > 0) {
|
|
150
|
+
const obj = {};
|
|
151
|
+
for (const col of columns) {
|
|
152
|
+
const field = typeof col === 'string' ? col : col.field || col.key;
|
|
153
|
+
const label = typeof col === 'string' ? col : col.label || col.field || col.key;
|
|
154
|
+
if (field) {
|
|
155
|
+
obj[label] = row[field] ?? '';
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return obj;
|
|
159
|
+
}
|
|
160
|
+
return row;
|
|
161
|
+
});
|
|
162
|
+
baseFormatOutput(rows, 'table');
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
// For other _photonType values, strip the marker and render normally
|
|
166
|
+
const { _photonType, ...rest } = data;
|
|
167
|
+
return formatOutput(rest, formatHint);
|
|
168
|
+
}
|
|
255
169
|
// Handle error responses
|
|
256
170
|
if (result && typeof result === 'object' && result.success === false) {
|
|
257
171
|
const errorMsg = result.error || result.message || 'Unknown error';
|
|
@@ -371,7 +285,18 @@ function parseCliArgs(args, params) {
|
|
|
371
285
|
let positionalIndex = 0;
|
|
372
286
|
for (let i = 0; i < args.length; i++) {
|
|
373
287
|
const arg = args[i];
|
|
374
|
-
if (arg.startsWith('--')) {
|
|
288
|
+
if (arg.startsWith('--no-') && !arg.includes('=')) {
|
|
289
|
+
// --no-<param> negation syntax for boolean params (e.g., --no-enabled → enabled=false)
|
|
290
|
+
const key = arg.substring(5);
|
|
291
|
+
if (paramTypes.has(key)) {
|
|
292
|
+
result[key] = false;
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
// Unknown param, store as-is (will be caught by validation later)
|
|
296
|
+
result[key] = false;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
else if (arg.startsWith('--')) {
|
|
375
300
|
// Named argument: --key value or --key=value
|
|
376
301
|
const eqIndex = arg.indexOf('=');
|
|
377
302
|
if (eqIndex !== -1) {
|
|
@@ -384,13 +309,17 @@ function parseCliArgs(args, params) {
|
|
|
384
309
|
else {
|
|
385
310
|
// --key value format (next arg is the value)
|
|
386
311
|
const key = arg.substring(2);
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
312
|
+
const expectedType = paramTypes.get(key) || 'any';
|
|
313
|
+
// For boolean params, treat bare --flag as true (no value consumed)
|
|
314
|
+
if (expectedType === 'boolean' && (i + 1 >= args.length || args[i + 1].startsWith('--'))) {
|
|
315
|
+
result[key] = true;
|
|
316
|
+
}
|
|
317
|
+
else if (i + 1 < args.length) {
|
|
318
|
+
i++;
|
|
390
319
|
result[key] = coerceValue(args[i], expectedType);
|
|
391
320
|
}
|
|
392
321
|
else {
|
|
393
|
-
//
|
|
322
|
+
// No value and not boolean - treat as boolean flag
|
|
394
323
|
result[key] = true;
|
|
395
324
|
}
|
|
396
325
|
}
|
|
@@ -412,7 +341,7 @@ function parseCliArgs(args, params) {
|
|
|
412
341
|
function coerceValue(value, expectedType) {
|
|
413
342
|
// Preserve strings starting with + or - for relative adjustments
|
|
414
343
|
// Must check BEFORE JSON.parse because JSON.parse("-3") returns -3 (number)
|
|
415
|
-
if (expectedType
|
|
344
|
+
if (expectedType.includes('number') && (value.startsWith('+') || value.startsWith('-'))) {
|
|
416
345
|
return value;
|
|
417
346
|
}
|
|
418
347
|
// Try to parse as JSON first (handles objects, arrays, true/false)
|
|
@@ -987,9 +916,57 @@ function formatLabel(name) {
|
|
|
987
916
|
/**
|
|
988
917
|
* Print help for a specific method
|
|
989
918
|
*/
|
|
919
|
+
function printParamHelp(param) {
|
|
920
|
+
const displayLabel = param.label || formatLabel(param.name);
|
|
921
|
+
// Build type hint suffix
|
|
922
|
+
let typeHint = '';
|
|
923
|
+
if (param.enum && param.enum.length > 0) {
|
|
924
|
+
typeHint = ` [values: ${param.enum.join(', ')}]`;
|
|
925
|
+
}
|
|
926
|
+
else if (param.type === 'boolean') {
|
|
927
|
+
typeHint = ' [--no-' + param.name + ' to disable]';
|
|
928
|
+
}
|
|
929
|
+
else if (param.type === 'array') {
|
|
930
|
+
typeHint = ` (JSON array, e.g., '["a","b"]')`;
|
|
931
|
+
}
|
|
932
|
+
else if (param.type === 'object') {
|
|
933
|
+
typeHint = ` (JSON object, e.g., '{"key":"value"}')`;
|
|
934
|
+
}
|
|
935
|
+
console.log(` --${param.name} (${displayLabel})${typeHint}`);
|
|
936
|
+
if (param.description) {
|
|
937
|
+
console.log(` ${param.description}`);
|
|
938
|
+
}
|
|
939
|
+
// Show example for complex types (JSON)
|
|
940
|
+
if (param.example) {
|
|
941
|
+
console.log(` Example: ${param.example}`);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
990
944
|
function printMethodHelp(photonName, method) {
|
|
945
|
+
// Truncate description at sentence boundary if too long, or clean up mid-sentence truncation
|
|
946
|
+
let description = method.description || 'No description';
|
|
947
|
+
if (description.length > 200) {
|
|
948
|
+
const sentenceEnd = description.substring(0, 200).lastIndexOf('.');
|
|
949
|
+
if (sentenceEnd > 80) {
|
|
950
|
+
description = description.substring(0, sentenceEnd + 1);
|
|
951
|
+
}
|
|
952
|
+
else {
|
|
953
|
+
description = description.substring(0, 197) + '...';
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
else if (description.length > 0 && !/[.!?]$/.test(description.trim())) {
|
|
957
|
+
// Description appears truncated mid-sentence (no terminal punctuation)
|
|
958
|
+
// Truncate at the last complete sentence if possible
|
|
959
|
+
const lastSentence = description.lastIndexOf('.');
|
|
960
|
+
if (lastSentence > 40) {
|
|
961
|
+
description = description.substring(0, lastSentence + 1);
|
|
962
|
+
}
|
|
963
|
+
else {
|
|
964
|
+
// No good sentence boundary — add ellipsis to signal truncation
|
|
965
|
+
description = description.trim() + '...';
|
|
966
|
+
}
|
|
967
|
+
}
|
|
991
968
|
console.log(`\nNAME:`);
|
|
992
|
-
console.log(` ${method.name} - ${
|
|
969
|
+
console.log(` ${method.name} - ${description}\n`);
|
|
993
970
|
console.log(`USAGE:`);
|
|
994
971
|
const requiredParams = method.params.filter((p) => !p.optional);
|
|
995
972
|
const optionalParams = method.params.filter((p) => p.optional);
|
|
@@ -1005,32 +982,14 @@ function printMethodHelp(photonName, method) {
|
|
|
1005
982
|
if (requiredParams.length > 0) {
|
|
1006
983
|
console.log(`REQUIRED:`);
|
|
1007
984
|
for (const param of requiredParams) {
|
|
1008
|
-
|
|
1009
|
-
const displayLabel = param.label || formatLabel(param.name);
|
|
1010
|
-
console.log(` --${param.name} (${displayLabel})`);
|
|
1011
|
-
if (param.description) {
|
|
1012
|
-
console.log(` ${param.description}`);
|
|
1013
|
-
}
|
|
1014
|
-
// Show example for complex types (JSON)
|
|
1015
|
-
if (param.example) {
|
|
1016
|
-
console.log(` Example: ${param.example}`);
|
|
1017
|
-
}
|
|
985
|
+
printParamHelp(param);
|
|
1018
986
|
}
|
|
1019
987
|
console.log('');
|
|
1020
988
|
}
|
|
1021
989
|
if (optionalParams.length > 0) {
|
|
1022
990
|
console.log(`OPTIONS:`);
|
|
1023
991
|
for (const param of optionalParams) {
|
|
1024
|
-
|
|
1025
|
-
const displayLabel = param.label || formatLabel(param.name);
|
|
1026
|
-
console.log(` --${param.name} (${displayLabel})`);
|
|
1027
|
-
if (param.description) {
|
|
1028
|
-
console.log(` ${param.description}`);
|
|
1029
|
-
}
|
|
1030
|
-
// Show example for complex types (JSON)
|
|
1031
|
-
if (param.example) {
|
|
1032
|
-
console.log(` Example: ${param.example}`);
|
|
1033
|
-
}
|
|
992
|
+
printParamHelp(param);
|
|
1034
993
|
}
|
|
1035
994
|
console.log('');
|
|
1036
995
|
}
|
|
@@ -1054,12 +1013,28 @@ export async function listMethods(photonName) {
|
|
|
1054
1013
|
try {
|
|
1055
1014
|
const resolvedPath = await resolvePhotonPathWithBundled(photonName);
|
|
1056
1015
|
if (!resolvedPath) {
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1016
|
+
exitWithError(`Photon '${photonName}' not found`, {
|
|
1017
|
+
exitCode: ExitCode.NOT_FOUND,
|
|
1018
|
+
searchedIn: '~/.photon/',
|
|
1019
|
+
suggestion: `Install it with: photon add ${photonName}\nIf '${photonName}' is a command, run 'photon --help' for available commands`,
|
|
1020
|
+
});
|
|
1061
1021
|
}
|
|
1062
|
-
const
|
|
1022
|
+
const allMethods = await extractMethods(resolvedPath);
|
|
1023
|
+
// Filter out internal methods: scheduled*, handle*, reportError
|
|
1024
|
+
const methods = allMethods.filter((m) => {
|
|
1025
|
+
if (m.scheduled)
|
|
1026
|
+
return false;
|
|
1027
|
+
if (m.webhook)
|
|
1028
|
+
return false;
|
|
1029
|
+
if (m.name.startsWith('scheduled'))
|
|
1030
|
+
return false;
|
|
1031
|
+
if (m.name.startsWith('handle'))
|
|
1032
|
+
return false;
|
|
1033
|
+
if (m.name === 'reportError')
|
|
1034
|
+
return false;
|
|
1035
|
+
return true;
|
|
1036
|
+
});
|
|
1037
|
+
const hiddenCount = allMethods.length - methods.length;
|
|
1063
1038
|
// Print usage
|
|
1064
1039
|
console.log(`\nUSAGE:`);
|
|
1065
1040
|
console.log(` photon cli ${photonName} <command> [options]\n`);
|
|
@@ -1067,11 +1042,32 @@ export async function listMethods(photonName) {
|
|
|
1067
1042
|
console.log(`COMMANDS:`);
|
|
1068
1043
|
// Find longest method name for alignment
|
|
1069
1044
|
const maxLength = Math.max(...methods.map((m) => m.name.length));
|
|
1045
|
+
const maxDescLength = 60;
|
|
1070
1046
|
for (const method of methods) {
|
|
1071
1047
|
const padding = ' '.repeat(maxLength - method.name.length + 4);
|
|
1072
|
-
|
|
1048
|
+
let description = method.description || 'No description';
|
|
1049
|
+
// Truncate long descriptions in listing, keeping first sentence
|
|
1050
|
+
if (description.length > maxDescLength) {
|
|
1051
|
+
const sentenceEnd = description.substring(0, maxDescLength).lastIndexOf('.');
|
|
1052
|
+
if (sentenceEnd > 20) {
|
|
1053
|
+
description = description.substring(0, sentenceEnd + 1);
|
|
1054
|
+
}
|
|
1055
|
+
else {
|
|
1056
|
+
description = description.substring(0, maxDescLength - 3) + '...';
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
// Show param signature for methods with no description
|
|
1060
|
+
if (!method.description && method.params.length > 0) {
|
|
1061
|
+
const sig = method.params
|
|
1062
|
+
.map((p) => (p.optional ? `${p.name}?` : p.name) + `: ${p.type}`)
|
|
1063
|
+
.join(', ');
|
|
1064
|
+
description = `(${sig})`;
|
|
1065
|
+
}
|
|
1073
1066
|
console.log(` ${method.name}${padding}${description}`);
|
|
1074
1067
|
}
|
|
1068
|
+
if (hiddenCount > 0) {
|
|
1069
|
+
console.log(`\n (${hiddenCount} internal/scheduled methods hidden)`);
|
|
1070
|
+
}
|
|
1075
1071
|
// Print footer
|
|
1076
1072
|
console.log(`\nFor detailed parameter information, run:`);
|
|
1077
1073
|
console.log(` photon cli ${photonName} <command> --help\n`);
|
|
@@ -1094,8 +1090,9 @@ export async function listMethods(photonName) {
|
|
|
1094
1090
|
}
|
|
1095
1091
|
}
|
|
1096
1092
|
catch (error) {
|
|
1097
|
-
|
|
1098
|
-
|
|
1093
|
+
exitWithError(`Cannot list methods for ${photonName}: ${getErrorMessage(error)}`, {
|
|
1094
|
+
suggestion: `Verify the photon file is valid: ~/.photon/${photonName}.photon.ts`,
|
|
1095
|
+
});
|
|
1099
1096
|
}
|
|
1100
1097
|
}
|
|
1101
1098
|
/**
|
|
@@ -1122,10 +1119,11 @@ export async function runMethod(photonName, methodName, args) {
|
|
|
1122
1119
|
// Resolve photon path
|
|
1123
1120
|
const resolvedPath = await resolvePhotonPathWithBundled(photonName);
|
|
1124
1121
|
if (!resolvedPath) {
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1122
|
+
exitWithError(`Photon '${photonName}' not found`, {
|
|
1123
|
+
exitCode: ExitCode.NOT_FOUND,
|
|
1124
|
+
searchedIn: '~/.photon/',
|
|
1125
|
+
suggestion: `Install it with: photon add ${photonName}\nIf '${photonName}' is a command, run 'photon --help' for available commands`,
|
|
1126
|
+
});
|
|
1129
1127
|
}
|
|
1130
1128
|
// Extract MCP name from filename
|
|
1131
1129
|
const mcpName = path.basename(resolvedPath).replace(/\.photon\.(ts|js)$/, '');
|
|
@@ -1138,10 +1136,11 @@ export async function runMethod(photonName, methodName, args) {
|
|
|
1138
1136
|
}
|
|
1139
1137
|
const method = methods.find((m) => m.name === methodName);
|
|
1140
1138
|
if (!method) {
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1139
|
+
const available = methods.map((m) => m.name).join(', ');
|
|
1140
|
+
exitWithError(`Method '${methodName}' not found in ${photonName}`, {
|
|
1141
|
+
exitCode: ExitCode.NOT_FOUND,
|
|
1142
|
+
suggestion: `Available methods: ${available}\nRun 'photon cli ${photonName}' to see details`,
|
|
1143
|
+
});
|
|
1145
1144
|
}
|
|
1146
1145
|
// Check for --help flag in args (e.g., photon cli test-cli-calc add --help)
|
|
1147
1146
|
if (args.includes('--help') || args.includes('-h')) {
|
|
@@ -1168,11 +1167,47 @@ export async function runMethod(photonName, methodName, args) {
|
|
|
1168
1167
|
}
|
|
1169
1168
|
}
|
|
1170
1169
|
if (missing.length > 0) {
|
|
1171
|
-
|
|
1172
|
-
console.error(`\nUsage: photon cli ${photonName} ${methodName} ${method.params
|
|
1170
|
+
const usage = method.params
|
|
1173
1171
|
.map((p) => (p.optional ? `[--${p.name}]` : `--${p.name} <value>`))
|
|
1174
|
-
.join(' ')
|
|
1175
|
-
|
|
1172
|
+
.join(' ');
|
|
1173
|
+
const details = missing
|
|
1174
|
+
.map((name) => {
|
|
1175
|
+
const p = method.params.find((mp) => mp.name === name);
|
|
1176
|
+
return ` --${name} (${p?.type || 'string'})${p?.description ? ': ' + p.description : ''}`;
|
|
1177
|
+
})
|
|
1178
|
+
.join('\n');
|
|
1179
|
+
exitWithError(`Missing required parameters: ${missing.join(', ')}`, {
|
|
1180
|
+
exitCode: ExitCode.INVALID_ARGUMENT,
|
|
1181
|
+
suggestion: `Usage: photon cli ${photonName} ${methodName} ${usage}\n\nRequired:\n${details}`,
|
|
1182
|
+
});
|
|
1183
|
+
}
|
|
1184
|
+
// Validate parameter types and enum values
|
|
1185
|
+
const validationErrors = [];
|
|
1186
|
+
for (const param of method.params) {
|
|
1187
|
+
if (!(param.name in parsedArgs))
|
|
1188
|
+
continue;
|
|
1189
|
+
const value = parsedArgs[param.name];
|
|
1190
|
+
// Validate number types
|
|
1191
|
+
if (param.type === 'number' && typeof value !== 'number') {
|
|
1192
|
+
// coerceToType returns the original string if NaN, so check for non-number
|
|
1193
|
+
if (typeof value === 'string' && !(value.startsWith('+') || value.startsWith('-'))) {
|
|
1194
|
+
validationErrors.push(` --${param.name}: expected a number, got '${value}'`);
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
// Validate enum values
|
|
1198
|
+
if (param.enum && param.enum.length > 0) {
|
|
1199
|
+
const strValue = String(value).toLowerCase();
|
|
1200
|
+
const validValues = param.enum.map((v) => v.toLowerCase());
|
|
1201
|
+
if (!validValues.includes(strValue)) {
|
|
1202
|
+
validationErrors.push(` --${param.name}: invalid value '${value}' (must be one of: ${param.enum.join(', ')})`);
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
if (validationErrors.length > 0) {
|
|
1207
|
+
exitWithError(`Invalid parameter values`, {
|
|
1208
|
+
exitCode: ExitCode.INVALID_ARGUMENT,
|
|
1209
|
+
suggestion: validationErrors.join('\n'),
|
|
1210
|
+
});
|
|
1176
1211
|
}
|
|
1177
1212
|
// Check if photon is stateful
|
|
1178
1213
|
const extractor = new PhotonDocExtractor(resolvedPath);
|
|
@@ -1181,8 +1216,8 @@ export async function runMethod(photonName, methodName, args) {
|
|
|
1181
1216
|
if (metadata.stateful) {
|
|
1182
1217
|
// STATEFUL PATH: Use daemon
|
|
1183
1218
|
// Check if daemon is running
|
|
1184
|
-
if (!
|
|
1185
|
-
await
|
|
1219
|
+
if (!isGlobalDaemonRunning()) {
|
|
1220
|
+
await startGlobalDaemon(true);
|
|
1186
1221
|
// Wait for daemon to be ready
|
|
1187
1222
|
let ready = false;
|
|
1188
1223
|
for (let i = 0; i < 10; i++) {
|
|
@@ -1193,12 +1228,19 @@ export async function runMethod(photonName, methodName, args) {
|
|
|
1193
1228
|
}
|
|
1194
1229
|
}
|
|
1195
1230
|
if (!ready) {
|
|
1196
|
-
|
|
1197
|
-
|
|
1231
|
+
exitWithError(`Failed to start daemon for ${photonName}`, {
|
|
1232
|
+
suggestion: `Check logs: cat ~/.photon/daemons/${photonName}/daemon.log\nOr try: photon daemon restart ${photonName}`,
|
|
1233
|
+
});
|
|
1198
1234
|
}
|
|
1199
1235
|
}
|
|
1200
|
-
//
|
|
1201
|
-
|
|
1236
|
+
// Read session-scoped instance set by `photon use` (scoped to this terminal)
|
|
1237
|
+
const { CLISessionStore } = await import('./context-store.js');
|
|
1238
|
+
const sessionInstance = new CLISessionStore().getCurrentInstance(photonName);
|
|
1239
|
+
const sessionId = `cli-${photonName}`;
|
|
1240
|
+
const sendOpts = { photonPath: resolvedPath, sessionId };
|
|
1241
|
+
await sendCommand(photonName, '_use', { name: sessionInstance }, sendOpts);
|
|
1242
|
+
// Send the actual command
|
|
1243
|
+
result = await sendCommand(photonName, methodName, parsedArgs, sendOpts);
|
|
1202
1244
|
}
|
|
1203
1245
|
else {
|
|
1204
1246
|
// STATELESS PATH: Direct execution
|
|
@@ -1244,15 +1286,13 @@ export async function runMethod(photonName, methodName, args) {
|
|
|
1244
1286
|
catch (error) {
|
|
1245
1287
|
// Check for custom user-facing message from photon
|
|
1246
1288
|
// Photons can throw: throw Object.assign(new Error('internal'), { userMessage: 'friendly msg', hint: 'try this' })
|
|
1247
|
-
const userMessage = (error && typeof error === 'object' && 'userMessage' in error && error.userMessage) ||
|
|
1289
|
+
const userMessage = (error && typeof error === 'object' && 'userMessage' in error && String(error.userMessage)) ||
|
|
1248
1290
|
getErrorMessage(error) ||
|
|
1249
1291
|
'Unknown error occurred';
|
|
1250
|
-
const hint = error && typeof error === 'object' && 'hint' in error ? error.hint : undefined;
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
}
|
|
1255
|
-
process.exit(1);
|
|
1292
|
+
const hint = error && typeof error === 'object' && 'hint' in error ? String(error.hint) : undefined;
|
|
1293
|
+
exitWithError(String(userMessage), {
|
|
1294
|
+
suggestion: hint ? String(hint) : undefined,
|
|
1295
|
+
});
|
|
1256
1296
|
}
|
|
1257
1297
|
}
|
|
1258
1298
|
/**
|