Swadhin Biswas feat: implement mervelas cli engine, built-in tools, and reactive UI · 4/3/2026
0358986
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
export const PRODUCT_NAME = 'Mervelas'
export const LEGACY_PRODUCT_NAME = 'Mervelas'
export const PRODUCT_SLUG = 'mervelas'
export const LEGACY_PRODUCT_SLUG = 'claude-code'
export const CLI_BINARY_NAME = 'mervelas'
export const LEGACY_CLI_BINARY_NAME = 'claude'
export const PRODUCT_CONFIG_DIRNAME = '.mervelas'
export const LEGACY_PRODUCT_CONFIG_DIRNAME = '.claude'
export const PROJECT_CONFIG_DIR_CANDIDATES = [
PRODUCT_CONFIG_DIRNAME,
LEGACY_PRODUCT_CONFIG_DIRNAME,
] as const
export const PRODUCT_CONFIG_ENV_VAR = 'MERVELAS_CONFIG_DIR'
export const LEGACY_PRODUCT_CONFIG_ENV_VAR = 'CLAUDE_CONFIG_DIR'
export const PRODUCT_URL = 'https://github.com/swadhinbiswas/Mervelas'
export const PRODUCT_ISSUES_URL =
'https://github.com/swadhinbiswas/Mervelas/issues'
export const PRODUCT_NOREPLY_EMAIL = '[email protected]'
// Anthropic web URLs are kept for the Anthropic provider and remote sessions.
export const ANTHROPIC_APP_BASE_URL = 'https://claude.ai'
export const ANTHROPIC_APP_STAGING_BASE_URL =
'https://claude-ai.staging.ant.dev'
export const ANTHROPIC_APP_LOCAL_BASE_URL = 'http://localhost:4000'
// Backward-compatible aliases while the Anthropic provider is still supported.
export const CLAUDE_AI_BASE_URL = ANTHROPIC_APP_BASE_URL
export const CLAUDE_AI_STAGING_BASE_URL = ANTHROPIC_APP_STAGING_BASE_URL
export const CLAUDE_AI_LOCAL_BASE_URL = ANTHROPIC_APP_LOCAL_BASE_URL
export function getConfiguredProductConfigDir(): string | undefined {
return process.env[PRODUCT_CONFIG_ENV_VAR]
}
/**
* Determine if we're in a staging environment for remote sessions.
* Checks session ID format and ingress URL.
*/
export function isRemoteSessionStaging(
sessionId?: string,
ingressUrl?: string,
): boolean {
return (
sessionId?.includes('_staging_') === true ||
ingressUrl?.includes('staging') === true
)
}
/**
* Determine if we're in a local-dev environment for remote sessions.
* Checks session ID format (e.g. `session_local_...`) and ingress URL.
*/
export function isRemoteSessionLocal(
sessionId?: string,
ingressUrl?: string,
): boolean {
return (
sessionId?.includes('_local_') === true ||
ingressUrl?.includes('localhost') === true
)
}
/**
* Get the base URL for Claude AI based on environment.
*/
export function getClaudeAiBaseUrl(
sessionId?: string,
ingressUrl?: string,
): string {
if (isRemoteSessionLocal(sessionId, ingressUrl)) {
return ANTHROPIC_APP_LOCAL_BASE_URL
}
if (isRemoteSessionStaging(sessionId, ingressUrl)) {
return ANTHROPIC_APP_STAGING_BASE_URL
}
return ANTHROPIC_APP_BASE_URL
}
/**
* Get the full session URL for a remote session.
*
* The cse_→session_ translation is a temporary shim gated by
* tengu_bridge_repl_v2_cse_shim_enabled (see isCseShimEnabled). Worker
* endpoints (/v1/code/sessions/{id}/worker/*) want `cse_*` but the claude.ai
* frontend currently routes on `session_*` (compat/convert.go:27 validates
* TagSession). Same UUID body, different tag prefix. Once the server tags by
* environment_kind and the frontend accepts `cse_*` directly, flip the gate
* off. No-op for IDs already in `session_*` form. See toCompatSessionId in
* src/bridge/sessionIdCompat.ts for the canonical helper (lazy-required here
* to keep constants/ leaf-of-DAG at module-load time).
*/
export function getRemoteSessionUrl(
sessionId: string,
ingressUrl?: string,
): string {
/* eslint-disable @typescript-eslint/no-require-imports */
const { toCompatSessionId } =
require('../bridge/sessionIdCompat.js') as typeof import('../bridge/sessionIdCompat.js')
/* eslint-enable @typescript-eslint/no-require-imports */
const compatId = toCompatSessionId(sessionId)
const baseUrl = getClaudeAiBaseUrl(compatId, ingressUrl)
return `${baseUrl}/code/${compatId}`
}