@portel/photon 1.6.1 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +111 -160
- package/dist/auto-ui/beam.d.ts.map +1 -1
- package/dist/auto-ui/beam.js +218 -106
- 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 +2 -2
- package/dist/auto-ui/design-system/tokens.js.map +1 -1
- package/dist/auto-ui/frontend/index.html +1 -1
- package/dist/auto-ui/platform-compat.d.ts.map +1 -1
- package/dist/auto-ui/platform-compat.js +12 -2
- package/dist/auto-ui/platform-compat.js.map +1 -1
- package/dist/auto-ui/playground-html.js +5 -5
- 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 +370 -26
- 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 +21932 -3307
- 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 +640 -17
- 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 +317 -83
- 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 +87 -77
- package/dist/loader.js.map +1 -1
- package/dist/markdown-utils.d.ts.map +1 -1
- package/dist/markdown-utils.js +2 -1
- package/dist/markdown-utils.js.map +1 -1
- package/dist/marketplace-manager.d.ts.map +1 -1
- package/dist/marketplace-manager.js +20 -3
- package/dist/marketplace-manager.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 +45 -7
- 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 +22 -4
- package/dist/photons/maker.photon.js.map +1 -1
- package/dist/photons/maker.photon.ts +47 -11
- package/dist/security-scanner.d.ts.map +1 -1
- package/dist/security-scanner.js +8 -2
- package/dist/security-scanner.js.map +1 -1
- package/dist/serv/index.d.ts +1 -1
- package/dist/serv/index.d.ts.map +1 -1
- package/dist/serv/index.js +6 -4
- 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 +525 -483
- package/dist/server.js.map +1 -1
- package/dist/shared/security.d.ts +79 -0
- package/dist/shared/security.d.ts.map +1 -0
- package/dist/shared/security.js +251 -0
- package/dist/shared/security.js.map +1 -0
- 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 +10 -3
- package/dist/template-manager.js.map +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +12 -7
package/dist/cli.js
CHANGED
|
@@ -24,13 +24,14 @@ import { toEnvVarName } from './shared/config-docs.js';
|
|
|
24
24
|
import { runTask } from './shared/task-runner.js';
|
|
25
25
|
import { normalizeLogLevel, logger } from './shared/logger.js';
|
|
26
26
|
import { printHeader, printInfo, printWarning, printError, printSuccess } from './cli-formatter.js';
|
|
27
|
-
import { handleError, getErrorMessage, ExitCode, exitWithError, } from './shared/error-handler.js';
|
|
27
|
+
import { handleError, getErrorMessage, ExitCode, exitWithError, isNodeError, } from './shared/error-handler.js';
|
|
28
28
|
import { validateOrThrow, inRange, isPositive, isInteger } from './shared/validation.js';
|
|
29
|
-
import { createReadline, promptWait } from './shared/cli-utils.js';
|
|
29
|
+
import { createReadline, promptText, promptWait } from './shared/cli-utils.js';
|
|
30
30
|
import { registerMarketplaceCommands } from './cli/commands/marketplace.js';
|
|
31
31
|
import { registerInfoCommand } from './cli/commands/info.js';
|
|
32
32
|
import { registerPackageCommands } from './cli/commands/package.js';
|
|
33
33
|
import { registerPackageAppCommand } from './cli/commands/package-app.js';
|
|
34
|
+
import { validateAssetPath, isPathWithin } from './shared/security.js';
|
|
34
35
|
// ══════════════════════════════════════════════════════════════════════════════
|
|
35
36
|
// BUNDLED PHOTONS
|
|
36
37
|
// ══════════════════════════════════════════════════════════════════════════════
|
|
@@ -252,11 +253,16 @@ async function handleUrlElicitation(ask) {
|
|
|
252
253
|
const platform = process.platform;
|
|
253
254
|
const openCommand = platform === 'darwin' ? 'open' : platform === 'win32' ? 'start' : 'xdg-open';
|
|
254
255
|
try {
|
|
255
|
-
|
|
256
|
-
|
|
256
|
+
// Security: validate URL and use execFile to prevent shell injection
|
|
257
|
+
new URL(ask.url); // throws on invalid URL
|
|
258
|
+
const { execFile } = await import('child_process');
|
|
259
|
+
execFile(openCommand, [ask.url]);
|
|
257
260
|
}
|
|
258
261
|
catch (error) {
|
|
259
|
-
|
|
262
|
+
const msg = isNodeError(error, 'ENOENT')
|
|
263
|
+
? `Could not find '${openCommand}' to open URLs`
|
|
264
|
+
: getErrorMessage(error);
|
|
265
|
+
cliHint(`Could not open browser: ${msg}. Please open the URL manually.`);
|
|
260
266
|
}
|
|
261
267
|
const shouldContinue = await promptWait('Press Enter when done', true);
|
|
262
268
|
return { action: shouldContinue ? 'accept' : 'cancel' };
|
|
@@ -755,6 +761,9 @@ program
|
|
|
755
761
|
.configureHelp({
|
|
756
762
|
sortSubcommands: false,
|
|
757
763
|
sortOptions: false,
|
|
764
|
+
// Hide Commander's auto-generated "Commands:" section since we show
|
|
765
|
+
// a custom categorized section in addHelpText('after', ...)
|
|
766
|
+
visibleCommands: () => [],
|
|
758
767
|
})
|
|
759
768
|
.addHelpText('after', `
|
|
760
769
|
Runtime Commands:
|
|
@@ -764,6 +773,11 @@ Runtime Commands:
|
|
|
764
773
|
beam Launch Photon Beam (interactive control panel)
|
|
765
774
|
serve Start local multi-tenant MCP hosting for development
|
|
766
775
|
|
|
776
|
+
Configuration:
|
|
777
|
+
use <photon> [instance] Switch to a named instance of a stateful photon
|
|
778
|
+
instances <photon> List all instances of a stateful photon
|
|
779
|
+
set <photon> [values] Configure environment for a photon
|
|
780
|
+
|
|
767
781
|
Hosting:
|
|
768
782
|
host <command> Manage cloud hosting (preview, deploy)
|
|
769
783
|
|
|
@@ -918,7 +932,13 @@ program
|
|
|
918
932
|
if (source.metadata.assets && source.metadata.assets.length > 0) {
|
|
919
933
|
const assets = await manager.fetchAssets(source.marketplace, source.metadata.assets);
|
|
920
934
|
for (const [assetPath, content] of assets) {
|
|
921
|
-
|
|
935
|
+
// Security: validate asset path to prevent traversal
|
|
936
|
+
const safePath = validateAssetPath(assetPath);
|
|
937
|
+
const assetTarget = path.join(workingDir, safePath);
|
|
938
|
+
if (!isPathWithin(assetTarget, workingDir)) {
|
|
939
|
+
console.error(`Skipping unsafe asset path: ${assetPath}`);
|
|
940
|
+
continue;
|
|
941
|
+
}
|
|
922
942
|
const assetDir = path.dirname(assetTarget);
|
|
923
943
|
await fs.mkdir(assetDir, { recursive: true });
|
|
924
944
|
await fs.writeFile(assetTarget, content, 'utf-8');
|
|
@@ -1104,8 +1124,12 @@ program
|
|
|
1104
1124
|
logger.debug(`Could not auto-open browser: ${err.message}`);
|
|
1105
1125
|
});
|
|
1106
1126
|
}
|
|
1107
|
-
// Handle shutdown signals
|
|
1127
|
+
// Handle shutdown signals (guard against duplicate Ctrl+C)
|
|
1128
|
+
let shuttingDown = false;
|
|
1108
1129
|
const shutdown = async () => {
|
|
1130
|
+
if (shuttingDown)
|
|
1131
|
+
return;
|
|
1132
|
+
shuttingDown = true;
|
|
1109
1133
|
console.error('\nShutting down Photon Beam...');
|
|
1110
1134
|
// Gracefully close external MCP clients to prevent ugly tracebacks
|
|
1111
1135
|
try {
|
|
@@ -1354,11 +1378,15 @@ maker
|
|
|
1354
1378
|
// Check if file already exists
|
|
1355
1379
|
try {
|
|
1356
1380
|
await fs.access(filePath);
|
|
1357
|
-
|
|
1358
|
-
|
|
1381
|
+
exitWithError(`File already exists: ${filePath}`, {
|
|
1382
|
+
suggestion: `Choose a different name or delete the existing file`,
|
|
1383
|
+
});
|
|
1359
1384
|
}
|
|
1360
|
-
catch {
|
|
1361
|
-
|
|
1385
|
+
catch (err) {
|
|
1386
|
+
if (!isNodeError(err, 'ENOENT')) {
|
|
1387
|
+
exitWithError(`Cannot access ${filePath}: ${getErrorMessage(err)}`);
|
|
1388
|
+
}
|
|
1389
|
+
// ENOENT = file doesn't exist — good, proceed
|
|
1362
1390
|
}
|
|
1363
1391
|
// Read template
|
|
1364
1392
|
const templatePath = path.join(__dirname, '..', 'templates', 'photon.template.ts');
|
|
@@ -1366,8 +1394,8 @@ maker
|
|
|
1366
1394
|
try {
|
|
1367
1395
|
template = await fs.readFile(templatePath, 'utf-8');
|
|
1368
1396
|
}
|
|
1369
|
-
catch {
|
|
1370
|
-
|
|
1397
|
+
catch (err) {
|
|
1398
|
+
logger.debug(`Template not found at ${templatePath}, using inline template`);
|
|
1371
1399
|
template = getInlineTemplate();
|
|
1372
1400
|
}
|
|
1373
1401
|
// Replace placeholders
|
|
@@ -1815,6 +1843,590 @@ SEE ALSO:
|
|
|
1815
1843
|
await runMethod(photon, method, args);
|
|
1816
1844
|
}
|
|
1817
1845
|
});
|
|
1846
|
+
// Use command: switch to a named instance of a stateful photon
|
|
1847
|
+
program
|
|
1848
|
+
.command('use')
|
|
1849
|
+
.argument('<photon>', 'Photon name')
|
|
1850
|
+
.argument('[instance]', 'Instance name (omit for default)')
|
|
1851
|
+
.description('Switch to a named instance of a stateful photon')
|
|
1852
|
+
.action(async (photonName, instance) => {
|
|
1853
|
+
try {
|
|
1854
|
+
const { CLISessionStore } = await import('./context-store.js');
|
|
1855
|
+
// Write to CLI session store only — each client manages its own instance
|
|
1856
|
+
new CLISessionStore().setCurrentInstance(photonName, instance || '');
|
|
1857
|
+
const label = instance || 'default';
|
|
1858
|
+
printSuccess(`${photonName} → instance: ${label}`);
|
|
1859
|
+
// Refresh completions cache (picks up new instance)
|
|
1860
|
+
try {
|
|
1861
|
+
const { generateCompletionCache } = await import('./shell-completions.js');
|
|
1862
|
+
await generateCompletionCache();
|
|
1863
|
+
}
|
|
1864
|
+
catch {
|
|
1865
|
+
// Best-effort: don't break the use command if cache refresh fails
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
catch (error) {
|
|
1869
|
+
printError(getErrorMessage(error));
|
|
1870
|
+
process.exit(1);
|
|
1871
|
+
}
|
|
1872
|
+
});
|
|
1873
|
+
// Instances command: list all instances of a stateful photon
|
|
1874
|
+
program
|
|
1875
|
+
.command('instances')
|
|
1876
|
+
.argument('<photon>', 'Photon name')
|
|
1877
|
+
.description('List all instances of a stateful photon')
|
|
1878
|
+
.action(async (photonName) => {
|
|
1879
|
+
try {
|
|
1880
|
+
const { InstanceStore } = await import('./context-store.js');
|
|
1881
|
+
const store = new InstanceStore();
|
|
1882
|
+
const instances = store.listInstances(photonName);
|
|
1883
|
+
const current = store.getCurrentInstance(photonName) || 'default';
|
|
1884
|
+
if (instances.length === 0) {
|
|
1885
|
+
printInfo(`No instances found for ${photonName}.`);
|
|
1886
|
+
return;
|
|
1887
|
+
}
|
|
1888
|
+
cliHeading(`${photonName} — Instances`);
|
|
1889
|
+
cliSpacer();
|
|
1890
|
+
for (const name of instances) {
|
|
1891
|
+
const marker = name === current ? ' ← current' : '';
|
|
1892
|
+
console.log(` ${name}${marker}`);
|
|
1893
|
+
}
|
|
1894
|
+
cliSpacer();
|
|
1895
|
+
}
|
|
1896
|
+
catch (error) {
|
|
1897
|
+
printError(getErrorMessage(error));
|
|
1898
|
+
process.exit(1);
|
|
1899
|
+
}
|
|
1900
|
+
});
|
|
1901
|
+
// Shell command group: shell integration utilities
|
|
1902
|
+
const shell = program.command('shell').description('Shell integration utilities');
|
|
1903
|
+
shell
|
|
1904
|
+
.command('init')
|
|
1905
|
+
.option('--hook', 'Output the shell hook script (used internally by eval/Invoke-Expression)')
|
|
1906
|
+
.description('Set up shell integration for direct photon commands and tab completion')
|
|
1907
|
+
.action(async (options) => {
|
|
1908
|
+
// Detect shell type
|
|
1909
|
+
const userShell = process.env.SHELL || '';
|
|
1910
|
+
const isPowerShell = !!process.env.PSModulePath;
|
|
1911
|
+
const isZsh = !isPowerShell && userShell.includes('zsh');
|
|
1912
|
+
const isBash = !isPowerShell && userShell.includes('bash');
|
|
1913
|
+
let shellType = 'unsupported';
|
|
1914
|
+
if (isZsh)
|
|
1915
|
+
shellType = 'zsh';
|
|
1916
|
+
else if (isBash)
|
|
1917
|
+
shellType = 'bash';
|
|
1918
|
+
else if (isPowerShell || process.platform === 'win32')
|
|
1919
|
+
shellType = 'powershell';
|
|
1920
|
+
// Unsupported shell — show supported list and exit
|
|
1921
|
+
if (shellType === 'unsupported') {
|
|
1922
|
+
const detected = userShell ? path.basename(userShell) : 'unknown';
|
|
1923
|
+
printError(`Unsupported shell: ${detected}`);
|
|
1924
|
+
console.log('');
|
|
1925
|
+
console.log(' Supported shells:');
|
|
1926
|
+
console.log(' zsh ~/.zshrc (macOS default)');
|
|
1927
|
+
console.log(' bash ~/.bashrc (Linux default)');
|
|
1928
|
+
console.log(' PowerShell $PROFILE (Windows default, cross-platform)');
|
|
1929
|
+
console.log('');
|
|
1930
|
+
console.log(' To use a specific shell, set $SHELL and retry:');
|
|
1931
|
+
console.log(' SHELL=/bin/zsh photon shell init');
|
|
1932
|
+
process.exit(1);
|
|
1933
|
+
}
|
|
1934
|
+
// RC file and eval/invoke line per shell
|
|
1935
|
+
let rcFile;
|
|
1936
|
+
let evalLine;
|
|
1937
|
+
const marker = '# photon shell integration';
|
|
1938
|
+
if (shellType === 'powershell') {
|
|
1939
|
+
// PowerShell profile path: cross-platform
|
|
1940
|
+
rcFile =
|
|
1941
|
+
process.platform === 'win32'
|
|
1942
|
+
? path.join(os.homedir(), 'Documents', 'PowerShell', 'Microsoft.PowerShell_profile.ps1')
|
|
1943
|
+
: path.join(os.homedir(), '.config', 'powershell', 'Microsoft.PowerShell_profile.ps1');
|
|
1944
|
+
evalLine = 'Invoke-Expression (& photon shell init --hook)';
|
|
1945
|
+
}
|
|
1946
|
+
else {
|
|
1947
|
+
rcFile = isZsh ? path.join(os.homedir(), '.zshrc') : path.join(os.homedir(), '.bashrc');
|
|
1948
|
+
evalLine = 'eval "$(photon shell init --hook)"';
|
|
1949
|
+
}
|
|
1950
|
+
// --hook flag: output the hook script
|
|
1951
|
+
if (options.hook) {
|
|
1952
|
+
const photonDir = path.join(os.homedir(), '.photon');
|
|
1953
|
+
let photonNames = [];
|
|
1954
|
+
try {
|
|
1955
|
+
const entries = await fs.readdir(photonDir);
|
|
1956
|
+
photonNames = entries
|
|
1957
|
+
.filter((e) => /\.photon\.(ts|js)$/.test(e))
|
|
1958
|
+
.map((e) => e.replace(/\.photon\.(ts|js)$/, ''));
|
|
1959
|
+
}
|
|
1960
|
+
catch {
|
|
1961
|
+
// ~/.photon/ doesn't exist yet
|
|
1962
|
+
}
|
|
1963
|
+
if (shellType === 'zsh') {
|
|
1964
|
+
const functions = photonNames
|
|
1965
|
+
.map((name) => `${name}() { photon cli ${name} "$@"; }`)
|
|
1966
|
+
.join('\n');
|
|
1967
|
+
console.log(`${marker}
|
|
1968
|
+
|
|
1969
|
+
# Shell functions for installed photons (direct invocation)
|
|
1970
|
+
${functions}
|
|
1971
|
+
|
|
1972
|
+
# Fallback for newly installed photons (before shell restart)
|
|
1973
|
+
command_not_found_handler() {
|
|
1974
|
+
if [ -f "$HOME/.photon/\$1.photon.ts" ] || [ -f "$HOME/.photon/\$1.photon.js" ]; then
|
|
1975
|
+
photon cli "$@"
|
|
1976
|
+
return $?
|
|
1977
|
+
fi
|
|
1978
|
+
echo "zsh: command not found: \$1" >&2
|
|
1979
|
+
return 127
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
# Tab completion for photon methods, params, and instances
|
|
1983
|
+
_photon_cache="$HOME/.photon/cache/completions.cache"
|
|
1984
|
+
|
|
1985
|
+
_photon_complete_direct() {
|
|
1986
|
+
local cmd="\$words[1]"
|
|
1987
|
+
local curcontext="\$curcontext" state line
|
|
1988
|
+
_arguments -C "1: :->method" "*::arg:->params"
|
|
1989
|
+
case "\$state" in
|
|
1990
|
+
method)
|
|
1991
|
+
if [[ -f "\$_photon_cache" ]]; then
|
|
1992
|
+
local -a methods
|
|
1993
|
+
methods=("\${(@f)$(grep "^method:\${cmd}:" "\$_photon_cache" | while IFS=: read -r _ _ name desc; do echo "\${name}:\${desc}"; done)}")
|
|
1994
|
+
_describe 'method' methods
|
|
1995
|
+
fi
|
|
1996
|
+
;;
|
|
1997
|
+
params)
|
|
1998
|
+
if [[ -f "\$_photon_cache" ]]; then
|
|
1999
|
+
local method="\$line[1]"
|
|
2000
|
+
local -a params
|
|
2001
|
+
params=("\${(@f)$(grep "^param:\${cmd}:\${method}:" "\$_photon_cache" | while IFS=: read -r _ _ _ name type req; do echo "--\${name}[\${type}]"; done)}")
|
|
2002
|
+
_describe 'parameter' params
|
|
2003
|
+
fi
|
|
2004
|
+
;;
|
|
2005
|
+
esac
|
|
2006
|
+
}
|
|
2007
|
+
|
|
2008
|
+
# Register completion for each photon function (guard for non-interactive shells)
|
|
2009
|
+
if (( $+functions[compdef] )); then
|
|
2010
|
+
${photonNames.map((name) => ` compdef _photon_complete_direct ${name}`).join('\n')}
|
|
2011
|
+
fi
|
|
2012
|
+
|
|
2013
|
+
# Completion for the photon command itself
|
|
2014
|
+
_photon() {
|
|
2015
|
+
local curcontext="\$curcontext" state line
|
|
2016
|
+
_arguments -C \\
|
|
2017
|
+
"1: :->cmds" \\
|
|
2018
|
+
"*::arg:->args"
|
|
2019
|
+
case "\$state" in
|
|
2020
|
+
cmds)
|
|
2021
|
+
local -a builtins
|
|
2022
|
+
builtins=(
|
|
2023
|
+
'cli:Run a photon method'
|
|
2024
|
+
'use:Switch to a named instance'
|
|
2025
|
+
'instances:List instances of a photon'
|
|
2026
|
+
'set:Configure environment for a photon'
|
|
2027
|
+
'beam:Start the interactive UI'
|
|
2028
|
+
'serve:Start MCP stdio server'
|
|
2029
|
+
'list:List installed photons'
|
|
2030
|
+
'add:Install a photon'
|
|
2031
|
+
'remove:Uninstall a photon'
|
|
2032
|
+
'search:Search for photons'
|
|
2033
|
+
'info:Show photon details'
|
|
2034
|
+
'shell:Shell integration'
|
|
2035
|
+
'test:Run photon tests'
|
|
2036
|
+
'doctor:Check system health'
|
|
2037
|
+
)
|
|
2038
|
+
_describe 'command' builtins
|
|
2039
|
+
;;
|
|
2040
|
+
args)
|
|
2041
|
+
case \$line[1] in
|
|
2042
|
+
cli)
|
|
2043
|
+
local curcontext="\$curcontext" state line
|
|
2044
|
+
_arguments -C "1: :->photon_name" "*::arg:->method_args"
|
|
2045
|
+
case "\$state" in
|
|
2046
|
+
photon_name)
|
|
2047
|
+
if [[ -f "\$_photon_cache" ]]; then
|
|
2048
|
+
local -a photons
|
|
2049
|
+
photons=("\${(@f)$(grep "^photon:" "\$_photon_cache" | while IFS=: read -r _ name desc; do echo "\${name}:\${desc}"; done)}")
|
|
2050
|
+
_describe 'photon' photons
|
|
2051
|
+
fi
|
|
2052
|
+
;;
|
|
2053
|
+
method_args)
|
|
2054
|
+
words[1]="\$line[1]"
|
|
2055
|
+
_photon_complete_direct
|
|
2056
|
+
;;
|
|
2057
|
+
esac
|
|
2058
|
+
;;
|
|
2059
|
+
use|instances|set|info|serve)
|
|
2060
|
+
if [[ -f "\$_photon_cache" ]]; then
|
|
2061
|
+
local curcontext="\$curcontext" state line
|
|
2062
|
+
_arguments -C "1: :->photon_name" "*::arg:->instance"
|
|
2063
|
+
case "\$state" in
|
|
2064
|
+
photon_name)
|
|
2065
|
+
local -a photons
|
|
2066
|
+
photons=("\${(@f)$(grep "^photon:" "\$_photon_cache" | while IFS=: read -r _ name desc; do echo "\${name}:\${desc}"; done)}")
|
|
2067
|
+
_describe 'photon' photons
|
|
2068
|
+
;;
|
|
2069
|
+
instance)
|
|
2070
|
+
if [[ "\$line[-2]" == "use" ]]; then
|
|
2071
|
+
local -a instances
|
|
2072
|
+
instances=("\${(@f)$(grep "^instance:\${line[1]}:" "\$_photon_cache" | cut -d: -f3)}")
|
|
2073
|
+
[[ \${#instances} -gt 0 ]] && _describe 'instance' instances
|
|
2074
|
+
fi
|
|
2075
|
+
;;
|
|
2076
|
+
esac
|
|
2077
|
+
fi
|
|
2078
|
+
;;
|
|
2079
|
+
shell)
|
|
2080
|
+
local -a subcmds
|
|
2081
|
+
subcmds=('init:Set up shell integration' 'completions:Manage completion cache')
|
|
2082
|
+
_describe 'subcommand' subcmds
|
|
2083
|
+
;;
|
|
2084
|
+
esac
|
|
2085
|
+
;;
|
|
2086
|
+
esac
|
|
2087
|
+
}
|
|
2088
|
+
|
|
2089
|
+
if (( $+functions[compdef] )); then
|
|
2090
|
+
compdef _photon photon
|
|
2091
|
+
fi`);
|
|
2092
|
+
}
|
|
2093
|
+
else if (shellType === 'bash') {
|
|
2094
|
+
const functions = photonNames
|
|
2095
|
+
.map((name) => `${name}() { photon cli ${name} "$@"; }`)
|
|
2096
|
+
.join('\n');
|
|
2097
|
+
console.log(`${marker}
|
|
2098
|
+
|
|
2099
|
+
# Shell functions for installed photons (direct invocation)
|
|
2100
|
+
${functions}
|
|
2101
|
+
|
|
2102
|
+
# Fallback for newly installed photons (before shell restart)
|
|
2103
|
+
command_not_found_handle() {
|
|
2104
|
+
if [ -f "$HOME/.photon/\$1.photon.ts" ] || [ -f "$HOME/.photon/\$1.photon.js" ]; then
|
|
2105
|
+
photon cli "$@"
|
|
2106
|
+
return $?
|
|
2107
|
+
fi
|
|
2108
|
+
echo "bash: \$1: command not found" >&2
|
|
2109
|
+
return 127
|
|
2110
|
+
}
|
|
2111
|
+
|
|
2112
|
+
# Tab completion for photon methods, params, and instances
|
|
2113
|
+
_photon_cache="$HOME/.photon/cache/completions.cache"
|
|
2114
|
+
|
|
2115
|
+
_photon_complete_direct() {
|
|
2116
|
+
local cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
2117
|
+
local cmd="\${COMP_WORDS[0]}"
|
|
2118
|
+
COMPREPLY=()
|
|
2119
|
+
|
|
2120
|
+
if [[ ! -f "\$_photon_cache" ]]; then return; fi
|
|
2121
|
+
|
|
2122
|
+
if [[ \$COMP_CWORD -eq 1 ]]; then
|
|
2123
|
+
local methods
|
|
2124
|
+
methods="$(grep "^method:\${cmd}:" "\$_photon_cache" | cut -d: -f3)"
|
|
2125
|
+
COMPREPLY=($(compgen -W "\$methods" -- "\$cur"))
|
|
2126
|
+
elif [[ \$COMP_CWORD -eq 2 ]]; then
|
|
2127
|
+
local method="\${COMP_WORDS[1]}"
|
|
2128
|
+
local params
|
|
2129
|
+
params="$(grep "^param:\${cmd}:\${method}:" "\$_photon_cache" | cut -d: -f4 | sed 's/^/--/')"
|
|
2130
|
+
COMPREPLY=($(compgen -W "\$params" -- "\$cur"))
|
|
2131
|
+
fi
|
|
2132
|
+
}
|
|
2133
|
+
|
|
2134
|
+
_photon_complete() {
|
|
2135
|
+
local cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
2136
|
+
local prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
2137
|
+
COMPREPLY=()
|
|
2138
|
+
|
|
2139
|
+
if [[ ! -f "\$_photon_cache" ]]; then return; fi
|
|
2140
|
+
|
|
2141
|
+
if [[ \$COMP_CWORD -eq 1 ]]; then
|
|
2142
|
+
COMPREPLY=($(compgen -W "cli use instances set beam serve list add remove search info shell test doctor" -- "\$cur"))
|
|
2143
|
+
elif [[ \$COMP_CWORD -eq 2 ]]; then
|
|
2144
|
+
case "\${COMP_WORDS[1]}" in
|
|
2145
|
+
cli|use|instances|set|info|serve)
|
|
2146
|
+
local photons
|
|
2147
|
+
photons="$(grep "^photon:" "\$_photon_cache" | cut -d: -f2)"
|
|
2148
|
+
COMPREPLY=($(compgen -W "\$photons" -- "\$cur"))
|
|
2149
|
+
;;
|
|
2150
|
+
shell)
|
|
2151
|
+
COMPREPLY=($(compgen -W "init completions" -- "\$cur"))
|
|
2152
|
+
;;
|
|
2153
|
+
esac
|
|
2154
|
+
elif [[ \$COMP_CWORD -eq 3 ]]; then
|
|
2155
|
+
case "\${COMP_WORDS[1]}" in
|
|
2156
|
+
cli)
|
|
2157
|
+
local methods
|
|
2158
|
+
methods="$(grep "^method:\${COMP_WORDS[2]}:" "\$_photon_cache" | cut -d: -f3)"
|
|
2159
|
+
COMPREPLY=($(compgen -W "\$methods" -- "\$cur"))
|
|
2160
|
+
;;
|
|
2161
|
+
use)
|
|
2162
|
+
local instances
|
|
2163
|
+
instances="$(grep "^instance:\${COMP_WORDS[2]}:" "\$_photon_cache" | cut -d: -f3)"
|
|
2164
|
+
COMPREPLY=($(compgen -W "\$instances" -- "\$cur"))
|
|
2165
|
+
;;
|
|
2166
|
+
esac
|
|
2167
|
+
elif [[ \$COMP_CWORD -ge 4 && "\${COMP_WORDS[1]}" == "cli" ]]; then
|
|
2168
|
+
local params
|
|
2169
|
+
params="$(grep "^param:\${COMP_WORDS[2]}:\${COMP_WORDS[3]}:" "\$_photon_cache" | cut -d: -f4 | sed 's/^/--/')"
|
|
2170
|
+
COMPREPLY=($(compgen -W "\$params" -- "\$cur"))
|
|
2171
|
+
fi
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2174
|
+
# Register completions
|
|
2175
|
+
${photonNames.map((name) => `complete -F _photon_complete_direct ${name}`).join('\n')}
|
|
2176
|
+
complete -F _photon_complete photon`);
|
|
2177
|
+
}
|
|
2178
|
+
else if (shellType === 'powershell') {
|
|
2179
|
+
// PowerShell functions and completion
|
|
2180
|
+
const functions = photonNames
|
|
2181
|
+
.map((name) => `function ${name} { photon cli ${name} @Args }`)
|
|
2182
|
+
.join('\n');
|
|
2183
|
+
const functionNames = photonNames.map((n) => `'${n}'`).join(', ');
|
|
2184
|
+
console.log(`${marker}
|
|
2185
|
+
|
|
2186
|
+
# Functions for installed photons (direct invocation)
|
|
2187
|
+
${functions}
|
|
2188
|
+
|
|
2189
|
+
# Fallback for newly installed photons (CommandNotFoundAction, PowerShell 7.4+)
|
|
2190
|
+
if ($PSVersionTable.PSVersion.Major -ge 7 -and $PSVersionTable.PSVersion.Minor -ge 4) {
|
|
2191
|
+
$ExecutionContext.InvokeCommand.CommandNotFoundAction = {
|
|
2192
|
+
param($Name, $EventArgs)
|
|
2193
|
+
$photonFile = Join-Path $HOME ".photon" "$Name.photon.ts"
|
|
2194
|
+
$photonFileJs = Join-Path $HOME ".photon" "$Name.photon.js"
|
|
2195
|
+
if ((Test-Path $photonFile) -or (Test-Path $photonFileJs)) {
|
|
2196
|
+
$EventArgs.CommandScriptBlock = { photon cli $Name @Args }.GetNewClosure()
|
|
2197
|
+
$EventArgs.StopSearch = $true
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
|
|
2202
|
+
# Tab completion for photon methods, params, and instances
|
|
2203
|
+
$_photonCache = Join-Path $HOME ".photon" "cache" "completions.cache"
|
|
2204
|
+
|
|
2205
|
+
# Completion for direct photon commands
|
|
2206
|
+
${photonNames
|
|
2207
|
+
.map((name) => `Register-ArgumentCompleter -CommandName ${name} -ScriptBlock {
|
|
2208
|
+
param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
|
|
2209
|
+
if (-not (Test-Path $_photonCache)) { return }
|
|
2210
|
+
$pos = $commandAst.CommandElements.Count
|
|
2211
|
+
if ($pos -le 1) {
|
|
2212
|
+
# Complete method names
|
|
2213
|
+
Get-Content $_photonCache | Where-Object { $_ -match "^method:\${commandName}:" } | ForEach-Object {
|
|
2214
|
+
$parts = $_ -split ':', 4
|
|
2215
|
+
[System.Management.Automation.CompletionResult]::new($parts[2], $parts[2], 'ParameterValue', ($parts[3] ?? $parts[2]))
|
|
2216
|
+
} | Where-Object { $_.CompletionText -like "$wordToComplete*" }
|
|
2217
|
+
} elseif ($pos -le 2) {
|
|
2218
|
+
# Complete parameter names
|
|
2219
|
+
$method = $commandAst.CommandElements[1].Value
|
|
2220
|
+
Get-Content $_photonCache | Where-Object { $_ -match "^param:\${commandName}:\${method}:" } | ForEach-Object {
|
|
2221
|
+
$parts = $_ -split ':', 6
|
|
2222
|
+
$paramName = "--$($parts[3])"
|
|
2223
|
+
[System.Management.Automation.CompletionResult]::new($paramName, $paramName, 'ParameterName', "$($parts[4]) parameter")
|
|
2224
|
+
} | Where-Object { $_.CompletionText -like "$wordToComplete*" }
|
|
2225
|
+
}
|
|
2226
|
+
}`)
|
|
2227
|
+
.join('\n')}
|
|
2228
|
+
|
|
2229
|
+
# Completion for the photon command itself
|
|
2230
|
+
Register-ArgumentCompleter -CommandName photon -ScriptBlock {
|
|
2231
|
+
param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
|
|
2232
|
+
$pos = $commandAst.CommandElements.Count
|
|
2233
|
+
if ($pos -le 1) {
|
|
2234
|
+
@('cli','use','instances','set','beam','serve','list','add','remove','search','info','shell','test','doctor') |
|
|
2235
|
+
Where-Object { $_ -like "$wordToComplete*" } |
|
|
2236
|
+
ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }
|
|
2237
|
+
} elseif ($pos -le 2) {
|
|
2238
|
+
$sub = $commandAst.CommandElements[1].Value
|
|
2239
|
+
switch ($sub) {
|
|
2240
|
+
{ $_ -in 'cli','use','instances','set','info','serve' } {
|
|
2241
|
+
if (Test-Path $_photonCache) {
|
|
2242
|
+
Get-Content $_photonCache | Where-Object { $_ -match "^photon:" } | ForEach-Object {
|
|
2243
|
+
$parts = $_ -split ':', 3
|
|
2244
|
+
[System.Management.Automation.CompletionResult]::new($parts[1], $parts[1], 'ParameterValue', ($parts[2] ?? $parts[1]))
|
|
2245
|
+
} | Where-Object { $_.CompletionText -like "$wordToComplete*" }
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
'shell' {
|
|
2249
|
+
@('init','completions') | Where-Object { $_ -like "$wordToComplete*" } |
|
|
2250
|
+
ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
} elseif ($pos -le 3) {
|
|
2254
|
+
$sub = $commandAst.CommandElements[1].Value
|
|
2255
|
+
$photonName = $commandAst.CommandElements[2].Value
|
|
2256
|
+
if ($sub -eq 'cli' -and (Test-Path $_photonCache)) {
|
|
2257
|
+
Get-Content $_photonCache | Where-Object { $_ -match "^method:\${photonName}:" } | ForEach-Object {
|
|
2258
|
+
$parts = $_ -split ':', 4
|
|
2259
|
+
[System.Management.Automation.CompletionResult]::new($parts[2], $parts[2], 'ParameterValue', ($parts[3] ?? $parts[2]))
|
|
2260
|
+
} | Where-Object { $_.CompletionText -like "$wordToComplete*" }
|
|
2261
|
+
} elseif ($sub -eq 'use' -and (Test-Path $_photonCache)) {
|
|
2262
|
+
Get-Content $_photonCache | Where-Object { $_ -match "^instance:\${photonName}:" } | ForEach-Object {
|
|
2263
|
+
$parts = $_ -split ':', 3
|
|
2264
|
+
[System.Management.Automation.CompletionResult]::new($parts[2], $parts[2], 'ParameterValue', $parts[2])
|
|
2265
|
+
} | Where-Object { $_.CompletionText -like "$wordToComplete*" }
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
}`);
|
|
2269
|
+
}
|
|
2270
|
+
// Silently generate cache on first hook (non-blocking)
|
|
2271
|
+
const { CACHE_FILE } = await import('./shell-completions.js');
|
|
2272
|
+
try {
|
|
2273
|
+
await fs.access(CACHE_FILE);
|
|
2274
|
+
}
|
|
2275
|
+
catch {
|
|
2276
|
+
// Cache doesn't exist yet — generate it
|
|
2277
|
+
const { generateCompletionCache } = await import('./shell-completions.js');
|
|
2278
|
+
await generateCompletionCache();
|
|
2279
|
+
}
|
|
2280
|
+
return;
|
|
2281
|
+
}
|
|
2282
|
+
// Interactive mode → install into rc file
|
|
2283
|
+
try {
|
|
2284
|
+
// Ensure profile directory exists (PowerShell profile dir may not)
|
|
2285
|
+
const rcDir = path.dirname(rcFile);
|
|
2286
|
+
await fs.mkdir(rcDir, { recursive: true });
|
|
2287
|
+
let rcContent = '';
|
|
2288
|
+
try {
|
|
2289
|
+
rcContent = await fs.readFile(rcFile, 'utf-8');
|
|
2290
|
+
}
|
|
2291
|
+
catch {
|
|
2292
|
+
// rc file doesn't exist, we'll create it
|
|
2293
|
+
}
|
|
2294
|
+
if (rcContent.includes(marker) || rcContent.includes(evalLine)) {
|
|
2295
|
+
printInfo(`Shell integration already installed in ${rcFile}`);
|
|
2296
|
+
if (shellType === 'powershell') {
|
|
2297
|
+
console.log(` Restart PowerShell or run: . $PROFILE`);
|
|
2298
|
+
}
|
|
2299
|
+
else {
|
|
2300
|
+
console.log(` Restart your shell or run: source ${rcFile}`);
|
|
2301
|
+
}
|
|
2302
|
+
return;
|
|
2303
|
+
}
|
|
2304
|
+
const block = `\n${marker}\n${evalLine}\n`;
|
|
2305
|
+
await fs.appendFile(rcFile, block);
|
|
2306
|
+
// Generate completions cache
|
|
2307
|
+
const { generateCompletionCache } = await import('./shell-completions.js');
|
|
2308
|
+
await generateCompletionCache();
|
|
2309
|
+
printSuccess(`Installed shell integration into ${rcFile}`);
|
|
2310
|
+
if (shellType === 'powershell') {
|
|
2311
|
+
console.log(` Restart PowerShell or run: . $PROFILE`);
|
|
2312
|
+
}
|
|
2313
|
+
else {
|
|
2314
|
+
console.log(` Restart your shell or run: source ${rcFile}`);
|
|
2315
|
+
}
|
|
2316
|
+
console.log('');
|
|
2317
|
+
console.log(` Then type any photon name directly:`);
|
|
2318
|
+
console.log(` list get → photon cli list get`);
|
|
2319
|
+
console.log(` list add "Milk" → photon cli list add "Milk"`);
|
|
2320
|
+
console.log('');
|
|
2321
|
+
console.log(' Tab completion is enabled for:');
|
|
2322
|
+
console.log(' Photon names, methods, parameters, and instances.');
|
|
2323
|
+
}
|
|
2324
|
+
catch (error) {
|
|
2325
|
+
printError(`Failed to update ${rcFile}: ${getErrorMessage(error)}`);
|
|
2326
|
+
console.log(` Add this line manually to your shell profile:`);
|
|
2327
|
+
console.log(` ${evalLine}`);
|
|
2328
|
+
process.exit(1);
|
|
2329
|
+
}
|
|
2330
|
+
});
|
|
2331
|
+
shell
|
|
2332
|
+
.command('completions')
|
|
2333
|
+
.option('--generate', 'Regenerate the completions cache')
|
|
2334
|
+
.description('Manage shell completion cache')
|
|
2335
|
+
.action(async (options) => {
|
|
2336
|
+
const { generateCompletionCache, CACHE_FILE } = await import('./shell-completions.js');
|
|
2337
|
+
if (options.generate) {
|
|
2338
|
+
await generateCompletionCache();
|
|
2339
|
+
printSuccess(`Completions cache updated: ${CACHE_FILE}`);
|
|
2340
|
+
return;
|
|
2341
|
+
}
|
|
2342
|
+
// Default: show cache status
|
|
2343
|
+
try {
|
|
2344
|
+
const stat = await fs.stat(CACHE_FILE);
|
|
2345
|
+
const age = Date.now() - stat.mtimeMs;
|
|
2346
|
+
const ageStr = age < 60_000
|
|
2347
|
+
? 'just now'
|
|
2348
|
+
: age < 3_600_000
|
|
2349
|
+
? `${Math.floor(age / 60_000)}m ago`
|
|
2350
|
+
: `${Math.floor(age / 3_600_000)}h ago`;
|
|
2351
|
+
printInfo(`Cache: ${CACHE_FILE}`);
|
|
2352
|
+
console.log(` Last updated: ${ageStr}`);
|
|
2353
|
+
console.log(` Run \`photon shell completions --generate\` to refresh`);
|
|
2354
|
+
}
|
|
2355
|
+
catch {
|
|
2356
|
+
printInfo('No completions cache found.');
|
|
2357
|
+
console.log(' Run `photon shell completions --generate` to create one.');
|
|
2358
|
+
}
|
|
2359
|
+
});
|
|
2360
|
+
// Set command: configure environment for photons (primitive params without defaults)
|
|
2361
|
+
program
|
|
2362
|
+
.command('set')
|
|
2363
|
+
.argument('<photon>', 'Photon name')
|
|
2364
|
+
.argument('[args...]', 'Environment values (name=value pairs)')
|
|
2365
|
+
.description('Configure environment for a photon (params without defaults)')
|
|
2366
|
+
.action(async (photonName, args) => {
|
|
2367
|
+
try {
|
|
2368
|
+
const workingDir = program.opts().dir || DEFAULT_WORKING_DIR;
|
|
2369
|
+
// Resolve photon path
|
|
2370
|
+
const filePath = await resolvePhotonPathWithBundled(photonName, workingDir);
|
|
2371
|
+
if (!filePath) {
|
|
2372
|
+
printError(`Photon not found: ${photonName}`);
|
|
2373
|
+
process.exit(1);
|
|
2374
|
+
}
|
|
2375
|
+
// Extract constructor params and filter env params
|
|
2376
|
+
const allParams = await extractConstructorParams(filePath);
|
|
2377
|
+
const { getEnvParams, EnvStore } = await import('./context-store.js');
|
|
2378
|
+
const envParams = getEnvParams(allParams);
|
|
2379
|
+
if (envParams.length === 0) {
|
|
2380
|
+
printInfo(`${photonName} has no environment parameters.`);
|
|
2381
|
+
return;
|
|
2382
|
+
}
|
|
2383
|
+
const store = new EnvStore();
|
|
2384
|
+
// Parse name=value pairs from args
|
|
2385
|
+
const values = {};
|
|
2386
|
+
const paramNames = new Set(envParams.map((p) => p.name));
|
|
2387
|
+
for (const arg of args) {
|
|
2388
|
+
const eqIdx = arg.indexOf('=');
|
|
2389
|
+
if (eqIdx > 0) {
|
|
2390
|
+
const key = arg.slice(0, eqIdx);
|
|
2391
|
+
const val = arg.slice(eqIdx + 1);
|
|
2392
|
+
if (paramNames.has(key)) {
|
|
2393
|
+
values[key] = val;
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
else if (envParams.length === 1) {
|
|
2397
|
+
// Single env param: positional value
|
|
2398
|
+
values[envParams[0].name] = arg;
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
2401
|
+
// Find params that still need values
|
|
2402
|
+
const remaining = envParams.filter((p) => !(p.name in values));
|
|
2403
|
+
if (remaining.length > 0) {
|
|
2404
|
+
// Interactive mode for remaining params
|
|
2405
|
+
cliHeading(`${photonName} — Environment`);
|
|
2406
|
+
cliSpacer();
|
|
2407
|
+
const masked = store.getMasked(photonName);
|
|
2408
|
+
for (const param of remaining) {
|
|
2409
|
+
const currentDisplay = masked[param.name] ? `Current: ${masked[param.name]}` : 'Not set';
|
|
2410
|
+
const answer = await promptText(` ${param.name} (required)\n ${currentDisplay}\n > `);
|
|
2411
|
+
if (answer.trim() !== '') {
|
|
2412
|
+
values[param.name] = answer.trim();
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2416
|
+
if (Object.keys(values).length > 0) {
|
|
2417
|
+
store.write(photonName, values);
|
|
2418
|
+
const summary = Object.keys(values).join(', ');
|
|
2419
|
+
printSuccess(`Environment saved: ${summary}`);
|
|
2420
|
+
}
|
|
2421
|
+
else {
|
|
2422
|
+
printInfo('No changes.');
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
catch (error) {
|
|
2426
|
+
printError(getErrorMessage(error));
|
|
2427
|
+
process.exit(1);
|
|
2428
|
+
}
|
|
2429
|
+
});
|
|
1818
2430
|
// Alias commands: create CLI shortcuts for photons
|
|
1819
2431
|
program
|
|
1820
2432
|
.command('alias', { hidden: true })
|
|
@@ -1899,6 +2511,10 @@ const RESERVED_COMMANDS = [
|
|
|
1899
2511
|
'doctor',
|
|
1900
2512
|
'clear-cache',
|
|
1901
2513
|
'clean',
|
|
2514
|
+
// Instance/env
|
|
2515
|
+
'use',
|
|
2516
|
+
'instances',
|
|
2517
|
+
'set',
|
|
1902
2518
|
// Aliases
|
|
1903
2519
|
'cli',
|
|
1904
2520
|
'alias',
|
|
@@ -1913,6 +2529,7 @@ const RESERVED_COMMANDS = [
|
|
|
1913
2529
|
'search',
|
|
1914
2530
|
'maker',
|
|
1915
2531
|
'host',
|
|
2532
|
+
'shell',
|
|
1916
2533
|
'diagram',
|
|
1917
2534
|
'diagrams',
|
|
1918
2535
|
'enable',
|
|
@@ -1947,6 +2564,9 @@ const knownCommands = [
|
|
|
1947
2564
|
'clear-cache',
|
|
1948
2565
|
'clean',
|
|
1949
2566
|
'doctor',
|
|
2567
|
+
'use',
|
|
2568
|
+
'instances',
|
|
2569
|
+
'set',
|
|
1950
2570
|
'cli',
|
|
1951
2571
|
'alias',
|
|
1952
2572
|
'unalias',
|
|
@@ -1956,12 +2576,14 @@ const knownCommands = [
|
|
|
1956
2576
|
'marketplace',
|
|
1957
2577
|
'maker',
|
|
1958
2578
|
'host',
|
|
2579
|
+
'shell',
|
|
1959
2580
|
'diagram',
|
|
1960
2581
|
'diagrams',
|
|
1961
2582
|
];
|
|
1962
2583
|
const knownSubcommands = {
|
|
1963
2584
|
marketplace: ['list', 'add', 'remove', 'enable', 'disable'],
|
|
1964
2585
|
maker: ['new', 'validate', 'sync', 'init'],
|
|
2586
|
+
shell: ['init', 'completions'],
|
|
1965
2587
|
};
|
|
1966
2588
|
/**
|
|
1967
2589
|
* Calculate Levenshtein distance between two strings
|
|
@@ -2039,9 +2661,10 @@ program.on('command:*', async (operands) => {
|
|
|
2039
2661
|
// This enables: `photon lg-remote volume +5` instead of `photon cli lg-remote volume +5`
|
|
2040
2662
|
function preprocessArgs() {
|
|
2041
2663
|
const args = process.argv.slice(2);
|
|
2042
|
-
// No args -
|
|
2664
|
+
// No args - launch Beam (the primary interface)
|
|
2665
|
+
// Use `photon -h` or `photon --help` for help
|
|
2043
2666
|
if (args.length === 0) {
|
|
2044
|
-
return [...process.argv, 'beam'
|
|
2667
|
+
return [...process.argv, 'beam'];
|
|
2045
2668
|
}
|
|
2046
2669
|
// Find the first non-flag argument (skip values of flags that take a parameter)
|
|
2047
2670
|
const flagsWithValues = ['--dir', '--log-level'];
|
|
@@ -2059,8 +2682,8 @@ function preprocessArgs() {
|
|
|
2059
2682
|
if (args.some((a) => a === '--help' || a === '-h' || a === '--version' || a === '-V')) {
|
|
2060
2683
|
return process.argv;
|
|
2061
2684
|
}
|
|
2062
|
-
// Otherwise
|
|
2063
|
-
return [...process.argv, 'beam'
|
|
2685
|
+
// Otherwise launch Beam (e.g., photon --dir=.)
|
|
2686
|
+
return [...process.argv, 'beam'];
|
|
2064
2687
|
}
|
|
2065
2688
|
const firstArg = args[firstArgIndex];
|
|
2066
2689
|
// If first arg is a reserved command, let commander handle normally
|