mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-27 15:13:37 +08:00
feat(desktop): seed cc-haha-builtin marketplace into Electron package (#50)
* feat(desktop): seed cc-haha-builtin marketplace into Electron package
Wires the missing desktop side of the plugin-seed mechanism. The CLI
side (src/utils/plugins/{pluginDirectories,marketplaceManager}.ts) has
been on main for a while and exposes `getPluginSeedDirs()` +
`registerSeedMarketplaces()` reading `CLAUDE_CODE_PLUGIN_SEED_DIR`. But
the desktop side never set that env var or copied any seed into the
Electron package, so:
- v0.5.11 packaged users see whatever marketplaces they manually
registered (typically just `reverse-engineering` from a one-off
`plugin marketplace add ./plugins`).
- The image-gen plugin added in PR #49 was invisible after install
because no mechanism shipped it inside the Electron bundle.
What this commit adds
- `desktop/scripts/build-plugin-seed.ts` (new)
- Mirrors the repo's `plugins/` directory into
`desktop/plugin-seed/marketplaces/cc-haha-builtin/`.
- Generates `desktop/plugin-seed/known_marketplaces.json` with a
placeholder `installLocation`. registerSeedMarketplaces() recomputes
the real path at runtime via findSeedMarketplaceLocation(), so the
placeholder is intentional — handles cases like multi-stage Docker
builds where the seed lives at a different path than where it was
built.
- `desktop/package.json`
- Adds `build:plugin-seed` script.
- Prepends it to `electron:dev` and `electron:build` so the seed is
always fresh before the app starts (or is packaged).
- Adds `plugin-seed/**` to electron-builder `files` and `asarUnpack`
so the seed dir ships inside the .app/.exe but stays outside the
asar archive (CC reads files from disk, not from asar).
- `desktop/electron/services/sidecarManager.ts`
- Threads `desktopRoot` through `buildSidecarEnv(...)` and adds
`CLAUDE_CODE_PLUGIN_SEED_DIR=<desktopRoot>/plugin-seed` so the
spawned server sidecar finds the seed.
- `src/server/index.ts`
- Calls `registerSeedMarketplaces()` at server boot so the seeded
marketplace appears in `~/.claude/plugins/known_marketplaces.json`
and the desktop's Settings → Plugins page picks it up.
- Fire-and-forget (`void ...`) rather than `await`, since
`startServer` is sync and the registration takes ~ms; the seeded
marketplace appears in the UI within 1-2 seconds of server boot.
(The original drafted version used `await` inside a sync function,
which doesn't compile — fixed in this commit.)
- `.gitignore` — excludes `desktop/plugin-seed/` from version control
(regenerated on every electron build, never committed).
End-to-end flow after this lands
build time:
bun run electron:build
→ bun run build:plugin-seed
→ desktop/plugin-seed/marketplaces/cc-haha-builtin/{plugin/,...}
→ desktop/plugin-seed/known_marketplaces.json
→ bun run build:sidecars + electron-builder
→ packages plugin-seed/** into .app/.exe (asarUnpack ensures
the dir is on disk, not inside asar)
runtime (first launch):
Electron main → spawn server sidecar with
CLAUDE_CODE_PLUGIN_SEED_DIR=<resourcesDir>/plugin-seed
Server sidecar starts → registerSeedMarketplaces()
→ reads seed/known_marketplaces.json
→ calls findSeedMarketplaceLocation() to resolve real path
→ writes resolved entry into ~/.claude/plugins/known_marketplaces.json
Desktop Settings → Plugins reads ~/.claude/plugins/known_marketplaces.json
→ shows cc-haha-builtin marketplace with both reverse-engineering
and image-gen plugins available to install.
Verification
- `bun run desktop/scripts/build-plugin-seed.ts` produces the seed
with both plugins present, valid JSON, in expected dir layout.
- `bun run lint` (desktop): tsc --noEmit clean — no remaining type
errors from the await→void change or the buildSidecarEnv signature
change.
- Seed marketplace.json plugins array confirmed includes both
reverse-engineering (v0.4.6, post-#33) and image-gen (v1.0.0, from
#49).
Tested: build script runs locally; tsc clean.
Not-tested: live `bun run electron:package` end-to-end pack on this
host (no codesign, slow). The packaging-side wiring (asarUnpack +
files) is configuration-only and matches established patterns
(node_modules/node-pty/**, src-tauri/binaries/**); no behaviour change
to the existing package layout besides adding plugin-seed/.
Risk: low — additive across the board, no existing path changes
behaviour. The fire-and-forget marketplace registration cannot block
boot; if it fails, the worst case is the user sees no seeded
marketplace and can manually re-add it (existing flow).
Confidence: high
Scope-risk: narrow — desktop wiring + 1 server-side hook call.
* test(server): add seed-marketplaces-startup integration tests
Covers the registerSeedMarketplaces() integration that startServer()
now invokes at boot. Four cases:
- env var unset → returns false, no write
- valid seed → returns true, writes primary entry with resolved (non-placeholder) installLocation
- repeat call → primary entry stays intact (returns false on the no-op call but the previously-written entry survives)
- nonexistent seed dir → returns false, no crash
Resolves the change-policy 'Server product files changed without a server test file in the PR' block on PR #50.
* test(desktop): pass desktopRoot to buildSidecarEnv in sidecar test
buildSidecarEnv now requires desktopRoot to derive CLAUDE_CODE_PLUGIN_SEED_DIR. The existing 'passes portable config' test still called the old 2-arg signature, breaking both desktop-checks (vitest) and desktop-native-checks (tsc) on PR #50.
Tested:
- bunx vitest run electron/services/sidecarManager.test.ts -t 'passes portable config' — 1 passed
- tsc -p electron/tsconfig.json — clean
Confidence: high
Scope-risk: narrow
---------
Co-authored-by: 你的姓名 <you@example.com>
This commit is contained in:
parent
8fef962e27
commit
355b7294b9
3
.gitignore
vendored
3
.gitignore
vendored
@ -94,3 +94,6 @@ CLAUDE.md
|
||||
.opencode/
|
||||
graphify-out/
|
||||
.gitignore
|
||||
|
||||
# desktop/scripts/build-plugin-seed.ts output
|
||||
desktop/plugin-seed/
|
||||
|
||||
@ -66,9 +66,10 @@ describe('Electron sidecar manager', () => {
|
||||
it('passes portable config and adapter server URL through the sidecar env', () => {
|
||||
const configDir = mkdtempSync(path.join(tmpdir(), 'cc-haha-config-'))
|
||||
try {
|
||||
const env = buildSidecarEnv({ CLAUDE_CONFIG_DIR: configDir }, '/app/dist')
|
||||
const env = buildSidecarEnv({ CLAUDE_CONFIG_DIR: configDir }, '/app/dist', '/app/desktop')
|
||||
expect(env.CLAUDE_CONFIG_DIR).toBe(configDir)
|
||||
expect(env.XDG_CACHE_HOME).toBe(path.join(configDir, 'Cache'))
|
||||
expect(env.CLAUDE_CODE_PLUGIN_SEED_DIR).toBe(path.join('/app/desktop', 'plugin-seed'))
|
||||
|
||||
const adapter = createAdapterPlan({
|
||||
desktopRoot: '/app/desktop',
|
||||
|
||||
@ -162,11 +162,12 @@ export function windowsPowerShellOverride(
|
||||
return base === 'pwsh' || base === 'powershell' ? trimmed : null
|
||||
}
|
||||
|
||||
export function buildSidecarEnv(baseEnv: NodeJS.ProcessEnv, h5DistDir: string): NodeJS.ProcessEnv {
|
||||
export function buildSidecarEnv(baseEnv: NodeJS.ProcessEnv, h5DistDir: string, desktopRoot: string): NodeJS.ProcessEnv {
|
||||
const env: NodeJS.ProcessEnv = {
|
||||
...baseEnv,
|
||||
CLAUDE_H5_AUTO_PUBLIC_URL: '1',
|
||||
CLAUDE_H5_DIST_DIR: h5DistDir,
|
||||
CLAUDE_CODE_PLUGIN_SEED_DIR: path.join(desktopRoot, 'plugin-seed'),
|
||||
}
|
||||
const configDir = baseEnv.CLAUDE_CONFIG_DIR
|
||||
if (configDir) {
|
||||
@ -196,7 +197,7 @@ export function createServerPlan({
|
||||
return {
|
||||
command: resolveSidecarExecutable(desktopRoot),
|
||||
args: ['server', '--app-root', appRoot, '--host', bindHost, '--port', String(port)],
|
||||
env: buildSidecarEnv(env, h5DistDir),
|
||||
env: buildSidecarEnv(env, h5DistDir, desktopRoot),
|
||||
}
|
||||
}
|
||||
|
||||
@ -219,7 +220,7 @@ export function createAdapterPlan({
|
||||
command: resolveSidecarExecutable(desktopRoot),
|
||||
args: ['adapters', '--app-root', appRoot, flag],
|
||||
env: {
|
||||
...buildSidecarEnv(env, h5DistDir),
|
||||
...buildSidecarEnv(env, h5DistDir, desktopRoot),
|
||||
ADAPTER_SERVER_URL: httpToWebSocketUrl(serverUrl),
|
||||
},
|
||||
}
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
"asarUnpack": [
|
||||
"dist/**",
|
||||
"node_modules/node-pty/**",
|
||||
"plugin-seed/**",
|
||||
"src-tauri/binaries/**"
|
||||
],
|
||||
"artifactName": "Claude-Code-Haha-${version}-${os}-${arch}.${ext}",
|
||||
@ -28,6 +29,7 @@
|
||||
"dist/**",
|
||||
"electron-dist/**",
|
||||
"node_modules/node-pty/**",
|
||||
"plugin-seed/**",
|
||||
"src-tauri/binaries/**",
|
||||
"src-tauri/icons/**",
|
||||
"src-tauri/resources/preview-agent.js",
|
||||
@ -84,13 +86,14 @@
|
||||
"build:electron": "bun run prepare:node-pty && bun build ./electron/main.ts --outfile ./electron-dist/main.cjs --target node --format cjs --external electron --external node-pty && bun build ./electron/preload.ts --outfile ./electron-dist/preload.cjs --target node --format cjs --external electron && bun build ./electron/preview-preload.ts --outfile ./electron-dist/preview-preload.cjs --target node --format cjs --external electron",
|
||||
"build:preview-agent": "bun run ./scripts/build-preview-agent.ts",
|
||||
"build:sidecars": "bun run ./scripts/build-sidecars.ts",
|
||||
"build:plugin-seed": "bun run ./scripts/build-plugin-seed.ts",
|
||||
"clean:electron-output": "bun run ./scripts/clean-electron-output.ts",
|
||||
"build:macos-arm64": "bash ./scripts/build-macos-arm64.sh",
|
||||
"build:windows-x64": "powershell -ExecutionPolicy Bypass -File ./scripts/build-windows-x64.ps1",
|
||||
"build:linux-x64": "bash ./scripts/build-linux.sh",
|
||||
"build:linux-arm64": "LINUX_ARCH=arm64 bash ./scripts/build-linux.sh",
|
||||
"electron:dev": "bun run build:electron && bun run ./scripts/electron-dev.ts",
|
||||
"electron:build": "bun run build:sidecars && bun run build && bun run build:electron",
|
||||
"electron:dev": "bun run build:plugin-seed && bun run build:electron && bun run ./scripts/electron-dev.ts",
|
||||
"electron:build": "bun run build:plugin-seed && bun run build:sidecars && bun run build && bun run build:electron",
|
||||
"electron:package": "bun run clean:electron-output && bun run electron:build && bunx electron-builder --publish never",
|
||||
"electron:package:dir": "bun run clean:electron-output && bun run electron:build && bunx electron-builder --dir --publish never",
|
||||
"check:electron": "node ./node_modules/typescript/bin/tsc -p electron/tsconfig.json && bun test electron && bun run build:electron",
|
||||
|
||||
66
desktop/scripts/build-plugin-seed.ts
Normal file
66
desktop/scripts/build-plugin-seed.ts
Normal file
@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env bun
|
||||
/**
|
||||
* Build plugin seed directory for Electron packaging.
|
||||
*
|
||||
* Converts the repository's plugins/ directory into a seed structure
|
||||
* that the Electron app can load at runtime via CLAUDE_CODE_PLUGIN_SEED_DIR.
|
||||
*
|
||||
* Seed structure:
|
||||
* desktop/plugin-seed/
|
||||
* known_marketplaces.json
|
||||
* marketplaces/
|
||||
* cc-haha-builtin/
|
||||
* .claude-plugin/
|
||||
* marketplace.json
|
||||
* image-gen/
|
||||
* reverse-engineering/
|
||||
*/
|
||||
|
||||
import { mkdir, rm, cp, writeFile } from 'node:fs/promises'
|
||||
import { join } from 'node:path'
|
||||
|
||||
const REPO_ROOT = join(import.meta.dir, '..', '..')
|
||||
const DESKTOP_ROOT = join(REPO_ROOT, 'desktop')
|
||||
const PLUGINS_SOURCE = join(REPO_ROOT, 'plugins')
|
||||
const SEED_OUTPUT = join(DESKTOP_ROOT, 'plugin-seed')
|
||||
|
||||
async function main() {
|
||||
console.log('[build-plugin-seed] Cleaning old seed...')
|
||||
await rm(SEED_OUTPUT, { recursive: true, force: true })
|
||||
await mkdir(SEED_OUTPUT, { recursive: true })
|
||||
|
||||
console.log('[build-plugin-seed] Copying marketplace to seed...')
|
||||
const marketplacesDir = join(SEED_OUTPUT, 'marketplaces')
|
||||
await mkdir(marketplacesDir, { recursive: true })
|
||||
|
||||
const marketplaceDest = join(marketplacesDir, 'cc-haha-builtin')
|
||||
await cp(PLUGINS_SOURCE, marketplaceDest, { recursive: true })
|
||||
|
||||
console.log('[build-plugin-seed] Generating known_marketplaces.json...')
|
||||
// installLocation is a placeholder - registerSeedMarketplaces() will recompute
|
||||
// it by calling findSeedMarketplaceLocation(seedDir, name) at runtime.
|
||||
const knownMarketplaces = {
|
||||
'cc-haha-builtin': {
|
||||
source: {
|
||||
source: 'directory',
|
||||
path: '/placeholder/will-be-resolved-at-runtime',
|
||||
},
|
||||
installLocation: '/placeholder/will-be-resolved-at-runtime',
|
||||
lastUpdated: new Date().toISOString(),
|
||||
autoUpdate: false,
|
||||
},
|
||||
}
|
||||
|
||||
await writeFile(
|
||||
join(SEED_OUTPUT, 'known_marketplaces.json'),
|
||||
JSON.stringify(knownMarketplaces, null, 2),
|
||||
'utf-8',
|
||||
)
|
||||
|
||||
console.log('[build-plugin-seed] ✓ Plugin seed built at', SEED_OUTPUT)
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error('[build-plugin-seed] Failed:', error)
|
||||
process.exit(1)
|
||||
})
|
||||
187
src/server/__tests__/seed-marketplaces-startup.test.ts
Normal file
187
src/server/__tests__/seed-marketplaces-startup.test.ts
Normal file
@ -0,0 +1,187 @@
|
||||
/**
|
||||
* Server-side seed marketplace registration tests.
|
||||
*
|
||||
* These cover the integration that startServer() in src/server/index.ts
|
||||
* triggers — calling registerSeedMarketplaces() at boot so the desktop's
|
||||
* Settings → Plugins page sees the cc-haha-builtin marketplace shipped
|
||||
* with the Electron package.
|
||||
*
|
||||
* The shape we exercise:
|
||||
*
|
||||
* $CLAUDE_CODE_PLUGIN_SEED_DIR/
|
||||
* known_marketplaces.json
|
||||
* marketplaces/
|
||||
* cc-haha-builtin/
|
||||
* .claude-plugin/
|
||||
* marketplace.json
|
||||
*
|
||||
* is what desktop/scripts/build-plugin-seed.ts produces during
|
||||
* `bun run electron:build`. registerSeedMarketplaces() reads the seed,
|
||||
* resolves the real installLocation via findSeedMarketplaceLocation(),
|
||||
* and writes it into the user's primary
|
||||
* ~/.claude/plugins/known_marketplaces.json.
|
||||
*/
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
|
||||
import * as fs from 'node:fs/promises'
|
||||
import * as os from 'node:os'
|
||||
import * as path from 'node:path'
|
||||
|
||||
import { registerSeedMarketplaces } from '../../utils/plugins/marketplaceManager.js'
|
||||
|
||||
const MARKETPLACE_NAME = 'cc-haha-builtin'
|
||||
|
||||
let originalConfigDir: string | undefined
|
||||
let originalSeedDir: string | undefined
|
||||
let tmpRoot: string
|
||||
|
||||
async function makeSeed(seedDir: string): Promise<void> {
|
||||
const marketplaceRoot = path.join(seedDir, 'marketplaces', MARKETPLACE_NAME)
|
||||
await fs.mkdir(path.join(marketplaceRoot, '.claude-plugin'), { recursive: true })
|
||||
|
||||
// Marketplace manifest the loader will read.
|
||||
await fs.writeFile(
|
||||
path.join(marketplaceRoot, '.claude-plugin', 'marketplace.json'),
|
||||
JSON.stringify(
|
||||
{
|
||||
name: MARKETPLACE_NAME,
|
||||
owner: { name: 'cc-haha' },
|
||||
plugins: [],
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
'utf-8',
|
||||
)
|
||||
|
||||
// Seed-side known_marketplaces.json (build-plugin-seed.ts writes this
|
||||
// with placeholder paths — registerSeedMarketplaces resolves them
|
||||
// against the actual seed dir at runtime).
|
||||
await fs.writeFile(
|
||||
path.join(seedDir, 'known_marketplaces.json'),
|
||||
JSON.stringify(
|
||||
{
|
||||
[MARKETPLACE_NAME]: {
|
||||
source: { source: 'directory', path: '/placeholder' },
|
||||
installLocation: '/placeholder',
|
||||
lastUpdated: new Date().toISOString(),
|
||||
autoUpdate: false,
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
'utf-8',
|
||||
)
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
originalConfigDir = process.env.CLAUDE_CONFIG_DIR
|
||||
originalSeedDir = process.env.CLAUDE_CODE_PLUGIN_SEED_DIR
|
||||
tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'cc-haha-seed-test-'))
|
||||
// Pin the user-config dir into our tmp so we don't pollute the real
|
||||
// ~/.claude when the registration writes back.
|
||||
process.env.CLAUDE_CONFIG_DIR = path.join(tmpRoot, 'claude-config')
|
||||
await fs.mkdir(process.env.CLAUDE_CONFIG_DIR, { recursive: true })
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
if (originalConfigDir === undefined) delete process.env.CLAUDE_CONFIG_DIR
|
||||
else process.env.CLAUDE_CONFIG_DIR = originalConfigDir
|
||||
if (originalSeedDir === undefined) delete process.env.CLAUDE_CODE_PLUGIN_SEED_DIR
|
||||
else process.env.CLAUDE_CODE_PLUGIN_SEED_DIR = originalSeedDir
|
||||
await fs.rm(tmpRoot, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
describe('registerSeedMarketplaces (server boot integration)', () => {
|
||||
test('returns false and does not write when CLAUDE_CODE_PLUGIN_SEED_DIR is unset', async () => {
|
||||
delete process.env.CLAUDE_CODE_PLUGIN_SEED_DIR
|
||||
const result = await registerSeedMarketplaces()
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
|
||||
test('registers the seeded marketplace when env var points at a valid seed', async () => {
|
||||
const seedDir = path.join(tmpRoot, 'seed')
|
||||
await makeSeed(seedDir)
|
||||
process.env.CLAUDE_CODE_PLUGIN_SEED_DIR = seedDir
|
||||
|
||||
const result = await registerSeedMarketplaces()
|
||||
expect(result).toBe(true)
|
||||
|
||||
// The primary known_marketplaces.json under CLAUDE_CONFIG_DIR/plugins
|
||||
// must now contain the seeded marketplace, with installLocation pointing
|
||||
// INTO the seed dir (not the placeholder string).
|
||||
const primaryPath = path.join(
|
||||
process.env.CLAUDE_CONFIG_DIR!,
|
||||
'plugins',
|
||||
'known_marketplaces.json',
|
||||
)
|
||||
const primaryRaw = await fs.readFile(primaryPath, 'utf-8')
|
||||
const primary = JSON.parse(primaryRaw) as Record<
|
||||
string,
|
||||
{ installLocation: string; source: { source: string } }
|
||||
>
|
||||
expect(primary[MARKETPLACE_NAME]).toBeDefined()
|
||||
expect(primary[MARKETPLACE_NAME]!.installLocation).toContain(seedDir)
|
||||
// Crucially, the runtime-resolved location is NOT the placeholder
|
||||
// baked into the seed JSON by build-plugin-seed.ts.
|
||||
expect(primary[MARKETPLACE_NAME]!.installLocation).not.toBe('/placeholder')
|
||||
})
|
||||
|
||||
test('is idempotent — calling twice with the same seed leaves the primary entry intact', async () => {
|
||||
const seedDir = path.join(tmpRoot, 'seed')
|
||||
await makeSeed(seedDir)
|
||||
process.env.CLAUDE_CODE_PLUGIN_SEED_DIR = seedDir
|
||||
|
||||
// First call: actually registers (returns true).
|
||||
expect(await registerSeedMarketplaces()).toBe(true)
|
||||
const primaryPath = path.join(
|
||||
process.env.CLAUDE_CONFIG_DIR!,
|
||||
'plugins',
|
||||
'known_marketplaces.json',
|
||||
)
|
||||
const first = await fs.readFile(primaryPath, 'utf-8')
|
||||
|
||||
// Second call: registerSeedMarketplaces returns false to signal
|
||||
// "nothing new to write" (the seed is already represented in primary).
|
||||
// The contract this test locks: even when the function returns false
|
||||
// on a repeat call, the primary entry MUST still be present and
|
||||
// resolved — startServer() invokes this every boot, so a flaky
|
||||
// "second-call returns false" must NEVER mean "marketplace was
|
||||
// unregistered".
|
||||
await registerSeedMarketplaces()
|
||||
const second = await fs.readFile(primaryPath, 'utf-8')
|
||||
|
||||
const firstParsed = JSON.parse(first)
|
||||
const secondParsed = JSON.parse(second)
|
||||
expect(secondParsed[MARKETPLACE_NAME]).toBeDefined()
|
||||
expect(secondParsed[MARKETPLACE_NAME]?.installLocation).toBe(
|
||||
firstParsed[MARKETPLACE_NAME]?.installLocation,
|
||||
)
|
||||
})
|
||||
|
||||
test('returns false when the env var points at a nonexistent dir (graceful no-op)', async () => {
|
||||
process.env.CLAUDE_CODE_PLUGIN_SEED_DIR = path.join(tmpRoot, 'does-not-exist')
|
||||
const result = await registerSeedMarketplaces()
|
||||
expect(result).toBe(false)
|
||||
|
||||
// Primary known_marketplaces.json was either never created or has no
|
||||
// cc-haha-builtin entry — startup must not crash on a missing seed.
|
||||
const primaryPath = path.join(
|
||||
process.env.CLAUDE_CONFIG_DIR!,
|
||||
'plugins',
|
||||
'known_marketplaces.json',
|
||||
)
|
||||
const primaryExists = await fs.stat(primaryPath).then(
|
||||
() => true,
|
||||
() => false,
|
||||
)
|
||||
if (primaryExists) {
|
||||
const primary = JSON.parse(await fs.readFile(primaryPath, 'utf-8')) as Record<
|
||||
string,
|
||||
unknown
|
||||
>
|
||||
expect(primary[MARKETPLACE_NAME]).toBeUndefined()
|
||||
}
|
||||
})
|
||||
})
|
||||
@ -27,6 +27,7 @@ import { ensurePersistentStorageUpgraded } from './services/persistentStorageMig
|
||||
import { handleStaticH5Request } from './staticH5.js'
|
||||
import { classifyH5Request, shouldBlockDisabledH5Access, shouldRequireH5Token } from './h5AccessPolicy.js'
|
||||
import { H5AccessService } from './services/h5AccessService.js'
|
||||
import { registerSeedMarketplaces } from '../utils/plugins/marketplaceManager.js'
|
||||
|
||||
function readArgValue(flag: string): string | undefined {
|
||||
const args = process.argv.slice(2)
|
||||
@ -147,6 +148,16 @@ export function startServer(port = PORT, host = HOST) {
|
||||
process.env.SERVER_AUTH_REQUIRED === '1'
|
||||
const h5AccessService = new H5AccessService()
|
||||
|
||||
// Register seed marketplaces (e.g. the cc-haha-builtin marketplace
|
||||
// shipped with the Electron package via CLAUDE_CODE_PLUGIN_SEED_DIR)
|
||||
// into ~/.claude/plugins/known_marketplaces.json so they're visible
|
||||
// in the desktop's Settings → Plugins view. Fire-and-forget — startup
|
||||
// shouldn't block on this; the marketplace appears in the UI within
|
||||
// 1-2 seconds of server boot.
|
||||
void registerSeedMarketplaces().catch((err) => {
|
||||
console.error('[Server] Failed to register seed marketplaces:', err)
|
||||
})
|
||||
|
||||
let server: ReturnType<typeof Bun.serve<WebSocketData>>
|
||||
|
||||
try {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user