@portel/photon 1.4.1 → 1.5.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 +287 -1160
- package/dist/auto-ui/beam.d.ts +9 -0
- package/dist/auto-ui/beam.d.ts.map +1 -0
- package/dist/auto-ui/beam.js +2381 -0
- package/dist/auto-ui/beam.js.map +1 -0
- package/dist/auto-ui/components/card.d.ts +13 -0
- package/dist/auto-ui/components/card.d.ts.map +1 -0
- package/dist/auto-ui/components/card.js +64 -0
- package/dist/auto-ui/components/card.js.map +1 -0
- package/dist/auto-ui/components/form.d.ts +15 -0
- package/dist/auto-ui/components/form.d.ts.map +1 -0
- package/dist/auto-ui/components/form.js +72 -0
- package/dist/auto-ui/components/form.js.map +1 -0
- package/dist/auto-ui/components/list.d.ts +13 -0
- package/dist/auto-ui/components/list.d.ts.map +1 -0
- package/dist/auto-ui/components/list.js +58 -0
- package/dist/auto-ui/components/list.js.map +1 -0
- package/dist/auto-ui/components/progress.d.ts +18 -0
- package/dist/auto-ui/components/progress.d.ts.map +1 -0
- package/dist/auto-ui/components/progress.js +125 -0
- package/dist/auto-ui/components/progress.js.map +1 -0
- package/dist/auto-ui/components/table.d.ts +13 -0
- package/dist/auto-ui/components/table.d.ts.map +1 -0
- package/dist/auto-ui/components/table.js +82 -0
- package/dist/auto-ui/components/table.js.map +1 -0
- package/dist/auto-ui/components/tree.d.ts +13 -0
- package/dist/auto-ui/components/tree.d.ts.map +1 -0
- package/dist/auto-ui/components/tree.js +61 -0
- package/dist/auto-ui/components/tree.js.map +1 -0
- package/dist/auto-ui/daemon-tools.d.ts +45 -0
- package/dist/auto-ui/daemon-tools.d.ts.map +1 -0
- package/dist/auto-ui/daemon-tools.js +580 -0
- package/dist/auto-ui/daemon-tools.js.map +1 -0
- package/dist/auto-ui/design-system/index.d.ts +21 -0
- package/dist/auto-ui/design-system/index.d.ts.map +1 -0
- package/dist/auto-ui/design-system/index.js +27 -0
- package/dist/auto-ui/design-system/index.js.map +1 -0
- package/dist/auto-ui/design-system/tokens.d.ts +9 -0
- package/dist/auto-ui/design-system/tokens.d.ts.map +1 -0
- package/dist/auto-ui/design-system/tokens.js +27 -0
- package/dist/auto-ui/design-system/tokens.js.map +1 -0
- package/dist/auto-ui/design-system/transaction-ui.d.ts +70 -0
- package/dist/auto-ui/design-system/transaction-ui.d.ts.map +1 -0
- package/dist/auto-ui/design-system/transaction-ui.js +982 -0
- package/dist/auto-ui/design-system/transaction-ui.js.map +1 -0
- package/dist/auto-ui/frontend/index.html +84 -0
- package/dist/auto-ui/index.d.ts +21 -0
- package/dist/auto-ui/index.d.ts.map +1 -0
- package/dist/auto-ui/index.js +25 -0
- package/dist/auto-ui/index.js.map +1 -0
- package/dist/auto-ui/openapi-generator.d.ts +71 -0
- package/dist/auto-ui/openapi-generator.d.ts.map +1 -0
- package/dist/auto-ui/openapi-generator.js +223 -0
- package/dist/auto-ui/openapi-generator.js.map +1 -0
- package/dist/auto-ui/photon-bridge.d.ts +159 -0
- package/dist/auto-ui/photon-bridge.d.ts.map +1 -0
- package/dist/auto-ui/photon-bridge.js +262 -0
- package/dist/auto-ui/photon-bridge.js.map +1 -0
- package/dist/auto-ui/photon-host.d.ts +113 -0
- package/dist/auto-ui/photon-host.d.ts.map +1 -0
- package/dist/auto-ui/photon-host.js +284 -0
- package/dist/auto-ui/photon-host.js.map +1 -0
- package/dist/auto-ui/platform-compat.d.ts +71 -0
- package/dist/auto-ui/platform-compat.d.ts.map +1 -0
- package/dist/auto-ui/platform-compat.js +574 -0
- package/dist/auto-ui/platform-compat.js.map +1 -0
- package/dist/auto-ui/playground-html.d.ts +15 -0
- package/dist/auto-ui/playground-html.d.ts.map +1 -0
- package/dist/auto-ui/playground-html.js +1113 -0
- package/dist/auto-ui/playground-html.js.map +1 -0
- package/dist/auto-ui/playground-server.d.ts +7 -0
- package/dist/auto-ui/playground-server.d.ts.map +1 -0
- package/dist/auto-ui/playground-server.js +840 -0
- package/dist/auto-ui/playground-server.js.map +1 -0
- package/dist/auto-ui/registry.d.ts +13 -0
- package/dist/auto-ui/registry.d.ts.map +1 -0
- package/dist/auto-ui/registry.js +62 -0
- package/dist/auto-ui/registry.js.map +1 -0
- package/dist/auto-ui/renderer.d.ts +14 -0
- package/dist/auto-ui/renderer.d.ts.map +1 -0
- package/dist/auto-ui/renderer.js +88 -0
- package/dist/auto-ui/renderer.js.map +1 -0
- package/dist/auto-ui/rendering/components.d.ts +29 -0
- package/dist/auto-ui/rendering/components.d.ts.map +1 -0
- package/dist/auto-ui/rendering/components.js +773 -0
- package/dist/auto-ui/rendering/components.js.map +1 -0
- package/dist/auto-ui/rendering/field-analyzer.d.ts +48 -0
- package/dist/auto-ui/rendering/field-analyzer.d.ts.map +1 -0
- package/dist/auto-ui/rendering/field-analyzer.js +270 -0
- package/dist/auto-ui/rendering/field-analyzer.js.map +1 -0
- package/dist/auto-ui/rendering/field-renderers.d.ts +64 -0
- package/dist/auto-ui/rendering/field-renderers.d.ts.map +1 -0
- package/dist/auto-ui/rendering/field-renderers.js +317 -0
- package/dist/auto-ui/rendering/field-renderers.js.map +1 -0
- package/dist/auto-ui/rendering/index.d.ts +28 -0
- package/dist/auto-ui/rendering/index.d.ts.map +1 -0
- package/dist/auto-ui/rendering/index.js +60 -0
- package/dist/auto-ui/rendering/index.js.map +1 -0
- package/dist/auto-ui/rendering/layout-selector.d.ts +48 -0
- package/dist/auto-ui/rendering/layout-selector.d.ts.map +1 -0
- package/dist/auto-ui/rendering/layout-selector.js +352 -0
- package/dist/auto-ui/rendering/layout-selector.js.map +1 -0
- package/dist/auto-ui/rendering/template-engine.d.ts +41 -0
- package/dist/auto-ui/rendering/template-engine.d.ts.map +1 -0
- package/dist/auto-ui/rendering/template-engine.js +238 -0
- package/dist/auto-ui/rendering/template-engine.js.map +1 -0
- package/dist/auto-ui/streamable-http-transport.d.ts +79 -0
- package/dist/auto-ui/streamable-http-transport.d.ts.map +1 -0
- package/dist/auto-ui/streamable-http-transport.js +1314 -0
- package/dist/auto-ui/streamable-http-transport.js.map +1 -0
- package/dist/auto-ui/types.d.ts +310 -0
- package/dist/auto-ui/types.d.ts.map +1 -0
- package/dist/auto-ui/types.js +71 -0
- package/dist/auto-ui/types.js.map +1 -0
- package/dist/beam.bundle.js +13506 -0
- package/dist/beam.bundle.js.map +7 -0
- package/dist/claude-code-plugin.d.ts.map +1 -1
- package/dist/claude-code-plugin.js +30 -30
- package/dist/claude-code-plugin.js.map +1 -1
- package/dist/cli/commands/info.d.ts +11 -0
- package/dist/cli/commands/info.d.ts.map +1 -0
- package/dist/cli/commands/info.js +313 -0
- package/dist/cli/commands/info.js.map +1 -0
- package/dist/cli/commands/marketplace.d.ts +11 -0
- package/dist/cli/commands/marketplace.d.ts.map +1 -0
- package/dist/cli/commands/marketplace.js +198 -0
- package/dist/cli/commands/marketplace.js.map +1 -0
- package/dist/cli/commands/package-app.d.ts +9 -0
- package/dist/cli/commands/package-app.d.ts.map +1 -0
- package/dist/cli/commands/package-app.js +191 -0
- package/dist/cli/commands/package-app.js.map +1 -0
- package/dist/cli/commands/package.d.ts +11 -0
- package/dist/cli/commands/package.d.ts.map +1 -0
- package/dist/cli/commands/package.js +573 -0
- package/dist/cli/commands/package.js.map +1 -0
- package/dist/cli-alias.d.ts.map +1 -1
- package/dist/cli-alias.js +30 -28
- package/dist/cli-alias.js.map +1 -1
- package/dist/cli-formatter.d.ts +8 -24
- package/dist/cli-formatter.d.ts.map +1 -1
- package/dist/cli-formatter.js +8 -325
- package/dist/cli-formatter.js.map +1 -1
- package/dist/cli.d.ts +15 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +1157 -1132
- package/dist/cli.js.map +1 -1
- package/dist/daemon/client.d.ts +79 -0
- package/dist/daemon/client.d.ts.map +1 -1
- package/dist/daemon/client.js +532 -8
- package/dist/daemon/client.js.map +1 -1
- package/dist/daemon/manager.d.ts +46 -12
- package/dist/daemon/manager.d.ts.map +1 -1
- package/dist/daemon/manager.js +102 -61
- package/dist/daemon/manager.js.map +1 -1
- package/dist/daemon/protocol.d.ts +62 -6
- package/dist/daemon/protocol.d.ts.map +1 -1
- package/dist/daemon/protocol.js +76 -1
- package/dist/daemon/protocol.js.map +1 -1
- package/dist/daemon/server.d.ts +6 -6
- package/dist/daemon/server.js +743 -133
- package/dist/daemon/server.js.map +1 -1
- package/dist/daemon/session-manager.d.ts +8 -1
- package/dist/daemon/session-manager.d.ts.map +1 -1
- package/dist/daemon/session-manager.js +32 -9
- package/dist/daemon/session-manager.js.map +1 -1
- package/dist/deploy/cloudflare.d.ts +12 -0
- package/dist/deploy/cloudflare.d.ts.map +1 -0
- package/dist/deploy/cloudflare.js +216 -0
- package/dist/deploy/cloudflare.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/loader.d.ts +168 -21
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +1120 -318
- package/dist/loader.js.map +1 -1
- package/dist/markdown-utils.d.ts +8 -0
- package/dist/markdown-utils.d.ts.map +1 -0
- package/dist/markdown-utils.js +63 -0
- package/dist/markdown-utils.js.map +1 -0
- package/dist/marketplace-manager.d.ts +10 -0
- package/dist/marketplace-manager.d.ts.map +1 -1
- package/dist/marketplace-manager.js +112 -28
- package/dist/marketplace-manager.js.map +1 -1
- package/dist/mcp-client.d.ts +9 -0
- package/dist/mcp-client.d.ts.map +1 -0
- package/dist/mcp-client.js +11 -0
- package/dist/mcp-client.js.map +1 -0
- package/dist/mcp-elicitation.d.ts +32 -0
- package/dist/mcp-elicitation.d.ts.map +1 -0
- package/dist/mcp-elicitation.js +26 -0
- package/dist/mcp-elicitation.js.map +1 -0
- package/dist/path-resolver.d.ts +9 -12
- package/dist/path-resolver.d.ts.map +1 -1
- package/dist/path-resolver.js +13 -43
- package/dist/path-resolver.js.map +1 -1
- package/dist/photon-cli-runner.d.ts.map +1 -1
- package/dist/photon-cli-runner.js +202 -77
- package/dist/photon-cli-runner.js.map +1 -1
- package/dist/photon-doc-extractor.d.ts +88 -0
- package/dist/photon-doc-extractor.d.ts.map +1 -1
- package/dist/photon-doc-extractor.js +536 -27
- package/dist/photon-doc-extractor.js.map +1 -1
- package/dist/photons/maker.photon.d.ts +182 -0
- package/dist/photons/maker.photon.d.ts.map +1 -0
- package/dist/photons/maker.photon.js +504 -0
- package/dist/photons/maker.photon.js.map +1 -0
- package/dist/photons/maker.photon.ts +626 -0
- package/dist/photons/marketplace.photon.d.ts +110 -0
- package/dist/photons/marketplace.photon.d.ts.map +1 -0
- package/dist/photons/marketplace.photon.js +260 -0
- package/dist/photons/marketplace.photon.js.map +1 -0
- package/dist/photons/marketplace.photon.ts +378 -0
- package/dist/photons/tunnel.photon.d.ts +80 -0
- package/dist/photons/tunnel.photon.d.ts.map +1 -0
- package/dist/photons/tunnel.photon.js +269 -0
- package/dist/photons/tunnel.photon.js.map +1 -0
- package/dist/photons/tunnel.photon.ts +345 -0
- package/dist/security-scanner.d.ts.map +1 -1
- package/dist/security-scanner.js +18 -15
- package/dist/security-scanner.js.map +1 -1
- package/dist/serv/auth/jwt.d.ts +89 -0
- package/dist/serv/auth/jwt.d.ts.map +1 -0
- package/dist/serv/auth/jwt.js +239 -0
- package/dist/serv/auth/jwt.js.map +1 -0
- package/dist/serv/auth/oauth.d.ts +117 -0
- package/dist/serv/auth/oauth.d.ts.map +1 -0
- package/dist/serv/auth/oauth.js +395 -0
- package/dist/serv/auth/oauth.js.map +1 -0
- package/dist/serv/auth/well-known.d.ts +60 -0
- package/dist/serv/auth/well-known.d.ts.map +1 -0
- package/dist/serv/auth/well-known.js +154 -0
- package/dist/serv/auth/well-known.js.map +1 -0
- package/dist/serv/db/d1-client.d.ts +65 -0
- package/dist/serv/db/d1-client.d.ts.map +1 -0
- package/dist/serv/db/d1-client.js +137 -0
- package/dist/serv/db/d1-client.js.map +1 -0
- package/dist/serv/db/d1-stores.d.ts +62 -0
- package/dist/serv/db/d1-stores.d.ts.map +1 -0
- package/dist/serv/db/d1-stores.js +307 -0
- package/dist/serv/db/d1-stores.js.map +1 -0
- package/dist/serv/index.d.ts +114 -0
- package/dist/serv/index.d.ts.map +1 -0
- package/dist/serv/index.js +172 -0
- package/dist/serv/index.js.map +1 -0
- package/dist/serv/local.d.ts +118 -0
- package/dist/serv/local.d.ts.map +1 -0
- package/dist/serv/local.js +392 -0
- package/dist/serv/local.js.map +1 -0
- package/dist/serv/middleware/auth.d.ts +66 -0
- package/dist/serv/middleware/auth.d.ts.map +1 -0
- package/dist/serv/middleware/auth.js +178 -0
- package/dist/serv/middleware/auth.js.map +1 -0
- package/dist/serv/middleware/tenant.d.ts +94 -0
- package/dist/serv/middleware/tenant.d.ts.map +1 -0
- package/dist/serv/middleware/tenant.js +152 -0
- package/dist/serv/middleware/tenant.js.map +1 -0
- package/dist/serv/runtime/executor.d.ts +76 -0
- package/dist/serv/runtime/executor.d.ts.map +1 -0
- package/dist/serv/runtime/executor.js +105 -0
- package/dist/serv/runtime/executor.js.map +1 -0
- package/dist/serv/runtime/index.d.ts +8 -0
- package/dist/serv/runtime/index.d.ts.map +1 -0
- package/dist/serv/runtime/index.js +10 -0
- package/dist/serv/runtime/index.js.map +1 -0
- package/dist/serv/runtime/oauth-context.d.ts +121 -0
- package/dist/serv/runtime/oauth-context.d.ts.map +1 -0
- package/dist/serv/runtime/oauth-context.js +153 -0
- package/dist/serv/runtime/oauth-context.js.map +1 -0
- package/dist/serv/session/kv-store.d.ts +54 -0
- package/dist/serv/session/kv-store.d.ts.map +1 -0
- package/dist/serv/session/kv-store.js +149 -0
- package/dist/serv/session/kv-store.js.map +1 -0
- package/dist/serv/session/store.d.ts +113 -0
- package/dist/serv/session/store.d.ts.map +1 -0
- package/dist/serv/session/store.js +284 -0
- package/dist/serv/session/store.js.map +1 -0
- package/dist/serv/types/index.d.ts +147 -0
- package/dist/serv/types/index.d.ts.map +1 -0
- package/dist/serv/types/index.js +8 -0
- package/dist/serv/types/index.js.map +1 -0
- package/dist/serv/vault/token-vault.d.ts +102 -0
- package/dist/serv/vault/token-vault.d.ts.map +1 -0
- package/dist/serv/vault/token-vault.js +177 -0
- package/dist/serv/vault/token-vault.js.map +1 -0
- package/dist/server.d.ts +173 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +1622 -86
- package/dist/server.js.map +1 -1
- package/dist/shared/cli-sections.d.ts +6 -0
- package/dist/shared/cli-sections.d.ts.map +1 -0
- package/dist/shared/cli-sections.js +16 -0
- package/dist/shared/cli-sections.js.map +1 -0
- package/dist/shared/cli-utils.d.ts +81 -0
- package/dist/shared/cli-utils.d.ts.map +1 -0
- package/dist/shared/cli-utils.js +174 -0
- package/dist/shared/cli-utils.js.map +1 -0
- package/dist/shared/config-docs.d.ts +6 -0
- package/dist/shared/config-docs.d.ts.map +1 -0
- package/dist/shared/config-docs.js +6 -0
- package/dist/shared/config-docs.js.map +1 -0
- package/dist/shared/error-handler.d.ts +128 -0
- package/dist/shared/error-handler.d.ts.map +1 -0
- package/dist/shared/error-handler.js +342 -0
- package/dist/shared/error-handler.js.map +1 -0
- package/dist/shared/logger.d.ts +42 -0
- package/dist/shared/logger.d.ts.map +1 -0
- package/dist/shared/logger.js +123 -0
- package/dist/shared/logger.js.map +1 -0
- package/dist/shared/performance.d.ts +65 -0
- package/dist/shared/performance.d.ts.map +1 -0
- package/dist/shared/performance.js +136 -0
- package/dist/shared/performance.js.map +1 -0
- package/dist/shared/task-runner.d.ts +2 -0
- package/dist/shared/task-runner.d.ts.map +1 -0
- package/dist/shared/task-runner.js +16 -0
- package/dist/shared/task-runner.js.map +1 -0
- package/dist/shared/validation.d.ts +6 -0
- package/dist/shared/validation.d.ts.map +1 -0
- package/dist/shared/validation.js +6 -0
- package/dist/shared/validation.js.map +1 -0
- package/dist/shared-utils.d.ts +63 -0
- package/dist/shared-utils.d.ts.map +1 -0
- package/dist/shared-utils.js +123 -0
- package/dist/shared-utils.js.map +1 -0
- package/dist/template-manager.d.ts +23 -2
- package/dist/template-manager.d.ts.map +1 -1
- package/dist/template-manager.js +175 -86
- package/dist/template-manager.js.map +1 -1
- package/dist/test-client.d.ts.map +1 -1
- package/dist/test-client.js +10 -8
- package/dist/test-client.js.map +1 -1
- package/dist/test-runner.d.ts +52 -0
- package/dist/test-runner.d.ts.map +1 -0
- package/dist/test-runner.js +785 -0
- package/dist/test-runner.js.map +1 -0
- package/dist/testing.d.ts +103 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +163 -0
- package/dist/testing.js.map +1 -0
- package/dist/version-checker.d.ts.map +1 -1
- package/dist/version-checker.js +2 -2
- package/dist/version-checker.js.map +1 -1
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +5 -0
- package/dist/version.js.map +1 -0
- package/dist/watcher.d.ts +6 -3
- package/dist/watcher.d.ts.map +1 -1
- package/dist/watcher.js +49 -10
- package/dist/watcher.js.map +1 -1
- package/package.json +47 -7
- package/templates/cloudflare/worker.ts.template +381 -0
- package/templates/cloudflare/wrangler.toml.template +9 -0
- package/dist/base.d.ts +0 -58
- package/dist/base.d.ts.map +0 -1
- package/dist/base.js +0 -92
- package/dist/base.js.map +0 -1
- package/dist/dependency-manager.d.ts +0 -49
- package/dist/dependency-manager.d.ts.map +0 -1
- package/dist/dependency-manager.js +0 -165
- package/dist/dependency-manager.js.map +0 -1
- package/dist/registry-manager.d.ts +0 -76
- package/dist/registry-manager.d.ts.map +0 -1
- package/dist/registry-manager.js +0 -220
- package/dist/registry-manager.js.map +0 -1
- package/dist/schema-extractor.d.ts +0 -110
- package/dist/schema-extractor.d.ts.map +0 -1
- package/dist/schema-extractor.js +0 -727
- package/dist/schema-extractor.js.map +0 -1
- package/dist/test-marketplace-sources.d.ts +0 -5
- package/dist/test-marketplace-sources.d.ts.map +0 -1
- package/dist/test-marketplace-sources.js +0 -53
- package/dist/test-marketplace-sources.js.map +0 -1
- package/dist/types.d.ts +0 -109
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -12
- package/dist/types.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -9,15 +9,305 @@ import * as path from 'path';
|
|
|
9
9
|
import * as fs from 'fs/promises';
|
|
10
10
|
import { existsSync } from 'fs';
|
|
11
11
|
import * as os from 'os';
|
|
12
|
-
import * as
|
|
12
|
+
import * as net from 'net';
|
|
13
13
|
import { PhotonServer } from './server.js';
|
|
14
14
|
import { FileWatcher } from './watcher.js';
|
|
15
|
-
import { resolvePhotonPath, listPhotonMCPs, ensureWorkingDir, DEFAULT_WORKING_DIR } from './path-resolver.js';
|
|
15
|
+
import { resolvePhotonPath, listPhotonMCPs, ensureWorkingDir, DEFAULT_WORKING_DIR, } from './path-resolver.js';
|
|
16
16
|
import { SchemaExtractor } from '@portel/photon-core';
|
|
17
17
|
import { createRequire } from 'module';
|
|
18
18
|
import { fileURLToPath } from 'url';
|
|
19
19
|
const require = createRequire(import.meta.url);
|
|
20
20
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
import { getBundledPhotonPath, DEFAULT_BUNDLED_PHOTONS } from './shared-utils.js';
|
|
22
|
+
import { PHOTON_VERSION } from './version.js';
|
|
23
|
+
import { toEnvVarName } from './shared/config-docs.js';
|
|
24
|
+
import { runTask } from './shared/task-runner.js';
|
|
25
|
+
import { normalizeLogLevel, logger } from './shared/logger.js';
|
|
26
|
+
import { printHeader, printInfo, printWarning, printError, printSuccess } from './cli-formatter.js';
|
|
27
|
+
import { handleError, getErrorMessage, ExitCode, exitWithError, } from './shared/error-handler.js';
|
|
28
|
+
import { validateOrThrow, inRange, isPositive, isInteger } from './shared/validation.js';
|
|
29
|
+
import { createReadline, promptWait } from './shared/cli-utils.js';
|
|
30
|
+
import { registerMarketplaceCommands } from './cli/commands/marketplace.js';
|
|
31
|
+
import { registerInfoCommand } from './cli/commands/info.js';
|
|
32
|
+
import { registerPackageCommands } from './cli/commands/package.js';
|
|
33
|
+
import { registerPackageAppCommand } from './cli/commands/package-app.js';
|
|
34
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
35
|
+
// BUNDLED PHOTONS
|
|
36
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
37
|
+
/** Bundled photon names that ship with the runtime */
|
|
38
|
+
// BUNDLED_PHOTONS and getBundledPhotonPath are imported from shared-utils.js
|
|
39
|
+
/**
|
|
40
|
+
* Parse extended photon name format
|
|
41
|
+
*
|
|
42
|
+
* Supports:
|
|
43
|
+
* - "rss-feed" → { name: "rss-feed" }
|
|
44
|
+
* - "alice/custom-photons:rss-feed" → { name: "rss-feed", marketplaceSource: "alice/custom-photons" }
|
|
45
|
+
*
|
|
46
|
+
* Rule: colon splits only when left side contains `/` (a marketplace source)
|
|
47
|
+
* and right side is a simple name (no `/`).
|
|
48
|
+
*/
|
|
49
|
+
export function parsePhotonSpec(spec) {
|
|
50
|
+
const colonIndex = spec.indexOf(':');
|
|
51
|
+
if (colonIndex > 0) {
|
|
52
|
+
const left = spec.slice(0, colonIndex);
|
|
53
|
+
const right = spec.slice(colonIndex + 1);
|
|
54
|
+
// Left must contain `/` (marketplace source) and right must be a simple name
|
|
55
|
+
if (left.includes('/') && right && !right.includes('/')) {
|
|
56
|
+
return { name: right, marketplaceSource: left };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return { name: spec };
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Resolve photon path - checks bundled first, then user directory
|
|
63
|
+
*/
|
|
64
|
+
async function resolvePhotonPathWithBundled(name, workingDir) {
|
|
65
|
+
// Check bundled photons first
|
|
66
|
+
const bundledPath = getBundledPhotonPath(name, __dirname);
|
|
67
|
+
if (bundledPath) {
|
|
68
|
+
return bundledPath;
|
|
69
|
+
}
|
|
70
|
+
// Fall back to user photons
|
|
71
|
+
return resolvePhotonPath(name, workingDir);
|
|
72
|
+
}
|
|
73
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
74
|
+
// PORT UTILITIES
|
|
75
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
76
|
+
/**
|
|
77
|
+
* Check if a port is available
|
|
78
|
+
*/
|
|
79
|
+
function isPortAvailable(port) {
|
|
80
|
+
return new Promise((resolve) => {
|
|
81
|
+
const server = net.createServer();
|
|
82
|
+
server.once('error', () => resolve(false));
|
|
83
|
+
server.once('listening', () => {
|
|
84
|
+
server.close();
|
|
85
|
+
resolve(true);
|
|
86
|
+
});
|
|
87
|
+
// Listen on all interfaces (same as http.createServer default)
|
|
88
|
+
server.listen(port);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
function getLogOptionsFromCommand(command) {
|
|
92
|
+
const root = command?.parent?.opts?.() ?? program.opts();
|
|
93
|
+
try {
|
|
94
|
+
const level = normalizeLogLevel(root.logLevel);
|
|
95
|
+
return {
|
|
96
|
+
level,
|
|
97
|
+
json: Boolean(root.jsonLogs),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
handleError(error, { exitOnError: true });
|
|
102
|
+
throw error; // TypeScript doesn't know handleError exits
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Find an available port starting from the given port
|
|
107
|
+
*/
|
|
108
|
+
async function findAvailablePort(startPort, maxAttempts = 10) {
|
|
109
|
+
// Validate port range
|
|
110
|
+
validateOrThrow(startPort, [
|
|
111
|
+
inRange('start port', 1, 65535),
|
|
112
|
+
isInteger('start port'),
|
|
113
|
+
isPositive('start port'),
|
|
114
|
+
]);
|
|
115
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
116
|
+
const port = startPort + i;
|
|
117
|
+
if (port > 65535) {
|
|
118
|
+
throw new Error(`Port ${port} exceeds maximum port number (65535)`);
|
|
119
|
+
}
|
|
120
|
+
if (await isPortAvailable(port)) {
|
|
121
|
+
return port;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
throw new Error(`No available port found between ${startPort} and ${startPort + maxAttempts - 1}`);
|
|
125
|
+
}
|
|
126
|
+
function cliHeading(title) {
|
|
127
|
+
console.log('');
|
|
128
|
+
printHeader(title);
|
|
129
|
+
}
|
|
130
|
+
function cliListItem(text) {
|
|
131
|
+
printInfo(` ${text}`);
|
|
132
|
+
}
|
|
133
|
+
function cliSpacer() {
|
|
134
|
+
console.log('');
|
|
135
|
+
}
|
|
136
|
+
function cliHint(message) {
|
|
137
|
+
printWarning(message);
|
|
138
|
+
}
|
|
139
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
140
|
+
// ELICITATION HANDLERS
|
|
141
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
142
|
+
/**
|
|
143
|
+
* Handle form-based elicitation (MCP-aligned)
|
|
144
|
+
* Renders a multi-field form in CLI using readline
|
|
145
|
+
*/
|
|
146
|
+
async function handleFormElicitation(ask) {
|
|
147
|
+
cliHeading(`📝 ${ask.message}`);
|
|
148
|
+
cliHint('Press Enter to accept defaults. Fields marked * are required.');
|
|
149
|
+
cliSpacer();
|
|
150
|
+
const rl = createReadline();
|
|
151
|
+
const question = (prompt) => {
|
|
152
|
+
return new Promise((resolve) => {
|
|
153
|
+
rl.question(prompt, (answer) => resolve(answer));
|
|
154
|
+
});
|
|
155
|
+
};
|
|
156
|
+
const result = {};
|
|
157
|
+
const required = ask.schema.required || [];
|
|
158
|
+
for (const [key, prop] of Object.entries(ask.schema.properties)) {
|
|
159
|
+
const title = prop.title || key;
|
|
160
|
+
const isRequired = required.includes(key);
|
|
161
|
+
const reqMark = isRequired ? '*' : '';
|
|
162
|
+
const defaultVal = prop.default !== undefined ? ` [${prop.default}]` : '';
|
|
163
|
+
let value;
|
|
164
|
+
// Handle different property types
|
|
165
|
+
if (prop.type === 'boolean') {
|
|
166
|
+
const answer = await question(`${title}${reqMark} (y/n)${defaultVal}: `);
|
|
167
|
+
if (answer === '' && prop.default !== undefined) {
|
|
168
|
+
value = prop.default;
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
value = answer.toLowerCase().startsWith('y');
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
else if (prop.enum || prop.oneOf) {
|
|
175
|
+
// Single select
|
|
176
|
+
const options = prop.oneOf
|
|
177
|
+
? prop.oneOf.map((o) => ({ value: o.const, label: o.title }))
|
|
178
|
+
: prop.enum.map((e) => ({ value: e, label: e }));
|
|
179
|
+
printInfo(`${title}${reqMark}:`);
|
|
180
|
+
options.forEach((opt, i) => {
|
|
181
|
+
const isDefault = opt.value === prop.default ? ' (default)' : '';
|
|
182
|
+
cliListItem(`${i + 1}. ${opt.label}${isDefault}`);
|
|
183
|
+
});
|
|
184
|
+
const answer = await question(`Choose (1-${options.length})${defaultVal}: `);
|
|
185
|
+
const idx = parseInt(answer) - 1;
|
|
186
|
+
if (idx >= 0 && idx < options.length) {
|
|
187
|
+
value = options[idx].value;
|
|
188
|
+
}
|
|
189
|
+
else if (answer === '' && prop.default !== undefined) {
|
|
190
|
+
value = prop.default;
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
value = options[0].value;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
else if (prop.type === 'array') {
|
|
197
|
+
// Multi-select
|
|
198
|
+
const items = prop.items?.anyOf || prop.items?.enum?.map((e) => ({ const: e, title: e }));
|
|
199
|
+
if (items) {
|
|
200
|
+
printInfo(`${title}${reqMark} (comma-separated numbers):`);
|
|
201
|
+
items.forEach((item, i) => {
|
|
202
|
+
const label = item.title || item.const || item;
|
|
203
|
+
cliListItem(`${i + 1}. ${label}`);
|
|
204
|
+
});
|
|
205
|
+
const answer = await question('Choose: ');
|
|
206
|
+
const indices = answer.split(',').map((s) => parseInt(s.trim()) - 1);
|
|
207
|
+
value = indices
|
|
208
|
+
.filter((idx) => idx >= 0 && idx < items.length)
|
|
209
|
+
.map((idx) => items[idx].const || items[idx]);
|
|
210
|
+
if (value.length === 0 && prop.default) {
|
|
211
|
+
value = prop.default;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
value = prop.default || [];
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
else if (prop.type === 'number' || prop.type === 'integer') {
|
|
219
|
+
const answer = await question(`${title}${reqMark}${defaultVal}: `);
|
|
220
|
+
if (answer === '' && prop.default !== undefined) {
|
|
221
|
+
value = prop.default;
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
value = prop.type === 'integer' ? parseInt(answer) : parseFloat(answer);
|
|
225
|
+
if (isNaN(value))
|
|
226
|
+
value = prop.default ?? 0;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
// String or default
|
|
231
|
+
const format = prop.format ? ` (${prop.format})` : '';
|
|
232
|
+
const answer = await question(`${title}${reqMark}${format}${defaultVal}: `);
|
|
233
|
+
value = answer || prop.default || '';
|
|
234
|
+
}
|
|
235
|
+
result[key] = value;
|
|
236
|
+
cliSpacer();
|
|
237
|
+
}
|
|
238
|
+
rl.close();
|
|
239
|
+
cliSpacer();
|
|
240
|
+
return { action: 'accept', content: result };
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Handle URL-based elicitation (OAuth flows)
|
|
244
|
+
* Opens URL in browser and waits for user confirmation
|
|
245
|
+
*/
|
|
246
|
+
async function handleUrlElicitation(ask) {
|
|
247
|
+
cliHeading(`🔗 ${ask.message}`);
|
|
248
|
+
printInfo(`URL: ${ask.url}`);
|
|
249
|
+
cliHint('Opening your default browser...');
|
|
250
|
+
cliSpacer();
|
|
251
|
+
// Open URL in default browser
|
|
252
|
+
const platform = process.platform;
|
|
253
|
+
const openCommand = platform === 'darwin' ? 'open' : platform === 'win32' ? 'start' : 'xdg-open';
|
|
254
|
+
try {
|
|
255
|
+
const { exec } = await import('child_process');
|
|
256
|
+
exec(`${openCommand} "${ask.url}"`);
|
|
257
|
+
}
|
|
258
|
+
catch (error) {
|
|
259
|
+
cliHint('Please open the URL manually in your browser.');
|
|
260
|
+
}
|
|
261
|
+
const shouldContinue = await promptWait('Press Enter when done', true);
|
|
262
|
+
return { action: shouldContinue ? 'accept' : 'cancel' };
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Handle select elicitation with options
|
|
266
|
+
*/
|
|
267
|
+
async function handleSelectElicitation(ask) {
|
|
268
|
+
cliHeading(ask.message);
|
|
269
|
+
const options = ask.options.map((opt) => typeof opt === 'string' ? { value: opt, label: opt } : opt);
|
|
270
|
+
options.forEach((opt, i) => {
|
|
271
|
+
const isDefault = ask.default === opt.value || (Array.isArray(ask.default) && ask.default.includes(opt.value));
|
|
272
|
+
const defaultMark = isDefault ? ' ✓' : '';
|
|
273
|
+
const desc = opt.description ? ` - ${opt.description}` : '';
|
|
274
|
+
cliListItem(`${i + 1}. ${opt.label}${desc}${defaultMark}`);
|
|
275
|
+
});
|
|
276
|
+
cliSpacer();
|
|
277
|
+
const rl = createReadline();
|
|
278
|
+
const prompt = ask.multi
|
|
279
|
+
? `Choose (comma-separated, 1-${options.length}): `
|
|
280
|
+
: `Choose (1-${options.length}): `;
|
|
281
|
+
return new Promise((resolve) => {
|
|
282
|
+
rl.question(prompt, (answer) => {
|
|
283
|
+
rl.close();
|
|
284
|
+
if (ask.multi) {
|
|
285
|
+
if (answer.trim() === '') {
|
|
286
|
+
resolve(Array.isArray(ask.default) ? ask.default : []);
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
const indices = answer.split(',').map((s) => parseInt(s.trim()) - 1);
|
|
290
|
+
const values = indices
|
|
291
|
+
.filter((idx) => idx >= 0 && idx < options.length)
|
|
292
|
+
.map((idx) => options[idx].value);
|
|
293
|
+
resolve(values);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
const idx = parseInt(answer) - 1;
|
|
298
|
+
if (idx >= 0 && idx < options.length) {
|
|
299
|
+
resolve(options[idx].value);
|
|
300
|
+
}
|
|
301
|
+
else if (answer.trim() === '' && ask.default) {
|
|
302
|
+
resolve(ask.default);
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
resolve(options[0].value);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
}
|
|
21
311
|
/**
|
|
22
312
|
* Extract constructor parameters from a Photon MCP file
|
|
23
313
|
*/
|
|
@@ -28,21 +318,10 @@ async function extractConstructorParams(filePath) {
|
|
|
28
318
|
return extractor.extractConstructorParams(source);
|
|
29
319
|
}
|
|
30
320
|
catch (error) {
|
|
31
|
-
|
|
321
|
+
printError(`Failed to extract constructor params: ${getErrorMessage(error)}`);
|
|
32
322
|
return [];
|
|
33
323
|
}
|
|
34
324
|
}
|
|
35
|
-
/**
|
|
36
|
-
* Convert MCP name and parameter name to environment variable name
|
|
37
|
-
*/
|
|
38
|
-
function toEnvVarName(mcpName, paramName) {
|
|
39
|
-
const mcpPrefix = mcpName.toUpperCase().replace(/-/g, '_');
|
|
40
|
-
const paramSuffix = paramName
|
|
41
|
-
.replace(/([A-Z])/g, '_$1')
|
|
42
|
-
.toUpperCase()
|
|
43
|
-
.replace(/^_/, '');
|
|
44
|
-
return `${mcpPrefix}_${paramSuffix}`;
|
|
45
|
-
}
|
|
46
325
|
/**
|
|
47
326
|
* Ensure .gitignore includes marketplace template directory
|
|
48
327
|
*/
|
|
@@ -67,7 +346,7 @@ async function ensureGitignore(workingDir) {
|
|
|
67
346
|
}
|
|
68
347
|
catch (error) {
|
|
69
348
|
// Non-fatal - just warn
|
|
70
|
-
console.error(` ⚠ Could not update .gitignore: ${error
|
|
349
|
+
console.error(` ⚠ Could not update .gitignore: ${getErrorMessage(error)}`);
|
|
71
350
|
}
|
|
72
351
|
}
|
|
73
352
|
/**
|
|
@@ -77,21 +356,23 @@ async function performMarketplaceSync(dirPath, options) {
|
|
|
77
356
|
const resolvedPath = path.resolve(dirPath);
|
|
78
357
|
const isDefaultDir = resolvedPath === DEFAULT_WORKING_DIR;
|
|
79
358
|
if (!existsSync(resolvedPath)) {
|
|
80
|
-
|
|
81
|
-
|
|
359
|
+
exitWithError(`Directory not found: ${resolvedPath}`, {
|
|
360
|
+
exitCode: ExitCode.NOT_FOUND,
|
|
361
|
+
suggestion: 'Check the path and ensure the directory exists',
|
|
362
|
+
});
|
|
82
363
|
}
|
|
83
364
|
// Scan for .photon.ts files
|
|
84
365
|
console.error('📦 Scanning for .photon.ts files...');
|
|
85
366
|
const files = await fs.readdir(resolvedPath);
|
|
86
|
-
let photonFiles = files.filter(f => f.endsWith('.photon.ts'));
|
|
367
|
+
let photonFiles = files.filter((f) => f.endsWith('.photon.ts'));
|
|
87
368
|
// Filter out installed photons if requested (for ~/.photon)
|
|
88
369
|
if (options.filterInstalled && isDefaultDir) {
|
|
89
370
|
const { readLocalMetadata } = await import('./marketplace-manager.js');
|
|
90
371
|
const metadata = await readLocalMetadata();
|
|
91
372
|
// Metadata keys may include .photon.ts extension
|
|
92
|
-
const installedNames = new Set(Object.keys(metadata.photons || {}).map(k => k.replace(/\.photon\.ts$/, '')));
|
|
373
|
+
const installedNames = new Set(Object.keys(metadata.photons || {}).map((k) => k.replace(/\.photon\.ts$/, '')));
|
|
93
374
|
const originalCount = photonFiles.length;
|
|
94
|
-
photonFiles = photonFiles.filter(f => {
|
|
375
|
+
photonFiles = photonFiles.filter((f) => {
|
|
95
376
|
const name = f.replace(/\.photon\.ts$/, '');
|
|
96
377
|
return !installedNames.has(name);
|
|
97
378
|
});
|
|
@@ -100,8 +381,11 @@ async function performMarketplaceSync(dirPath, options) {
|
|
|
100
381
|
}
|
|
101
382
|
}
|
|
102
383
|
if (photonFiles.length === 0) {
|
|
103
|
-
|
|
104
|
-
|
|
384
|
+
exitWithError(`No .photon.ts files found`, {
|
|
385
|
+
exitCode: ExitCode.NOT_FOUND,
|
|
386
|
+
searchedIn: resolvedPath,
|
|
387
|
+
suggestion: "Create a .photon.ts file or use 'photon maker new' to generate one",
|
|
388
|
+
});
|
|
105
389
|
}
|
|
106
390
|
console.error(` Found ${photonFiles.length} photons\n`);
|
|
107
391
|
// Initialize template manager
|
|
@@ -136,7 +420,10 @@ async function performMarketplaceSync(dirPath, options) {
|
|
|
136
420
|
homepage: metadata.homepage,
|
|
137
421
|
source: `../${file}`,
|
|
138
422
|
hash,
|
|
139
|
-
tools: metadata.tools?.map(t => t.name),
|
|
423
|
+
tools: metadata.tools?.map((t) => t.name),
|
|
424
|
+
assets: metadata.assets,
|
|
425
|
+
photonType: metadata.photonType,
|
|
426
|
+
features: metadata.features,
|
|
140
427
|
});
|
|
141
428
|
// Generate individual photon documentation
|
|
142
429
|
const photonMarkdown = await templateMgr.renderTemplate('photon.md', metadata);
|
|
@@ -146,18 +433,31 @@ async function performMarketplaceSync(dirPath, options) {
|
|
|
146
433
|
// Create manifest
|
|
147
434
|
console.error('\n📋 Updating manifest...');
|
|
148
435
|
const baseName = path.basename(resolvedPath);
|
|
436
|
+
const marketplaceDir = path.join(resolvedPath, '.marketplace');
|
|
437
|
+
await fs.mkdir(marketplaceDir, { recursive: true });
|
|
438
|
+
const manifestPath = path.join(marketplaceDir, 'photons.json');
|
|
439
|
+
// Read existing manifest to preserve owner if not explicitly provided
|
|
440
|
+
let existingOwner;
|
|
441
|
+
if (existsSync(manifestPath) && !options.owner) {
|
|
442
|
+
try {
|
|
443
|
+
const existingManifest = JSON.parse(await fs.readFile(manifestPath, 'utf-8'));
|
|
444
|
+
existingOwner = existingManifest.owner;
|
|
445
|
+
}
|
|
446
|
+
catch {
|
|
447
|
+
// Ignore parse errors
|
|
448
|
+
}
|
|
449
|
+
}
|
|
149
450
|
const manifest = {
|
|
150
451
|
name: options.name || baseName,
|
|
151
|
-
version:
|
|
452
|
+
version: PHOTON_VERSION,
|
|
152
453
|
description: options.description || undefined,
|
|
153
|
-
owner: options.owner
|
|
154
|
-
|
|
155
|
-
|
|
454
|
+
owner: options.owner
|
|
455
|
+
? {
|
|
456
|
+
name: options.owner,
|
|
457
|
+
}
|
|
458
|
+
: existingOwner,
|
|
156
459
|
photons,
|
|
157
460
|
};
|
|
158
|
-
const marketplaceDir = path.join(resolvedPath, '.marketplace');
|
|
159
|
-
await fs.mkdir(marketplaceDir, { recursive: true });
|
|
160
|
-
const manifestPath = path.join(marketplaceDir, 'photons.json');
|
|
161
461
|
await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2), 'utf-8');
|
|
162
462
|
console.error(' ✓ .marketplace/photons.json');
|
|
163
463
|
// Sync README with generated content
|
|
@@ -169,12 +469,14 @@ async function performMarketplaceSync(dirPath, options) {
|
|
|
169
469
|
const readmeContent = await templateMgr.renderTemplate('readme.md', {
|
|
170
470
|
marketplaceName: manifest.name,
|
|
171
471
|
marketplaceDescription: manifest.description || '',
|
|
172
|
-
photons: photons.map(p => ({
|
|
472
|
+
photons: photons.map((p) => ({
|
|
173
473
|
name: p.name,
|
|
174
474
|
description: p.description,
|
|
175
475
|
version: p.version,
|
|
176
476
|
license: p.license,
|
|
177
477
|
tools: p.tools || [],
|
|
478
|
+
photonType: p.photonType || 'api',
|
|
479
|
+
features: p.features || [],
|
|
178
480
|
})),
|
|
179
481
|
});
|
|
180
482
|
const isUpdate = await syncer.sync(readmeContent);
|
|
@@ -206,8 +508,11 @@ async function performMarketplaceInit(dirPath, options) {
|
|
|
206
508
|
// Check if it's a git repository
|
|
207
509
|
const gitDir = path.join(absolutePath, '.git');
|
|
208
510
|
if (!existsSync(gitDir)) {
|
|
209
|
-
|
|
210
|
-
|
|
511
|
+
exitWithError('Not a git repository', {
|
|
512
|
+
exitCode: ExitCode.CONFIG_ERROR,
|
|
513
|
+
searchedIn: absolutePath,
|
|
514
|
+
suggestion: 'Initialize with: git init',
|
|
515
|
+
});
|
|
211
516
|
}
|
|
212
517
|
// Create .githooks directory
|
|
213
518
|
const hooksDir = path.join(absolutePath, '.githooks');
|
|
@@ -315,59 +620,29 @@ function formatDefaultValue(value) {
|
|
|
315
620
|
*/
|
|
316
621
|
function getConfigPath() {
|
|
317
622
|
const platform = process.platform;
|
|
623
|
+
const home = os.homedir();
|
|
318
624
|
if (platform === 'darwin') {
|
|
319
|
-
return path.join(
|
|
625
|
+
return path.join(home, 'Library/Application Support/Claude/claude_desktop_config.json');
|
|
320
626
|
}
|
|
321
627
|
else if (platform === 'win32') {
|
|
322
|
-
|
|
628
|
+
// On Windows, use APPDATA if available, otherwise fall back to home/AppData/Roaming
|
|
629
|
+
const appData = process.env.APPDATA || path.join(home, 'AppData', 'Roaming');
|
|
630
|
+
return path.join(appData, 'Claude', 'claude_desktop_config.json');
|
|
323
631
|
}
|
|
324
632
|
else {
|
|
325
633
|
// Linux/other
|
|
326
|
-
return path.join(
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
/**
|
|
330
|
-
* Check if photon is installed globally
|
|
331
|
-
* @returns "photon" if available globally (cross-platform), null otherwise
|
|
332
|
-
*/
|
|
333
|
-
async function getGlobalPhotonPath() {
|
|
334
|
-
const { execFile } = await import('child_process');
|
|
335
|
-
const { promisify } = await import('util');
|
|
336
|
-
const execFileAsync = promisify(execFile);
|
|
337
|
-
try {
|
|
338
|
-
const command = process.platform === 'win32' ? 'where' : 'which';
|
|
339
|
-
const { stdout } = await execFileAsync(command, ['photon']);
|
|
340
|
-
const photonPath = stdout.trim().split('\n')[0]; // Take first match
|
|
341
|
-
if (photonPath) {
|
|
342
|
-
// Verify it's actually the @portel/photon package by checking version
|
|
343
|
-
try {
|
|
344
|
-
const { stdout: versionOutput } = await execFileAsync(photonPath, ['--version']);
|
|
345
|
-
// If it outputs a version, it's likely our photon
|
|
346
|
-
if (versionOutput.trim()) {
|
|
347
|
-
// Return "photon" instead of full path for cross-platform compatibility
|
|
348
|
-
// The shell will resolve it from PATH
|
|
349
|
-
return 'photon';
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
catch {
|
|
353
|
-
// Version check failed, might not be our photon
|
|
354
|
-
return null;
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
catch {
|
|
359
|
-
// which/where command failed, photon not in PATH
|
|
634
|
+
return path.join(home, '.config/Claude/claude_desktop_config.json');
|
|
360
635
|
}
|
|
361
|
-
return null;
|
|
362
636
|
}
|
|
363
637
|
/**
|
|
364
638
|
* Validate configuration for an MCP
|
|
365
639
|
*/
|
|
366
640
|
async function validateConfiguration(filePath, mcpName) {
|
|
367
|
-
|
|
641
|
+
cliHeading(`🔍 Validating configuration for: ${mcpName}`);
|
|
642
|
+
cliSpacer();
|
|
368
643
|
const params = await extractConstructorParams(filePath);
|
|
369
644
|
if (params.length === 0) {
|
|
370
|
-
|
|
645
|
+
printSuccess('No configuration required for this MCP.');
|
|
371
646
|
return;
|
|
372
647
|
}
|
|
373
648
|
let hasErrors = false;
|
|
@@ -381,15 +656,15 @@ async function validateConfiguration(filePath, mcpName) {
|
|
|
381
656
|
results.push({
|
|
382
657
|
name: param.name,
|
|
383
658
|
envVar: envVarName,
|
|
384
|
-
status: '❌
|
|
659
|
+
status: '❌ Missing (required)',
|
|
385
660
|
});
|
|
386
661
|
}
|
|
387
662
|
else if (envValue) {
|
|
388
663
|
results.push({
|
|
389
664
|
name: param.name,
|
|
390
665
|
envVar: envVarName,
|
|
391
|
-
status: '✅
|
|
392
|
-
value: envValue.length > 20 ? envValue.substring(0, 17)
|
|
666
|
+
status: '✅ Set',
|
|
667
|
+
value: envValue.length > 20 ? `${envValue.substring(0, 17)}...` : envValue,
|
|
393
668
|
});
|
|
394
669
|
}
|
|
395
670
|
else {
|
|
@@ -401,55 +676,51 @@ async function validateConfiguration(filePath, mcpName) {
|
|
|
401
676
|
});
|
|
402
677
|
}
|
|
403
678
|
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
console.log(` ${r.status} ${r.envVar}`);
|
|
679
|
+
printHeader('Configuration status');
|
|
680
|
+
results.forEach((r) => {
|
|
681
|
+
printInfo(` ${r.status} ${r.envVar}`);
|
|
408
682
|
if (r.value) {
|
|
409
|
-
|
|
683
|
+
printInfo(` Value: ${r.value}`);
|
|
410
684
|
}
|
|
411
|
-
console.log();
|
|
412
685
|
});
|
|
686
|
+
cliSpacer();
|
|
413
687
|
if (hasErrors) {
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
process.exit(1);
|
|
688
|
+
exitWithError('Validation failed: Missing required environment variables', {
|
|
689
|
+
exitCode: ExitCode.CONFIG_ERROR,
|
|
690
|
+
suggestion: `Run 'photon mcp ${mcpName} --config' to see the configuration template`,
|
|
691
|
+
});
|
|
419
692
|
}
|
|
420
693
|
else {
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
console.log('\nYou can now run: photon mcp ' + mcpName);
|
|
694
|
+
printSuccess('Configuration valid!');
|
|
695
|
+
cliHint(`Run: photon mcp ${mcpName}`);
|
|
424
696
|
}
|
|
425
697
|
}
|
|
426
698
|
/**
|
|
427
699
|
* Show configuration template for an MCP
|
|
428
700
|
*/
|
|
429
701
|
async function showConfigTemplate(filePath, mcpName, workingDir = DEFAULT_WORKING_DIR) {
|
|
430
|
-
|
|
702
|
+
cliHeading(`📋 Configuration template for: ${mcpName}`);
|
|
703
|
+
cliSpacer();
|
|
431
704
|
const params = await extractConstructorParams(filePath);
|
|
432
705
|
if (params.length === 0) {
|
|
433
|
-
|
|
706
|
+
printSuccess('No configuration required for this MCP.');
|
|
434
707
|
return;
|
|
435
708
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
params.forEach(param => {
|
|
709
|
+
printHeader('Environment variables');
|
|
710
|
+
params.forEach((param) => {
|
|
439
711
|
const envVarName = toEnvVarName(mcpName, param.name);
|
|
440
712
|
const isRequired = !param.isOptional && !param.hasDefault;
|
|
441
713
|
const status = isRequired ? '[REQUIRED]' : '[OPTIONAL]';
|
|
442
|
-
|
|
443
|
-
|
|
714
|
+
printInfo(` ${envVarName} ${status}`);
|
|
715
|
+
printInfo(` Type: ${param.type}`);
|
|
444
716
|
if (param.hasDefault) {
|
|
445
|
-
|
|
717
|
+
printInfo(` Default: ${formatDefaultValue(param.defaultValue)}`);
|
|
446
718
|
}
|
|
447
|
-
|
|
719
|
+
cliSpacer();
|
|
448
720
|
});
|
|
449
|
-
|
|
450
|
-
console.log('Claude Desktop Configuration:\n');
|
|
721
|
+
printHeader('Claude Desktop configuration');
|
|
451
722
|
const envExample = {};
|
|
452
|
-
params.forEach(param => {
|
|
723
|
+
params.forEach((param) => {
|
|
453
724
|
const envVarName = toEnvVarName(mcpName, param.name);
|
|
454
725
|
if (!param.isOptional && !param.hasDefault) {
|
|
455
726
|
envExample[envVarName] = `<your-${param.name}>`;
|
|
@@ -468,25 +739,19 @@ async function showConfigTemplate(filePath, mcpName, workingDir = DEFAULT_WORKIN
|
|
|
468
739
|
},
|
|
469
740
|
};
|
|
470
741
|
console.log(JSON.stringify(config, null, 2));
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
}
|
|
475
|
-
// Get version from package.json
|
|
476
|
-
let version = '1.0.0';
|
|
477
|
-
try {
|
|
478
|
-
const packageJson = require('../package.json');
|
|
479
|
-
version = packageJson.version;
|
|
480
|
-
}
|
|
481
|
-
catch {
|
|
482
|
-
// Fallback version
|
|
742
|
+
cliSpacer();
|
|
743
|
+
cliHint(`Add this to: ${getConfigPath()}`);
|
|
744
|
+
cliHint(`Validate with: photon mcp ${mcpName} --validate`);
|
|
483
745
|
}
|
|
746
|
+
const version = PHOTON_VERSION;
|
|
484
747
|
const program = new Command();
|
|
485
748
|
program
|
|
486
749
|
.name('photon')
|
|
487
750
|
.description('Universal runtime for single-file TypeScript programs')
|
|
488
751
|
.version(version)
|
|
489
752
|
.option('--dir <path>', 'Photon directory (default: ~/.photon)', DEFAULT_WORKING_DIR)
|
|
753
|
+
.option('--log-level <level>', 'Set log verbosity (error|warn|info|debug)', 'info')
|
|
754
|
+
.option('--json-logs', 'Emit newline-delimited JSON logs for runtime output')
|
|
490
755
|
.configureHelp({
|
|
491
756
|
sortSubcommands: false,
|
|
492
757
|
sortOptions: false,
|
|
@@ -495,6 +760,12 @@ program
|
|
|
495
760
|
Runtime Commands:
|
|
496
761
|
mcp <name> Run a photon as MCP server (for AI assistants)
|
|
497
762
|
cli <photon> [method] Run photon methods from command line
|
|
763
|
+
sse <name> Run Photon as HTTP server with SSE transport
|
|
764
|
+
beam Launch Photon Beam (interactive control panel)
|
|
765
|
+
serve Start local multi-tenant MCP hosting for development
|
|
766
|
+
|
|
767
|
+
Hosting:
|
|
768
|
+
host <command> Manage cloud hosting (preview, deploy)
|
|
498
769
|
|
|
499
770
|
Package Management:
|
|
500
771
|
add <name> Install a photon from marketplace
|
|
@@ -525,49 +796,55 @@ program
|
|
|
525
796
|
.description('Update marketplace indexes and check for CLI updates')
|
|
526
797
|
.action(async () => {
|
|
527
798
|
try {
|
|
528
|
-
const { printInfo, printSuccess, printWarning } = await import('./cli-formatter.js');
|
|
529
|
-
// Update all marketplace caches
|
|
530
|
-
printInfo('Refreshing marketplace indexes...\n');
|
|
799
|
+
const { printInfo, printSuccess, printWarning, printHeader } = await import('./cli-formatter.js');
|
|
531
800
|
const { MarketplaceManager } = await import('./marketplace-manager.js');
|
|
532
801
|
const manager = new MarketplaceManager();
|
|
533
802
|
await manager.initialize();
|
|
534
|
-
const results = await
|
|
535
|
-
|
|
803
|
+
const results = await runTask('Refreshing marketplace indexes', async () => {
|
|
804
|
+
return manager.updateAllCaches();
|
|
805
|
+
});
|
|
806
|
+
console.log('');
|
|
807
|
+
const entries = Array.from(results.entries());
|
|
808
|
+
let successCount = 0;
|
|
809
|
+
for (const [marketplaceName, success] of entries) {
|
|
536
810
|
if (success) {
|
|
537
|
-
printSuccess(
|
|
811
|
+
printSuccess(marketplaceName);
|
|
812
|
+
successCount++;
|
|
538
813
|
}
|
|
539
814
|
else {
|
|
540
815
|
printWarning(`${marketplaceName} (no manifest)`);
|
|
541
816
|
}
|
|
542
817
|
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
printInfo(`Updated ${successCount}/${results.size} marketplaces`);
|
|
546
|
-
// Check for CLI updates
|
|
547
|
-
console.log('');
|
|
548
|
-
printInfo('Checking for Photon CLI updates...');
|
|
818
|
+
printInfo(`\nUpdated ${successCount}/${entries.length} marketplaces`);
|
|
819
|
+
let latestVersion = null;
|
|
549
820
|
try {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
821
|
+
latestVersion = await runTask('Checking for Photon CLI updates', async () => {
|
|
822
|
+
const { execSync } = await import('child_process');
|
|
823
|
+
return execSync('npm view @portel/photon version', {
|
|
824
|
+
encoding: 'utf-8',
|
|
825
|
+
timeout: 10000,
|
|
826
|
+
}).trim();
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
catch {
|
|
830
|
+
printWarning('\nCould not check for CLI updates');
|
|
831
|
+
}
|
|
832
|
+
if (latestVersion) {
|
|
833
|
+
console.log('');
|
|
834
|
+
if (latestVersion !== version) {
|
|
835
|
+
printHeader('Update available');
|
|
836
|
+
printWarning(`Current: ${version}`);
|
|
837
|
+
printInfo(`Latest: ${latestVersion}`);
|
|
558
838
|
printInfo(`Update with: npm install -g @portel/photon`);
|
|
559
839
|
}
|
|
560
840
|
else {
|
|
561
841
|
printSuccess(`Photon CLI is up to date (${version})`);
|
|
562
842
|
}
|
|
563
843
|
}
|
|
564
|
-
catch {
|
|
565
|
-
printWarning('Could not check for CLI updates');
|
|
566
|
-
}
|
|
567
844
|
}
|
|
568
845
|
catch (error) {
|
|
569
846
|
const { printError } = await import('./cli-formatter.js');
|
|
570
|
-
printError(error
|
|
847
|
+
printError(getErrorMessage(error));
|
|
571
848
|
process.exit(1);
|
|
572
849
|
}
|
|
573
850
|
});
|
|
@@ -579,32 +856,127 @@ program
|
|
|
579
856
|
.option('--dev', 'Enable development mode with hot reload')
|
|
580
857
|
.option('--validate', 'Validate configuration without running server')
|
|
581
858
|
.option('--config', 'Show configuration template and exit')
|
|
582
|
-
.
|
|
859
|
+
.option('--transport <type>', 'Transport type: stdio (default) or sse', 'stdio')
|
|
860
|
+
.option('--port <number>', 'Port for SSE transport (default: 3000)', '3000')
|
|
861
|
+
.action(async (rawName, options, command) => {
|
|
583
862
|
try {
|
|
863
|
+
// Parse extended name format (e.g., "alice/repo:rss-feed")
|
|
864
|
+
const { name, marketplaceSource } = parsePhotonSpec(rawName);
|
|
584
865
|
// Get working directory from global options
|
|
585
866
|
const workingDir = program.opts().dir || DEFAULT_WORKING_DIR;
|
|
586
|
-
|
|
587
|
-
|
|
867
|
+
const logOptions = getLogOptionsFromCommand(command);
|
|
868
|
+
// Resolve file path - check bundled photons first, then user directory
|
|
869
|
+
let filePath = await resolvePhotonPathWithBundled(name, workingDir);
|
|
870
|
+
// Auto-install from marketplace if not found locally
|
|
871
|
+
let unresolvedPhoton;
|
|
588
872
|
if (!filePath) {
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
873
|
+
const { MarketplaceManager, calculateHash } = await import('./marketplace-manager.js');
|
|
874
|
+
const manager = new MarketplaceManager();
|
|
875
|
+
await manager.initialize();
|
|
876
|
+
// If marketplace source given, add it (persistent, idempotent)
|
|
877
|
+
if (marketplaceSource) {
|
|
878
|
+
const { marketplace: addedMp, added } = await manager.add(marketplaceSource);
|
|
879
|
+
if (added) {
|
|
880
|
+
console.error(`Added marketplace: ${addedMp.name}`);
|
|
881
|
+
await manager.updateMarketplaceCache(addedMp.name);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
// Check for conflicts (multiple sources)
|
|
885
|
+
const conflict = await manager.checkConflict(name);
|
|
886
|
+
if (conflict.sources.length === 0) {
|
|
887
|
+
// Not found anywhere
|
|
888
|
+
exitWithError(`MCP not found: ${name}`, {
|
|
889
|
+
exitCode: ExitCode.NOT_FOUND,
|
|
890
|
+
searchedIn: workingDir,
|
|
891
|
+
suggestion: DEFAULT_BUNDLED_PHOTONS.includes(name)
|
|
892
|
+
? `'${name}' is a bundled photon but could not be found`
|
|
893
|
+
: marketplaceSource
|
|
894
|
+
? `Photon '${name}' not found in ${marketplaceSource}`
|
|
895
|
+
: "Use 'photon search <name>' to find it or 'photon marketplace add <source>' to add a marketplace",
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
else if (conflict.sources.length === 1 || !conflict.hasConflict) {
|
|
899
|
+
// Single source — auto-download
|
|
900
|
+
const source = conflict.sources[0];
|
|
901
|
+
console.error(`Installing ${name} from ${source.marketplace.name}...`);
|
|
902
|
+
const result = await manager.fetchMCP(name);
|
|
903
|
+
if (!result) {
|
|
904
|
+
exitWithError(`Failed to download: ${name}`, {
|
|
905
|
+
exitCode: ExitCode.ERROR,
|
|
906
|
+
suggestion: 'Check your internet connection and marketplace configuration',
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
// Ensure working directory exists and save
|
|
910
|
+
await ensureWorkingDir(workingDir);
|
|
911
|
+
const targetPath = path.join(workingDir, `${name}.photon.ts`);
|
|
912
|
+
await fs.writeFile(targetPath, result.content, 'utf-8');
|
|
913
|
+
// Save metadata
|
|
914
|
+
if (source.metadata) {
|
|
915
|
+
const contentHash = calculateHash(result.content);
|
|
916
|
+
await manager.savePhotonMetadata(`${name}.photon.ts`, source.marketplace, source.metadata, contentHash);
|
|
917
|
+
// Download assets
|
|
918
|
+
if (source.metadata.assets && source.metadata.assets.length > 0) {
|
|
919
|
+
const assets = await manager.fetchAssets(source.marketplace, source.metadata.assets);
|
|
920
|
+
for (const [assetPath, content] of assets) {
|
|
921
|
+
const assetTarget = path.join(workingDir, assetPath);
|
|
922
|
+
const assetDir = path.dirname(assetTarget);
|
|
923
|
+
await fs.mkdir(assetDir, { recursive: true });
|
|
924
|
+
await fs.writeFile(assetTarget, content, 'utf-8');
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
console.error(`Installed ${name}`);
|
|
929
|
+
filePath = targetPath;
|
|
930
|
+
}
|
|
931
|
+
else {
|
|
932
|
+
// Multiple sources — defer to server for elicitation
|
|
933
|
+
unresolvedPhoton = {
|
|
934
|
+
name,
|
|
935
|
+
workingDir,
|
|
936
|
+
sources: conflict.sources,
|
|
937
|
+
recommendation: conflict.recommendation,
|
|
938
|
+
};
|
|
939
|
+
}
|
|
593
940
|
}
|
|
594
|
-
// Handle --validate flag
|
|
941
|
+
// Handle --validate flag (requires resolved filePath)
|
|
595
942
|
if (options.validate) {
|
|
943
|
+
if (!filePath) {
|
|
944
|
+
exitWithError(`Cannot validate: ${name} has multiple sources. Install it first with 'photon add ${name}'.`, {
|
|
945
|
+
exitCode: ExitCode.CONFIG_ERROR,
|
|
946
|
+
});
|
|
947
|
+
}
|
|
596
948
|
await validateConfiguration(filePath, name);
|
|
597
949
|
return;
|
|
598
950
|
}
|
|
599
951
|
// Handle --config flag
|
|
600
952
|
if (options.config) {
|
|
953
|
+
if (!filePath) {
|
|
954
|
+
exitWithError(`Cannot show config: ${name} has multiple sources. Install it first with 'photon add ${name}'.`, {
|
|
955
|
+
exitCode: ExitCode.CONFIG_ERROR,
|
|
956
|
+
});
|
|
957
|
+
}
|
|
601
958
|
await showConfigTemplate(filePath, name, workingDir);
|
|
602
959
|
return;
|
|
603
960
|
}
|
|
961
|
+
// Validate transport option
|
|
962
|
+
const transport = options.transport;
|
|
963
|
+
if (transport !== 'stdio' && transport !== 'sse') {
|
|
964
|
+
exitWithError(`Invalid transport: ${options.transport}`, {
|
|
965
|
+
exitCode: ExitCode.INVALID_ARGUMENT,
|
|
966
|
+
suggestion: 'Valid options: stdio, sse',
|
|
967
|
+
});
|
|
968
|
+
}
|
|
969
|
+
// Set PHOTON_NAME for daemon broker pub/sub to work
|
|
970
|
+
// This ensures channel messages go to the correct daemon socket
|
|
971
|
+
process.env.PHOTON_NAME = name;
|
|
604
972
|
// Start MCP server
|
|
605
973
|
const server = new PhotonServer({
|
|
606
|
-
filePath,
|
|
974
|
+
filePath: filePath || '', // empty when unresolved — server handles it
|
|
607
975
|
devMode: options.dev,
|
|
976
|
+
transport,
|
|
977
|
+
port: parseInt(options.port, 10),
|
|
978
|
+
logOptions: { ...logOptions, scope: transport },
|
|
979
|
+
unresolvedPhoton,
|
|
608
980
|
});
|
|
609
981
|
// Handle shutdown signals
|
|
610
982
|
const shutdown = async () => {
|
|
@@ -616,9 +988,9 @@ program
|
|
|
616
988
|
process.on('SIGTERM', shutdown);
|
|
617
989
|
// Start the server
|
|
618
990
|
await server.start();
|
|
619
|
-
// Start file watcher in dev mode
|
|
620
|
-
if (options.dev) {
|
|
621
|
-
const watcher = new FileWatcher(server, filePath);
|
|
991
|
+
// Start file watcher in dev mode (only if resolved)
|
|
992
|
+
if (options.dev && filePath) {
|
|
993
|
+
const watcher = new FileWatcher(server, filePath, server.createScopedLogger('watcher'));
|
|
622
994
|
watcher.start();
|
|
623
995
|
// Clean up watcher on shutdown
|
|
624
996
|
process.on('SIGINT', async () => {
|
|
@@ -630,290 +1002,297 @@ program
|
|
|
630
1002
|
}
|
|
631
1003
|
}
|
|
632
1004
|
catch (error) {
|
|
633
|
-
|
|
1005
|
+
logger.error(`Error: ${getErrorMessage(error)}`);
|
|
634
1006
|
process.exit(1);
|
|
635
1007
|
}
|
|
636
1008
|
});
|
|
637
|
-
//
|
|
1009
|
+
// SSE command: quick SSE server with auto port detection (formerly serve)
|
|
638
1010
|
program
|
|
639
|
-
.command('
|
|
640
|
-
.argument('
|
|
641
|
-
.option('--
|
|
642
|
-
.
|
|
643
|
-
.
|
|
644
|
-
.description('Show installed and available Photons')
|
|
1011
|
+
.command('sse', { hidden: true })
|
|
1012
|
+
.argument('<name>', 'Photon name (without .photon.ts extension)')
|
|
1013
|
+
.option('-p, --port <number>', 'Port to start from (auto-finds available)', '3000')
|
|
1014
|
+
.option('--dev', 'Enable development mode with hot reload')
|
|
1015
|
+
.description('Run Photon as HTTP server with SSE transport (auto port detection)')
|
|
645
1016
|
.action(async (name, options, command) => {
|
|
646
1017
|
try {
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
const
|
|
650
|
-
|
|
651
|
-
const
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
if (name) {
|
|
659
|
-
const filePath = await resolvePhotonPath(name, workingDir);
|
|
660
|
-
const isInstalled = !!filePath;
|
|
661
|
-
if (asMcp) {
|
|
662
|
-
// MCP config only works for installed photons
|
|
663
|
-
if (!isInstalled) {
|
|
664
|
-
printError(`'${name}' is not installed`);
|
|
665
|
-
printInfo(`Install with: photon add ${name}`);
|
|
666
|
-
process.exit(1);
|
|
667
|
-
}
|
|
668
|
-
// Show as MCP config for single Photon
|
|
669
|
-
const constructorParams = await extractConstructorParams(filePath);
|
|
670
|
-
const env = {};
|
|
671
|
-
for (const param of constructorParams) {
|
|
672
|
-
const envVarName = toEnvVarName(name, param.name);
|
|
673
|
-
const defaultDisplay = param.defaultValue !== undefined
|
|
674
|
-
? formatDefaultValue(param.defaultValue)
|
|
675
|
-
: `<your-${param.name}>`;
|
|
676
|
-
env[envVarName] = defaultDisplay;
|
|
677
|
-
}
|
|
678
|
-
// Check for global photon installation
|
|
679
|
-
const globalPhotonPath = await getGlobalPhotonPath();
|
|
680
|
-
const needsWorkingDir = workingDir !== DEFAULT_WORKING_DIR;
|
|
681
|
-
const config = globalPhotonPath
|
|
682
|
-
? {
|
|
683
|
-
command: globalPhotonPath,
|
|
684
|
-
args: needsWorkingDir
|
|
685
|
-
? ['mcp', name, '--dir', workingDir]
|
|
686
|
-
: ['mcp', name],
|
|
687
|
-
...(Object.keys(env).length > 0 && { env }),
|
|
688
|
-
}
|
|
689
|
-
: {
|
|
690
|
-
command: 'npx',
|
|
691
|
-
args: needsWorkingDir
|
|
692
|
-
? ['@portel/photon', 'mcp', name, '--dir', workingDir]
|
|
693
|
-
: ['@portel/photon', 'mcp', name],
|
|
694
|
-
...(Object.keys(env).length > 0 && { env }),
|
|
695
|
-
};
|
|
696
|
-
// Get OS-specific config path
|
|
697
|
-
const configPath = getConfigPath();
|
|
698
|
-
console.log(`# Photon MCP Server Configuration: ${name}`);
|
|
699
|
-
console.log(`# Add to mcpServers in: ${configPath}\n`);
|
|
700
|
-
console.log(JSON.stringify({ [name]: config }, null, 2));
|
|
701
|
-
}
|
|
702
|
-
else {
|
|
703
|
-
// Show info for specific photon - both local and marketplace
|
|
704
|
-
// Show local installation if present
|
|
705
|
-
if (isInstalled) {
|
|
706
|
-
const { PhotonDocExtractor } = await import('./photon-doc-extractor.js');
|
|
707
|
-
const extractor = new PhotonDocExtractor(filePath);
|
|
708
|
-
const photonMetadata = await extractor.extractFullMetadata();
|
|
709
|
-
const fileName = `${name}.photon.ts`;
|
|
710
|
-
const metadata = await manager.getPhotonInstallMetadata(fileName);
|
|
711
|
-
const isModified = metadata ? await manager.isPhotonModified(filePath, fileName) : false;
|
|
712
|
-
printInfo(`Installed in ${workingDir}:\n`);
|
|
713
|
-
// Build info as tree structure
|
|
714
|
-
const infoData = {
|
|
715
|
-
name: name,
|
|
716
|
-
version: photonMetadata.version || '-',
|
|
717
|
-
location: filePath,
|
|
718
|
-
};
|
|
719
|
-
if (photonMetadata.description) {
|
|
720
|
-
infoData.description = photonMetadata.description;
|
|
721
|
-
}
|
|
722
|
-
if (metadata) {
|
|
723
|
-
infoData.installed = new Date(metadata.installedAt).toLocaleDateString();
|
|
724
|
-
infoData.source = metadata.marketplace;
|
|
725
|
-
if (isModified) {
|
|
726
|
-
infoData.status = 'modified locally';
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
const toolCount = photonMetadata.tools?.length || 0;
|
|
730
|
-
if (toolCount > 0) {
|
|
731
|
-
infoData.tools = toolCount;
|
|
732
|
-
}
|
|
733
|
-
formatOutput(infoData, 'tree');
|
|
734
|
-
console.log('');
|
|
735
|
-
// Show appropriate run command
|
|
736
|
-
if (metadata && !isModified) {
|
|
737
|
-
printInfo(`Run with: photon mcp ${name}`);
|
|
738
|
-
printInfo(`To customize: Copy to a new name and run with --dev for hot reload`);
|
|
739
|
-
}
|
|
740
|
-
else if (metadata && isModified) {
|
|
741
|
-
printInfo(`Run with: photon mcp ${name} --dev`);
|
|
742
|
-
printInfo(`Note: Modified from marketplace - consider renaming to avoid upgrade conflicts`);
|
|
743
|
-
}
|
|
744
|
-
else {
|
|
745
|
-
printInfo(`Run with: photon mcp ${name} --dev`);
|
|
746
|
-
}
|
|
747
|
-
console.log('');
|
|
748
|
-
}
|
|
749
|
-
// Show marketplace availability in tree format
|
|
750
|
-
const searchResults = await manager.search(name);
|
|
751
|
-
if (searchResults.size > 0) {
|
|
752
|
-
printInfo('Available in marketplaces:\n');
|
|
753
|
-
// Get all sources for this specific photon
|
|
754
|
-
const sources = searchResults.get(name);
|
|
755
|
-
if (sources && sources.length > 0) {
|
|
756
|
-
// Get local installation info to mark which one is installed
|
|
757
|
-
const fileName = `${name}.photon.ts`;
|
|
758
|
-
const installMetadata = await manager.getPhotonInstallMetadata(fileName);
|
|
759
|
-
// Build marketplace data as tree
|
|
760
|
-
const marketplaceData = {};
|
|
761
|
-
for (const source of sources) {
|
|
762
|
-
const isCurrentlyInstalled = installMetadata?.marketplace === source.marketplace.name;
|
|
763
|
-
const version = source.metadata?.version || 'unknown';
|
|
764
|
-
marketplaceData[source.marketplace.name] = {
|
|
765
|
-
version,
|
|
766
|
-
source: source.marketplace.repo,
|
|
767
|
-
status: isCurrentlyInstalled ? 'installed' : '-',
|
|
768
|
-
};
|
|
769
|
-
}
|
|
770
|
-
formatOutput(marketplaceData, 'tree');
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
else if (!isInstalled) {
|
|
774
|
-
printError(`'${name}' not found locally or in any marketplace`);
|
|
775
|
-
printInfo(`Tip: Use 'photon search <query>' to find similar MCPs`);
|
|
776
|
-
process.exit(1);
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
return;
|
|
780
|
-
}
|
|
781
|
-
// Show all Photons
|
|
782
|
-
if (asMcp) {
|
|
783
|
-
// MCP config mode for all Photons
|
|
784
|
-
const allConfigs = {};
|
|
785
|
-
// Check for global photon installation once
|
|
786
|
-
const globalPhotonPath = await getGlobalPhotonPath();
|
|
787
|
-
const needsWorkingDir = workingDir !== DEFAULT_WORKING_DIR;
|
|
788
|
-
for (const mcpName of mcps) {
|
|
789
|
-
const filePath = await resolvePhotonPath(mcpName, workingDir);
|
|
790
|
-
if (!filePath)
|
|
791
|
-
continue;
|
|
792
|
-
const constructorParams = await extractConstructorParams(filePath);
|
|
793
|
-
const env = {};
|
|
794
|
-
for (const param of constructorParams) {
|
|
795
|
-
const envVarName = toEnvVarName(mcpName, param.name);
|
|
796
|
-
const defaultDisplay = param.defaultValue !== undefined
|
|
797
|
-
? formatDefaultValue(param.defaultValue)
|
|
798
|
-
: `<your-${param.name}>`;
|
|
799
|
-
env[envVarName] = defaultDisplay;
|
|
800
|
-
}
|
|
801
|
-
allConfigs[mcpName] = globalPhotonPath
|
|
802
|
-
? {
|
|
803
|
-
command: globalPhotonPath,
|
|
804
|
-
args: needsWorkingDir
|
|
805
|
-
? ['mcp', mcpName, '--dir', workingDir]
|
|
806
|
-
: ['mcp', mcpName],
|
|
807
|
-
...(Object.keys(env).length > 0 && { env }),
|
|
808
|
-
}
|
|
809
|
-
: {
|
|
810
|
-
command: 'npx',
|
|
811
|
-
args: needsWorkingDir
|
|
812
|
-
? ['@portel/photon', 'mcp', mcpName, '--dir', workingDir]
|
|
813
|
-
: ['@portel/photon', 'mcp', mcpName],
|
|
814
|
-
...(Object.keys(env).length > 0 && { env }),
|
|
815
|
-
};
|
|
816
|
-
}
|
|
817
|
-
// Get OS-specific config path
|
|
818
|
-
const configPath = getConfigPath();
|
|
819
|
-
console.log(`# Photon MCP Server Configuration (${mcps.length} servers)`);
|
|
820
|
-
console.log(`# Add to mcpServers in: ${configPath}\n`);
|
|
821
|
-
console.log(JSON.stringify({ mcpServers: allConfigs }, null, 2));
|
|
822
|
-
return;
|
|
1018
|
+
// Get working directory from global options
|
|
1019
|
+
const workingDir = program.opts().dir || DEFAULT_WORKING_DIR;
|
|
1020
|
+
const logOptions = getLogOptionsFromCommand(command);
|
|
1021
|
+
// Resolve file path from name
|
|
1022
|
+
const filePath = await resolvePhotonPath(name, workingDir);
|
|
1023
|
+
if (!filePath) {
|
|
1024
|
+
exitWithError(`Photon not found: ${name}`, {
|
|
1025
|
+
exitCode: ExitCode.NOT_FOUND,
|
|
1026
|
+
searchedIn: workingDir,
|
|
1027
|
+
suggestion: "Use 'photon info' to see available photons",
|
|
1028
|
+
});
|
|
823
1029
|
}
|
|
824
|
-
//
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
for (const mcpName of mcps) {
|
|
830
|
-
const fileName = `${mcpName}.photon.ts`;
|
|
831
|
-
const filePath = path.join(workingDir, fileName);
|
|
832
|
-
// Get installation metadata
|
|
833
|
-
const metadata = await manager.getPhotonInstallMetadata(fileName);
|
|
834
|
-
if (metadata) {
|
|
835
|
-
// Has metadata - show version and status
|
|
836
|
-
const isModified = await manager.isPhotonModified(filePath, fileName);
|
|
837
|
-
tableData.push({
|
|
838
|
-
name: mcpName,
|
|
839
|
-
version: metadata.version,
|
|
840
|
-
source: metadata.marketplace,
|
|
841
|
-
status: isModified ? 'modified' : STATUS.OK,
|
|
842
|
-
});
|
|
843
|
-
}
|
|
844
|
-
else {
|
|
845
|
-
// No metadata - local or pre-metadata Photon
|
|
846
|
-
tableData.push({
|
|
847
|
-
name: mcpName,
|
|
848
|
-
version: '-',
|
|
849
|
-
source: 'local',
|
|
850
|
-
status: STATUS.OK,
|
|
851
|
-
});
|
|
852
|
-
}
|
|
853
|
-
}
|
|
854
|
-
formatOutput(tableData, 'table');
|
|
855
|
-
console.log('');
|
|
1030
|
+
// Find available port
|
|
1031
|
+
const startPort = parseInt(options.port, 10);
|
|
1032
|
+
const port = await findAvailablePort(startPort);
|
|
1033
|
+
if (port !== startPort) {
|
|
1034
|
+
console.error(`⚠️ Port ${startPort} is in use, using ${port} instead\n`);
|
|
856
1035
|
}
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
//
|
|
866
|
-
const
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
}
|
|
885
|
-
else {
|
|
886
|
-
marketplaceTree[marketplace.name] = 'no manifest';
|
|
887
|
-
}
|
|
1036
|
+
// Start SSE server
|
|
1037
|
+
const server = new PhotonServer({
|
|
1038
|
+
filePath,
|
|
1039
|
+
devMode: options.dev,
|
|
1040
|
+
transport: 'sse',
|
|
1041
|
+
port,
|
|
1042
|
+
logOptions: { ...logOptions, scope: 'sse' },
|
|
1043
|
+
});
|
|
1044
|
+
// Handle shutdown signals
|
|
1045
|
+
const shutdown = async () => {
|
|
1046
|
+
console.error('\nShutting down...');
|
|
1047
|
+
await server.stop();
|
|
1048
|
+
process.exit(0);
|
|
1049
|
+
};
|
|
1050
|
+
process.on('SIGINT', shutdown);
|
|
1051
|
+
process.on('SIGTERM', shutdown);
|
|
1052
|
+
// Start the server
|
|
1053
|
+
await server.start();
|
|
1054
|
+
// Start file watcher in dev mode
|
|
1055
|
+
if (options.dev) {
|
|
1056
|
+
const watcher = new FileWatcher(server, filePath, server.createScopedLogger('watcher'));
|
|
1057
|
+
watcher.start();
|
|
1058
|
+
process.on('SIGINT', async () => {
|
|
1059
|
+
await watcher.stop();
|
|
1060
|
+
});
|
|
1061
|
+
process.on('SIGTERM', async () => {
|
|
1062
|
+
await watcher.stop();
|
|
1063
|
+
});
|
|
888
1064
|
}
|
|
889
|
-
formatOutput(marketplaceTree, 'tree');
|
|
890
|
-
console.log('');
|
|
891
|
-
printInfo(`Details: photon info <name>`);
|
|
892
|
-
printInfo(`MCP config: photon info <name> --mcp`);
|
|
893
1065
|
}
|
|
894
1066
|
catch (error) {
|
|
895
|
-
|
|
896
|
-
printError(error.message);
|
|
1067
|
+
logger.error(`Error: ${getErrorMessage(error)}`);
|
|
897
1068
|
process.exit(1);
|
|
898
1069
|
}
|
|
899
1070
|
});
|
|
900
|
-
//
|
|
1071
|
+
// Beam command: interactive UI for all photons
|
|
901
1072
|
program
|
|
902
|
-
.command('
|
|
903
|
-
.
|
|
904
|
-
.
|
|
905
|
-
.
|
|
1073
|
+
.command('beam', { hidden: true })
|
|
1074
|
+
.option('-p, --port <number>', 'Port to start from (auto-finds available)', '3000')
|
|
1075
|
+
.option('-o, --open', 'Auto-open browser after starting')
|
|
1076
|
+
.option('--no-open', 'Do not auto-open browser')
|
|
1077
|
+
.description('Launch Photon Beam - interactive control panel for all your photons')
|
|
1078
|
+
.action(async (options, command) => {
|
|
906
1079
|
try {
|
|
907
|
-
|
|
908
|
-
const
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
1080
|
+
// Get working directory from global options
|
|
1081
|
+
const workingDir = program.opts().dir || DEFAULT_WORKING_DIR;
|
|
1082
|
+
// Find available port
|
|
1083
|
+
const startPort = parseInt(options.port, 10);
|
|
1084
|
+
const port = await findAvailablePort(startPort);
|
|
1085
|
+
if (port !== startPort) {
|
|
1086
|
+
console.error(`⚠️ Port ${startPort} is in use, using ${port} instead\n`);
|
|
1087
|
+
}
|
|
1088
|
+
// Import and start Beam server
|
|
1089
|
+
const { startBeam } = await import('./auto-ui/beam.js');
|
|
1090
|
+
await startBeam(workingDir, port);
|
|
1091
|
+
// Auto-open browser if requested
|
|
1092
|
+
if (options.open) {
|
|
1093
|
+
const url = `http://localhost:${port}`;
|
|
1094
|
+
const { exec } = await import('child_process');
|
|
1095
|
+
const openCmd = process.platform === 'darwin'
|
|
1096
|
+
? 'open'
|
|
1097
|
+
: process.platform === 'win32'
|
|
1098
|
+
? 'start'
|
|
1099
|
+
: 'xdg-open';
|
|
1100
|
+
exec(`${openCmd} ${url}`, (err) => {
|
|
1101
|
+
if (err)
|
|
1102
|
+
logger.debug(`Could not auto-open browser: ${err.message}`);
|
|
1103
|
+
});
|
|
915
1104
|
}
|
|
916
|
-
|
|
1105
|
+
// Handle shutdown signals
|
|
1106
|
+
const shutdown = () => {
|
|
1107
|
+
console.error('\nShutting down Photon Beam...');
|
|
1108
|
+
process.exit(0);
|
|
1109
|
+
};
|
|
1110
|
+
process.on('SIGINT', shutdown);
|
|
1111
|
+
process.on('SIGTERM', shutdown);
|
|
1112
|
+
}
|
|
1113
|
+
catch (error) {
|
|
1114
|
+
logger.error(`Error: ${getErrorMessage(error)}`);
|
|
1115
|
+
process.exit(1);
|
|
1116
|
+
}
|
|
1117
|
+
});
|
|
1118
|
+
// Serve command: multi-tenant MCP hosting (formerly serv)
|
|
1119
|
+
program
|
|
1120
|
+
.command('serve', { hidden: true })
|
|
1121
|
+
.option('-p, --port <number>', 'Port to run on', '4000')
|
|
1122
|
+
.option('-d, --debug', 'Enable debug logging')
|
|
1123
|
+
.description('Start local multi-tenant MCP hosting for development')
|
|
1124
|
+
.action(async (options) => {
|
|
1125
|
+
try {
|
|
1126
|
+
const port = parseInt(options.port, 10);
|
|
1127
|
+
const availablePort = await findAvailablePort(port);
|
|
1128
|
+
if (availablePort !== port) {
|
|
1129
|
+
console.error(`⚠️ Port ${port} is in use, using ${availablePort} instead\n`);
|
|
1130
|
+
}
|
|
1131
|
+
// Import and start LocalServ
|
|
1132
|
+
const { createLocalServ, getTestToken } = await import('./serv/local.js');
|
|
1133
|
+
const { serv, tenant, user } = createLocalServ({
|
|
1134
|
+
port: availablePort,
|
|
1135
|
+
baseUrl: `http://localhost:${availablePort}`,
|
|
1136
|
+
debug: options.debug,
|
|
1137
|
+
});
|
|
1138
|
+
// Get a test token
|
|
1139
|
+
const token = await getTestToken(serv, tenant, user);
|
|
1140
|
+
console.error(`
|
|
1141
|
+
⚡ Photon Serve (Multi-tenant Development)
|
|
1142
|
+
|
|
1143
|
+
URL: http://localhost:${availablePort}
|
|
1144
|
+
Tenant: ${tenant.slug} (${tenant.name})
|
|
1145
|
+
User: ${user.email}
|
|
1146
|
+
|
|
1147
|
+
Test Token:
|
|
1148
|
+
${token}
|
|
1149
|
+
|
|
1150
|
+
MCP Endpoint:
|
|
1151
|
+
http://localhost:${availablePort}/tenant/${tenant.slug}/mcp
|
|
1152
|
+
|
|
1153
|
+
Well-Known:
|
|
1154
|
+
http://localhost:${availablePort}/.well-known/oauth-protected-resource
|
|
1155
|
+
|
|
1156
|
+
Press Ctrl+C to stop
|
|
1157
|
+
`);
|
|
1158
|
+
// Simple HTTP server
|
|
1159
|
+
const http = await import('http');
|
|
1160
|
+
const server = http.createServer(async (req, res) => {
|
|
1161
|
+
const url = req.url || '/';
|
|
1162
|
+
const method = req.method || 'GET';
|
|
1163
|
+
const headers = {};
|
|
1164
|
+
for (const [key, value] of Object.entries(req.headers)) {
|
|
1165
|
+
if (typeof value === 'string')
|
|
1166
|
+
headers[key] = value;
|
|
1167
|
+
}
|
|
1168
|
+
// Read body if present
|
|
1169
|
+
let body = '';
|
|
1170
|
+
if (method === 'POST') {
|
|
1171
|
+
body = await new Promise((resolve) => {
|
|
1172
|
+
let data = '';
|
|
1173
|
+
req.on('data', (chunk) => (data += chunk));
|
|
1174
|
+
req.on('end', () => resolve(data));
|
|
1175
|
+
});
|
|
1176
|
+
}
|
|
1177
|
+
const result = await serv.handleRequest(method, url, headers, body);
|
|
1178
|
+
res.writeHead(result.status, result.headers);
|
|
1179
|
+
res.end(result.body);
|
|
1180
|
+
});
|
|
1181
|
+
server.listen(availablePort);
|
|
1182
|
+
// Handle shutdown
|
|
1183
|
+
const shutdown = async () => {
|
|
1184
|
+
console.error('\nShutting down Photon Serve...');
|
|
1185
|
+
await serv.shutdown();
|
|
1186
|
+
server.close();
|
|
1187
|
+
process.exit(0);
|
|
1188
|
+
};
|
|
1189
|
+
process.on('SIGINT', shutdown);
|
|
1190
|
+
process.on('SIGTERM', shutdown);
|
|
1191
|
+
}
|
|
1192
|
+
catch (error) {
|
|
1193
|
+
logger.error(`Error: ${getErrorMessage(error)}`);
|
|
1194
|
+
process.exit(1);
|
|
1195
|
+
}
|
|
1196
|
+
});
|
|
1197
|
+
// Host command: manage hosting and deployment (preview, deploy)
|
|
1198
|
+
const host = program
|
|
1199
|
+
.command('host', { hidden: true })
|
|
1200
|
+
.description('Manage cloud hosting and deployment');
|
|
1201
|
+
host
|
|
1202
|
+
.command('preview')
|
|
1203
|
+
.argument('<target>', 'Deployment target: cloudflare (or cf)')
|
|
1204
|
+
.argument('<name>', 'Photon name (without .photon.ts extension)')
|
|
1205
|
+
.option('--output <dir>', 'Output directory for generated project')
|
|
1206
|
+
.description('Run Photon locally in a simulated deployment environment')
|
|
1207
|
+
.action(async (target, name, options) => {
|
|
1208
|
+
try {
|
|
1209
|
+
// Get working directory from global options
|
|
1210
|
+
const workingDir = program.opts().dir || DEFAULT_WORKING_DIR;
|
|
1211
|
+
// Resolve file path from name
|
|
1212
|
+
const photonPath = await resolvePhotonPath(name, workingDir);
|
|
1213
|
+
if (!photonPath) {
|
|
1214
|
+
logger.error(`Photon not found: ${name}`);
|
|
1215
|
+
console.error(`Searched in: ${workingDir}`);
|
|
1216
|
+
console.error(`Tip: Use 'photon info' to see available photons`);
|
|
1217
|
+
process.exit(1);
|
|
1218
|
+
}
|
|
1219
|
+
const normalizedTarget = target.toLowerCase();
|
|
1220
|
+
if (normalizedTarget === 'cloudflare' || normalizedTarget === 'cf') {
|
|
1221
|
+
const { devCloudflare } = await import('./deploy/cloudflare.js');
|
|
1222
|
+
await devCloudflare({
|
|
1223
|
+
photonPath,
|
|
1224
|
+
outputDir: options.output,
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1227
|
+
else {
|
|
1228
|
+
logger.error(`Unknown target: ${target}`);
|
|
1229
|
+
console.error('Supported targets: cloudflare (cf)');
|
|
1230
|
+
process.exit(1);
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
catch (error) {
|
|
1234
|
+
logger.error(`Error: ${getErrorMessage(error)}`);
|
|
1235
|
+
process.exit(1);
|
|
1236
|
+
}
|
|
1237
|
+
});
|
|
1238
|
+
host
|
|
1239
|
+
.command('deploy')
|
|
1240
|
+
.argument('<target>', 'Deployment target: cloudflare (or cf)')
|
|
1241
|
+
.argument('<name>', 'Photon name (without .photon.ts extension)')
|
|
1242
|
+
.option('--dev', 'Enable Beam UI in deployment')
|
|
1243
|
+
.option('--dry-run', 'Generate project without deploying')
|
|
1244
|
+
.option('--output <dir>', 'Output directory for generated project')
|
|
1245
|
+
.description('Deploy a Photon to cloud platforms')
|
|
1246
|
+
.action(async (target, name, options) => {
|
|
1247
|
+
try {
|
|
1248
|
+
// Get working directory from global options
|
|
1249
|
+
const workingDir = program.opts().dir || DEFAULT_WORKING_DIR;
|
|
1250
|
+
// Resolve file path from name
|
|
1251
|
+
const photonPath = await resolvePhotonPath(name, workingDir);
|
|
1252
|
+
if (!photonPath) {
|
|
1253
|
+
logger.error(`Photon not found: ${name}`);
|
|
1254
|
+
console.error(`Searched in: ${workingDir}`);
|
|
1255
|
+
console.error(`Tip: Use 'photon info' to see available photons`);
|
|
1256
|
+
process.exit(1);
|
|
1257
|
+
}
|
|
1258
|
+
const normalizedTarget = target.toLowerCase();
|
|
1259
|
+
if (normalizedTarget === 'cloudflare' || normalizedTarget === 'cf') {
|
|
1260
|
+
const { deployToCloudflare } = await import('./deploy/cloudflare.js');
|
|
1261
|
+
await deployToCloudflare({
|
|
1262
|
+
photonPath,
|
|
1263
|
+
devMode: options.dev,
|
|
1264
|
+
dryRun: options.dryRun,
|
|
1265
|
+
outputDir: options.output,
|
|
1266
|
+
});
|
|
1267
|
+
}
|
|
1268
|
+
else {
|
|
1269
|
+
logger.error(`Unknown deployment target: ${target}`);
|
|
1270
|
+
console.error('Supported targets: cloudflare (cf)');
|
|
1271
|
+
process.exit(1);
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
catch (error) {
|
|
1275
|
+
logger.error(`Deployment failed: ${getErrorMessage(error)}`);
|
|
1276
|
+
process.exit(1);
|
|
1277
|
+
}
|
|
1278
|
+
});
|
|
1279
|
+
// Search command: search for MCPs across marketplaces
|
|
1280
|
+
program
|
|
1281
|
+
.command('search', { hidden: true })
|
|
1282
|
+
.argument('<query>', 'MCP name or keyword to search for')
|
|
1283
|
+
.description('Search for MCP in all enabled marketplaces')
|
|
1284
|
+
.action(async (query) => {
|
|
1285
|
+
try {
|
|
1286
|
+
const { MarketplaceManager } = await import('./marketplace-manager.js');
|
|
1287
|
+
const { formatOutput, printInfo, printError } = await import('./cli-formatter.js');
|
|
1288
|
+
const manager = new MarketplaceManager();
|
|
1289
|
+
await manager.initialize();
|
|
1290
|
+
// Auto-update stale caches
|
|
1291
|
+
const updated = await manager.autoUpdateStaleCaches();
|
|
1292
|
+
if (updated) {
|
|
1293
|
+
printInfo('Refreshed marketplace data...\n');
|
|
1294
|
+
}
|
|
1295
|
+
printInfo(`Searching for '${query}' in marketplaces...`);
|
|
917
1296
|
const results = await manager.search(query);
|
|
918
1297
|
if (results.size === 0) {
|
|
919
1298
|
printError(`No results found for '${query}'`);
|
|
@@ -926,9 +1305,10 @@ program
|
|
|
926
1305
|
for (const entry of entries) {
|
|
927
1306
|
tableData.push({
|
|
928
1307
|
name: mcpName,
|
|
929
|
-
version: entry.metadata?.version ||
|
|
1308
|
+
version: entry.metadata?.version || PHOTON_VERSION,
|
|
930
1309
|
description: entry.metadata?.description
|
|
931
|
-
? entry.metadata.description.substring(0, 50) +
|
|
1310
|
+
? entry.metadata.description.substring(0, 50) +
|
|
1311
|
+
(entry.metadata.description.length > 50 ? '...' : '')
|
|
932
1312
|
: '-',
|
|
933
1313
|
marketplace: entry.marketplace.name,
|
|
934
1314
|
});
|
|
@@ -940,7 +1320,7 @@ program
|
|
|
940
1320
|
}
|
|
941
1321
|
catch (error) {
|
|
942
1322
|
const { printError } = await import('./cli-formatter.js');
|
|
943
|
-
printError(error
|
|
1323
|
+
printError(getErrorMessage(error));
|
|
944
1324
|
process.exit(1);
|
|
945
1325
|
}
|
|
946
1326
|
});
|
|
@@ -964,7 +1344,7 @@ maker
|
|
|
964
1344
|
// Check if file already exists
|
|
965
1345
|
try {
|
|
966
1346
|
await fs.access(filePath);
|
|
967
|
-
|
|
1347
|
+
logger.error(`File already exists: ${filePath}`);
|
|
968
1348
|
process.exit(1);
|
|
969
1349
|
}
|
|
970
1350
|
catch {
|
|
@@ -984,18 +1364,16 @@ maker
|
|
|
984
1364
|
// Convert kebab-case to PascalCase for class name
|
|
985
1365
|
const className = name
|
|
986
1366
|
.split(/[-_]/)
|
|
987
|
-
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
1367
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
988
1368
|
.join('');
|
|
989
|
-
const content = template
|
|
990
|
-
.replace(/TemplateName/g, className)
|
|
991
|
-
.replace(/template-name/g, name);
|
|
1369
|
+
const content = template.replace(/TemplateName/g, className).replace(/template-name/g, name);
|
|
992
1370
|
// Write file
|
|
993
1371
|
await fs.writeFile(filePath, content, 'utf-8');
|
|
994
1372
|
console.error(`✅ Created ${fileName} in ${workingDir}`);
|
|
995
1373
|
console.error(`Run with: photon mcp ${name} --dev`);
|
|
996
1374
|
}
|
|
997
1375
|
catch (error) {
|
|
998
|
-
|
|
1376
|
+
logger.error(`Error: ${getErrorMessage(error)}`);
|
|
999
1377
|
process.exit(1);
|
|
1000
1378
|
}
|
|
1001
1379
|
});
|
|
@@ -1011,10 +1389,11 @@ maker
|
|
|
1011
1389
|
// Resolve file path from name in working directory
|
|
1012
1390
|
const filePath = await resolvePhotonPath(name, workingDir);
|
|
1013
1391
|
if (!filePath) {
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1392
|
+
exitWithError(`Photon not found: ${name}`, {
|
|
1393
|
+
exitCode: ExitCode.NOT_FOUND,
|
|
1394
|
+
searchedIn: workingDir,
|
|
1395
|
+
suggestion: "Use 'photon info' to see available photons",
|
|
1396
|
+
});
|
|
1018
1397
|
}
|
|
1019
1398
|
console.error(`Validating ${path.basename(filePath)}...\n`);
|
|
1020
1399
|
// Import loader and try to load
|
|
@@ -1030,7 +1409,7 @@ maker
|
|
|
1030
1409
|
process.exit(0);
|
|
1031
1410
|
}
|
|
1032
1411
|
catch (error) {
|
|
1033
|
-
|
|
1412
|
+
logger.error(`Validation failed: ${getErrorMessage(error)}`);
|
|
1034
1413
|
process.exit(1);
|
|
1035
1414
|
}
|
|
1036
1415
|
});
|
|
@@ -1057,8 +1436,8 @@ maker
|
|
|
1057
1436
|
}
|
|
1058
1437
|
}
|
|
1059
1438
|
catch (error) {
|
|
1060
|
-
|
|
1061
|
-
if (process.env.DEBUG) {
|
|
1439
|
+
logger.error(`Error: ${getErrorMessage(error)}`);
|
|
1440
|
+
if (process.env.DEBUG && error instanceof Error) {
|
|
1062
1441
|
console.error(error.stack);
|
|
1063
1442
|
}
|
|
1064
1443
|
process.exit(1);
|
|
@@ -1077,848 +1456,342 @@ maker
|
|
|
1077
1456
|
await performMarketplaceInit(dirPath, options);
|
|
1078
1457
|
}
|
|
1079
1458
|
catch (error) {
|
|
1080
|
-
|
|
1081
|
-
if (process.env.DEBUG) {
|
|
1459
|
+
logger.error(`Error: ${getErrorMessage(error)}`);
|
|
1460
|
+
if (process.env.DEBUG && error instanceof Error) {
|
|
1082
1461
|
console.error(error.stack);
|
|
1083
1462
|
}
|
|
1084
1463
|
process.exit(1);
|
|
1085
1464
|
}
|
|
1086
1465
|
});
|
|
1087
|
-
//
|
|
1088
|
-
|
|
1089
|
-
.command('
|
|
1090
|
-
.
|
|
1091
|
-
|
|
1092
|
-
.
|
|
1093
|
-
.description('List all configured marketplaces')
|
|
1094
|
-
.action(async () => {
|
|
1095
|
-
try {
|
|
1096
|
-
const { MarketplaceManager } = await import('./marketplace-manager.js');
|
|
1097
|
-
const { formatOutput, printInfo, STATUS } = await import('./cli-formatter.js');
|
|
1098
|
-
const manager = new MarketplaceManager();
|
|
1099
|
-
await manager.initialize();
|
|
1100
|
-
const marketplaces = manager.getAll();
|
|
1101
|
-
if (marketplaces.length === 0) {
|
|
1102
|
-
printInfo('No marketplaces configured');
|
|
1103
|
-
printInfo('Add one with: photon marketplace add portel-dev/photons');
|
|
1104
|
-
return;
|
|
1105
|
-
}
|
|
1106
|
-
// Get MCP counts
|
|
1107
|
-
const counts = await manager.getMarketplaceCounts();
|
|
1108
|
-
// Build table data
|
|
1109
|
-
const tableData = marketplaces.map(m => ({
|
|
1110
|
-
name: m.name,
|
|
1111
|
-
source: m.source || m.repo || '-',
|
|
1112
|
-
photons: counts.get(m.name) || 0,
|
|
1113
|
-
status: m.enabled ? STATUS.OK : STATUS.OFF,
|
|
1114
|
-
}));
|
|
1115
|
-
printInfo(`Configured marketplaces (${marketplaces.length}):\n`);
|
|
1116
|
-
formatOutput(tableData, 'table');
|
|
1117
|
-
}
|
|
1118
|
-
catch (error) {
|
|
1119
|
-
const { printError } = await import('./cli-formatter.js');
|
|
1120
|
-
printError(error.message);
|
|
1121
|
-
process.exit(1);
|
|
1122
|
-
}
|
|
1123
|
-
});
|
|
1124
|
-
marketplace
|
|
1125
|
-
.command('add')
|
|
1126
|
-
.argument('<repo>', 'GitHub repository (username/repo or github.com URL)')
|
|
1127
|
-
.description('Add a new MCP marketplace from GitHub')
|
|
1128
|
-
.action(async (repo) => {
|
|
1466
|
+
// maker diagram: generate Mermaid diagram for a Photon
|
|
1467
|
+
maker
|
|
1468
|
+
.command('diagram <photon>')
|
|
1469
|
+
.option('--dir <path>', 'Directory containing photon (defaults to current directory)')
|
|
1470
|
+
.description('Generate Mermaid diagram for a Photon')
|
|
1471
|
+
.action(async (photonName, options) => {
|
|
1129
1472
|
try {
|
|
1130
|
-
const {
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
// Auto-fetch marketplace.json
|
|
1139
|
-
console.error(`Fetching marketplace metadata...`);
|
|
1140
|
-
const success = await manager.updateMarketplaceCache(result.name);
|
|
1141
|
-
if (success) {
|
|
1142
|
-
console.error(`✅ Marketplace ready to use`);
|
|
1473
|
+
const { PhotonDocExtractor } = await import('./photon-doc-extractor.js');
|
|
1474
|
+
// Resolve photon path
|
|
1475
|
+
const dirPath = options.dir || '.';
|
|
1476
|
+
let photonPath = photonName;
|
|
1477
|
+
// If not a path, look in the directory
|
|
1478
|
+
if (!photonName.includes('/') && !photonName.includes('\\')) {
|
|
1479
|
+
if (!photonName.endsWith('.photon.ts')) {
|
|
1480
|
+
photonName = `${photonName}.photon.ts`;
|
|
1143
1481
|
}
|
|
1482
|
+
photonPath = path.resolve(dirPath, photonName);
|
|
1144
1483
|
}
|
|
1145
1484
|
else {
|
|
1146
|
-
|
|
1147
|
-
console.error(`Source: ${result.source}`);
|
|
1148
|
-
console.error(`Skipping duplicate addition`);
|
|
1149
|
-
}
|
|
1150
|
-
}
|
|
1151
|
-
catch (error) {
|
|
1152
|
-
console.error(`❌ Error: ${error.message}`);
|
|
1153
|
-
process.exit(1);
|
|
1154
|
-
}
|
|
1155
|
-
});
|
|
1156
|
-
marketplace
|
|
1157
|
-
.command('remove')
|
|
1158
|
-
.argument('<name>', 'Marketplace name')
|
|
1159
|
-
.description('Remove a marketplace')
|
|
1160
|
-
.action(async (name) => {
|
|
1161
|
-
try {
|
|
1162
|
-
const { MarketplaceManager } = await import('./marketplace-manager.js');
|
|
1163
|
-
const manager = new MarketplaceManager();
|
|
1164
|
-
await manager.initialize();
|
|
1165
|
-
const removed = await manager.remove(name);
|
|
1166
|
-
if (removed) {
|
|
1167
|
-
console.error(`✅ Removed marketplace: ${name}`);
|
|
1485
|
+
photonPath = path.resolve(photonName);
|
|
1168
1486
|
}
|
|
1169
|
-
|
|
1170
|
-
|
|
1487
|
+
if (!existsSync(photonPath)) {
|
|
1488
|
+
logger.error(`Photon not found: ${photonPath}`);
|
|
1171
1489
|
process.exit(1);
|
|
1172
1490
|
}
|
|
1491
|
+
const extractor = new PhotonDocExtractor(photonPath);
|
|
1492
|
+
const diagram = await extractor.generateDiagram();
|
|
1493
|
+
// Output just the diagram (can be piped or copied)
|
|
1494
|
+
console.log(diagram);
|
|
1173
1495
|
}
|
|
1174
1496
|
catch (error) {
|
|
1175
|
-
|
|
1176
|
-
process.
|
|
1177
|
-
|
|
1178
|
-
});
|
|
1179
|
-
marketplace
|
|
1180
|
-
.command('enable')
|
|
1181
|
-
.argument('<name>', 'Marketplace name')
|
|
1182
|
-
.description('Enable a marketplace')
|
|
1183
|
-
.action(async (name) => {
|
|
1184
|
-
try {
|
|
1185
|
-
const { MarketplaceManager } = await import('./marketplace-manager.js');
|
|
1186
|
-
const manager = new MarketplaceManager();
|
|
1187
|
-
await manager.initialize();
|
|
1188
|
-
const success = await manager.setEnabled(name, true);
|
|
1189
|
-
if (success) {
|
|
1190
|
-
console.error(`✅ Enabled marketplace: ${name}`);
|
|
1191
|
-
}
|
|
1192
|
-
else {
|
|
1193
|
-
console.error(`❌ Marketplace '${name}' not found`);
|
|
1194
|
-
process.exit(1);
|
|
1195
|
-
}
|
|
1196
|
-
}
|
|
1197
|
-
catch (error) {
|
|
1198
|
-
console.error(`❌ Error: ${error.message}`);
|
|
1199
|
-
process.exit(1);
|
|
1200
|
-
}
|
|
1201
|
-
});
|
|
1202
|
-
marketplace
|
|
1203
|
-
.command('disable')
|
|
1204
|
-
.argument('<name>', 'Marketplace name')
|
|
1205
|
-
.description('Disable a marketplace')
|
|
1206
|
-
.action(async (name) => {
|
|
1207
|
-
try {
|
|
1208
|
-
const { MarketplaceManager } = await import('./marketplace-manager.js');
|
|
1209
|
-
const manager = new MarketplaceManager();
|
|
1210
|
-
await manager.initialize();
|
|
1211
|
-
const success = await manager.setEnabled(name, false);
|
|
1212
|
-
if (success) {
|
|
1213
|
-
console.error(`✅ Disabled marketplace: ${name}`);
|
|
1214
|
-
}
|
|
1215
|
-
else {
|
|
1216
|
-
console.error(`❌ Marketplace '${name}' not found`);
|
|
1217
|
-
process.exit(1);
|
|
1218
|
-
}
|
|
1219
|
-
}
|
|
1220
|
-
catch (error) {
|
|
1221
|
-
console.error(`❌ Error: ${error.message}`);
|
|
1222
|
-
process.exit(1);
|
|
1223
|
-
}
|
|
1224
|
-
});
|
|
1225
|
-
// Add command: add MCP from marketplace
|
|
1226
|
-
program
|
|
1227
|
-
.command('add', { hidden: true })
|
|
1228
|
-
.argument('<name>', 'MCP name to add')
|
|
1229
|
-
.option('--marketplace <name>', 'Specific marketplace to use')
|
|
1230
|
-
.option('-y, --yes', 'Automatically select first suggestion without prompting')
|
|
1231
|
-
.description('Add an MCP from a marketplace')
|
|
1232
|
-
.action(async (name, options, command) => {
|
|
1233
|
-
try {
|
|
1234
|
-
// Get working directory from global options
|
|
1235
|
-
const workingDir = command.parent?.opts().dir || DEFAULT_WORKING_DIR;
|
|
1236
|
-
await ensureWorkingDir(workingDir);
|
|
1237
|
-
const { MarketplaceManager } = await import('./marketplace-manager.js');
|
|
1238
|
-
const manager = new MarketplaceManager();
|
|
1239
|
-
await manager.initialize();
|
|
1240
|
-
// Check for conflicts
|
|
1241
|
-
let conflict = await manager.checkConflict(name, options.marketplace);
|
|
1242
|
-
if (!conflict.sources || conflict.sources.length === 0) {
|
|
1243
|
-
console.error(`❌ MCP '${name}' not found in any enabled marketplace\n`);
|
|
1244
|
-
// Search for similar names
|
|
1245
|
-
const searchResults = await manager.search(name);
|
|
1246
|
-
if (searchResults.size > 0) {
|
|
1247
|
-
console.error(`Did you mean one of these?\n`);
|
|
1248
|
-
// Convert search results to array for selection
|
|
1249
|
-
const suggestions = [];
|
|
1250
|
-
let count = 0;
|
|
1251
|
-
for (const [mcpName, sources] of searchResults) {
|
|
1252
|
-
if (count >= 5)
|
|
1253
|
-
break; // Limit to 5 suggestions
|
|
1254
|
-
const source = sources[0]; // Use first marketplace
|
|
1255
|
-
const version = source.metadata?.version || 'unknown';
|
|
1256
|
-
const description = source.metadata?.description || 'No description';
|
|
1257
|
-
suggestions.push({ name: mcpName, version, description });
|
|
1258
|
-
console.error(` [${count + 1}] ${mcpName} (v${version})`);
|
|
1259
|
-
console.error(` ${description}`);
|
|
1260
|
-
count++;
|
|
1261
|
-
}
|
|
1262
|
-
// Interactive selection or auto-select with -y
|
|
1263
|
-
let selectedIndex;
|
|
1264
|
-
if (options.yes) {
|
|
1265
|
-
// Auto-select first suggestion
|
|
1266
|
-
selectedIndex = 0;
|
|
1267
|
-
}
|
|
1268
|
-
else {
|
|
1269
|
-
// Interactive selection
|
|
1270
|
-
selectedIndex = await new Promise((resolve) => {
|
|
1271
|
-
const rl = readline.createInterface({
|
|
1272
|
-
input: process.stdin,
|
|
1273
|
-
output: process.stderr,
|
|
1274
|
-
});
|
|
1275
|
-
const askQuestion = () => {
|
|
1276
|
-
rl.question(`\nWhich one? [1-${suggestions.length}] (or press Enter to cancel): `, (answer) => {
|
|
1277
|
-
const trimmed = answer.trim();
|
|
1278
|
-
// Empty input = cancel
|
|
1279
|
-
if (trimmed === '') {
|
|
1280
|
-
rl.close();
|
|
1281
|
-
resolve(null);
|
|
1282
|
-
return;
|
|
1283
|
-
}
|
|
1284
|
-
const choice = parseInt(trimmed, 10);
|
|
1285
|
-
// Validate input
|
|
1286
|
-
if (isNaN(choice) || choice < 1 || choice > suggestions.length) {
|
|
1287
|
-
console.error(`Invalid choice. Please enter a number between 1 and ${suggestions.length}.`);
|
|
1288
|
-
askQuestion();
|
|
1289
|
-
}
|
|
1290
|
-
else {
|
|
1291
|
-
rl.close();
|
|
1292
|
-
resolve(choice - 1);
|
|
1293
|
-
}
|
|
1294
|
-
});
|
|
1295
|
-
};
|
|
1296
|
-
askQuestion();
|
|
1297
|
-
});
|
|
1298
|
-
if (selectedIndex === null) {
|
|
1299
|
-
console.error('\nCancelled.');
|
|
1300
|
-
process.exit(0);
|
|
1301
|
-
}
|
|
1302
|
-
}
|
|
1303
|
-
// Update name to the selected MCP
|
|
1304
|
-
name = suggestions[selectedIndex].name;
|
|
1305
|
-
console.error(`\n✓ Selected: ${name}`);
|
|
1306
|
-
// Re-check for conflicts with the new name
|
|
1307
|
-
conflict = await manager.checkConflict(name, options.marketplace);
|
|
1308
|
-
if (!conflict.sources || conflict.sources.length === 0) {
|
|
1309
|
-
console.error(`❌ MCP '${name}' is no longer available`);
|
|
1310
|
-
process.exit(1);
|
|
1311
|
-
}
|
|
1312
|
-
}
|
|
1313
|
-
else {
|
|
1314
|
-
console.error(`Run 'photon info' to see all available MCPs`);
|
|
1315
|
-
process.exit(1);
|
|
1316
|
-
}
|
|
1317
|
-
}
|
|
1318
|
-
// Check if already exists locally
|
|
1319
|
-
const filePath = path.join(workingDir, `${name}.photon.ts`);
|
|
1320
|
-
const fileName = `${name}.photon.ts`;
|
|
1321
|
-
if (existsSync(filePath)) {
|
|
1322
|
-
console.error(`⚠️ MCP '${name}' already exists`);
|
|
1323
|
-
console.error(`Use 'photon upgrade ${name}' to update it`);
|
|
1324
|
-
process.exit(1);
|
|
1325
|
-
}
|
|
1326
|
-
// Handle conflicts
|
|
1327
|
-
let selectedMarketplace;
|
|
1328
|
-
let selectedMetadata;
|
|
1329
|
-
if (conflict.hasConflict) {
|
|
1330
|
-
console.error(`⚠️ MCP '${name}' found in multiple marketplaces:\n`);
|
|
1331
|
-
conflict.sources.forEach((source, index) => {
|
|
1332
|
-
const marker = source.marketplace.name === conflict.recommendation ? '→' : ' ';
|
|
1333
|
-
const version = source.metadata?.version || 'unknown';
|
|
1334
|
-
console.error(` ${marker} [${index + 1}] ${source.marketplace.name} (v${version})`);
|
|
1335
|
-
console.error(` ${source.marketplace.repo || source.marketplace.url}`);
|
|
1336
|
-
});
|
|
1337
|
-
if (conflict.recommendation) {
|
|
1338
|
-
console.error(`\n💡 Recommended: ${conflict.recommendation} (newest version)`);
|
|
1339
|
-
}
|
|
1340
|
-
// Get default choice (recommended or first)
|
|
1341
|
-
const recommendedIndex = conflict.sources.findIndex(s => s.marketplace.name === conflict.recommendation);
|
|
1342
|
-
const defaultChoice = recommendedIndex !== -1 ? recommendedIndex + 1 : 1;
|
|
1343
|
-
// Interactive selection
|
|
1344
|
-
const selectedIndex = await new Promise((resolve) => {
|
|
1345
|
-
const rl = readline.createInterface({
|
|
1346
|
-
input: process.stdin,
|
|
1347
|
-
output: process.stderr,
|
|
1348
|
-
});
|
|
1349
|
-
const askQuestion = () => {
|
|
1350
|
-
rl.question(`\nWhich marketplace? [1-${conflict.sources.length}] (default: ${defaultChoice}): `, (answer) => {
|
|
1351
|
-
const trimmed = answer.trim();
|
|
1352
|
-
// Empty input = use default
|
|
1353
|
-
if (trimmed === '') {
|
|
1354
|
-
rl.close();
|
|
1355
|
-
resolve(defaultChoice - 1);
|
|
1356
|
-
return;
|
|
1357
|
-
}
|
|
1358
|
-
const choice = parseInt(trimmed, 10);
|
|
1359
|
-
// Validate input
|
|
1360
|
-
if (isNaN(choice) || choice < 1 || choice > conflict.sources.length) {
|
|
1361
|
-
console.error(`Invalid choice. Please enter a number between 1 and ${conflict.sources.length}.`);
|
|
1362
|
-
askQuestion();
|
|
1363
|
-
}
|
|
1364
|
-
else {
|
|
1365
|
-
rl.close();
|
|
1366
|
-
resolve(choice - 1);
|
|
1367
|
-
}
|
|
1368
|
-
});
|
|
1369
|
-
};
|
|
1370
|
-
askQuestion();
|
|
1371
|
-
});
|
|
1372
|
-
const selectedSource = conflict.sources[selectedIndex];
|
|
1373
|
-
selectedMarketplace = selectedSource.marketplace;
|
|
1374
|
-
selectedMetadata = selectedSource.metadata;
|
|
1375
|
-
console.error(`\n✓ Using: ${selectedMarketplace.name}`);
|
|
1376
|
-
}
|
|
1377
|
-
else {
|
|
1378
|
-
selectedMarketplace = conflict.sources[0].marketplace;
|
|
1379
|
-
selectedMetadata = conflict.sources[0].metadata;
|
|
1380
|
-
console.error(`Adding ${name} from ${selectedMarketplace.name}...`);
|
|
1381
|
-
}
|
|
1382
|
-
// Fetch content from selected marketplace
|
|
1383
|
-
const result = await manager.fetchMCP(name);
|
|
1384
|
-
if (!result) {
|
|
1385
|
-
console.error(`❌ Failed to fetch MCP content`);
|
|
1386
|
-
process.exit(1);
|
|
1387
|
-
}
|
|
1388
|
-
const content = result.content;
|
|
1389
|
-
// Write file
|
|
1390
|
-
await fs.writeFile(filePath, content, 'utf-8');
|
|
1391
|
-
// Save installation metadata if we have it
|
|
1392
|
-
if (selectedMetadata) {
|
|
1393
|
-
const { calculateHash } = await import('./marketplace-manager.js');
|
|
1394
|
-
const contentHash = calculateHash(content);
|
|
1395
|
-
await manager.savePhotonMetadata(fileName, selectedMarketplace, selectedMetadata, contentHash);
|
|
1396
|
-
}
|
|
1397
|
-
console.error(`✅ Added ${name} from ${selectedMarketplace.name}`);
|
|
1398
|
-
if (selectedMetadata?.version) {
|
|
1399
|
-
console.error(`Version: ${selectedMetadata.version}`);
|
|
1497
|
+
logger.error(`Error: ${getErrorMessage(error)}`);
|
|
1498
|
+
if (process.env.DEBUG && error instanceof Error) {
|
|
1499
|
+
console.error(error.stack);
|
|
1400
1500
|
}
|
|
1401
|
-
console.error(`Location: ${filePath}`);
|
|
1402
|
-
console.error(`\nRun with: photon mcp ${name}`);
|
|
1403
|
-
console.error(`\nTo customize: Copy to a new name and run with --dev for hot reload`);
|
|
1404
|
-
}
|
|
1405
|
-
catch (error) {
|
|
1406
|
-
console.error(`❌ Error: ${error.message}`);
|
|
1407
1501
|
process.exit(1);
|
|
1408
1502
|
}
|
|
1409
1503
|
});
|
|
1410
|
-
//
|
|
1411
|
-
|
|
1412
|
-
.command('
|
|
1413
|
-
.
|
|
1414
|
-
.
|
|
1415
|
-
.
|
|
1416
|
-
.description('Remove an installed photon')
|
|
1417
|
-
.action(async (name, options, command) => {
|
|
1504
|
+
// maker diagrams: generate Mermaid diagrams for all Photons in a directory
|
|
1505
|
+
maker
|
|
1506
|
+
.command('diagrams')
|
|
1507
|
+
.option('--dir <path>', 'Directory to scan (defaults to current directory)')
|
|
1508
|
+
.description('Generate Mermaid diagrams for all Photons in a directory')
|
|
1509
|
+
.action(async (options) => {
|
|
1418
1510
|
try {
|
|
1419
|
-
const {
|
|
1420
|
-
const
|
|
1421
|
-
|
|
1422
|
-
const
|
|
1423
|
-
if (
|
|
1424
|
-
|
|
1425
|
-
printInfo(`Searched in: ${workingDir}`);
|
|
1426
|
-
printInfo(`Tip: Use 'photon info' to see installed photons`);
|
|
1511
|
+
const { PhotonDocExtractor } = await import('./photon-doc-extractor.js');
|
|
1512
|
+
const dirPath = path.resolve(options.dir || '.');
|
|
1513
|
+
const files = await fs.readdir(dirPath);
|
|
1514
|
+
const photonFiles = files.filter((f) => f.endsWith('.photon.ts'));
|
|
1515
|
+
if (photonFiles.length === 0) {
|
|
1516
|
+
console.error('No .photon.ts files found');
|
|
1427
1517
|
process.exit(1);
|
|
1428
1518
|
}
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
// Clear compiled cache unless --keep-cache
|
|
1434
|
-
if (!options.keepCache) {
|
|
1435
|
-
const cacheDir = path.join(os.homedir(), '.cache', 'photon-mcp', 'compiled');
|
|
1436
|
-
const cachedFiles = [
|
|
1437
|
-
path.join(cacheDir, `${name}.js`),
|
|
1438
|
-
path.join(cacheDir, `${name}.js.map`),
|
|
1439
|
-
];
|
|
1440
|
-
for (const cachedFile of cachedFiles) {
|
|
1441
|
-
try {
|
|
1442
|
-
await fs.unlink(cachedFile);
|
|
1443
|
-
}
|
|
1444
|
-
catch {
|
|
1445
|
-
// Ignore if cache doesn't exist
|
|
1446
|
-
}
|
|
1447
|
-
}
|
|
1448
|
-
printSuccess(`Cleared cache`);
|
|
1449
|
-
}
|
|
1450
|
-
console.log('');
|
|
1451
|
-
printSuccess(`Successfully removed ${name}`);
|
|
1452
|
-
printInfo(`To reinstall: photon add ${name}`);
|
|
1453
|
-
}
|
|
1454
|
-
catch (error) {
|
|
1455
|
-
const { printError } = await import('./cli-formatter.js');
|
|
1456
|
-
printError(error.message);
|
|
1457
|
-
process.exit(1);
|
|
1458
|
-
}
|
|
1459
|
-
});
|
|
1460
|
-
// Upgrade command: update MCPs from marketplace
|
|
1461
|
-
program
|
|
1462
|
-
.command('upgrade', { hidden: true })
|
|
1463
|
-
.argument('[name]', 'MCP name to upgrade (upgrades all if omitted)')
|
|
1464
|
-
.option('--check', 'Check for updates without upgrading')
|
|
1465
|
-
.alias('up')
|
|
1466
|
-
.description('Upgrade MCP(s) from marketplaces')
|
|
1467
|
-
.action(async (name, options, command) => {
|
|
1468
|
-
try {
|
|
1469
|
-
const { formatOutput, printInfo, printSuccess, printWarning, printError, STATUS } = await import('./cli-formatter.js');
|
|
1470
|
-
// Get working directory from global options
|
|
1471
|
-
const workingDir = command.parent?.opts().dir || DEFAULT_WORKING_DIR;
|
|
1472
|
-
const { VersionChecker } = await import('./version-checker.js');
|
|
1473
|
-
const checker = new VersionChecker();
|
|
1474
|
-
await checker.initialize();
|
|
1475
|
-
if (name) {
|
|
1476
|
-
// Upgrade single MCP
|
|
1477
|
-
const filePath = await resolvePhotonPath(name, workingDir);
|
|
1478
|
-
if (!filePath) {
|
|
1479
|
-
printError(`MCP not found: ${name}`);
|
|
1480
|
-
printInfo(`Searched in: ${workingDir}`);
|
|
1481
|
-
process.exit(1);
|
|
1482
|
-
}
|
|
1483
|
-
printInfo(`Checking ${name} for updates...`);
|
|
1484
|
-
const versionInfo = await checker.checkForUpdate(name, filePath);
|
|
1485
|
-
if (!versionInfo.local) {
|
|
1486
|
-
printWarning('Could not determine local version');
|
|
1487
|
-
return;
|
|
1488
|
-
}
|
|
1489
|
-
if (!versionInfo.remote) {
|
|
1490
|
-
printWarning('Not found in any marketplace. This might be a local-only MCP.');
|
|
1491
|
-
return;
|
|
1492
|
-
}
|
|
1493
|
-
if (options.check) {
|
|
1494
|
-
const tableData = [{
|
|
1495
|
-
name,
|
|
1496
|
-
local: versionInfo.local,
|
|
1497
|
-
remote: versionInfo.remote,
|
|
1498
|
-
status: versionInfo.needsUpdate ? STATUS.UPDATE : STATUS.OK,
|
|
1499
|
-
}];
|
|
1500
|
-
formatOutput(tableData, 'table');
|
|
1501
|
-
return;
|
|
1502
|
-
}
|
|
1503
|
-
if (!versionInfo.needsUpdate) {
|
|
1504
|
-
printSuccess(`Already up to date (${versionInfo.local})`);
|
|
1505
|
-
return;
|
|
1506
|
-
}
|
|
1507
|
-
printInfo(`Upgrading ${name}: ${versionInfo.local} → ${versionInfo.remote}`);
|
|
1508
|
-
const success = await checker.updateMCP(name, filePath);
|
|
1509
|
-
if (success) {
|
|
1510
|
-
printSuccess(`Successfully upgraded ${name} to ${versionInfo.remote}`);
|
|
1511
|
-
}
|
|
1512
|
-
else {
|
|
1513
|
-
printError(`Failed to upgrade ${name}`);
|
|
1514
|
-
process.exit(1);
|
|
1515
|
-
}
|
|
1516
|
-
}
|
|
1517
|
-
else {
|
|
1518
|
-
// Check/upgrade all MCPs
|
|
1519
|
-
printInfo(`Checking all MCPs in ${workingDir}...\n`);
|
|
1520
|
-
const updates = await checker.checkAllUpdates(workingDir);
|
|
1521
|
-
if (updates.size === 0) {
|
|
1522
|
-
printInfo('No MCPs found');
|
|
1523
|
-
return;
|
|
1524
|
-
}
|
|
1525
|
-
const needsUpdate = [];
|
|
1526
|
-
// Build table data
|
|
1527
|
-
const tableData = [];
|
|
1528
|
-
for (const [mcpName, info] of updates) {
|
|
1529
|
-
if (info.needsUpdate) {
|
|
1530
|
-
needsUpdate.push(mcpName);
|
|
1531
|
-
tableData.push({
|
|
1532
|
-
name: mcpName,
|
|
1533
|
-
local: info.local || '-',
|
|
1534
|
-
remote: info.remote || '-',
|
|
1535
|
-
status: STATUS.UPDATE,
|
|
1536
|
-
});
|
|
1537
|
-
}
|
|
1538
|
-
else if (info.local && info.remote) {
|
|
1539
|
-
tableData.push({
|
|
1540
|
-
name: mcpName,
|
|
1541
|
-
local: info.local,
|
|
1542
|
-
remote: info.remote,
|
|
1543
|
-
status: STATUS.OK,
|
|
1544
|
-
});
|
|
1545
|
-
}
|
|
1546
|
-
else {
|
|
1547
|
-
tableData.push({
|
|
1548
|
-
name: mcpName,
|
|
1549
|
-
local: info.local || '-',
|
|
1550
|
-
remote: info.remote || 'local only',
|
|
1551
|
-
status: STATUS.UNKNOWN,
|
|
1552
|
-
});
|
|
1553
|
-
}
|
|
1554
|
-
}
|
|
1555
|
-
formatOutput(tableData, 'table');
|
|
1556
|
-
if (needsUpdate.length === 0) {
|
|
1557
|
-
console.log('');
|
|
1558
|
-
printSuccess('All MCPs are up to date!');
|
|
1559
|
-
return;
|
|
1560
|
-
}
|
|
1561
|
-
if (options.check) {
|
|
1562
|
-
console.log('');
|
|
1563
|
-
printInfo(`${needsUpdate.length} MCP(s) have updates available`);
|
|
1564
|
-
printInfo(`Run 'photon upgrade' to upgrade all`);
|
|
1565
|
-
return;
|
|
1566
|
-
}
|
|
1567
|
-
// Upgrade all that need updates
|
|
1568
|
-
console.log('');
|
|
1569
|
-
printInfo(`Upgrading ${needsUpdate.length} MCP(s)...`);
|
|
1570
|
-
for (const mcpName of needsUpdate) {
|
|
1571
|
-
const filePath = path.join(workingDir, `${mcpName}.photon.ts`);
|
|
1572
|
-
const success = await checker.updateMCP(mcpName, filePath);
|
|
1573
|
-
if (success) {
|
|
1574
|
-
printSuccess(`Upgraded ${mcpName}`);
|
|
1575
|
-
}
|
|
1576
|
-
else {
|
|
1577
|
-
printError(`Failed to upgrade ${mcpName}`);
|
|
1578
|
-
}
|
|
1579
|
-
}
|
|
1580
|
-
}
|
|
1581
|
-
}
|
|
1582
|
-
catch (error) {
|
|
1583
|
-
const { printError } = await import('./cli-formatter.js');
|
|
1584
|
-
printError(error.message);
|
|
1585
|
-
process.exit(1);
|
|
1586
|
-
}
|
|
1587
|
-
});
|
|
1588
|
-
// Clear-cache command: clear compiled photon cache
|
|
1589
|
-
program
|
|
1590
|
-
.command('clear-cache', { hidden: true })
|
|
1591
|
-
.argument('[name]', 'MCP name to clear cache for (clears all if omitted)')
|
|
1592
|
-
.alias('clean')
|
|
1593
|
-
.description('Clear compiled photon cache')
|
|
1594
|
-
.action(async (name, options, command) => {
|
|
1595
|
-
try {
|
|
1596
|
-
const { printInfo, printSuccess, printError } = await import('./cli-formatter.js');
|
|
1597
|
-
const cacheDir = path.join(os.homedir(), '.cache', 'photon-mcp', 'compiled');
|
|
1598
|
-
if (name) {
|
|
1599
|
-
// Clear cache for specific photon
|
|
1600
|
-
const workingDir = command.parent?.opts().dir || DEFAULT_WORKING_DIR;
|
|
1601
|
-
const filePath = await resolvePhotonPath(name, workingDir);
|
|
1602
|
-
if (!filePath) {
|
|
1603
|
-
printError(`Photon not found: ${name}`);
|
|
1604
|
-
printInfo(`Tip: Use 'photon info' to see installed photons`);
|
|
1605
|
-
process.exit(1);
|
|
1606
|
-
}
|
|
1607
|
-
printInfo(`Clearing cache for ${name}...`);
|
|
1608
|
-
const cachedFiles = [
|
|
1609
|
-
path.join(cacheDir, `${name}.js`),
|
|
1610
|
-
path.join(cacheDir, `${name}.js.map`),
|
|
1611
|
-
];
|
|
1612
|
-
let cleared = false;
|
|
1613
|
-
for (const cachedFile of cachedFiles) {
|
|
1614
|
-
try {
|
|
1615
|
-
await fs.unlink(cachedFile);
|
|
1616
|
-
cleared = true;
|
|
1617
|
-
}
|
|
1618
|
-
catch {
|
|
1619
|
-
// Ignore if file doesn't exist
|
|
1620
|
-
}
|
|
1621
|
-
}
|
|
1622
|
-
if (cleared) {
|
|
1623
|
-
printSuccess(`Cleared cache for ${name}`);
|
|
1624
|
-
}
|
|
1625
|
-
else {
|
|
1626
|
-
printInfo(`No cache found for ${name}`);
|
|
1627
|
-
}
|
|
1628
|
-
}
|
|
1629
|
-
else {
|
|
1630
|
-
// Clear all cache
|
|
1631
|
-
printInfo('Clearing all compiled photon cache...');
|
|
1519
|
+
console.error(`📦 Found ${photonFiles.length} photons\n`);
|
|
1520
|
+
for (const file of photonFiles) {
|
|
1521
|
+
const photonPath = path.join(dirPath, file);
|
|
1522
|
+
const name = file.replace('.photon.ts', '');
|
|
1632
1523
|
try {
|
|
1633
|
-
const
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
count++;
|
|
1640
|
-
}
|
|
1641
|
-
catch {
|
|
1642
|
-
// Ignore errors
|
|
1643
|
-
}
|
|
1644
|
-
}
|
|
1645
|
-
if (count > 0) {
|
|
1646
|
-
printSuccess(`Cleared ${count} cached file(s)`);
|
|
1647
|
-
}
|
|
1648
|
-
else {
|
|
1649
|
-
printInfo(`Cache is already empty`);
|
|
1650
|
-
}
|
|
1524
|
+
const extractor = new PhotonDocExtractor(photonPath);
|
|
1525
|
+
const diagram = await extractor.generateDiagram();
|
|
1526
|
+
console.log(`## ${name}\n`);
|
|
1527
|
+
console.log('```mermaid');
|
|
1528
|
+
console.log(diagram);
|
|
1529
|
+
console.log('```\n');
|
|
1651
1530
|
}
|
|
1652
|
-
catch (
|
|
1653
|
-
|
|
1654
|
-
printInfo(`No cache directory found`);
|
|
1655
|
-
}
|
|
1656
|
-
else {
|
|
1657
|
-
throw error;
|
|
1658
|
-
}
|
|
1531
|
+
catch (err) {
|
|
1532
|
+
console.error(`⚠️ Failed to generate diagram for ${name}: ${err.message}`);
|
|
1659
1533
|
}
|
|
1660
1534
|
}
|
|
1661
1535
|
}
|
|
1662
1536
|
catch (error) {
|
|
1663
|
-
|
|
1664
|
-
|
|
1537
|
+
logger.error(`Error: ${getErrorMessage(error)}`);
|
|
1538
|
+
if (process.env.DEBUG && error instanceof Error) {
|
|
1539
|
+
console.error(error.stack);
|
|
1540
|
+
}
|
|
1665
1541
|
process.exit(1);
|
|
1666
1542
|
}
|
|
1667
1543
|
});
|
|
1544
|
+
// Register marketplace commands (list, add, remove, enable, disable)
|
|
1545
|
+
registerMarketplaceCommands(program);
|
|
1546
|
+
// Register info command
|
|
1547
|
+
registerInfoCommand(program, DEFAULT_WORKING_DIR);
|
|
1548
|
+
// Register package management commands
|
|
1549
|
+
registerPackageCommands(program, DEFAULT_WORKING_DIR);
|
|
1550
|
+
// Register package-app command (cross-platform PWA launchers)
|
|
1551
|
+
registerPackageAppCommand(program, DEFAULT_WORKING_DIR);
|
|
1668
1552
|
// Doctor command: diagnose photon environment
|
|
1669
1553
|
program
|
|
1670
|
-
.command('doctor'
|
|
1554
|
+
.command('doctor')
|
|
1671
1555
|
.argument('[name]', 'Photon name to diagnose (checks environment if omitted)')
|
|
1672
|
-
.description('Run diagnostics on photon environment and
|
|
1556
|
+
.description('Run diagnostics on photon environment, ports, and configuration')
|
|
1557
|
+
.option('--port <number>', 'Port to check for availability', '3000')
|
|
1673
1558
|
.action(async (name, options, command) => {
|
|
1674
1559
|
try {
|
|
1675
|
-
const { formatOutput, printInfo, printSuccess, printWarning, STATUS } = await import('./cli-formatter.js');
|
|
1560
|
+
const { formatOutput, printHeader, printInfo, printSuccess, printWarning, STATUS } = await import('./cli-formatter.js');
|
|
1676
1561
|
const workingDir = command.parent?.opts().dir || DEFAULT_WORKING_DIR;
|
|
1677
|
-
let issuesFound = 0;
|
|
1678
|
-
printInfo('Running Photon diagnostics...\n');
|
|
1679
|
-
// Build diagnostic data
|
|
1680
1562
|
const diagnostics = {};
|
|
1681
|
-
|
|
1563
|
+
const suggestions = [];
|
|
1564
|
+
let issuesFound = 0;
|
|
1565
|
+
printHeader('Photon Doctor');
|
|
1566
|
+
printInfo('Running environment checks...\n');
|
|
1567
|
+
// Node runtime
|
|
1682
1568
|
const nodeVersion = process.version;
|
|
1683
1569
|
const majorVersion = parseInt(nodeVersion.slice(1).split('.')[0]);
|
|
1684
1570
|
diagnostics['Node.js'] = {
|
|
1685
1571
|
version: nodeVersion,
|
|
1686
1572
|
status: majorVersion >= 18 ? STATUS.OK : STATUS.ERROR,
|
|
1687
|
-
note: majorVersion >= 18 ? 'supported' : 'requires Node.js 18+',
|
|
1688
1573
|
};
|
|
1689
|
-
if (majorVersion < 18)
|
|
1574
|
+
if (majorVersion < 18) {
|
|
1690
1575
|
issuesFound++;
|
|
1691
|
-
|
|
1576
|
+
suggestions.push('Upgrade to Node.js 18+ (https://nodejs.org).');
|
|
1577
|
+
}
|
|
1578
|
+
// npm availability
|
|
1692
1579
|
try {
|
|
1693
1580
|
const { execSync } = await import('child_process');
|
|
1694
1581
|
const npmVersion = execSync('npm --version', { encoding: 'utf-8' }).trim();
|
|
1695
|
-
diagnostics['Package
|
|
1696
|
-
npm: npmVersion,
|
|
1697
|
-
status: STATUS.OK,
|
|
1698
|
-
};
|
|
1582
|
+
diagnostics['Package manager'] = { npm: npmVersion, status: STATUS.OK };
|
|
1699
1583
|
}
|
|
1700
1584
|
catch {
|
|
1701
|
-
diagnostics['Package
|
|
1702
|
-
npm: 'not found',
|
|
1703
|
-
status: STATUS.ERROR,
|
|
1704
|
-
};
|
|
1585
|
+
diagnostics['Package manager'] = { npm: 'not found', status: STATUS.ERROR };
|
|
1705
1586
|
issuesFound++;
|
|
1587
|
+
suggestions.push('Install npm / npx so Photon can install dependencies.');
|
|
1706
1588
|
}
|
|
1707
|
-
//
|
|
1589
|
+
// Working directory health
|
|
1708
1590
|
try {
|
|
1709
1591
|
await fs.access(workingDir);
|
|
1710
1592
|
const stats = await fs.stat(workingDir);
|
|
1711
1593
|
if (stats.isDirectory()) {
|
|
1712
1594
|
const mcps = await listPhotonMCPs(workingDir);
|
|
1713
|
-
diagnostics['Working
|
|
1595
|
+
diagnostics['Working directory'] = {
|
|
1714
1596
|
path: workingDir,
|
|
1715
1597
|
status: STATUS.OK,
|
|
1716
1598
|
photons: mcps.length,
|
|
1717
1599
|
};
|
|
1718
1600
|
}
|
|
1719
1601
|
else {
|
|
1720
|
-
diagnostics['Working
|
|
1602
|
+
diagnostics['Working directory'] = {
|
|
1721
1603
|
path: workingDir,
|
|
1722
1604
|
status: STATUS.ERROR,
|
|
1723
|
-
note: '
|
|
1605
|
+
note: 'Not a directory',
|
|
1724
1606
|
};
|
|
1725
1607
|
issuesFound++;
|
|
1608
|
+
suggestions.push(`Fix working directory: rm ${workingDir} && mkdir -p ${workingDir}`);
|
|
1726
1609
|
}
|
|
1727
1610
|
}
|
|
1728
1611
|
catch {
|
|
1729
|
-
diagnostics['Working
|
|
1612
|
+
diagnostics['Working directory'] = {
|
|
1730
1613
|
path: workingDir,
|
|
1731
|
-
status: STATUS.
|
|
1732
|
-
note: '
|
|
1614
|
+
status: STATUS.WARN,
|
|
1615
|
+
note: 'Will be created on first use',
|
|
1733
1616
|
};
|
|
1734
1617
|
}
|
|
1735
|
-
//
|
|
1618
|
+
// Cache directory insight
|
|
1736
1619
|
const cacheDir = path.join(os.homedir(), '.cache', 'photon-mcp', 'compiled');
|
|
1737
1620
|
try {
|
|
1738
1621
|
await fs.access(cacheDir);
|
|
1739
1622
|
const files = await fs.readdir(cacheDir);
|
|
1740
|
-
diagnostics['Cache
|
|
1623
|
+
diagnostics['Cache directory'] = {
|
|
1741
1624
|
path: cacheDir,
|
|
1742
1625
|
status: STATUS.OK,
|
|
1743
1626
|
cachedFiles: files.length,
|
|
1744
1627
|
};
|
|
1745
1628
|
}
|
|
1746
1629
|
catch {
|
|
1747
|
-
diagnostics['Cache
|
|
1630
|
+
diagnostics['Cache directory'] = {
|
|
1748
1631
|
path: cacheDir,
|
|
1749
1632
|
status: STATUS.UNKNOWN,
|
|
1750
|
-
note: '
|
|
1633
|
+
note: 'Created on demand',
|
|
1751
1634
|
};
|
|
1752
1635
|
}
|
|
1753
|
-
//
|
|
1754
|
-
|
|
1636
|
+
// Port availability
|
|
1637
|
+
const port = parseInt(options.port, 10);
|
|
1638
|
+
const available = await isPortAvailable(port);
|
|
1639
|
+
diagnostics['Ports'] = {
|
|
1640
|
+
port,
|
|
1641
|
+
status: available ? STATUS.OK : STATUS.ERROR,
|
|
1642
|
+
note: available ? 'Available' : 'In use by another process',
|
|
1643
|
+
};
|
|
1644
|
+
if (!available) {
|
|
1645
|
+
issuesFound++;
|
|
1646
|
+
suggestions.push(`Port ${port} is busy. Run Photon with '--port ${port + 1}' or stop the conflicting service.`);
|
|
1647
|
+
}
|
|
1648
|
+
// Marketplace configuration
|
|
1755
1649
|
try {
|
|
1756
1650
|
const { MarketplaceManager } = await import('./marketplace-manager.js');
|
|
1757
|
-
manager = new MarketplaceManager();
|
|
1651
|
+
const manager = new MarketplaceManager();
|
|
1758
1652
|
await manager.initialize();
|
|
1759
|
-
const
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
enabled: enabled.length,
|
|
1767
|
-
status: STATUS.WARN,
|
|
1768
|
-
sources: enabled.map((m) => m.name),
|
|
1769
|
-
conflicts: `${conflicts.size} photon(s) in multiple marketplaces`,
|
|
1770
|
-
};
|
|
1771
|
-
issuesFound++;
|
|
1772
|
-
}
|
|
1773
|
-
else {
|
|
1774
|
-
diagnostics['Marketplaces'] = {
|
|
1775
|
-
enabled: enabled.length,
|
|
1776
|
-
status: STATUS.OK,
|
|
1777
|
-
sources: enabled.map((m) => m.name),
|
|
1778
|
-
};
|
|
1779
|
-
}
|
|
1653
|
+
const enabled = manager.getAll().filter((m) => m.enabled);
|
|
1654
|
+
if (enabled.length === 0) {
|
|
1655
|
+
diagnostics['Marketplaces'] = {
|
|
1656
|
+
status: STATUS.WARN,
|
|
1657
|
+
note: 'No marketplaces configured. Add one with: photon marketplace add portel-dev/photons',
|
|
1658
|
+
};
|
|
1659
|
+
suggestions.push('Add at least one marketplace so you can install community photons.');
|
|
1780
1660
|
}
|
|
1781
1661
|
else {
|
|
1662
|
+
const conflicts = await manager.detectAllConflicts();
|
|
1782
1663
|
diagnostics['Marketplaces'] = {
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1664
|
+
status: conflicts.size > 0 ? STATUS.WARN : STATUS.OK,
|
|
1665
|
+
enabled: enabled.map((m) => m.name),
|
|
1666
|
+
conflicts: conflicts.size,
|
|
1786
1667
|
};
|
|
1668
|
+
if (conflicts.size > 0) {
|
|
1669
|
+
issuesFound++;
|
|
1670
|
+
suggestions.push('Resolve duplicate photons with: photon marketplace resolve');
|
|
1671
|
+
}
|
|
1787
1672
|
}
|
|
1788
1673
|
}
|
|
1789
1674
|
catch (error) {
|
|
1790
|
-
diagnostics['Marketplaces'] = {
|
|
1791
|
-
|
|
1792
|
-
error: error.message,
|
|
1793
|
-
};
|
|
1675
|
+
diagnostics['Marketplaces'] = { status: STATUS.ERROR, error: getErrorMessage(error) };
|
|
1676
|
+
suggestions.push('Marketplace config failed to load. Run photon marketplace list to debug.');
|
|
1794
1677
|
issuesFound++;
|
|
1795
1678
|
}
|
|
1796
|
-
//
|
|
1679
|
+
// Photon-specific checks
|
|
1797
1680
|
if (name) {
|
|
1798
|
-
const
|
|
1681
|
+
const photonSection = {};
|
|
1799
1682
|
const filePath = await resolvePhotonPath(name, workingDir);
|
|
1800
1683
|
if (!filePath) {
|
|
1801
|
-
|
|
1802
|
-
|
|
1684
|
+
photonSection.status = STATUS.ERROR;
|
|
1685
|
+
photonSection.note = `Not installed in ${workingDir}`;
|
|
1686
|
+
suggestions.push(`Install ${name} with: photon add ${name}`);
|
|
1803
1687
|
issuesFound++;
|
|
1804
1688
|
}
|
|
1805
1689
|
else {
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1690
|
+
photonSection.status = STATUS.OK;
|
|
1691
|
+
photonSection.path = filePath;
|
|
1692
|
+
const params = await extractConstructorParams(filePath);
|
|
1693
|
+
if (params.length > 0) {
|
|
1694
|
+
photonSection.environment = params.map((param) => {
|
|
1695
|
+
const envVar = toEnvVarName(name, param.name);
|
|
1696
|
+
const value = process.env[envVar];
|
|
1697
|
+
const ok = Boolean(value) || param.isOptional || param.hasDefault;
|
|
1698
|
+
if (!ok) {
|
|
1699
|
+
issuesFound++;
|
|
1700
|
+
suggestions.push(`Set ${envVar} for ${name} (e.g. export ${envVar}=value).`);
|
|
1701
|
+
}
|
|
1702
|
+
return {
|
|
1703
|
+
name: envVar,
|
|
1704
|
+
status: ok ? STATUS.OK : STATUS.ERROR,
|
|
1705
|
+
value: value
|
|
1706
|
+
? 'configured'
|
|
1707
|
+
: param.hasDefault
|
|
1708
|
+
? `default: ${formatDefaultValue(param.defaultValue)}`
|
|
1709
|
+
: 'missing',
|
|
1710
|
+
};
|
|
1711
|
+
});
|
|
1820
1712
|
}
|
|
1821
|
-
// Check cache
|
|
1822
1713
|
const cachedFile = path.join(cacheDir, `${name}.js`);
|
|
1823
1714
|
try {
|
|
1824
1715
|
await fs.access(cachedFile);
|
|
1825
|
-
|
|
1716
|
+
photonSection.cache = { status: STATUS.OK, note: 'Warm' };
|
|
1826
1717
|
}
|
|
1827
1718
|
catch {
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
const { DependencyManager } = await import('@portel/photon-core');
|
|
1833
|
-
const depManager = new DependencyManager();
|
|
1834
|
-
const dependencies = await depManager.extractDependencies(filePath);
|
|
1835
|
-
if (dependencies.length > 0) {
|
|
1836
|
-
photonDiag['dependencies'] = dependencies.map(dep => `${dep.name}@${dep.version}`);
|
|
1837
|
-
// Security audit
|
|
1838
|
-
try {
|
|
1839
|
-
const { SecurityScanner } = await import('./security-scanner.js');
|
|
1840
|
-
const scanner = new SecurityScanner();
|
|
1841
|
-
const depStrings = dependencies.map(d => `${d.name}@${d.version}`);
|
|
1842
|
-
const result = await scanner.auditMCP(name, depStrings);
|
|
1843
|
-
if (result.totalVulnerabilities > 0) {
|
|
1844
|
-
photonDiag['security'] = STATUS.ERROR;
|
|
1845
|
-
photonDiag['vulnerabilities'] = `${result.totalVulnerabilities} (${result.criticalCount} critical, ${result.highCount} high)`;
|
|
1846
|
-
issuesFound++;
|
|
1847
|
-
}
|
|
1848
|
-
else {
|
|
1849
|
-
photonDiag['security'] = STATUS.OK;
|
|
1850
|
-
}
|
|
1851
|
-
}
|
|
1852
|
-
catch {
|
|
1853
|
-
photonDiag['security'] = 'could not check';
|
|
1854
|
-
}
|
|
1855
|
-
}
|
|
1856
|
-
else {
|
|
1857
|
-
photonDiag['dependencies'] = 'none';
|
|
1858
|
-
photonDiag['security'] = STATUS.OK;
|
|
1859
|
-
}
|
|
1860
|
-
}
|
|
1861
|
-
catch (error) {
|
|
1862
|
-
photonDiag['dependencies'] = `error: ${error.message}`;
|
|
1719
|
+
photonSection.cache = {
|
|
1720
|
+
status: STATUS.WARN,
|
|
1721
|
+
note: 'Not compiled yet (first run will compile)',
|
|
1722
|
+
};
|
|
1863
1723
|
}
|
|
1864
1724
|
}
|
|
1865
|
-
diagnostics[`Photon: ${name}`] =
|
|
1725
|
+
diagnostics[`Photon: ${name}`] = photonSection;
|
|
1866
1726
|
}
|
|
1867
1727
|
formatOutput(diagnostics, 'tree');
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1728
|
+
if (suggestions.length > 0) {
|
|
1729
|
+
printHeader('Suggested fixes');
|
|
1730
|
+
suggestions.forEach((tip, idx) => console.log(` ${idx + 1}. ${tip}`));
|
|
1731
|
+
}
|
|
1732
|
+
if (issuesFound === 0 && suggestions.length === 0) {
|
|
1733
|
+
printSuccess('\nAll checks passed!');
|
|
1872
1734
|
}
|
|
1873
1735
|
else {
|
|
1874
|
-
printWarning(
|
|
1875
|
-
process.exit(1);
|
|
1736
|
+
printWarning(`\nDetected ${issuesFound || suggestions.length} potential issue(s).`);
|
|
1876
1737
|
}
|
|
1877
1738
|
}
|
|
1878
1739
|
catch (error) {
|
|
1879
1740
|
const { printError } = await import('./cli-formatter.js');
|
|
1880
|
-
printError(error
|
|
1741
|
+
printError(getErrorMessage(error));
|
|
1881
1742
|
process.exit(1);
|
|
1882
1743
|
}
|
|
1883
1744
|
});
|
|
1884
1745
|
// CLI command: directly invoke photon methods
|
|
1746
|
+
// Also serves as escape hatch for photons with reserved names (e.g., photon cli list get)
|
|
1885
1747
|
program
|
|
1886
|
-
.command('cli <photon> [method] [args...]'
|
|
1887
|
-
.description('Run photon methods
|
|
1748
|
+
.command('cli <photon> [method] [args...]')
|
|
1749
|
+
.description('Run photon methods from command line (escape hatch for reserved names)')
|
|
1888
1750
|
.allowUnknownOption()
|
|
1889
1751
|
.helpOption(false) // Disable default help so we can handle it ourselves
|
|
1890
1752
|
.action(async (photon, method, args) => {
|
|
1891
1753
|
// Handle help flag
|
|
1892
1754
|
if (photon === '--help' || photon === '-h') {
|
|
1893
1755
|
console.log(`USAGE:
|
|
1894
|
-
photon
|
|
1756
|
+
photon <photon-name> [method] [args...]
|
|
1757
|
+
photon cli <photon-name> [method] [args...] (explicit form)
|
|
1895
1758
|
|
|
1896
1759
|
DESCRIPTION:
|
|
1897
1760
|
Run photon methods directly from the command line. Photons provide
|
|
1898
1761
|
a CLI interface automatically based on their exported methods.
|
|
1899
1762
|
|
|
1763
|
+
The 'cli' command is optional - you can run photons directly:
|
|
1764
|
+
photon lg-remote volume +5 (implicit)
|
|
1765
|
+
photon cli lg-remote volume +5 (explicit)
|
|
1766
|
+
|
|
1767
|
+
Use 'photon cli' explicitly when your photon name conflicts with
|
|
1768
|
+
a reserved command (serve, beam, list, init, etc.)
|
|
1769
|
+
|
|
1900
1770
|
EXAMPLES:
|
|
1901
1771
|
# List all methods for a photon
|
|
1902
|
-
photon
|
|
1772
|
+
photon lg-remote
|
|
1903
1773
|
|
|
1904
1774
|
# Call a method with no parameters
|
|
1905
|
-
photon
|
|
1775
|
+
photon lg-remote status
|
|
1906
1776
|
|
|
1907
1777
|
# Call a method with parameters
|
|
1908
|
-
photon
|
|
1909
|
-
photon
|
|
1910
|
-
photon
|
|
1778
|
+
photon lg-remote volume 50
|
|
1779
|
+
photon lg-remote volume +5
|
|
1780
|
+
photon spotify play
|
|
1911
1781
|
|
|
1912
1782
|
# Get method-specific help
|
|
1913
|
-
photon
|
|
1783
|
+
photon lg-remote volume --help
|
|
1914
1784
|
|
|
1915
1785
|
# Output raw JSON instead of formatted text
|
|
1916
|
-
photon
|
|
1786
|
+
photon lg-remote status --json
|
|
1787
|
+
|
|
1788
|
+
# Escape hatch for reserved-name photons
|
|
1789
|
+
photon cli list get (photon named "list", method "get")
|
|
1790
|
+
photon cli serve status (photon named "serve", method "status")
|
|
1917
1791
|
|
|
1918
1792
|
SEE ALSO:
|
|
1919
|
-
photon
|
|
1793
|
+
photon list List all installed photons
|
|
1920
1794
|
photon add <name> Install a photon from marketplace
|
|
1921
|
-
photon alias Create CLI shortcuts for photons
|
|
1922
1795
|
`);
|
|
1923
1796
|
return;
|
|
1924
1797
|
}
|
|
@@ -1957,13 +1830,124 @@ program
|
|
|
1957
1830
|
const { listAliases } = await import('./cli-alias.js');
|
|
1958
1831
|
await listAliases();
|
|
1959
1832
|
});
|
|
1833
|
+
// Test command: run tests for photons
|
|
1834
|
+
program
|
|
1835
|
+
.command('test')
|
|
1836
|
+
.argument('[photon]', 'Photon to test (tests all if omitted)')
|
|
1837
|
+
.argument('[test]', 'Specific test to run')
|
|
1838
|
+
.option('--json', 'Output results as JSON')
|
|
1839
|
+
.option('--mode <mode>', 'Test mode: direct (unit), cli (integration via CLI), mcp (integration via MCP), all', 'direct')
|
|
1840
|
+
.description('Run test methods in photons')
|
|
1841
|
+
.action(async (photon, test, options) => {
|
|
1842
|
+
try {
|
|
1843
|
+
const workingDir = program.opts().dir || DEFAULT_WORKING_DIR;
|
|
1844
|
+
const { runTests } = await import('./test-runner.js');
|
|
1845
|
+
// Validate mode
|
|
1846
|
+
const validModes = ['direct', 'cli', 'mcp', 'all'];
|
|
1847
|
+
if (!validModes.includes(options.mode)) {
|
|
1848
|
+
logger.error(`Invalid mode: ${options.mode}. Valid modes: ${validModes.join(', ')}`);
|
|
1849
|
+
process.exit(1);
|
|
1850
|
+
}
|
|
1851
|
+
const summary = await runTests(workingDir, photon, test, {
|
|
1852
|
+
json: options.json,
|
|
1853
|
+
mode: options.mode,
|
|
1854
|
+
});
|
|
1855
|
+
// Exit with error code if any tests failed
|
|
1856
|
+
if (summary.failed > 0) {
|
|
1857
|
+
process.exit(1);
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
catch (error) {
|
|
1861
|
+
logger.error(`Error: ${getErrorMessage(error)}`);
|
|
1862
|
+
process.exit(1);
|
|
1863
|
+
}
|
|
1864
|
+
});
|
|
1865
|
+
// Reserved commands that should NOT be treated as photon names
|
|
1866
|
+
// Reserved commands that should NOT be treated as photon names
|
|
1867
|
+
// If first arg is not in this list, it's assumed to be a photon name (implicit CLI mode)
|
|
1868
|
+
const RESERVED_COMMANDS = [
|
|
1869
|
+
// Core commands
|
|
1870
|
+
'serve',
|
|
1871
|
+
'sse',
|
|
1872
|
+
'beam',
|
|
1873
|
+
'list',
|
|
1874
|
+
'ls',
|
|
1875
|
+
'info',
|
|
1876
|
+
'test',
|
|
1877
|
+
// Photon management
|
|
1878
|
+
'new',
|
|
1879
|
+
'init',
|
|
1880
|
+
'validate',
|
|
1881
|
+
'sync',
|
|
1882
|
+
'add',
|
|
1883
|
+
'remove',
|
|
1884
|
+
'rm',
|
|
1885
|
+
// Maintenance
|
|
1886
|
+
'upgrade',
|
|
1887
|
+
'up',
|
|
1888
|
+
'update',
|
|
1889
|
+
'doctor',
|
|
1890
|
+
'clear-cache',
|
|
1891
|
+
'clean',
|
|
1892
|
+
// Aliases
|
|
1893
|
+
'cli',
|
|
1894
|
+
'alias',
|
|
1895
|
+
'unalias',
|
|
1896
|
+
'aliases',
|
|
1897
|
+
// Marketplace
|
|
1898
|
+
'marketplace',
|
|
1899
|
+
// Packaging
|
|
1900
|
+
'package',
|
|
1901
|
+
// Hidden/advanced
|
|
1902
|
+
'mcp',
|
|
1903
|
+
'search',
|
|
1904
|
+
'maker',
|
|
1905
|
+
'host',
|
|
1906
|
+
'diagram',
|
|
1907
|
+
'diagrams',
|
|
1908
|
+
'enable',
|
|
1909
|
+
'disable',
|
|
1910
|
+
// Help/version (handled by commander)
|
|
1911
|
+
'help',
|
|
1912
|
+
'--help',
|
|
1913
|
+
'-h',
|
|
1914
|
+
'version',
|
|
1915
|
+
'--version',
|
|
1916
|
+
'-V',
|
|
1917
|
+
];
|
|
1960
1918
|
// All known commands for "did you mean" suggestions
|
|
1961
1919
|
const knownCommands = [
|
|
1962
|
-
'
|
|
1963
|
-
'
|
|
1964
|
-
'
|
|
1965
|
-
'
|
|
1966
|
-
'
|
|
1920
|
+
'serve',
|
|
1921
|
+
'sse',
|
|
1922
|
+
'beam',
|
|
1923
|
+
'list',
|
|
1924
|
+
'ls',
|
|
1925
|
+
'info',
|
|
1926
|
+
'test',
|
|
1927
|
+
'new',
|
|
1928
|
+
'init',
|
|
1929
|
+
'validate',
|
|
1930
|
+
'sync',
|
|
1931
|
+
'add',
|
|
1932
|
+
'remove',
|
|
1933
|
+
'rm',
|
|
1934
|
+
'upgrade',
|
|
1935
|
+
'up',
|
|
1936
|
+
'update',
|
|
1937
|
+
'clear-cache',
|
|
1938
|
+
'clean',
|
|
1939
|
+
'doctor',
|
|
1940
|
+
'cli',
|
|
1941
|
+
'alias',
|
|
1942
|
+
'unalias',
|
|
1943
|
+
'aliases',
|
|
1944
|
+
'mcp',
|
|
1945
|
+
'search',
|
|
1946
|
+
'marketplace',
|
|
1947
|
+
'maker',
|
|
1948
|
+
'host',
|
|
1949
|
+
'diagram',
|
|
1950
|
+
'diagrams',
|
|
1967
1951
|
];
|
|
1968
1952
|
const knownSubcommands = {
|
|
1969
1953
|
marketplace: ['list', 'add', 'remove', 'enable', 'disable'],
|
|
@@ -2019,7 +2003,7 @@ program.on('command:*', async (operands) => {
|
|
|
2019
2003
|
printError(`Unknown command: ${unknownCommand}`);
|
|
2020
2004
|
// Check if it's a subcommand typo for a known parent
|
|
2021
2005
|
const args = process.argv.slice(2);
|
|
2022
|
-
const parentIndex = args.findIndex(arg => knownSubcommands[arg]);
|
|
2006
|
+
const parentIndex = args.findIndex((arg) => knownSubcommands[arg]);
|
|
2023
2007
|
if (parentIndex !== -1 && parentIndex < args.indexOf(unknownCommand)) {
|
|
2024
2008
|
const parent = args[parentIndex];
|
|
2025
2009
|
const suggestion = findClosestCommand(unknownCommand, knownSubcommands[parent]);
|
|
@@ -2038,7 +2022,48 @@ program.on('command:*', async (operands) => {
|
|
|
2038
2022
|
printInfo(`Run 'photon --help' for usage`);
|
|
2039
2023
|
process.exit(1);
|
|
2040
2024
|
});
|
|
2041
|
-
|
|
2025
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
2026
|
+
// IMPLICIT CLI MODE
|
|
2027
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
2028
|
+
// If the first argument is not a reserved command, treat it as a photon name
|
|
2029
|
+
// This enables: `photon lg-remote volume +5` instead of `photon cli lg-remote volume +5`
|
|
2030
|
+
function preprocessArgs() {
|
|
2031
|
+
const args = process.argv.slice(2);
|
|
2032
|
+
// No args - default to beam with auto-open browser
|
|
2033
|
+
if (args.length === 0) {
|
|
2034
|
+
return [...process.argv, 'beam', '--open'];
|
|
2035
|
+
}
|
|
2036
|
+
// Find the first non-flag argument (skip values of flags that take a parameter)
|
|
2037
|
+
const flagsWithValues = ['--dir', '--log-level'];
|
|
2038
|
+
const firstArgIndex = args.findIndex((arg, i) => {
|
|
2039
|
+
if (arg.startsWith('-'))
|
|
2040
|
+
return false;
|
|
2041
|
+
// Skip values of preceding flags (e.g., "." in "--dir .")
|
|
2042
|
+
if (i > 0 && flagsWithValues.includes(args[i - 1]))
|
|
2043
|
+
return false;
|
|
2044
|
+
return true;
|
|
2045
|
+
});
|
|
2046
|
+
if (firstArgIndex === -1) {
|
|
2047
|
+
// No subcommand — only flags present (e.g., --dir=. --log-level debug)
|
|
2048
|
+
// photon --help / -h / --version / -V → show program help/version
|
|
2049
|
+
if (args.some((a) => a === '--help' || a === '-h' || a === '--version' || a === '-V')) {
|
|
2050
|
+
return process.argv;
|
|
2051
|
+
}
|
|
2052
|
+
// Otherwise default to beam (e.g., photon --dir=. → photon --dir=. beam --open)
|
|
2053
|
+
return [...process.argv, 'beam', '--open'];
|
|
2054
|
+
}
|
|
2055
|
+
const firstArg = args[firstArgIndex];
|
|
2056
|
+
// If first arg is a reserved command, let commander handle normally
|
|
2057
|
+
if (RESERVED_COMMANDS.includes(firstArg)) {
|
|
2058
|
+
return process.argv;
|
|
2059
|
+
}
|
|
2060
|
+
// First arg looks like a photon name - inject 'cli' command
|
|
2061
|
+
// photon lg-remote volume +5 → photon cli lg-remote volume +5
|
|
2062
|
+
const newArgs = [...process.argv];
|
|
2063
|
+
newArgs.splice(2 + firstArgIndex, 0, 'cli');
|
|
2064
|
+
return newArgs;
|
|
2065
|
+
}
|
|
2066
|
+
program.parse(preprocessArgs());
|
|
2042
2067
|
/**
|
|
2043
2068
|
* Inline template fallback
|
|
2044
2069
|
*/
|