refactor: rename clawdbot to moltbot with legacy compat

This commit is contained in:
Peter Steinberger
2026-01-27 12:19:58 +00:00
parent 83460df96f
commit 6d16a658e5
1839 changed files with 11250 additions and 11199 deletions

View File

@@ -65,7 +65,7 @@ androidComponents {
val versionName = output.versionName.orNull ?: "0"
val buildType = variant.buildType
val outputFileName = "clawdbot-${versionName}-${buildType}.apk"
val outputFileName = "moltbot-${versionName}-${buildType}.apk"
output.outputFileName = outputFileName
}
}

View File

@@ -32,7 +32,7 @@
android:label="@string/app_name"
android:supportsRtl="true"
android:networkSecurityConfig="@xml/network_security_config"
android:theme="@style/Theme.ClawdbotNode">
android:theme="@style/Theme.MoltbotNode">
<service
android:name=".NodeForegroundService"
android:exported="false"

View File

@@ -19,7 +19,7 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.clawdbot.android.ui.RootScreen
import com.clawdbot.android.ui.ClawdbotTheme
import com.clawdbot.android.ui.MoltbotTheme
import kotlinx.coroutines.launch
class MainActivity : ComponentActivity() {
@@ -56,7 +56,7 @@ class MainActivity : ComponentActivity() {
}
setContent {
ClawdbotTheme {
MoltbotTheme {
Surface(modifier = Modifier) {
RootScreen(viewModel = viewModel)
}

View File

@@ -29,7 +29,7 @@ class NodeForegroundService : Service() {
override fun onCreate() {
super.onCreate()
ensureChannel()
val initial = buildNotification(title = "Clawdbot Node", text = "Starting…")
val initial = buildNotification(title = "Moltbot Node", text = "Starting…")
startForegroundWithTypes(notification = initial, requiresMic = false)
val runtime = (application as NodeApp).runtime
@@ -44,7 +44,7 @@ class NodeForegroundService : Service() {
) { status, server, connected, voiceMode, voiceListening ->
Quint(status, server, connected, voiceMode, voiceListening)
}.collect { (status, server, connected, voiceMode, voiceListening) ->
val title = if (connected) "Clawdbot Node · Connected" else "Clawdbot Node"
val title = if (connected) "Moltbot Node · Connected" else "Moltbot Node"
val voiceSuffix =
if (voiceMode == VoiceWakeMode.Always) {
if (voiceListening) " · Voice Wake: Listening" else " · Voice Wake: Paused"
@@ -91,7 +91,7 @@ class NodeForegroundService : Service() {
"Connection",
NotificationManager.IMPORTANCE_LOW,
).apply {
description = "Clawdbot node connection status"
description = "Moltbot node connection status"
setShowBadge(false)
}
mgr.createNotificationChannel(channel)

View File

@@ -26,14 +26,14 @@ import com.clawdbot.android.BuildConfig
import com.clawdbot.android.node.CanvasController
import com.clawdbot.android.node.ScreenRecordManager
import com.clawdbot.android.node.SmsManager
import com.clawdbot.android.protocol.ClawdbotCapability
import com.clawdbot.android.protocol.ClawdbotCameraCommand
import com.clawdbot.android.protocol.ClawdbotCanvasA2UIAction
import com.clawdbot.android.protocol.ClawdbotCanvasA2UICommand
import com.clawdbot.android.protocol.ClawdbotCanvasCommand
import com.clawdbot.android.protocol.ClawdbotScreenCommand
import com.clawdbot.android.protocol.ClawdbotLocationCommand
import com.clawdbot.android.protocol.ClawdbotSmsCommand
import com.clawdbot.android.protocol.MoltbotCapability
import com.clawdbot.android.protocol.MoltbotCameraCommand
import com.clawdbot.android.protocol.MoltbotCanvasA2UIAction
import com.clawdbot.android.protocol.MoltbotCanvasA2UICommand
import com.clawdbot.android.protocol.MoltbotCanvasCommand
import com.clawdbot.android.protocol.MoltbotScreenCommand
import com.clawdbot.android.protocol.MoltbotLocationCommand
import com.clawdbot.android.protocol.MoltbotSmsCommand
import com.clawdbot.android.voice.TalkModeManager
import com.clawdbot.android.voice.VoiceWakeManager
import kotlinx.coroutines.CoroutineScope
@@ -451,38 +451,38 @@ class NodeRuntime(context: Context) {
private fun buildInvokeCommands(): List<String> =
buildList {
add(ClawdbotCanvasCommand.Present.rawValue)
add(ClawdbotCanvasCommand.Hide.rawValue)
add(ClawdbotCanvasCommand.Navigate.rawValue)
add(ClawdbotCanvasCommand.Eval.rawValue)
add(ClawdbotCanvasCommand.Snapshot.rawValue)
add(ClawdbotCanvasA2UICommand.Push.rawValue)
add(ClawdbotCanvasA2UICommand.PushJSONL.rawValue)
add(ClawdbotCanvasA2UICommand.Reset.rawValue)
add(ClawdbotScreenCommand.Record.rawValue)
add(MoltbotCanvasCommand.Present.rawValue)
add(MoltbotCanvasCommand.Hide.rawValue)
add(MoltbotCanvasCommand.Navigate.rawValue)
add(MoltbotCanvasCommand.Eval.rawValue)
add(MoltbotCanvasCommand.Snapshot.rawValue)
add(MoltbotCanvasA2UICommand.Push.rawValue)
add(MoltbotCanvasA2UICommand.PushJSONL.rawValue)
add(MoltbotCanvasA2UICommand.Reset.rawValue)
add(MoltbotScreenCommand.Record.rawValue)
if (cameraEnabled.value) {
add(ClawdbotCameraCommand.Snap.rawValue)
add(ClawdbotCameraCommand.Clip.rawValue)
add(MoltbotCameraCommand.Snap.rawValue)
add(MoltbotCameraCommand.Clip.rawValue)
}
if (locationMode.value != LocationMode.Off) {
add(ClawdbotLocationCommand.Get.rawValue)
add(MoltbotLocationCommand.Get.rawValue)
}
if (sms.canSendSms()) {
add(ClawdbotSmsCommand.Send.rawValue)
add(MoltbotSmsCommand.Send.rawValue)
}
}
private fun buildCapabilities(): List<String> =
buildList {
add(ClawdbotCapability.Canvas.rawValue)
add(ClawdbotCapability.Screen.rawValue)
if (cameraEnabled.value) add(ClawdbotCapability.Camera.rawValue)
if (sms.canSendSms()) add(ClawdbotCapability.Sms.rawValue)
add(MoltbotCapability.Canvas.rawValue)
add(MoltbotCapability.Screen.rawValue)
if (cameraEnabled.value) add(MoltbotCapability.Camera.rawValue)
if (sms.canSendSms()) add(MoltbotCapability.Sms.rawValue)
if (voiceWakeMode.value != VoiceWakeMode.Off && hasRecordAudioPermission()) {
add(ClawdbotCapability.VoiceWake.rawValue)
add(MoltbotCapability.VoiceWake.rawValue)
}
if (locationMode.value != LocationMode.Off) {
add(ClawdbotCapability.Location.rawValue)
add(MoltbotCapability.Location.rawValue)
}
}
@@ -506,7 +506,7 @@ class NodeRuntime(context: Context) {
val version = resolvedVersionName()
val release = Build.VERSION.RELEASE?.trim().orEmpty()
val releaseLabel = if (release.isEmpty()) "unknown" else release
return "ClawdbotAndroid/$version (Android $releaseLabel; SDK ${Build.VERSION.SDK_INT})"
return "MoltbotAndroid/$version (Android $releaseLabel; SDK ${Build.VERSION.SDK_INT})"
}
private fun buildClientInfo(clientId: String, clientMode: String): GatewayClientInfo {
@@ -529,7 +529,7 @@ class NodeRuntime(context: Context) {
caps = buildCapabilities(),
commands = buildInvokeCommands(),
permissions = emptyMap(),
client = buildClientInfo(clientId = "clawdbot-android", clientMode = "node"),
client = buildClientInfo(clientId = "moltbot-android", clientMode = "node"),
userAgent = buildUserAgent(),
)
}
@@ -541,7 +541,7 @@ class NodeRuntime(context: Context) {
caps = emptyList(),
commands = emptyList(),
permissions = emptyMap(),
client = buildClientInfo(clientId = "clawdbot-control-ui", clientMode = "ui"),
client = buildClientInfo(clientId = "moltbot-control-ui", clientMode = "ui"),
userAgent = buildUserAgent(),
)
}
@@ -665,7 +665,7 @@ class NodeRuntime(context: Context) {
val actionId = (userActionObj["id"] as? JsonPrimitive)?.content?.trim().orEmpty().ifEmpty {
java.util.UUID.randomUUID().toString()
}
val name = ClawdbotCanvasA2UIAction.extractActionName(userActionObj) ?: return@launch
val name = MoltbotCanvasA2UIAction.extractActionName(userActionObj) ?: return@launch
val surfaceId =
(userActionObj["surfaceId"] as? JsonPrimitive)?.content?.trim().orEmpty().ifEmpty { "main" }
@@ -675,7 +675,7 @@ class NodeRuntime(context: Context) {
val sessionKey = resolveMainSessionKey()
val message =
ClawdbotCanvasA2UIAction.formatAgentMessage(
MoltbotCanvasA2UIAction.formatAgentMessage(
actionName = name,
sessionKey = sessionKey,
surfaceId = surfaceId,
@@ -709,7 +709,7 @@ class NodeRuntime(context: Context) {
try {
canvas.eval(
ClawdbotCanvasA2UIAction.jsDispatchA2UIActionStatus(
MoltbotCanvasA2UIAction.jsDispatchA2UIActionStatus(
actionId = actionId,
ok = connected && error == null,
error = error,
@@ -827,10 +827,10 @@ class NodeRuntime(context: Context) {
private suspend fun handleInvoke(command: String, paramsJson: String?): GatewaySession.InvokeResult {
if (
command.startsWith(ClawdbotCanvasCommand.NamespacePrefix) ||
command.startsWith(ClawdbotCanvasA2UICommand.NamespacePrefix) ||
command.startsWith(ClawdbotCameraCommand.NamespacePrefix) ||
command.startsWith(ClawdbotScreenCommand.NamespacePrefix)
command.startsWith(MoltbotCanvasCommand.NamespacePrefix) ||
command.startsWith(MoltbotCanvasA2UICommand.NamespacePrefix) ||
command.startsWith(MoltbotCameraCommand.NamespacePrefix) ||
command.startsWith(MoltbotScreenCommand.NamespacePrefix)
) {
if (!isForeground.value) {
return GatewaySession.InvokeResult.error(
@@ -839,13 +839,13 @@ class NodeRuntime(context: Context) {
)
}
}
if (command.startsWith(ClawdbotCameraCommand.NamespacePrefix) && !cameraEnabled.value) {
if (command.startsWith(MoltbotCameraCommand.NamespacePrefix) && !cameraEnabled.value) {
return GatewaySession.InvokeResult.error(
code = "CAMERA_DISABLED",
message = "CAMERA_DISABLED: enable Camera in Settings",
)
}
if (command.startsWith(ClawdbotLocationCommand.NamespacePrefix) &&
if (command.startsWith(MoltbotLocationCommand.NamespacePrefix) &&
locationMode.value == LocationMode.Off
) {
return GatewaySession.InvokeResult.error(
@@ -855,18 +855,18 @@ class NodeRuntime(context: Context) {
}
return when (command) {
ClawdbotCanvasCommand.Present.rawValue -> {
MoltbotCanvasCommand.Present.rawValue -> {
val url = CanvasController.parseNavigateUrl(paramsJson)
canvas.navigate(url)
GatewaySession.InvokeResult.ok(null)
}
ClawdbotCanvasCommand.Hide.rawValue -> GatewaySession.InvokeResult.ok(null)
ClawdbotCanvasCommand.Navigate.rawValue -> {
MoltbotCanvasCommand.Hide.rawValue -> GatewaySession.InvokeResult.ok(null)
MoltbotCanvasCommand.Navigate.rawValue -> {
val url = CanvasController.parseNavigateUrl(paramsJson)
canvas.navigate(url)
GatewaySession.InvokeResult.ok(null)
}
ClawdbotCanvasCommand.Eval.rawValue -> {
MoltbotCanvasCommand.Eval.rawValue -> {
val js =
CanvasController.parseEvalJs(paramsJson)
?: return GatewaySession.InvokeResult.error(
@@ -884,7 +884,7 @@ class NodeRuntime(context: Context) {
}
GatewaySession.InvokeResult.ok("""{"result":${result.toJsonString()}}""")
}
ClawdbotCanvasCommand.Snapshot.rawValue -> {
MoltbotCanvasCommand.Snapshot.rawValue -> {
val snapshotParams = CanvasController.parseSnapshotParams(paramsJson)
val base64 =
try {
@@ -901,7 +901,7 @@ class NodeRuntime(context: Context) {
}
GatewaySession.InvokeResult.ok("""{"format":"${snapshotParams.format.rawValue}","base64":"$base64"}""")
}
ClawdbotCanvasA2UICommand.Reset.rawValue -> {
MoltbotCanvasA2UICommand.Reset.rawValue -> {
val a2uiUrl = resolveA2uiHostUrl()
?: return GatewaySession.InvokeResult.error(
code = "A2UI_HOST_NOT_CONFIGURED",
@@ -917,7 +917,7 @@ class NodeRuntime(context: Context) {
val res = canvas.eval(a2uiResetJS)
GatewaySession.InvokeResult.ok(res)
}
ClawdbotCanvasA2UICommand.Push.rawValue, ClawdbotCanvasA2UICommand.PushJSONL.rawValue -> {
MoltbotCanvasA2UICommand.Push.rawValue, MoltbotCanvasA2UICommand.PushJSONL.rawValue -> {
val messages =
try {
decodeA2uiMessages(command, paramsJson)
@@ -940,7 +940,7 @@ class NodeRuntime(context: Context) {
val res = canvas.eval(js)
GatewaySession.InvokeResult.ok(res)
}
ClawdbotCameraCommand.Snap.rawValue -> {
MoltbotCameraCommand.Snap.rawValue -> {
showCameraHud(message = "Taking photo…", kind = CameraHudKind.Photo)
triggerCameraFlash()
val res =
@@ -954,7 +954,7 @@ class NodeRuntime(context: Context) {
showCameraHud(message = "Photo captured", kind = CameraHudKind.Success, autoHideMs = 1600)
GatewaySession.InvokeResult.ok(res.payloadJson)
}
ClawdbotCameraCommand.Clip.rawValue -> {
MoltbotCameraCommand.Clip.rawValue -> {
val includeAudio = paramsJson?.contains("\"includeAudio\":true") != false
if (includeAudio) externalAudioCaptureActive.value = true
try {
@@ -973,7 +973,7 @@ class NodeRuntime(context: Context) {
if (includeAudio) externalAudioCaptureActive.value = false
}
}
ClawdbotLocationCommand.Get.rawValue -> {
MoltbotLocationCommand.Get.rawValue -> {
val mode = locationMode.value
if (!isForeground.value && mode != LocationMode.Always) {
return GatewaySession.InvokeResult.error(
@@ -1026,7 +1026,7 @@ class NodeRuntime(context: Context) {
GatewaySession.InvokeResult.error(code = "LOCATION_UNAVAILABLE", message = message)
}
}
ClawdbotScreenCommand.Record.rawValue -> {
MoltbotScreenCommand.Record.rawValue -> {
// Status pill mirrors screen recording state so it stays visible without overlay stacking.
_screenRecordActive.value = true
try {
@@ -1042,7 +1042,7 @@ class NodeRuntime(context: Context) {
_screenRecordActive.value = false
}
}
ClawdbotSmsCommand.Send.rawValue -> {
MoltbotSmsCommand.Send.rawValue -> {
val res = sms.send(paramsJson)
if (res.ok) {
GatewaySession.InvokeResult.ok(res.payloadJson)
@@ -1115,7 +1115,7 @@ class NodeRuntime(context: Context) {
val raw = if (nodeRaw.isNotBlank()) nodeRaw else operatorRaw
if (raw.isBlank()) return null
val base = raw.trimEnd('/')
return "${base}/__clawdbot__/a2ui/?platform=android"
return "${base}/__moltbot__/a2ui/?platform=android"
}
private suspend fun ensureA2uiReady(a2uiUrl: String): Boolean {
@@ -1150,7 +1150,7 @@ class NodeRuntime(context: Context) {
val jsonlField = (obj["jsonl"] as? JsonPrimitive)?.content?.trim().orEmpty()
val hasMessagesArray = obj["messages"] is JsonArray
if (command == ClawdbotCanvasA2UICommand.PushJSONL.rawValue || (!hasMessagesArray && jsonlField.isNotBlank())) {
if (command == MoltbotCanvasA2UICommand.PushJSONL.rawValue || (!hasMessagesArray && jsonlField.isNotBlank())) {
val jsonl = jsonlField
if (jsonl.isBlank()) throw IllegalArgumentException("INVALID_REQUEST: jsonl required")
val messages =
@@ -1218,7 +1218,7 @@ private const val a2uiResetJS: String =
"""
(() => {
try {
if (!globalThis.clawdbotA2UI) return { ok: false, error: "missing clawdbotA2UI" };
if (!globalThis.clawdbotA2UI) return { ok: false, error: "missing moltbotA2UI" };
return globalThis.clawdbotA2UI.reset();
} catch (e) {
return { ok: false, error: String(e?.message ?? e) };
@@ -1230,7 +1230,7 @@ private fun a2uiApplyMessagesJS(messagesJson: String): String {
return """
(() => {
try {
if (!globalThis.clawdbotA2UI) return { ok: false, error: "missing clawdbotA2UI" };
if (!globalThis.clawdbotA2UI) return { ok: false, error: "missing moltbotA2UI" };
const messages = $messagesJson;
return globalThis.clawdbotA2UI.applyMessages(messages);
} catch (e) {

View File

@@ -115,7 +115,7 @@ class PermissionRequester(private val activity: ComponentActivity) {
private fun buildRationaleMessage(permissions: List<String>): String {
val labels = permissions.map { permissionLabel(it) }
return "Clawdbot needs ${labels.joinToString(", ")} permissions to continue."
return "Moltbot needs ${labels.joinToString(", ")} permissions to continue."
}
private fun buildSettingsMessage(permissions: List<String>): String {

View File

@@ -55,7 +55,7 @@ class ScreenCaptureRequester(private val activity: ComponentActivity) {
suspendCancellableCoroutine { cont ->
AlertDialog.Builder(activity)
.setTitle("Screen recording required")
.setMessage("Clawdbot needs to record the screen for this command.")
.setMessage("Moltbot needs to record the screen for this command.")
.setPositiveButton("Continue") { _, _ -> cont.resume(true) }
.setNegativeButton("Not now") { _, _ -> cont.resume(false) }
.setOnCancelListener { cont.resume(false) }

View File

@@ -31,7 +31,7 @@ class SecurePrefs(context: Context) {
private val prefs =
EncryptedSharedPreferences.create(
context,
"clawdbot.node.secure",
"moltbot.node.secure",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM,

View File

@@ -21,7 +21,7 @@ data class DeviceIdentity(
class DeviceIdentityStore(context: Context) {
private val json = Json { ignoreUnknownKeys = true }
private val identityFile = File(context.filesDir, "clawdbot/identity/device.json")
private val identityFile = File(context.filesDir, "moltbot/identity/device.json")
@Synchronized
fun loadOrCreate(): DeviceIdentity {

View File

@@ -51,9 +51,9 @@ class GatewayDiscovery(
private val nsd = context.getSystemService(NsdManager::class.java)
private val connectivity = context.getSystemService(ConnectivityManager::class.java)
private val dns = DnsResolver.getInstance()
private val serviceType = "_clawdbot-gw._tcp."
private val wideAreaDomain = "clawdbot.internal."
private val logTag = "Clawdbot/GatewayDiscovery"
private val serviceType = "_moltbot-gw._tcp."
private val wideAreaDomain = "moltbot.internal."
private val logTag = "Moltbot/GatewayDiscovery"
private val localById = ConcurrentHashMap<String, GatewayEndpoint>()
private val unicastById = ConcurrentHashMap<String, GatewayEndpoint>()

View File

@@ -148,7 +148,7 @@ class GatewaySession(
try {
conn.request("node.event", params, timeoutMs = 8_000)
} catch (err: Throwable) {
Log.w("ClawdbotGateway", "node.event failed: ${err.message ?: err::class.java.simpleName}")
Log.w("MoltbotGateway", "node.event failed: ${err.message ?: err::class.java.simpleName}")
}
}
@@ -181,7 +181,7 @@ class GatewaySession(
private val connectNonceDeferred = CompletableDeferred<String?>()
private val client: OkHttpClient = buildClient()
private var socket: WebSocket? = null
private val loggerTag = "ClawdbotGateway"
private val loggerTag = "MoltbotGateway"
val remoteAddress: String =
if (endpoint.host.contains(":")) {

View File

@@ -155,7 +155,7 @@ class CameraCaptureManager(private val context: Context) {
provider.unbindAll()
provider.bindToLifecycle(owner, selector, videoCapture)
val file = File.createTempFile("clawdbot-clip-", ".mp4")
val file = File.createTempFile("moltbot-clip-", ".mp4")
val outputOptions = FileOutputOptions.Builder(file).build()
val finalized = kotlinx.coroutines.CompletableDeferred<VideoRecordEvent.Finalize>()
@@ -285,7 +285,7 @@ private suspend fun Context.cameraProvider(): ProcessCameraProvider =
/** Returns (jpegBytes, exifOrientation) so caller can rotate the decoded bitmap. */
private suspend fun ImageCapture.takeJpegWithExif(executor: Executor): Pair<ByteArray, Int> =
suspendCancellableCoroutine { cont ->
val file = File.createTempFile("clawdbot-snap-", ".jpg")
val file = File.createTempFile("moltbot-snap-", ".jpg")
val options = ImageCapture.OutputFileOptions.Builder(file).build()
takePicture(
options,

View File

@@ -84,12 +84,12 @@ class CanvasController {
withWebViewOnMain { wv ->
if (currentUrl == null) {
if (BuildConfig.DEBUG) {
Log.d("ClawdbotCanvas", "load scaffold: $scaffoldAssetUrl")
Log.d("MoltbotCanvas", "load scaffold: $scaffoldAssetUrl")
}
wv.loadUrl(scaffoldAssetUrl)
} else {
if (BuildConfig.DEBUG) {
Log.d("ClawdbotCanvas", "load url: $currentUrl")
Log.d("MoltbotCanvas", "load url: $currentUrl")
}
wv.loadUrl(currentUrl)
}
@@ -106,7 +106,7 @@ class CanvasController {
val js = """
(() => {
try {
const api = globalThis.__clawdbot;
const api = globalThis.__moltbot;
if (!api) return;
if (typeof api.setDebugStatusEnabled === 'function') {
api.setDebugStatusEnabled(${if (enabled) "true" else "false"});

View File

@@ -63,7 +63,7 @@ class ScreenRecordManager(private val context: Context) {
val height = metrics.heightPixels
val densityDpi = metrics.densityDpi
val file = File.createTempFile("clawdbot-screen-", ".mp4")
val file = File.createTempFile("moltbot-screen-", ".mp4")
if (includeAudio) ensureMicPermission()
val recorder = createMediaRecorder()
@@ -90,7 +90,7 @@ class ScreenRecordManager(private val context: Context) {
val surface = recorder.surface
virtualDisplay =
projection.createVirtualDisplay(
"clawdbot-screen",
"moltbot-screen",
width,
height,
densityDpi,

View File

@@ -3,7 +3,7 @@ package com.clawdbot.android.protocol
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
object ClawdbotCanvasA2UIAction {
object MoltbotCanvasA2UIAction {
fun extractActionName(userAction: JsonObject): String? {
val name =
(userAction["name"] as? JsonPrimitive)
@@ -61,6 +61,6 @@ object ClawdbotCanvasA2UIAction {
val err = (error ?: "").replace("\\", "\\\\").replace("\"", "\\\"")
val okLiteral = if (ok) "true" else "false"
val idEscaped = actionId.replace("\\", "\\\\").replace("\"", "\\\"")
return "window.dispatchEvent(new CustomEvent('clawdbot:a2ui-action-status', { detail: { id: \"${idEscaped}\", ok: ${okLiteral}, error: \"${err}\" } }));"
return "window.dispatchEvent(new CustomEvent('moltbot:a2ui-action-status', { detail: { id: \"${idEscaped}\", ok: ${okLiteral}, error: \"${err}\" } }));"
}
}

View File

@@ -1,6 +1,6 @@
package com.clawdbot.android.protocol
enum class ClawdbotCapability(val rawValue: String) {
enum class MoltbotCapability(val rawValue: String) {
Canvas("canvas"),
Camera("camera"),
Screen("screen"),
@@ -9,7 +9,7 @@ enum class ClawdbotCapability(val rawValue: String) {
Location("location"),
}
enum class ClawdbotCanvasCommand(val rawValue: String) {
enum class MoltbotCanvasCommand(val rawValue: String) {
Present("canvas.present"),
Hide("canvas.hide"),
Navigate("canvas.navigate"),
@@ -22,7 +22,7 @@ enum class ClawdbotCanvasCommand(val rawValue: String) {
}
}
enum class ClawdbotCanvasA2UICommand(val rawValue: String) {
enum class MoltbotCanvasA2UICommand(val rawValue: String) {
Push("canvas.a2ui.push"),
PushJSONL("canvas.a2ui.pushJSONL"),
Reset("canvas.a2ui.reset"),
@@ -33,7 +33,7 @@ enum class ClawdbotCanvasA2UICommand(val rawValue: String) {
}
}
enum class ClawdbotCameraCommand(val rawValue: String) {
enum class MoltbotCameraCommand(val rawValue: String) {
Snap("camera.snap"),
Clip("camera.clip"),
;
@@ -43,7 +43,7 @@ enum class ClawdbotCameraCommand(val rawValue: String) {
}
}
enum class ClawdbotScreenCommand(val rawValue: String) {
enum class MoltbotScreenCommand(val rawValue: String) {
Record("screen.record"),
;
@@ -52,7 +52,7 @@ enum class ClawdbotScreenCommand(val rawValue: String) {
}
}
enum class ClawdbotSmsCommand(val rawValue: String) {
enum class MoltbotSmsCommand(val rawValue: String) {
Send("sms.send"),
;
@@ -61,7 +61,7 @@ enum class ClawdbotSmsCommand(val rawValue: String) {
}
}
enum class ClawdbotLocationCommand(val rawValue: String) {
enum class MoltbotLocationCommand(val rawValue: String) {
Get("location.get"),
;

View File

@@ -9,7 +9,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
@Composable
fun ClawdbotTheme(content: @Composable () -> Unit) {
fun MoltbotTheme(content: @Composable () -> Unit) {
val context = LocalContext.current
val isDark = isSystemInDarkTheme()
val colorScheme = if (isDark) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)

View File

@@ -333,7 +333,7 @@ private fun CanvasView(viewModel: MainViewModel, modifier: Modifier = Modifier)
disableForceDarkIfSupported(settings)
}
if (isDebuggable) {
Log.d("ClawdbotWebView", "userAgent: ${settings.userAgentString}")
Log.d("MoltbotWebView", "userAgent: ${settings.userAgentString}")
}
isScrollContainer = true
overScrollMode = View.OVER_SCROLL_IF_CONTENT_SCROLLS
@@ -348,7 +348,7 @@ private fun CanvasView(viewModel: MainViewModel, modifier: Modifier = Modifier)
) {
if (!isDebuggable) return
if (!request.isForMainFrame) return
Log.e("ClawdbotWebView", "onReceivedError: ${error.errorCode} ${error.description} ${request.url}")
Log.e("MoltbotWebView", "onReceivedError: ${error.errorCode} ${error.description} ${request.url}")
}
override fun onReceivedHttpError(
@@ -359,14 +359,14 @@ private fun CanvasView(viewModel: MainViewModel, modifier: Modifier = Modifier)
if (!isDebuggable) return
if (!request.isForMainFrame) return
Log.e(
"ClawdbotWebView",
"MoltbotWebView",
"onReceivedHttpError: ${errorResponse.statusCode} ${errorResponse.reasonPhrase} ${request.url}",
)
}
override fun onPageFinished(view: WebView, url: String?) {
if (isDebuggable) {
Log.d("ClawdbotWebView", "onPageFinished: $url")
Log.d("MoltbotWebView", "onPageFinished: $url")
}
viewModel.canvas.onPageFinished()
}
@@ -377,7 +377,7 @@ private fun CanvasView(viewModel: MainViewModel, modifier: Modifier = Modifier)
): Boolean {
if (isDebuggable) {
Log.e(
"ClawdbotWebView",
"MoltbotWebView",
"onRenderProcessGone didCrash=${detail.didCrash()} priorityAtExit=${detail.rendererPriorityAtExit()}",
)
}
@@ -390,7 +390,7 @@ private fun CanvasView(viewModel: MainViewModel, modifier: Modifier = Modifier)
if (!isDebuggable) return false
val msg = consoleMessage ?: return false
Log.d(
"ClawdbotWebView",
"MoltbotWebView",
"console ${msg.messageLevel()} @ ${msg.sourceId()}:${msg.lineNumber()} ${msg.message()}",
)
return false
@@ -428,7 +428,7 @@ private class CanvasA2UIActionBridge(private val onMessage: (String) -> Unit) {
}
companion object {
const val interfaceName: String = "clawdbotCanvasA2UIAction"
const val interfaceName: String = "moltbotCanvasA2UIAction"
}
}

View File

@@ -457,7 +457,7 @@ fun SettingsSheet(viewModel: MainViewModel) {
Column(verticalArrangement = Arrangement.spacedBy(6.dp), modifier = Modifier.fillMaxWidth()) {
ListItem(
headlineContent = { Text("Foreground Only") },
supportingContent = { Text("Listens only while Clawdbot is open.") },
supportingContent = { Text("Listens only while Moltbot is open.") },
trailingContent = {
RadioButton(
selected = voiceWakeMode == VoiceWakeMode.Foreground,
@@ -603,7 +603,7 @@ fun SettingsSheet(viewModel: MainViewModel) {
)
ListItem(
headlineContent = { Text("While Using") },
supportingContent = { Text("Only while Clawdbot is open.") },
supportingContent = { Text("Only while Moltbot is open.") },
trailingContent = {
RadioButton(
selected = locationMode == LocationMode.WhileUsing,
@@ -650,7 +650,7 @@ fun SettingsSheet(viewModel: MainViewModel) {
item {
ListItem(
headlineContent = { Text("Prevent Sleep") },
supportingContent = { Text("Keeps the screen awake while Clawdbot is open.") },
supportingContent = { Text("Keeps the screen awake while Moltbot is open.") },
trailingContent = { Switch(checked = preventSleep, onCheckedChange = viewModel::setPreventSleep) },
)
}

View File

@@ -1,3 +1,3 @@
<resources>
<string name="app_name">Clawdbot Node</string>
<string name="app_name">Moltbot Node</string>
</resources>

View File

@@ -1,5 +1,5 @@
<resources>
<style name="Theme.ClawdbotNode" parent="Theme.Material3.DayNight.NoActionBar">
<style name="Theme.MoltbotNode" parent="Theme.Material3.DayNight.NoActionBar">
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:windowLightStatusBar">false</item>

View File

@@ -4,7 +4,7 @@
<base-config cleartextTrafficPermitted="true" tools:ignore="InsecureBaseConfiguration" />
<!-- Allow HTTP for tailnet/local dev endpoints (e.g. canvas/background web). -->
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">clawdbot.internal</domain>
<domain includeSubdomains="true">moltbot.internal</domain>
</domain-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">ts.net</domain>

View File

@@ -12,7 +12,7 @@ class BonjourEscapesTest {
@Test
fun decodeDecodesDecimalEscapes() {
assertEquals("Clawdbot Gateway", BonjourEscapes.decode("Clawdbot\\032Gateway"))
assertEquals("Moltbot Gateway", BonjourEscapes.decode("Moltbot\\032Gateway"))
assertEquals("A B", BonjourEscapes.decode("A\\032B"))
assertEquals("Peter\u2019s Mac", BonjourEscapes.decode("Peter\\226\\128\\153s Mac"))
}

View File

@@ -5,24 +5,24 @@ import kotlinx.serialization.json.jsonObject
import org.junit.Assert.assertEquals
import org.junit.Test
class ClawdbotCanvasA2UIActionTest {
class MoltbotCanvasA2UIActionTest {
@Test
fun extractActionNameAcceptsNameOrAction() {
val nameObj = Json.parseToJsonElement("{\"name\":\"Hello\"}").jsonObject
assertEquals("Hello", ClawdbotCanvasA2UIAction.extractActionName(nameObj))
assertEquals("Hello", MoltbotCanvasA2UIAction.extractActionName(nameObj))
val actionObj = Json.parseToJsonElement("{\"action\":\"Wave\"}").jsonObject
assertEquals("Wave", ClawdbotCanvasA2UIAction.extractActionName(actionObj))
assertEquals("Wave", MoltbotCanvasA2UIAction.extractActionName(actionObj))
val fallbackObj =
Json.parseToJsonElement("{\"name\":\" \",\"action\":\"Fallback\"}").jsonObject
assertEquals("Fallback", ClawdbotCanvasA2UIAction.extractActionName(fallbackObj))
assertEquals("Fallback", MoltbotCanvasA2UIAction.extractActionName(fallbackObj))
}
@Test
fun formatAgentMessageMatchesSharedSpec() {
val msg =
ClawdbotCanvasA2UIAction.formatAgentMessage(
MoltbotCanvasA2UIAction.formatAgentMessage(
actionName = "Get Weather",
sessionKey = "main",
surfaceId = "main",
@@ -40,9 +40,9 @@ class ClawdbotCanvasA2UIActionTest {
@Test
fun jsDispatchA2uiStatusIsStable() {
val js = ClawdbotCanvasA2UIAction.jsDispatchA2UIActionStatus(actionId = "a1", ok = true, error = null)
val js = MoltbotCanvasA2UIAction.jsDispatchA2UIActionStatus(actionId = "a1", ok = true, error = null)
assertEquals(
"window.dispatchEvent(new CustomEvent('clawdbot:a2ui-action-status', { detail: { id: \"a1\", ok: true, error: \"\" } }));",
"window.dispatchEvent(new CustomEvent('moltbot:a2ui-action-status', { detail: { id: \"a1\", ok: true, error: \"\" } }));",
js,
)
}

View File

@@ -3,33 +3,33 @@ package com.clawdbot.android.protocol
import org.junit.Assert.assertEquals
import org.junit.Test
class ClawdbotProtocolConstantsTest {
class MoltbotProtocolConstantsTest {
@Test
fun canvasCommandsUseStableStrings() {
assertEquals("canvas.present", ClawdbotCanvasCommand.Present.rawValue)
assertEquals("canvas.hide", ClawdbotCanvasCommand.Hide.rawValue)
assertEquals("canvas.navigate", ClawdbotCanvasCommand.Navigate.rawValue)
assertEquals("canvas.eval", ClawdbotCanvasCommand.Eval.rawValue)
assertEquals("canvas.snapshot", ClawdbotCanvasCommand.Snapshot.rawValue)
assertEquals("canvas.present", MoltbotCanvasCommand.Present.rawValue)
assertEquals("canvas.hide", MoltbotCanvasCommand.Hide.rawValue)
assertEquals("canvas.navigate", MoltbotCanvasCommand.Navigate.rawValue)
assertEquals("canvas.eval", MoltbotCanvasCommand.Eval.rawValue)
assertEquals("canvas.snapshot", MoltbotCanvasCommand.Snapshot.rawValue)
}
@Test
fun a2uiCommandsUseStableStrings() {
assertEquals("canvas.a2ui.push", ClawdbotCanvasA2UICommand.Push.rawValue)
assertEquals("canvas.a2ui.pushJSONL", ClawdbotCanvasA2UICommand.PushJSONL.rawValue)
assertEquals("canvas.a2ui.reset", ClawdbotCanvasA2UICommand.Reset.rawValue)
assertEquals("canvas.a2ui.push", MoltbotCanvasA2UICommand.Push.rawValue)
assertEquals("canvas.a2ui.pushJSONL", MoltbotCanvasA2UICommand.PushJSONL.rawValue)
assertEquals("canvas.a2ui.reset", MoltbotCanvasA2UICommand.Reset.rawValue)
}
@Test
fun capabilitiesUseStableStrings() {
assertEquals("canvas", ClawdbotCapability.Canvas.rawValue)
assertEquals("camera", ClawdbotCapability.Camera.rawValue)
assertEquals("screen", ClawdbotCapability.Screen.rawValue)
assertEquals("voiceWake", ClawdbotCapability.VoiceWake.rawValue)
assertEquals("canvas", MoltbotCapability.Canvas.rawValue)
assertEquals("camera", MoltbotCapability.Camera.rawValue)
assertEquals("screen", MoltbotCapability.Screen.rawValue)
assertEquals("voiceWake", MoltbotCapability.VoiceWake.rawValue)
}
@Test
fun screenCommandsUseStableStrings() {
assertEquals("screen.record", ClawdbotScreenCommand.Record.rawValue)
assertEquals("screen.record", MoltbotScreenCommand.Record.rawValue)
}
}

View File

@@ -14,5 +14,5 @@ dependencyResolutionManagement {
}
}
rootProject.name = "ClawdbotNodeAndroid"
rootProject.name = "MoltbotNodeAndroid"
include(":app")

View File

@@ -1,5 +1,5 @@
import AVFoundation
import ClawdbotKit
import MoltbotKit
import Foundation
actor CameraController {
@@ -36,7 +36,7 @@ actor CameraController {
}
}
func snap(params: ClawdbotCameraSnapParams) async throws -> (
func snap(params: MoltbotCameraSnapParams) async throws -> (
format: String,
base64: String,
width: Int,
@@ -109,7 +109,7 @@ actor CameraController {
height: res.heightPx)
}
func clip(params: ClawdbotCameraClipParams) async throws -> (
func clip(params: MoltbotCameraClipParams) async throws -> (
format: String,
base64: String,
durationMs: Int,
@@ -161,9 +161,9 @@ actor CameraController {
await Self.warmUpCaptureSession()
let movURL = FileManager().temporaryDirectory
.appendingPathComponent("clawdbot-camera-\(UUID().uuidString).mov")
.appendingPathComponent("moltbot-camera-\(UUID().uuidString).mov")
let mp4URL = FileManager().temporaryDirectory
.appendingPathComponent("clawdbot-camera-\(UUID().uuidString).mp4")
.appendingPathComponent("moltbot-camera-\(UUID().uuidString).mp4")
defer {
try? FileManager().removeItem(at: movURL)
@@ -221,7 +221,7 @@ actor CameraController {
}
private nonisolated static func pickCamera(
facing: ClawdbotCameraFacing,
facing: MoltbotCameraFacing,
deviceId: String?) -> AVCaptureDevice?
{
if let deviceId, !deviceId.isEmpty {

View File

@@ -1,16 +1,16 @@
import ClawdbotChatUI
import ClawdbotKit
import MoltbotChatUI
import MoltbotKit
import SwiftUI
struct ChatSheet: View {
@Environment(\.dismiss) private var dismiss
@State private var viewModel: ClawdbotChatViewModel
@State private var viewModel: MoltbotChatViewModel
private let userAccent: Color?
init(gateway: GatewayNodeSession, sessionKey: String, userAccent: Color? = nil) {
let transport = IOSGatewayChatTransport(gateway: gateway)
self._viewModel = State(
initialValue: ClawdbotChatViewModel(
initialValue: MoltbotChatViewModel(
sessionKey: sessionKey,
transport: transport))
self.userAccent = userAccent
@@ -18,7 +18,7 @@ struct ChatSheet: View {
var body: some View {
NavigationStack {
ClawdbotChatView(
MoltbotChatView(
viewModel: self.viewModel,
showsSessionSwitcher: true,
userAccent: self.userAccent)

View File

@@ -1,9 +1,9 @@
import ClawdbotChatUI
import ClawdbotKit
import ClawdbotProtocol
import MoltbotChatUI
import MoltbotKit
import MoltbotProtocol
import Foundation
struct IOSGatewayChatTransport: ClawdbotChatTransport, Sendable {
struct IOSGatewayChatTransport: MoltbotChatTransport, Sendable {
private let gateway: GatewayNodeSession
init(gateway: GatewayNodeSession) {
@@ -20,7 +20,7 @@ struct IOSGatewayChatTransport: ClawdbotChatTransport, Sendable {
_ = try await self.gateway.request(method: "chat.abort", paramsJSON: json, timeoutSeconds: 10)
}
func listSessions(limit: Int?) async throws -> ClawdbotChatSessionsListResponse {
func listSessions(limit: Int?) async throws -> MoltbotChatSessionsListResponse {
struct Params: Codable {
var includeGlobal: Bool
var includeUnknown: Bool
@@ -29,7 +29,7 @@ struct IOSGatewayChatTransport: ClawdbotChatTransport, Sendable {
let data = try JSONEncoder().encode(Params(includeGlobal: true, includeUnknown: false, limit: limit))
let json = String(data: data, encoding: .utf8)
let res = try await self.gateway.request(method: "sessions.list", paramsJSON: json, timeoutSeconds: 15)
return try JSONDecoder().decode(ClawdbotChatSessionsListResponse.self, from: res)
return try JSONDecoder().decode(MoltbotChatSessionsListResponse.self, from: res)
}
func setActiveSessionKey(_ sessionKey: String) async throws {
@@ -39,12 +39,12 @@ struct IOSGatewayChatTransport: ClawdbotChatTransport, Sendable {
await self.gateway.sendEvent(event: "chat.subscribe", payloadJSON: json)
}
func requestHistory(sessionKey: String) async throws -> ClawdbotChatHistoryPayload {
func requestHistory(sessionKey: String) async throws -> MoltbotChatHistoryPayload {
struct Params: Codable { var sessionKey: String }
let data = try JSONEncoder().encode(Params(sessionKey: sessionKey))
let json = String(data: data, encoding: .utf8)
let res = try await self.gateway.request(method: "chat.history", paramsJSON: json, timeoutSeconds: 15)
return try JSONDecoder().decode(ClawdbotChatHistoryPayload.self, from: res)
return try JSONDecoder().decode(MoltbotChatHistoryPayload.self, from: res)
}
func sendMessage(
@@ -52,13 +52,13 @@ struct IOSGatewayChatTransport: ClawdbotChatTransport, Sendable {
message: String,
thinking: String,
idempotencyKey: String,
attachments: [ClawdbotChatAttachmentPayload]) async throws -> ClawdbotChatSendResponse
attachments: [MoltbotChatAttachmentPayload]) async throws -> MoltbotChatSendResponse
{
struct Params: Codable {
var sessionKey: String
var message: String
var thinking: String
var attachments: [ClawdbotChatAttachmentPayload]?
var attachments: [MoltbotChatAttachmentPayload]?
var timeoutMs: Int
var idempotencyKey: String
}
@@ -73,16 +73,16 @@ struct IOSGatewayChatTransport: ClawdbotChatTransport, Sendable {
let data = try JSONEncoder().encode(params)
let json = String(data: data, encoding: .utf8)
let res = try await self.gateway.request(method: "chat.send", paramsJSON: json, timeoutSeconds: 35)
return try JSONDecoder().decode(ClawdbotChatSendResponse.self, from: res)
return try JSONDecoder().decode(MoltbotChatSendResponse.self, from: res)
}
func requestHealth(timeoutMs: Int) async throws -> Bool {
let seconds = max(1, Int(ceil(Double(timeoutMs) / 1000.0)))
let res = try await self.gateway.request(method: "health", paramsJSON: nil, timeoutSeconds: seconds)
return (try? JSONDecoder().decode(ClawdbotGatewayHealthOK.self, from: res))?.ok ?? true
return (try? JSONDecoder().decode(MoltbotGatewayHealthOK.self, from: res))?.ok ?? true
}
func events() -> AsyncStream<ClawdbotChatTransportEvent> {
func events() -> AsyncStream<MoltbotChatTransportEvent> {
AsyncStream { continuation in
let task = Task {
let stream = await self.gateway.subscribeServerEvents()
@@ -97,13 +97,13 @@ struct IOSGatewayChatTransport: ClawdbotChatTransport, Sendable {
guard let payload = evt.payload else { break }
let ok = (try? GatewayPayloadDecoding.decode(
payload,
as: ClawdbotGatewayHealthOK.self))?.ok ?? true
as: MoltbotGatewayHealthOK.self))?.ok ?? true
continuation.yield(.health(ok: ok))
case "chat":
guard let payload = evt.payload else { break }
if let chatPayload = try? GatewayPayloadDecoding.decode(
payload,
as: ClawdbotChatEventPayload.self)
as: MoltbotChatEventPayload.self)
{
continuation.yield(.chat(chatPayload))
}
@@ -111,7 +111,7 @@ struct IOSGatewayChatTransport: ClawdbotChatTransport, Sendable {
guard let payload = evt.payload else { break }
if let agentPayload = try? GatewayPayloadDecoding.decode(
payload,
as: ClawdbotAgentEventPayload.self)
as: MoltbotAgentEventPayload.self)
{
continuation.yield(.agent(agentPayload))
}

View File

@@ -1,7 +1,7 @@
import SwiftUI
@main
struct ClawdbotApp: App {
struct MoltbotApp: App {
@State private var appModel: NodeAppModel
@State private var gatewayController: GatewayConnectionController
@Environment(\.scenePhase) private var scenePhase

View File

@@ -1,4 +1,4 @@
import ClawdbotKit
import MoltbotKit
import Darwin
import Foundation
import Network
@@ -283,7 +283,7 @@ final class GatewayConnectionController {
caps: self.currentCaps(),
commands: self.currentCommands(),
permissions: [:],
clientId: "clawdbot-ios",
clientId: "moltbot-ios",
clientMode: "node",
clientDisplayName: displayName)
}
@@ -304,51 +304,51 @@ final class GatewayConnectionController {
}
private func currentCaps() -> [String] {
var caps = [ClawdbotCapability.canvas.rawValue, ClawdbotCapability.screen.rawValue]
var caps = [MoltbotCapability.canvas.rawValue, MoltbotCapability.screen.rawValue]
// Default-on: if the key doesn't exist yet, treat it as enabled.
let cameraEnabled =
UserDefaults.standard.object(forKey: "camera.enabled") == nil
? true
: UserDefaults.standard.bool(forKey: "camera.enabled")
if cameraEnabled { caps.append(ClawdbotCapability.camera.rawValue) }
if cameraEnabled { caps.append(MoltbotCapability.camera.rawValue) }
let voiceWakeEnabled = UserDefaults.standard.bool(forKey: VoiceWakePreferences.enabledKey)
if voiceWakeEnabled { caps.append(ClawdbotCapability.voiceWake.rawValue) }
if voiceWakeEnabled { caps.append(MoltbotCapability.voiceWake.rawValue) }
let locationModeRaw = UserDefaults.standard.string(forKey: "location.enabledMode") ?? "off"
let locationMode = ClawdbotLocationMode(rawValue: locationModeRaw) ?? .off
if locationMode != .off { caps.append(ClawdbotCapability.location.rawValue) }
let locationMode = MoltbotLocationMode(rawValue: locationModeRaw) ?? .off
if locationMode != .off { caps.append(MoltbotCapability.location.rawValue) }
return caps
}
private func currentCommands() -> [String] {
var commands: [String] = [
ClawdbotCanvasCommand.present.rawValue,
ClawdbotCanvasCommand.hide.rawValue,
ClawdbotCanvasCommand.navigate.rawValue,
ClawdbotCanvasCommand.evalJS.rawValue,
ClawdbotCanvasCommand.snapshot.rawValue,
ClawdbotCanvasA2UICommand.push.rawValue,
ClawdbotCanvasA2UICommand.pushJSONL.rawValue,
ClawdbotCanvasA2UICommand.reset.rawValue,
ClawdbotScreenCommand.record.rawValue,
ClawdbotSystemCommand.notify.rawValue,
ClawdbotSystemCommand.which.rawValue,
ClawdbotSystemCommand.run.rawValue,
ClawdbotSystemCommand.execApprovalsGet.rawValue,
ClawdbotSystemCommand.execApprovalsSet.rawValue,
MoltbotCanvasCommand.present.rawValue,
MoltbotCanvasCommand.hide.rawValue,
MoltbotCanvasCommand.navigate.rawValue,
MoltbotCanvasCommand.evalJS.rawValue,
MoltbotCanvasCommand.snapshot.rawValue,
MoltbotCanvasA2UICommand.push.rawValue,
MoltbotCanvasA2UICommand.pushJSONL.rawValue,
MoltbotCanvasA2UICommand.reset.rawValue,
MoltbotScreenCommand.record.rawValue,
MoltbotSystemCommand.notify.rawValue,
MoltbotSystemCommand.which.rawValue,
MoltbotSystemCommand.run.rawValue,
MoltbotSystemCommand.execApprovalsGet.rawValue,
MoltbotSystemCommand.execApprovalsSet.rawValue,
]
let caps = Set(self.currentCaps())
if caps.contains(ClawdbotCapability.camera.rawValue) {
commands.append(ClawdbotCameraCommand.list.rawValue)
commands.append(ClawdbotCameraCommand.snap.rawValue)
commands.append(ClawdbotCameraCommand.clip.rawValue)
if caps.contains(MoltbotCapability.camera.rawValue) {
commands.append(MoltbotCameraCommand.list.rawValue)
commands.append(MoltbotCameraCommand.snap.rawValue)
commands.append(MoltbotCameraCommand.clip.rawValue)
}
if caps.contains(ClawdbotCapability.location.rawValue) {
commands.append(ClawdbotLocationCommand.get.rawValue)
if caps.contains(MoltbotCapability.location.rawValue) {
commands.append(MoltbotLocationCommand.get.rawValue)
}
return commands

View File

@@ -1,4 +1,4 @@
import ClawdbotKit
import MoltbotKit
import Foundation
import Network
import Observation
@@ -52,11 +52,11 @@ final class GatewayDiscoveryModel {
if !self.browsers.isEmpty { return }
self.appendDebugLog("start()")
for domain in ClawdbotBonjour.gatewayServiceDomains {
for domain in MoltbotBonjour.gatewayServiceDomains {
let params = NWParameters.tcp
params.includePeerToPeer = true
let browser = NWBrowser(
for: .bonjour(type: ClawdbotBonjour.gatewayServiceType, domain: domain),
for: .bonjour(type: MoltbotBonjour.gatewayServiceType, domain: domain),
using: params)
browser.stateUpdateHandler = { [weak self] state in
@@ -202,7 +202,7 @@ final class GatewayDiscoveryModel {
private static func prettifyInstanceName(_ decodedName: String) -> String {
let normalized = decodedName.split(whereSeparator: \.isWhitespace).joined(separator: " ")
let stripped = normalized.replacingOccurrences(of: " (Clawdbot)", with: "")
let stripped = normalized.replacingOccurrences(of: " (Moltbot)", with: "")
.replacingOccurrences(of: #"\s+\(\d+\)$"#, with: "", options: .regularExpression)
return stripped.trimmingCharacters(in: .whitespacesAndNewlines)
}

View File

@@ -5,7 +5,7 @@
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Clawdbot</string>
<string>Moltbot</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIconName</key>
@@ -29,20 +29,20 @@
</dict>
<key>NSBonjourServices</key>
<array>
<string>_clawdbot-gw._tcp</string>
<string>_moltbot-gw._tcp</string>
</array>
<key>NSCameraUsageDescription</key>
<string>Clawdbot can capture photos or short video clips when requested via the gateway.</string>
<string>Moltbot can capture photos or short video clips when requested via the gateway.</string>
<key>NSLocalNetworkUsageDescription</key>
<string>Clawdbot discovers and connects to your Clawdbot gateway on the local network.</string>
<string>Moltbot discovers and connects to your Moltbot gateway on the local network.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Clawdbot can share your location in the background when you enable Always.</string>
<string>Moltbot can share your location in the background when you enable Always.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Clawdbot uses your location when you allow location sharing.</string>
<string>Moltbot uses your location when you allow location sharing.</string>
<key>NSMicrophoneUsageDescription</key>
<string>Clawdbot needs microphone access for voice wake.</string>
<string>Moltbot needs microphone access for voice wake.</string>
<key>NSSpeechRecognitionUsageDescription</key>
<string>Clawdbot uses on-device speech recognition for voice wake.</string>
<string>Moltbot uses on-device speech recognition for voice wake.</string>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>

View File

@@ -1,4 +1,4 @@
import ClawdbotKit
import MoltbotKit
import CoreLocation
import Foundation
@@ -30,7 +30,7 @@ final class LocationService: NSObject, CLLocationManagerDelegate {
return .fullAccuracy
}
func ensureAuthorization(mode: ClawdbotLocationMode) async -> CLAuthorizationStatus {
func ensureAuthorization(mode: MoltbotLocationMode) async -> CLAuthorizationStatus {
guard CLLocationManager.locationServicesEnabled() else { return .denied }
let status = self.manager.authorizationStatus
@@ -53,8 +53,8 @@ final class LocationService: NSObject, CLLocationManagerDelegate {
}
func currentLocation(
params: ClawdbotLocationGetParams,
desiredAccuracy: ClawdbotLocationAccuracy,
params: MoltbotLocationGetParams,
desiredAccuracy: MoltbotLocationAccuracy,
maxAgeMs: Int?,
timeoutMs: Int?) async throws -> CLLocation
{
@@ -93,7 +93,7 @@ final class LocationService: NSObject, CLLocationManagerDelegate {
try await AsyncTimeout.withTimeoutMs(timeoutMs: timeoutMs, onTimeout: { Error.timeout }, operation: operation)
}
private static func accuracyValue(_ accuracy: ClawdbotLocationAccuracy) -> CLLocationAccuracy {
private static func accuracyValue(_ accuracy: MoltbotLocationAccuracy) -> CLLocationAccuracy {
switch accuracy {
case .coarse:
kCLLocationAccuracyKilometer

View File

@@ -1,4 +1,4 @@
import ClawdbotKit
import MoltbotKit
import Network
import Observation
import SwiftUI
@@ -90,7 +90,7 @@ final class NodeAppModel {
}()
guard !userAction.isEmpty else { return }
guard let name = ClawdbotCanvasA2UIAction.extractActionName(userAction) else { return }
guard let name = MoltbotCanvasA2UIAction.extractActionName(userAction) else { return }
let actionId: String = {
let id = (userAction["id"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
return id.isEmpty ? UUID().uuidString : id
@@ -109,15 +109,15 @@ final class NodeAppModel {
let host = UserDefaults.standard.string(forKey: "node.displayName") ?? UIDevice.current.name
let instanceId = (UserDefaults.standard.string(forKey: "node.instanceId") ?? "ios-node").lowercased()
let contextJSON = ClawdbotCanvasA2UIAction.compactJSON(userAction["context"])
let contextJSON = MoltbotCanvasA2UIAction.compactJSON(userAction["context"])
let sessionKey = self.mainSessionKey
let messageContext = ClawdbotCanvasA2UIAction.AgentMessageContext(
let messageContext = MoltbotCanvasA2UIAction.AgentMessageContext(
actionName: name,
session: .init(key: sessionKey, surfaceId: surfaceId),
component: .init(id: sourceComponentId, host: host, instanceId: instanceId),
contextJSON: contextJSON)
let message = ClawdbotCanvasA2UIAction.formatAgentMessage(messageContext)
let message = MoltbotCanvasA2UIAction.formatAgentMessage(messageContext)
let ok: Bool
var errorText: String?
@@ -142,7 +142,7 @@ final class NodeAppModel {
}
}
let js = ClawdbotCanvasA2UIAction.jsDispatchA2UIActionStatus(actionId: actionId, ok: ok, error: errorText)
let js = MoltbotCanvasA2UIAction.jsDispatchA2UIActionStatus(actionId: actionId, ok: ok, error: errorText)
do {
_ = try await self.screen.eval(javaScript: js)
} catch {
@@ -154,7 +154,7 @@ final class NodeAppModel {
guard let raw = await self.gateway.currentCanvasHostUrl() else { return nil }
let trimmed = raw.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmed.isEmpty, let base = URL(string: trimmed) else { return nil }
return base.appendingPathComponent("__clawdbot__/a2ui/").absoluteString + "?platform=ios"
return base.appendingPathComponent("__moltbot__/a2ui/").absoluteString + "?platform=ios"
}
private func showA2UIOnConnectIfNeeded() async {
@@ -190,7 +190,7 @@ final class NodeAppModel {
self.talkMode.setEnabled(enabled)
}
func requestLocationPermissions(mode: ClawdbotLocationMode) async -> Bool {
func requestLocationPermissions(mode: MoltbotLocationMode) async -> Bool {
guard mode != .off else { return true }
let status = await self.locationService.ensureAuthorization(mode: mode)
switch status {
@@ -272,7 +272,7 @@ final class NodeAppModel {
return BridgeInvokeResponse(
id: req.id,
ok: false,
error: ClawdbotNodeError(
error: MoltbotNodeError(
code: .unavailable,
message: "UNAVAILABLE: node not ready"))
}
@@ -487,7 +487,7 @@ final class NodeAppModel {
}
// iOS gateway forwards to the gateway; no local auth prompts here.
// (Key-based unattended auth is handled on macOS for clawdbot:// links.)
// (Key-based unattended auth is handled on macOS for moltbot:// links.)
let data = try JSONEncoder().encode(link)
guard let json = String(bytes: data, encoding: .utf8) else {
throw NSError(domain: "NodeAppModel", code: 2, userInfo: [
@@ -508,7 +508,7 @@ final class NodeAppModel {
return BridgeInvokeResponse(
id: req.id,
ok: false,
error: ClawdbotNodeError(
error: MoltbotNodeError(
code: .backgroundUnavailable,
message: "NODE_BACKGROUND_UNAVAILABLE: canvas/camera/screen commands require foreground"))
}
@@ -517,36 +517,36 @@ final class NodeAppModel {
return BridgeInvokeResponse(
id: req.id,
ok: false,
error: ClawdbotNodeError(
error: MoltbotNodeError(
code: .unavailable,
message: "CAMERA_DISABLED: enable Camera in iOS Settings → Camera → Allow Camera"))
}
do {
switch command {
case ClawdbotLocationCommand.get.rawValue:
case MoltbotLocationCommand.get.rawValue:
return try await self.handleLocationInvoke(req)
case ClawdbotCanvasCommand.present.rawValue,
ClawdbotCanvasCommand.hide.rawValue,
ClawdbotCanvasCommand.navigate.rawValue,
ClawdbotCanvasCommand.evalJS.rawValue,
ClawdbotCanvasCommand.snapshot.rawValue:
case MoltbotCanvasCommand.present.rawValue,
MoltbotCanvasCommand.hide.rawValue,
MoltbotCanvasCommand.navigate.rawValue,
MoltbotCanvasCommand.evalJS.rawValue,
MoltbotCanvasCommand.snapshot.rawValue:
return try await self.handleCanvasInvoke(req)
case ClawdbotCanvasA2UICommand.reset.rawValue,
ClawdbotCanvasA2UICommand.push.rawValue,
ClawdbotCanvasA2UICommand.pushJSONL.rawValue:
case MoltbotCanvasA2UICommand.reset.rawValue,
MoltbotCanvasA2UICommand.push.rawValue,
MoltbotCanvasA2UICommand.pushJSONL.rawValue:
return try await self.handleCanvasA2UIInvoke(req)
case ClawdbotCameraCommand.list.rawValue,
ClawdbotCameraCommand.snap.rawValue,
ClawdbotCameraCommand.clip.rawValue:
case MoltbotCameraCommand.list.rawValue,
MoltbotCameraCommand.snap.rawValue,
MoltbotCameraCommand.clip.rawValue:
return try await self.handleCameraInvoke(req)
case ClawdbotScreenCommand.record.rawValue:
case MoltbotScreenCommand.record.rawValue:
return try await self.handleScreenRecordInvoke(req)
default:
return BridgeInvokeResponse(
id: req.id,
ok: false,
error: ClawdbotNodeError(code: .invalidRequest, message: "INVALID_REQUEST: unknown command"))
error: MoltbotNodeError(code: .invalidRequest, message: "INVALID_REQUEST: unknown command"))
}
} catch {
if command.hasPrefix("camera.") {
@@ -556,7 +556,7 @@ final class NodeAppModel {
return BridgeInvokeResponse(
id: req.id,
ok: false,
error: ClawdbotNodeError(code: .unavailable, message: error.localizedDescription))
error: MoltbotNodeError(code: .unavailable, message: error.localizedDescription))
}
}
@@ -570,7 +570,7 @@ final class NodeAppModel {
return BridgeInvokeResponse(
id: req.id,
ok: false,
error: ClawdbotNodeError(
error: MoltbotNodeError(
code: .unavailable,
message: "LOCATION_DISABLED: enable Location in Settings"))
}
@@ -578,12 +578,12 @@ final class NodeAppModel {
return BridgeInvokeResponse(
id: req.id,
ok: false,
error: ClawdbotNodeError(
error: MoltbotNodeError(
code: .backgroundUnavailable,
message: "LOCATION_BACKGROUND_UNAVAILABLE: background location requires Always"))
}
let params = (try? Self.decodeParams(ClawdbotLocationGetParams.self, from: req.paramsJSON)) ??
ClawdbotLocationGetParams()
let params = (try? Self.decodeParams(MoltbotLocationGetParams.self, from: req.paramsJSON)) ??
MoltbotLocationGetParams()
let desired = params.desiredAccuracy ??
(self.isLocationPreciseEnabled() ? .precise : .balanced)
let status = self.locationService.authorizationStatus()
@@ -591,7 +591,7 @@ final class NodeAppModel {
return BridgeInvokeResponse(
id: req.id,
ok: false,
error: ClawdbotNodeError(
error: MoltbotNodeError(
code: .unavailable,
message: "LOCATION_PERMISSION_REQUIRED: grant Location permission"))
}
@@ -599,7 +599,7 @@ final class NodeAppModel {
return BridgeInvokeResponse(
id: req.id,
ok: false,
error: ClawdbotNodeError(
error: MoltbotNodeError(
code: .unavailable,
message: "LOCATION_PERMISSION_REQUIRED: enable Always for background access"))
}
@@ -609,7 +609,7 @@ final class NodeAppModel {
maxAgeMs: params.maxAgeMs,
timeoutMs: params.timeoutMs)
let isPrecise = self.locationService.accuracyAuthorization() == .fullAccuracy
let payload = ClawdbotLocationPayload(
let payload = MoltbotLocationPayload(
lat: location.coordinate.latitude,
lon: location.coordinate.longitude,
accuracyMeters: location.horizontalAccuracy,
@@ -625,9 +625,9 @@ final class NodeAppModel {
private func handleCanvasInvoke(_ req: BridgeInvokeRequest) async throws -> BridgeInvokeResponse {
switch req.command {
case ClawdbotCanvasCommand.present.rawValue:
let params = (try? Self.decodeParams(ClawdbotCanvasPresentParams.self, from: req.paramsJSON)) ??
ClawdbotCanvasPresentParams()
case MoltbotCanvasCommand.present.rawValue:
let params = (try? Self.decodeParams(MoltbotCanvasPresentParams.self, from: req.paramsJSON)) ??
MoltbotCanvasPresentParams()
let url = params.url?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
if url.isEmpty {
self.screen.showDefaultCanvas()
@@ -635,19 +635,19 @@ final class NodeAppModel {
self.screen.navigate(to: url)
}
return BridgeInvokeResponse(id: req.id, ok: true)
case ClawdbotCanvasCommand.hide.rawValue:
case MoltbotCanvasCommand.hide.rawValue:
return BridgeInvokeResponse(id: req.id, ok: true)
case ClawdbotCanvasCommand.navigate.rawValue:
let params = try Self.decodeParams(ClawdbotCanvasNavigateParams.self, from: req.paramsJSON)
case MoltbotCanvasCommand.navigate.rawValue:
let params = try Self.decodeParams(MoltbotCanvasNavigateParams.self, from: req.paramsJSON)
self.screen.navigate(to: params.url)
return BridgeInvokeResponse(id: req.id, ok: true)
case ClawdbotCanvasCommand.evalJS.rawValue:
let params = try Self.decodeParams(ClawdbotCanvasEvalParams.self, from: req.paramsJSON)
case MoltbotCanvasCommand.evalJS.rawValue:
let params = try Self.decodeParams(MoltbotCanvasEvalParams.self, from: req.paramsJSON)
let result = try await self.screen.eval(javaScript: params.javaScript)
let payload = try Self.encodePayload(["result": result])
return BridgeInvokeResponse(id: req.id, ok: true, payloadJSON: payload)
case ClawdbotCanvasCommand.snapshot.rawValue:
let params = try? Self.decodeParams(ClawdbotCanvasSnapshotParams.self, from: req.paramsJSON)
case MoltbotCanvasCommand.snapshot.rawValue:
let params = try? Self.decodeParams(MoltbotCanvasSnapshotParams.self, from: req.paramsJSON)
let format = params?.format ?? .jpeg
let maxWidth: CGFloat? = {
if let raw = params?.maxWidth, raw > 0 { return CGFloat(raw) }
@@ -671,19 +671,19 @@ final class NodeAppModel {
return BridgeInvokeResponse(
id: req.id,
ok: false,
error: ClawdbotNodeError(code: .invalidRequest, message: "INVALID_REQUEST: unknown command"))
error: MoltbotNodeError(code: .invalidRequest, message: "INVALID_REQUEST: unknown command"))
}
}
private func handleCanvasA2UIInvoke(_ req: BridgeInvokeRequest) async throws -> BridgeInvokeResponse {
let command = req.command
switch command {
case ClawdbotCanvasA2UICommand.reset.rawValue:
case MoltbotCanvasA2UICommand.reset.rawValue:
guard let a2uiUrl = await self.resolveA2UIHostURL() else {
return BridgeInvokeResponse(
id: req.id,
ok: false,
error: ClawdbotNodeError(
error: MoltbotNodeError(
code: .unavailable,
message: "A2UI_HOST_NOT_CONFIGURED: gateway did not advertise canvas host"))
}
@@ -692,31 +692,31 @@ final class NodeAppModel {
return BridgeInvokeResponse(
id: req.id,
ok: false,
error: ClawdbotNodeError(
error: MoltbotNodeError(
code: .unavailable,
message: "A2UI_HOST_UNAVAILABLE: A2UI host not reachable"))
}
let json = try await self.screen.eval(javaScript: """
(() => {
if (!globalThis.clawdbotA2UI) return JSON.stringify({ ok: false, error: "missing clawdbotA2UI" });
if (!globalThis.clawdbotA2UI) return JSON.stringify({ ok: false, error: "missing moltbotA2UI" });
return JSON.stringify(globalThis.clawdbotA2UI.reset());
})()
""")
return BridgeInvokeResponse(id: req.id, ok: true, payloadJSON: json)
case ClawdbotCanvasA2UICommand.push.rawValue, ClawdbotCanvasA2UICommand.pushJSONL.rawValue:
case MoltbotCanvasA2UICommand.push.rawValue, MoltbotCanvasA2UICommand.pushJSONL.rawValue:
let messages: [AnyCodable]
if command == ClawdbotCanvasA2UICommand.pushJSONL.rawValue {
let params = try Self.decodeParams(ClawdbotCanvasA2UIPushJSONLParams.self, from: req.paramsJSON)
messages = try ClawdbotCanvasA2UIJSONL.decodeMessagesFromJSONL(params.jsonl)
if command == MoltbotCanvasA2UICommand.pushJSONL.rawValue {
let params = try Self.decodeParams(MoltbotCanvasA2UIPushJSONLParams.self, from: req.paramsJSON)
messages = try MoltbotCanvasA2UIJSONL.decodeMessagesFromJSONL(params.jsonl)
} else {
do {
let params = try Self.decodeParams(ClawdbotCanvasA2UIPushParams.self, from: req.paramsJSON)
let params = try Self.decodeParams(MoltbotCanvasA2UIPushParams.self, from: req.paramsJSON)
messages = params.messages
} catch {
// Be forgiving: some clients still send JSONL payloads to `canvas.a2ui.push`.
let params = try Self.decodeParams(ClawdbotCanvasA2UIPushJSONLParams.self, from: req.paramsJSON)
messages = try ClawdbotCanvasA2UIJSONL.decodeMessagesFromJSONL(params.jsonl)
let params = try Self.decodeParams(MoltbotCanvasA2UIPushJSONLParams.self, from: req.paramsJSON)
messages = try MoltbotCanvasA2UIJSONL.decodeMessagesFromJSONL(params.jsonl)
}
}
@@ -724,7 +724,7 @@ final class NodeAppModel {
return BridgeInvokeResponse(
id: req.id,
ok: false,
error: ClawdbotNodeError(
error: MoltbotNodeError(
code: .unavailable,
message: "A2UI_HOST_NOT_CONFIGURED: gateway did not advertise canvas host"))
}
@@ -733,16 +733,16 @@ final class NodeAppModel {
return BridgeInvokeResponse(
id: req.id,
ok: false,
error: ClawdbotNodeError(
error: MoltbotNodeError(
code: .unavailable,
message: "A2UI_HOST_UNAVAILABLE: A2UI host not reachable"))
}
let messagesJSON = try ClawdbotCanvasA2UIJSONL.encodeMessagesJSONArray(messages)
let messagesJSON = try MoltbotCanvasA2UIJSONL.encodeMessagesJSONArray(messages)
let js = """
(() => {
try {
if (!globalThis.clawdbotA2UI) return JSON.stringify({ ok: false, error: "missing clawdbotA2UI" });
if (!globalThis.clawdbotA2UI) return JSON.stringify({ ok: false, error: "missing moltbotA2UI" });
const messages = \(messagesJSON);
return JSON.stringify(globalThis.clawdbotA2UI.applyMessages(messages));
} catch (e) {
@@ -756,24 +756,24 @@ final class NodeAppModel {
return BridgeInvokeResponse(
id: req.id,
ok: false,
error: ClawdbotNodeError(code: .invalidRequest, message: "INVALID_REQUEST: unknown command"))
error: MoltbotNodeError(code: .invalidRequest, message: "INVALID_REQUEST: unknown command"))
}
}
private func handleCameraInvoke(_ req: BridgeInvokeRequest) async throws -> BridgeInvokeResponse {
switch req.command {
case ClawdbotCameraCommand.list.rawValue:
case MoltbotCameraCommand.list.rawValue:
let devices = await self.camera.listDevices()
struct Payload: Codable {
var devices: [CameraController.CameraDeviceInfo]
}
let payload = try Self.encodePayload(Payload(devices: devices))
return BridgeInvokeResponse(id: req.id, ok: true, payloadJSON: payload)
case ClawdbotCameraCommand.snap.rawValue:
case MoltbotCameraCommand.snap.rawValue:
self.showCameraHUD(text: "Taking photo…", kind: .photo)
self.triggerCameraFlash()
let params = (try? Self.decodeParams(ClawdbotCameraSnapParams.self, from: req.paramsJSON)) ??
ClawdbotCameraSnapParams()
let params = (try? Self.decodeParams(MoltbotCameraSnapParams.self, from: req.paramsJSON)) ??
MoltbotCameraSnapParams()
let res = try await self.camera.snap(params: params)
struct Payload: Codable {
@@ -789,9 +789,9 @@ final class NodeAppModel {
height: res.height))
self.showCameraHUD(text: "Photo captured", kind: .success, autoHideSeconds: 1.6)
return BridgeInvokeResponse(id: req.id, ok: true, payloadJSON: payload)
case ClawdbotCameraCommand.clip.rawValue:
let params = (try? Self.decodeParams(ClawdbotCameraClipParams.self, from: req.paramsJSON)) ??
ClawdbotCameraClipParams()
case MoltbotCameraCommand.clip.rawValue:
let params = (try? Self.decodeParams(MoltbotCameraClipParams.self, from: req.paramsJSON)) ??
MoltbotCameraClipParams()
let suspended = (params.includeAudio ?? true) ? self.voiceWake.suspendForExternalAudioCapture() : false
defer { self.voiceWake.resumeAfterExternalAudioCapture(wasSuspended: suspended) }
@@ -816,13 +816,13 @@ final class NodeAppModel {
return BridgeInvokeResponse(
id: req.id,
ok: false,
error: ClawdbotNodeError(code: .invalidRequest, message: "INVALID_REQUEST: unknown command"))
error: MoltbotNodeError(code: .invalidRequest, message: "INVALID_REQUEST: unknown command"))
}
}
private func handleScreenRecordInvoke(_ req: BridgeInvokeRequest) async throws -> BridgeInvokeResponse {
let params = (try? Self.decodeParams(ClawdbotScreenRecordParams.self, from: req.paramsJSON)) ??
ClawdbotScreenRecordParams()
let params = (try? Self.decodeParams(MoltbotScreenRecordParams.self, from: req.paramsJSON)) ??
MoltbotScreenRecordParams()
if let format = params.format, format.lowercased() != "mp4" {
throw NSError(domain: "Screen", code: 30, userInfo: [
NSLocalizedDescriptionKey: "INVALID_REQUEST: screen format must be mp4",
@@ -860,9 +860,9 @@ final class NodeAppModel {
}
private extension NodeAppModel {
func locationMode() -> ClawdbotLocationMode {
func locationMode() -> MoltbotLocationMode {
let raw = UserDefaults.standard.string(forKey: "location.enabledMode") ?? "off"
return ClawdbotLocationMode(rawValue: raw) ?? .off
return MoltbotLocationMode(rawValue: raw) ?? .off
}
func isLocationPreciseEnabled() -> Bool {

View File

@@ -1,4 +1,4 @@
import ClawdbotKit
import MoltbotKit
import Observation
import SwiftUI
import WebKit
@@ -13,7 +13,7 @@ final class ScreenController {
var urlString: String = ""
var errorText: String?
/// Callback invoked when a clawdbot:// deep link is tapped in the canvas
/// Callback invoked when a moltbot:// deep link is tapped in the canvas
var onDeepLink: ((URL) -> Void)?
/// Callback invoked when the user clicks an A2UI action (e.g. button) inside the canvas web UI.
@@ -101,7 +101,7 @@ final class ScreenController {
let js = """
(() => {
try {
const api = globalThis.__clawdbot;
const api = globalThis.__moltbot;
if (!api) return;
if (typeof api.setDebugStatusEnabled === 'function') {
api.setDebugStatusEnabled(\(enabled ? "true" : "false"));
@@ -184,7 +184,7 @@ final class ScreenController {
func snapshotBase64(
maxWidth: CGFloat? = nil,
format: ClawdbotCanvasSnapshotFormat,
format: MoltbotCanvasSnapshotFormat,
quality: Double? = nil) async throws -> String
{
let config = WKSnapshotConfiguration()
@@ -229,7 +229,7 @@ final class ScreenController {
subdirectory: String)
-> URL?
{
let bundle = ClawdbotKitResources.bundle
let bundle = MoltbotKitResources.bundle
return bundle.url(forResource: name, withExtension: ext, subdirectory: subdirectory)
?? bundle.url(forResource: name, withExtension: ext)
}
@@ -342,7 +342,7 @@ extension Double {
// MARK: - Navigation Delegate
/// Handles navigation policy to intercept clawdbot:// deep links from canvas
/// Handles navigation policy to intercept moltbot:// deep links from canvas
@MainActor
private final class ScreenNavigationDelegate: NSObject, WKNavigationDelegate {
weak var controller: ScreenController?
@@ -357,8 +357,8 @@ private final class ScreenNavigationDelegate: NSObject, WKNavigationDelegate {
return
}
// Intercept clawdbot:// deep links
if url.scheme == "clawdbot" {
// Intercept moltbot:// deep links
if url.scheme == "moltbot" {
decisionHandler(.cancel)
self.controller?.onDeepLink?(url)
return
@@ -386,7 +386,7 @@ private final class ScreenNavigationDelegate: NSObject, WKNavigationDelegate {
}
private final class CanvasA2UIActionMessageHandler: NSObject, WKScriptMessageHandler {
static let messageName = "clawdbotCanvasA2UIAction"
static let messageName = "moltbotCanvasA2UIAction"
static let legacyMessageNames = ["canvas", "a2ui", "userAction", "action"]
static let handlerNames = [messageName] + legacyMessageNames

View File

@@ -105,7 +105,7 @@ final class ScreenRecordService: @unchecked Sendable {
return URL(fileURLWithPath: outPath)
}
return FileManager().temporaryDirectory
.appendingPathComponent("clawdbot-screen-record-\(UUID().uuidString).mp4")
.appendingPathComponent("moltbot-screen-record-\(UUID().uuidString).mp4")
}
private func startCapture(

View File

@@ -1,4 +1,4 @@
import ClawdbotKit
import MoltbotKit
import SwiftUI
struct ScreenTab: View {

View File

@@ -1,4 +1,4 @@
import ClawdbotKit
import MoltbotKit
import SwiftUI
import WebKit

View File

@@ -1,4 +1,4 @@
import ClawdbotKit
import MoltbotKit
import Network
import Observation
import SwiftUI
@@ -23,7 +23,7 @@ struct SettingsTab: View {
@AppStorage("talk.enabled") private var talkEnabled: Bool = false
@AppStorage("talk.button.enabled") private var talkButtonEnabled: Bool = true
@AppStorage("camera.enabled") private var cameraEnabled: Bool = true
@AppStorage("location.enabledMode") private var locationEnabledModeRaw: String = ClawdbotLocationMode.off.rawValue
@AppStorage("location.enabledMode") private var locationEnabledModeRaw: String = MoltbotLocationMode.off.rawValue
@AppStorage("location.preciseEnabled") private var locationPreciseEnabled: Bool = true
@AppStorage("screen.preventSleep") private var preventSleep: Bool = true
@AppStorage("gateway.preferredStableID") private var preferredGatewayStableID: String = ""
@@ -37,7 +37,7 @@ struct SettingsTab: View {
@State private var connectStatus = ConnectStatusStore()
@State private var connectingGatewayID: String?
@State private var localIPAddress: String?
@State private var lastLocationModeRaw: String = ClawdbotLocationMode.off.rawValue
@State private var lastLocationModeRaw: String = MoltbotLocationMode.off.rawValue
@State private var gatewayToken: String = ""
@State private var gatewayPassword: String = ""
@@ -197,9 +197,9 @@ struct SettingsTab: View {
Section("Location") {
Picker("Location Access", selection: self.$locationEnabledModeRaw) {
Text("Off").tag(ClawdbotLocationMode.off.rawValue)
Text("While Using").tag(ClawdbotLocationMode.whileUsing.rawValue)
Text("Always").tag(ClawdbotLocationMode.always.rawValue)
Text("Off").tag(MoltbotLocationMode.off.rawValue)
Text("While Using").tag(MoltbotLocationMode.whileUsing.rawValue)
Text("Always").tag(MoltbotLocationMode.always.rawValue)
}
.pickerStyle(.segmented)
@@ -213,7 +213,7 @@ struct SettingsTab: View {
Section("Screen") {
Toggle("Prevent Sleep", isOn: self.$preventSleep)
Text("Keeps the screen awake while Clawdbot is open.")
Text("Keeps the screen awake while Moltbot is open.")
.font(.footnote)
.foregroundStyle(.secondary)
}
@@ -261,7 +261,7 @@ struct SettingsTab: View {
.onChange(of: self.locationEnabledModeRaw) { _, newValue in
let previous = self.lastLocationModeRaw
self.lastLocationModeRaw = newValue
guard let mode = ClawdbotLocationMode(rawValue: newValue) else { return }
guard let mode = MoltbotLocationMode(rawValue: newValue) else { return }
Task {
let granted = await self.appModel.requestLocationPermissions(mode: mode)
if !granted {
@@ -336,8 +336,8 @@ struct SettingsTab: View {
return "iOS \(v.majorVersion).\(v.minorVersion).\(v.patchVersion)"
}
private var locationMode: ClawdbotLocationMode {
ClawdbotLocationMode(rawValue: self.locationEnabledModeRaw) ?? .off
private var locationMode: MoltbotLocationMode {
MoltbotLocationMode(rawValue: self.locationEnabledModeRaw) ?? .off
}
private func appVersion() -> String {

View File

@@ -36,7 +36,7 @@ struct VoiceWakeWordsSettingsView: View {
Text("Wake Words")
} footer: {
Text(
"Clawdbot reacts when any trigger appears in a transcription. "
"Moltbot reacts when any trigger appears in a transcription. "
+ "Keep them short to avoid false positives.")
}
}

View File

@@ -1,6 +1,6 @@
import AVFAudio
import ClawdbotKit
import ClawdbotProtocol
import MoltbotKit
import MoltbotProtocol
import Foundation
import Observation
import OSLog

View File

@@ -1,6 +1,6 @@
import SwiftUI
import Testing
@testable import Clawdbot
@testable import Moltbot
@Suite struct AppCoverageTests {
@Test @MainActor func nodeAppModelUpdatesBackgroundedState() {

View File

@@ -1,5 +1,5 @@
import Testing
@testable import Clawdbot
@testable import Moltbot
@Suite struct CameraControllerClampTests {
@Test func clampQualityDefaultsAndBounds() {

View File

@@ -1,5 +1,5 @@
import Testing
@testable import Clawdbot
@testable import Moltbot
@Suite struct CameraControllerErrorTests {
@Test func errorDescriptionsAreStable() {

View File

@@ -1,15 +1,15 @@
import ClawdbotKit
import MoltbotKit
import Foundation
import Testing
@Suite struct DeepLinkParserTests {
@Test func parseRejectsUnknownHost() {
let url = URL(string: "clawdbot://nope?message=hi")!
let url = URL(string: "moltbot://nope?message=hi")!
#expect(DeepLinkParser.parse(url) == nil)
}
@Test func parseHostIsCaseInsensitive() {
let url = URL(string: "clawdbot://AGENT?message=Hello")!
let url = URL(string: "moltbot://AGENT?message=Hello")!
#expect(DeepLinkParser.parse(url) == .agent(.init(
message: "Hello",
sessionKey: nil,
@@ -21,19 +21,19 @@ import Testing
key: nil)))
}
@Test func parseRejectsNonClawdbotScheme() {
@Test func parseRejectsNonMoltbotScheme() {
let url = URL(string: "https://example.com/agent?message=hi")!
#expect(DeepLinkParser.parse(url) == nil)
}
@Test func parseRejectsEmptyMessage() {
let url = URL(string: "clawdbot://agent?message=%20%20%0A")!
let url = URL(string: "moltbot://agent?message=%20%20%0A")!
#expect(DeepLinkParser.parse(url) == nil)
}
@Test func parseAgentLinkParsesCommonFields() {
let url =
URL(string: "clawdbot://agent?message=Hello&deliver=1&sessionKey=node-test&thinking=low&timeoutSeconds=30")!
URL(string: "moltbot://agent?message=Hello&deliver=1&sessionKey=node-test&thinking=low&timeoutSeconds=30")!
#expect(
DeepLinkParser.parse(url) == .agent(
.init(
@@ -50,7 +50,7 @@ import Testing
@Test func parseAgentLinkParsesTargetRoutingFields() {
let url =
URL(
string: "clawdbot://agent?message=Hello%20World&deliver=1&to=%2B15551234567&channel=whatsapp&key=secret")!
string: "moltbot://agent?message=Hello%20World&deliver=1&to=%2B15551234567&channel=whatsapp&key=secret")!
#expect(
DeepLinkParser.parse(url) == .agent(
.init(
@@ -65,7 +65,7 @@ import Testing
}
@Test func parseRejectsNegativeTimeoutSeconds() {
let url = URL(string: "clawdbot://agent?message=Hello&timeoutSeconds=-1")!
let url = URL(string: "moltbot://agent?message=Hello&timeoutSeconds=-1")!
#expect(DeepLinkParser.parse(url) == .agent(.init(
message: "Hello",
sessionKey: nil,

View File

@@ -1,8 +1,8 @@
import ClawdbotKit
import MoltbotKit
import Foundation
import Testing
import UIKit
@testable import Clawdbot
@testable import Moltbot
private func withUserDefaults<T>(_ updates: [String: Any?], _ body: () throws -> T) rethrows -> T {
let defaults = UserDefaults.standard
@@ -49,31 +49,31 @@ private func withUserDefaults<T>(_ updates: [String: Any?], _ body: () throws ->
"node.instanceId": "ios-test",
"node.displayName": "Test Node",
"camera.enabled": true,
"location.enabledMode": ClawdbotLocationMode.always.rawValue,
"location.enabledMode": MoltbotLocationMode.always.rawValue,
VoiceWakePreferences.enabledKey: true,
]) {
let appModel = NodeAppModel()
let controller = GatewayConnectionController(appModel: appModel, startDiscovery: false)
let caps = Set(controller._test_currentCaps())
#expect(caps.contains(ClawdbotCapability.canvas.rawValue))
#expect(caps.contains(ClawdbotCapability.screen.rawValue))
#expect(caps.contains(ClawdbotCapability.camera.rawValue))
#expect(caps.contains(ClawdbotCapability.location.rawValue))
#expect(caps.contains(ClawdbotCapability.voiceWake.rawValue))
#expect(caps.contains(MoltbotCapability.canvas.rawValue))
#expect(caps.contains(MoltbotCapability.screen.rawValue))
#expect(caps.contains(MoltbotCapability.camera.rawValue))
#expect(caps.contains(MoltbotCapability.location.rawValue))
#expect(caps.contains(MoltbotCapability.voiceWake.rawValue))
}
}
@Test @MainActor func currentCommandsIncludeLocationWhenEnabled() {
withUserDefaults([
"node.instanceId": "ios-test",
"location.enabledMode": ClawdbotLocationMode.whileUsing.rawValue,
"location.enabledMode": MoltbotLocationMode.whileUsing.rawValue,
]) {
let appModel = NodeAppModel()
let controller = GatewayConnectionController(appModel: appModel, startDiscovery: false)
let commands = Set(controller._test_currentCommands())
#expect(commands.contains(ClawdbotLocationCommand.get.rawValue))
#expect(commands.contains(MoltbotLocationCommand.get.rawValue))
}
}
}

View File

@@ -1,5 +1,5 @@
import Testing
@testable import Clawdbot
@testable import Moltbot
@Suite(.serialized) struct GatewayDiscoveryModelTests {
@Test @MainActor func debugLoggingCapturesLifecycleAndResets() {

View File

@@ -1,17 +1,17 @@
import ClawdbotKit
import MoltbotKit
import Network
import Testing
@testable import Clawdbot
@testable import Moltbot
@Suite struct GatewayEndpointIDTests {
@Test func stableIDForServiceDecodesAndNormalizesName() {
let endpoint = NWEndpoint.service(
name: "Clawdbot\\032Gateway \\032 Node\n",
type: "_clawdbot-gw._tcp",
name: "Moltbot\\032Gateway \\032 Node\n",
type: "_moltbot-gw._tcp",
domain: "local.",
interface: nil)
#expect(GatewayEndpointID.stableID(endpoint) == "_clawdbot-gw._tcp|local.|Clawdbot Gateway Node")
#expect(GatewayEndpointID.stableID(endpoint) == "_moltbot-gw._tcp|local.|Moltbot Gateway Node")
}
@Test func stableIDForNonServiceUsesEndpointDescription() {
@@ -21,8 +21,8 @@ import Testing
@Test func prettyDescriptionDecodesBonjourEscapes() {
let endpoint = NWEndpoint.service(
name: "Clawdbot\\032Gateway",
type: "_clawdbot-gw._tcp",
name: "Moltbot\\032Gateway",
type: "_moltbot-gw._tcp",
domain: "local.",
interface: nil)

View File

@@ -1,6 +1,6 @@
import Foundation
import Testing
@testable import Clawdbot
@testable import Moltbot
private struct KeychainEntry: Hashable {
let service: String

View File

@@ -1,6 +1,6 @@
import ClawdbotKit
import MoltbotKit
import Testing
@testable import Clawdbot
@testable import Moltbot
@Suite struct IOSGatewayChatTransportTests {
@Test func requestsFailFastWhenGatewayNotConnected() async {

View File

@@ -5,7 +5,7 @@
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>ClawdbotTests</string>
<string>MoltbotTests</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>

View File

@@ -1,6 +1,6 @@
import Foundation
import Testing
@testable import Clawdbot
@testable import Moltbot
@Suite struct KeychainStoreTests {
@Test func saveLoadUpdateDeleteRoundTrip() {

View File

@@ -1,8 +1,8 @@
import ClawdbotKit
import MoltbotKit
import Foundation
import Testing
import UIKit
@testable import Clawdbot
@testable import Moltbot
private func withUserDefaults<T>(_ updates: [String: Any?], _ body: () throws -> T) rethrows -> T {
let defaults = UserDefaults.standard
@@ -32,7 +32,7 @@ private func withUserDefaults<T>(_ updates: [String: Any?], _ body: () throws ->
@Suite(.serialized) struct NodeAppModelInvokeTests {
@Test @MainActor func decodeParamsFailsWithoutJSON() {
#expect(throws: Error.self) {
_ = try NodeAppModel._test_decodeParams(ClawdbotCanvasNavigateParams.self, from: nil)
_ = try NodeAppModel._test_decodeParams(MoltbotCanvasNavigateParams.self, from: nil)
}
}
@@ -48,7 +48,7 @@ private func withUserDefaults<T>(_ updates: [String: Any?], _ body: () throws ->
let appModel = NodeAppModel()
appModel.setScenePhase(.background)
let req = BridgeInvokeRequest(id: "bg", command: ClawdbotCanvasCommand.present.rawValue)
let req = BridgeInvokeRequest(id: "bg", command: MoltbotCanvasCommand.present.rawValue)
let res = await appModel._test_handleInvoke(req)
#expect(res.ok == false)
#expect(res.error?.code == .backgroundUnavailable)
@@ -56,7 +56,7 @@ private func withUserDefaults<T>(_ updates: [String: Any?], _ body: () throws ->
@Test @MainActor func handleInvokeRejectsCameraWhenDisabled() async {
let appModel = NodeAppModel()
let req = BridgeInvokeRequest(id: "cam", command: ClawdbotCameraCommand.snap.rawValue)
let req = BridgeInvokeRequest(id: "cam", command: MoltbotCameraCommand.snap.rawValue)
let defaults = UserDefaults.standard
let key = "camera.enabled"
@@ -78,13 +78,13 @@ private func withUserDefaults<T>(_ updates: [String: Any?], _ body: () throws ->
@Test @MainActor func handleInvokeRejectsInvalidScreenFormat() async {
let appModel = NodeAppModel()
let params = ClawdbotScreenRecordParams(format: "gif")
let params = MoltbotScreenRecordParams(format: "gif")
let data = try? JSONEncoder().encode(params)
let json = data.flatMap { String(data: $0, encoding: .utf8) }
let req = BridgeInvokeRequest(
id: "screen",
command: ClawdbotScreenCommand.record.rawValue,
command: MoltbotScreenCommand.record.rawValue,
paramsJSON: json)
let res = await appModel._test_handleInvoke(req)
@@ -96,28 +96,28 @@ private func withUserDefaults<T>(_ updates: [String: Any?], _ body: () throws ->
let appModel = NodeAppModel()
appModel.screen.navigate(to: "http://example.com")
let present = BridgeInvokeRequest(id: "present", command: ClawdbotCanvasCommand.present.rawValue)
let present = BridgeInvokeRequest(id: "present", command: MoltbotCanvasCommand.present.rawValue)
let presentRes = await appModel._test_handleInvoke(present)
#expect(presentRes.ok == true)
#expect(appModel.screen.urlString.isEmpty)
let navigateParams = ClawdbotCanvasNavigateParams(url: "http://localhost:18789/")
let navigateParams = MoltbotCanvasNavigateParams(url: "http://localhost:18789/")
let navData = try JSONEncoder().encode(navigateParams)
let navJSON = String(decoding: navData, as: UTF8.self)
let navigate = BridgeInvokeRequest(
id: "nav",
command: ClawdbotCanvasCommand.navigate.rawValue,
command: MoltbotCanvasCommand.navigate.rawValue,
paramsJSON: navJSON)
let navRes = await appModel._test_handleInvoke(navigate)
#expect(navRes.ok == true)
#expect(appModel.screen.urlString == "http://localhost:18789/")
let evalParams = ClawdbotCanvasEvalParams(javaScript: "1+1")
let evalParams = MoltbotCanvasEvalParams(javaScript: "1+1")
let evalData = try JSONEncoder().encode(evalParams)
let evalJSON = String(decoding: evalData, as: UTF8.self)
let eval = BridgeInvokeRequest(
id: "eval",
command: ClawdbotCanvasCommand.evalJS.rawValue,
command: MoltbotCanvasCommand.evalJS.rawValue,
paramsJSON: evalJSON)
let evalRes = await appModel._test_handleInvoke(eval)
#expect(evalRes.ok == true)
@@ -129,18 +129,18 @@ private func withUserDefaults<T>(_ updates: [String: Any?], _ body: () throws ->
@Test @MainActor func handleInvokeA2UICommandsFailWhenHostMissing() async throws {
let appModel = NodeAppModel()
let reset = BridgeInvokeRequest(id: "reset", command: ClawdbotCanvasA2UICommand.reset.rawValue)
let reset = BridgeInvokeRequest(id: "reset", command: MoltbotCanvasA2UICommand.reset.rawValue)
let resetRes = await appModel._test_handleInvoke(reset)
#expect(resetRes.ok == false)
#expect(resetRes.error?.message.contains("A2UI_HOST_NOT_CONFIGURED") == true)
let jsonl = "{\"beginRendering\":{}}"
let pushParams = ClawdbotCanvasA2UIPushJSONLParams(jsonl: jsonl)
let pushParams = MoltbotCanvasA2UIPushJSONLParams(jsonl: jsonl)
let pushData = try JSONEncoder().encode(pushParams)
let pushJSON = String(decoding: pushData, as: UTF8.self)
let push = BridgeInvokeRequest(
id: "push",
command: ClawdbotCanvasA2UICommand.pushJSONL.rawValue,
command: MoltbotCanvasA2UICommand.pushJSONL.rawValue,
paramsJSON: pushJSON)
let pushRes = await appModel._test_handleInvoke(push)
#expect(pushRes.ok == false)
@@ -157,7 +157,7 @@ private func withUserDefaults<T>(_ updates: [String: Any?], _ body: () throws ->
@Test @MainActor func handleDeepLinkSetsErrorWhenNotConnected() async {
let appModel = NodeAppModel()
let url = URL(string: "clawdbot://agent?message=hello")!
let url = URL(string: "moltbot://agent?message=hello")!
await appModel.handleDeepLink(url: url)
#expect(appModel.screen.errorText?.contains("Gateway not connected") == true)
}
@@ -165,7 +165,7 @@ private func withUserDefaults<T>(_ updates: [String: Any?], _ body: () throws ->
@Test @MainActor func handleDeepLinkRejectsOversizedMessage() async {
let appModel = NodeAppModel()
let msg = String(repeating: "a", count: 20001)
let url = URL(string: "clawdbot://agent?message=\(msg)")!
let url = URL(string: "moltbot://agent?message=\(msg)")!
await appModel.handleDeepLink(url: url)
#expect(appModel.screen.errorText?.contains("Deep link too large") == true)
}

View File

@@ -1,6 +1,6 @@
import Testing
import WebKit
@testable import Clawdbot
@testable import Moltbot
@Suite struct ScreenControllerTests {
@Test @MainActor func canvasModeConfiguresWebViewForTouch() {

View File

@@ -1,5 +1,5 @@
import Testing
@testable import Clawdbot
@testable import Moltbot
@Suite(.serialized) struct ScreenRecordServiceTests {
@Test func clampDefaultsAndBounds() {

View File

@@ -1,5 +1,5 @@
import Testing
@testable import Clawdbot
@testable import Moltbot
@Suite struct SettingsNetworkingHelpersTests {
@Test func parseHostPortParsesIPv4() {

View File

@@ -1,8 +1,8 @@
import ClawdbotKit
import MoltbotKit
import SwiftUI
import Testing
import UIKit
@testable import Clawdbot
@testable import Moltbot
@Suite struct SwiftUIRenderSmokeTests {
@MainActor private static func host(_ view: some View) -> UIWindow {
@@ -75,7 +75,7 @@ import UIKit
}
@Test @MainActor func voiceWakeToastBuildsAViewHierarchy() {
let root = VoiceWakeToast(command: "clawdbot: do something")
let root = VoiceWakeToast(command: "moltbot: do something")
_ = Self.host(root)
}
}

View File

@@ -1,6 +1,6 @@
import Foundation
import Testing
@testable import Clawdbot
@testable import Moltbot
@Suite struct VoiceWakeGatewaySyncTests {
@Test func decodeGatewayTriggersFromJSONSanitizes() {

View File

@@ -1,7 +1,7 @@
import Foundation
import SwabbleKit
import Testing
@testable import Clawdbot
@testable import Moltbot
@Suite struct VoiceWakeManagerExtractCommandTests {
@Test func extractCommandReturnsNilWhenNoTriggerFound() {

View File

@@ -1,7 +1,7 @@
import Foundation
import SwabbleKit
import Testing
@testable import Clawdbot
@testable import Moltbot
@Suite(.serialized) struct VoiceWakeManagerStateTests {
@Test @MainActor func suspendAndResumeCycleUpdatesState() async {

View File

@@ -1,6 +1,6 @@
import Foundation
import Testing
@testable import Clawdbot
@testable import Moltbot
@Suite struct VoiceWakePreferencesTests {
@Test func sanitizeTriggerWordsTrimsAndDropsEmpty() {

View File

@@ -72,8 +72,8 @@ platform :ios do
UI.user_error!("Missing IOS_DEVELOPMENT_TEAM (Apple Team ID). Add it to fastlane/.env or export it in your shell.") if team_id.nil? || team_id.strip.empty?
build_app(
project: "Clawdbot.xcodeproj",
scheme: "Clawdbot",
project: "Moltbot.xcodeproj",
scheme: "Moltbot",
export_method: "app-store",
clean: true,
xcargs: "DEVELOPMENT_TEAM=#{team_id} -allowProvisioningUpdates",

View File

@@ -1,4 +1,4 @@
# fastlane setup (Clawdbot iOS)
# fastlane setup (Moltbot iOS)
Install:

View File

@@ -1,4 +1,4 @@
name: Clawdbot
name: Moltbot
options:
bundleIdPrefix: com.clawdbot
deploymentTarget:
@@ -10,33 +10,33 @@ settings:
SWIFT_VERSION: "6.0"
packages:
ClawdbotKit:
MoltbotKit:
path: ../shared/ClawdbotKit
Swabble:
path: ../../Swabble
schemes:
Clawdbot:
Moltbot:
shared: true
build:
targets:
Clawdbot: all
Moltbot: all
test:
targets:
- ClawdbotTests
- MoltbotTests
targets:
Clawdbot:
Moltbot:
type: application
platform: iOS
sources:
- path: Sources
dependencies:
- package: ClawdbotKit
- package: ClawdbotKit
product: ClawdbotChatUI
- package: ClawdbotKit
product: ClawdbotProtocol
- package: MoltbotKit
- package: MoltbotKit
product: MoltbotChatUI
- package: MoltbotKit
product: MoltbotProtocol
- package: Swabble
product: SwabbleKit
- sdk: AppIntents.framework
@@ -79,7 +79,7 @@ targets:
info:
path: Sources/Info.plist
properties:
CFBundleDisplayName: Clawdbot
CFBundleDisplayName: Moltbot
CFBundleIconName: AppIcon
CFBundleShortVersionString: "2026.1.26"
CFBundleVersion: "20260126"
@@ -88,16 +88,16 @@ targets:
UIApplicationSupportsMultipleScenes: false
UIBackgroundModes:
- audio
NSLocalNetworkUsageDescription: Clawdbot discovers and connects to your Clawdbot gateway on the local network.
NSLocalNetworkUsageDescription: Moltbot discovers and connects to your Moltbot gateway on the local network.
NSAppTransportSecurity:
NSAllowsArbitraryLoadsInWebContent: true
NSBonjourServices:
- _clawdbot-gw._tcp
NSCameraUsageDescription: Clawdbot can capture photos or short video clips when requested via the gateway.
NSLocationWhenInUseUsageDescription: Clawdbot uses your location when you allow location sharing.
NSLocationAlwaysAndWhenInUseUsageDescription: Clawdbot can share your location in the background when you enable Always.
NSMicrophoneUsageDescription: Clawdbot needs microphone access for voice wake.
NSSpeechRecognitionUsageDescription: Clawdbot uses on-device speech recognition for voice wake.
- _moltbot-gw._tcp
NSCameraUsageDescription: Moltbot can capture photos or short video clips when requested via the gateway.
NSLocationWhenInUseUsageDescription: Moltbot uses your location when you allow location sharing.
NSLocationAlwaysAndWhenInUseUsageDescription: Moltbot can share your location in the background when you enable Always.
NSMicrophoneUsageDescription: Moltbot needs microphone access for voice wake.
NSSpeechRecognitionUsageDescription: Moltbot uses on-device speech recognition for voice wake.
UISupportedInterfaceOrientations:
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationPortraitUpsideDown
@@ -109,13 +109,13 @@ targets:
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
ClawdbotTests:
MoltbotTests:
type: bundle.unit-test
platform: iOS
sources:
- path: Tests
dependencies:
- target: Clawdbot
- target: Moltbot
- package: Swabble
product: SwabbleKit
- sdk: AppIntents.framework
@@ -124,11 +124,11 @@ targets:
PRODUCT_BUNDLE_IDENTIFIER: com.clawdbot.ios.tests
SWIFT_VERSION: "6.0"
SWIFT_STRICT_CONCURRENCY: complete
TEST_HOST: "$(BUILT_PRODUCTS_DIR)/Clawdbot.app/Clawdbot"
TEST_HOST: "$(BUILT_PRODUCTS_DIR)/Moltbot.app/Moltbot"
BUNDLE_LOADER: "$(TEST_HOST)"
info:
path: Tests/Info.plist
properties:
CFBundleDisplayName: ClawdbotTests
CFBundleDisplayName: MoltbotTests
CFBundleShortVersionString: "2026.1.26"
CFBundleVersion: "20260126"

View File

@@ -6,8 +6,8 @@
{
"layers" : [
{
"image-name" : "clawdbot-mac.png",
"name" : "clawdbot-mac",
"image-name" : "moltbot-mac.png",
"name" : "moltbot-mac",
"position" : {
"scale" : 1.07,
"translation-in-points" : [

View File

@@ -1,18 +1,18 @@
// swift-tools-version: 6.2
// Package manifest for the Clawdbot macOS companion (menu bar app + IPC library).
// Package manifest for the Moltbot macOS companion (menu bar app + IPC library).
import PackageDescription
let package = Package(
name: "Clawdbot",
name: "Moltbot",
platforms: [
.macOS(.v15),
],
products: [
.library(name: "ClawdbotIPC", targets: ["ClawdbotIPC"]),
.library(name: "ClawdbotDiscovery", targets: ["ClawdbotDiscovery"]),
.executable(name: "Clawdbot", targets: ["Clawdbot"]),
.executable(name: "clawdbot-mac", targets: ["ClawdbotMacCLI"]),
.library(name: "MoltbotIPC", targets: ["MoltbotIPC"]),
.library(name: "MoltbotDiscovery", targets: ["MoltbotDiscovery"]),
.executable(name: "Moltbot", targets: ["Moltbot"]),
.executable(name: "moltbot-mac", targets: ["MoltbotMacCLI"]),
],
dependencies: [
.package(url: "https://github.com/orchetect/MenuBarExtraAccess", exact: "1.2.2"),
@@ -25,28 +25,28 @@ let package = Package(
],
targets: [
.target(
name: "ClawdbotIPC",
name: "MoltbotIPC",
dependencies: [],
swiftSettings: [
.enableUpcomingFeature("StrictConcurrency"),
]),
.target(
name: "ClawdbotDiscovery",
name: "MoltbotDiscovery",
dependencies: [
.product(name: "ClawdbotKit", package: "ClawdbotKit"),
.product(name: "MoltbotKit", package: "MoltbotKit"),
],
path: "Sources/ClawdbotDiscovery",
path: "Sources/MoltbotDiscovery",
swiftSettings: [
.enableUpcomingFeature("StrictConcurrency"),
]),
.executableTarget(
name: "Clawdbot",
name: "Moltbot",
dependencies: [
"ClawdbotIPC",
"ClawdbotDiscovery",
.product(name: "ClawdbotKit", package: "ClawdbotKit"),
.product(name: "ClawdbotChatUI", package: "ClawdbotKit"),
.product(name: "ClawdbotProtocol", package: "ClawdbotKit"),
"MoltbotIPC",
"MoltbotDiscovery",
.product(name: "MoltbotKit", package: "MoltbotKit"),
.product(name: "MoltbotChatUI", package: "MoltbotKit"),
.product(name: "MoltbotProtocol", package: "MoltbotKit"),
.product(name: "SwabbleKit", package: "swabble"),
.product(name: "MenuBarExtraAccess", package: "MenuBarExtraAccess"),
.product(name: "Subprocess", package: "swift-subprocess"),
@@ -59,30 +59,30 @@ let package = Package(
"Resources/Info.plist",
],
resources: [
.copy("Resources/Clawdbot.icns"),
.copy("Resources/Moltbot.icns"),
.copy("Resources/DeviceModels"),
],
swiftSettings: [
.enableUpcomingFeature("StrictConcurrency"),
]),
.executableTarget(
name: "ClawdbotMacCLI",
name: "MoltbotMacCLI",
dependencies: [
"ClawdbotDiscovery",
.product(name: "ClawdbotKit", package: "ClawdbotKit"),
.product(name: "ClawdbotProtocol", package: "ClawdbotKit"),
"MoltbotDiscovery",
.product(name: "MoltbotKit", package: "MoltbotKit"),
.product(name: "MoltbotProtocol", package: "MoltbotKit"),
],
path: "Sources/ClawdbotMacCLI",
path: "Sources/MoltbotMacCLI",
swiftSettings: [
.enableUpcomingFeature("StrictConcurrency"),
]),
.testTarget(
name: "ClawdbotIPCTests",
name: "MoltbotIPCTests",
dependencies: [
"ClawdbotIPC",
"Clawdbot",
"ClawdbotDiscovery",
.product(name: "ClawdbotProtocol", package: "ClawdbotKit"),
"MoltbotIPC",
"Moltbot",
"MoltbotDiscovery",
.product(name: "MoltbotProtocol", package: "MoltbotKit"),
.product(name: "SwabbleKit", package: "swabble"),
],
swiftSettings: [

View File

@@ -10,7 +10,7 @@ struct AboutSettings: View {
VStack(spacing: 8) {
let appIcon = NSApplication.shared.applicationIconImage ?? CritterIconRenderer.makeIcon(blink: 0)
Button {
if let url = URL(string: "https://github.com/clawdbot/clawdbot") {
if let url = URL(string: "https://github.com/moltbot/moltbot") {
NSWorkspace.shared.open(url)
}
} label: {
@@ -29,7 +29,7 @@ struct AboutSettings: View {
}
VStack(spacing: 3) {
Text("Clawdbot")
Text("Moltbot")
.font(.title3.bold())
Text("Version \(self.versionString)")
.foregroundStyle(.secondary)
@@ -49,7 +49,7 @@ struct AboutSettings: View {
AboutLinkRow(
icon: "chevron.left.slash.chevron.right",
title: "GitHub",
url: "https://github.com/clawdbot/clawdbot")
url: "https://github.com/moltbot/moltbot")
AboutLinkRow(icon: "globe", title: "Website", url: "https://steipete.me")
AboutLinkRow(icon: "bird", title: "Twitter", url: "https://twitter.com/steipete")
AboutLinkRow(icon: "envelope", title: "Email", url: "mailto:peter@steipete.me")
@@ -108,7 +108,7 @@ struct AboutSettings: View {
}
private var buildTimestamp: String? {
guard let raw = Bundle.main.object(forInfoDictionaryKey: "ClawdbotBuildTimestamp") as? String
guard let raw = Bundle.main.object(forInfoDictionaryKey: "MoltbotBuildTimestamp") as? String
else { return nil }
let parser = ISO8601DateFormatter()
parser.formatOptions = [.withInternetDateTime]
@@ -122,7 +122,7 @@ struct AboutSettings: View {
}
private var gitCommit: String {
Bundle.main.object(forInfoDictionaryKey: "ClawdbotGitCommit") as? String ?? "unknown"
Bundle.main.object(forInfoDictionaryKey: "MoltbotGitCommit") as? String ?? "unknown"
}
private var bundleID: String {

View File

@@ -1,4 +1,4 @@
import ClawdbotProtocol
import MoltbotProtocol
import SwiftUI
@MainActor
@@ -81,7 +81,7 @@ private struct EventRow: View {
return f.string(from: date)
}
private func prettyJSON(_ dict: [String: ClawdbotProtocol.AnyCodable]) -> String? {
private func prettyJSON(_ dict: [String: MoltbotProtocol.AnyCodable]) -> String? {
let normalized = dict.mapValues { $0.value }
guard JSONSerialization.isValidJSONObject(normalized),
let data = try? JSONSerialization.data(withJSONObject: normalized, options: [.prettyPrinted]),
@@ -99,8 +99,8 @@ struct AgentEventsWindow_Previews: PreviewProvider {
stream: "tool",
ts: Date().timeIntervalSince1970 * 1000,
data: [
"phase": ClawdbotProtocol.AnyCodable("start"),
"name": ClawdbotProtocol.AnyCodable("bash"),
"phase": MoltbotProtocol.AnyCodable("start"),
"name": MoltbotProtocol.AnyCodable("bash"),
],
summary: nil)
AgentEventStore.shared.append(sample)

View File

@@ -34,7 +34,7 @@ enum AgentWorkspace {
static func resolveWorkspaceURL(from userInput: String?) -> URL {
let trimmed = userInput?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
if trimmed.isEmpty { return ClawdbotConfigFile.defaultWorkspaceURL() }
if trimmed.isEmpty { return MoltbotConfigFile.defaultWorkspaceURL() }
let expanded = (trimmed as NSString).expandingTildeInPath
return URL(fileURLWithPath: expanded, isDirectory: true)
}
@@ -154,7 +154,7 @@ enum AgentWorkspace {
static func defaultTemplate() -> String {
let fallback = """
# AGENTS.md - Clawdbot Workspace
# AGENTS.md - Moltbot Workspace
This folder is the assistant's working directory.
@@ -265,7 +265,7 @@ enum AgentWorkspace {
- Timezone (optional)
- Notes
3) ~/.clawdbot/clawdbot.json
3) ~/.clawdbot/moltbot.json
Set identity.name, identity.theme, identity.emoji to match IDENTITY.md.
## Cleanup

View File

@@ -6,7 +6,7 @@ import SwiftUI
struct AnthropicAuthControls: View {
let connectionMode: AppState.ConnectionMode
@State private var oauthStatus: ClawdbotOAuthStore.AnthropicOAuthStatus = ClawdbotOAuthStore.anthropicOAuthStatus()
@State private var oauthStatus: MoltbotOAuthStore.AnthropicOAuthStatus = MoltbotOAuthStore.anthropicOAuthStatus()
@State private var pkce: AnthropicOAuth.PKCE?
@State private var code: String = ""
@State private var busy = false
@@ -42,10 +42,10 @@ struct AnthropicAuthControls: View {
.foregroundStyle(.secondary)
Spacer()
Button("Reveal") {
NSWorkspace.shared.activateFileViewerSelecting([ClawdbotOAuthStore.oauthURL()])
NSWorkspace.shared.activateFileViewerSelecting([MoltbotOAuthStore.oauthURL()])
}
.buttonStyle(.bordered)
.disabled(!FileManager().fileExists(atPath: ClawdbotOAuthStore.oauthURL().path))
.disabled(!FileManager().fileExists(atPath: MoltbotOAuthStore.oauthURL().path))
Button("Refresh") {
self.refresh()
@@ -53,7 +53,7 @@ struct AnthropicAuthControls: View {
.buttonStyle(.bordered)
}
Text(ClawdbotOAuthStore.oauthURL().path)
Text(MoltbotOAuthStore.oauthURL().path)
.font(.caption.monospaced())
.foregroundStyle(.secondary)
.lineLimit(1)
@@ -130,8 +130,8 @@ struct AnthropicAuthControls: View {
}
private func refresh() {
let imported = ClawdbotOAuthStore.importLegacyAnthropicOAuthIfNeeded()
self.oauthStatus = ClawdbotOAuthStore.anthropicOAuthStatus()
let imported = MoltbotOAuthStore.importLegacyAnthropicOAuthIfNeeded()
self.oauthStatus = MoltbotOAuthStore.anthropicOAuthStatus()
if imported != nil {
self.statusText = "Imported existing OAuth credentials."
}
@@ -172,11 +172,11 @@ struct AnthropicAuthControls: View {
code: parsed.code,
state: parsed.state,
verifier: pkce.verifier)
try ClawdbotOAuthStore.saveAnthropicOAuth(creds)
try MoltbotOAuthStore.saveAnthropicOAuth(creds)
self.refresh()
self.pkce = nil
self.code = ""
self.statusText = "Connected. Clawdbot can now use Claude via OAuth."
self.statusText = "Connected. Moltbot can now use Claude via OAuth."
} catch {
self.statusText = "OAuth failed: \(error.localizedDescription)"
}
@@ -212,7 +212,7 @@ struct AnthropicAuthControls: View {
extension AnthropicAuthControls {
init(
connectionMode: AppState.ConnectionMode,
oauthStatus: ClawdbotOAuthStore.AnthropicOAuthStatus,
oauthStatus: MoltbotOAuthStore.AnthropicOAuthStatus,
pkce: AnthropicOAuth.PKCE? = nil,
code: String = "",
busy: Bool = false,

View File

@@ -18,7 +18,7 @@ enum AnthropicAuthMode: Equatable {
var shortLabel: String {
switch self {
case .oauthFile: "OAuth (Clawdbot token file)"
case .oauthFile: "OAuth (Moltbot token file)"
case .oauthEnv: "OAuth (env var)"
case .apiKeyEnv: "API key (env var)"
case .missing: "Missing credentials"
@@ -36,7 +36,7 @@ enum AnthropicAuthMode: Equatable {
enum AnthropicAuthResolver {
static func resolve(
environment: [String: String] = ProcessInfo.processInfo.environment,
oauthStatus: ClawdbotOAuthStore.AnthropicOAuthStatus = ClawdbotOAuthStore
oauthStatus: MoltbotOAuthStore.AnthropicOAuthStatus = MoltbotOAuthStore
.anthropicOAuthStatus()) -> AnthropicAuthMode
{
if oauthStatus.isConnected { return .oauthFile }
@@ -194,10 +194,10 @@ enum AnthropicOAuth {
}
}
enum ClawdbotOAuthStore {
enum MoltbotOAuthStore {
static let oauthFilename = "oauth.json"
private static let providerKey = "anthropic"
private static let clawdbotOAuthDirEnv = "CLAWDBOT_OAUTH_DIR"
private static let moltbotOAuthDirEnv = "CLAWDBOT_OAUTH_DIR"
private static let legacyPiDirEnv = "PI_CODING_AGENT_DIR"
enum AnthropicOAuthStatus: Equatable {
@@ -215,12 +215,12 @@ enum ClawdbotOAuthStore {
var shortDescription: String {
switch self {
case .missingFile: "Clawdbot OAuth token file not found"
case .unreadableFile: "Clawdbot OAuth token file not readable"
case .invalidJSON: "Clawdbot OAuth token file invalid"
case .missingProviderEntry: "No Anthropic entry in Clawdbot OAuth token file"
case .missingFile: "Moltbot OAuth token file not found"
case .unreadableFile: "Moltbot OAuth token file not readable"
case .invalidJSON: "Moltbot OAuth token file invalid"
case .missingProviderEntry: "No Anthropic entry in Moltbot OAuth token file"
case .missingTokens: "Anthropic entry missing tokens"
case .connected: "Clawdbot OAuth credentials found"
case .connected: "Moltbot OAuth credentials found"
}
}
}

View File

@@ -1,10 +1,10 @@
import ClawdbotKit
import ClawdbotProtocol
import MoltbotKit
import MoltbotProtocol
import Foundation
// Prefer the ClawdbotKit wrapper to keep gateway request payloads consistent.
typealias AnyCodable = ClawdbotKit.AnyCodable
typealias InstanceIdentity = ClawdbotKit.InstanceIdentity
// Prefer the MoltbotKit wrapper to keep gateway request payloads consistent.
typealias AnyCodable = MoltbotKit.AnyCodable
typealias InstanceIdentity = MoltbotKit.InstanceIdentity
extension AnyCodable {
var stringValue: String? { self.value as? String }
@@ -26,19 +26,19 @@ extension AnyCodable {
}
}
extension ClawdbotProtocol.AnyCodable {
extension MoltbotProtocol.AnyCodable {
var stringValue: String? { self.value as? String }
var boolValue: Bool? { self.value as? Bool }
var intValue: Int? { self.value as? Int }
var doubleValue: Double? { self.value as? Double }
var dictionaryValue: [String: ClawdbotProtocol.AnyCodable]? { self.value as? [String: ClawdbotProtocol.AnyCodable] }
var arrayValue: [ClawdbotProtocol.AnyCodable]? { self.value as? [ClawdbotProtocol.AnyCodable] }
var dictionaryValue: [String: MoltbotProtocol.AnyCodable]? { self.value as? [String: MoltbotProtocol.AnyCodable] }
var arrayValue: [MoltbotProtocol.AnyCodable]? { self.value as? [MoltbotProtocol.AnyCodable] }
var foundationValue: Any {
switch self.value {
case let dict as [String: ClawdbotProtocol.AnyCodable]:
case let dict as [String: MoltbotProtocol.AnyCodable]:
dict.mapValues { $0.foundationValue }
case let array as [ClawdbotProtocol.AnyCodable]:
case let array as [MoltbotProtocol.AnyCodable]:
array.map(\.foundationValue)
default:
self.value

View File

@@ -41,13 +41,13 @@ final class AppState {
}
var onboardingSeen: Bool {
didSet { self.ifNotPreview { UserDefaults.standard.set(self.onboardingSeen, forKey: "clawdbot.onboardingSeen") }
didSet { self.ifNotPreview { UserDefaults.standard.set(self.onboardingSeen, forKey: "moltbot.onboardingSeen") }
}
}
var debugPaneEnabled: Bool {
didSet {
self.ifNotPreview { UserDefaults.standard.set(self.debugPaneEnabled, forKey: "clawdbot.debugPaneEnabled") }
self.ifNotPreview { UserDefaults.standard.set(self.debugPaneEnabled, forKey: "moltbot.debugPaneEnabled") }
CanvasManager.shared.refreshDebugStatus()
}
}
@@ -229,11 +229,11 @@ final class AppState {
init(preview: Bool = false) {
self.isPreview = preview || ProcessInfo.processInfo.isRunningTests
let onboardingSeen = UserDefaults.standard.bool(forKey: "clawdbot.onboardingSeen")
let onboardingSeen = UserDefaults.standard.bool(forKey: "moltbot.onboardingSeen")
self.isPaused = UserDefaults.standard.bool(forKey: pauseDefaultsKey)
self.launchAtLogin = false
self.onboardingSeen = onboardingSeen
self.debugPaneEnabled = UserDefaults.standard.bool(forKey: "clawdbot.debugPaneEnabled")
self.debugPaneEnabled = UserDefaults.standard.bool(forKey: "moltbot.debugPaneEnabled")
let savedVoiceWake = UserDefaults.standard.bool(forKey: swabbleEnabledKey)
self.swabbleEnabled = voiceWakeSupported ? savedVoiceWake : false
self.swabbleTriggerWords = UserDefaults.standard
@@ -275,7 +275,7 @@ final class AppState {
UserDefaults.standard.set(IconOverrideSelection.system.rawValue, forKey: iconOverrideKey)
}
let configRoot = ClawdbotConfigFile.loadDict()
let configRoot = MoltbotConfigFile.loadDict()
let configRemoteUrl = GatewayRemoteConfig.resolveUrlString(root: configRoot)
let configRemoteTransport = GatewayRemoteConfig.resolveTransport(root: configRoot)
let resolvedConnectionMode = ConnectionModeResolver.resolve(root: configRoot).mode
@@ -353,7 +353,7 @@ final class AppState {
}
private func startConfigWatcher() {
let configUrl = ClawdbotConfigFile.url()
let configUrl = MoltbotConfigFile.url()
self.configWatcher = ConfigFileWatcher(url: configUrl) { [weak self] in
Task { @MainActor in
self?.applyConfigFromDisk()
@@ -363,7 +363,7 @@ final class AppState {
}
private func applyConfigFromDisk() {
let root = ClawdbotConfigFile.loadDict()
let root = MoltbotConfigFile.loadDict()
self.applyConfigOverrides(root)
}
@@ -451,7 +451,7 @@ final class AppState {
Task { @MainActor in
// Keep app-only connection settings local to avoid overwriting remote gateway config.
var root = ClawdbotConfigFile.loadDict()
var root = MoltbotConfigFile.loadDict()
var gateway = root["gateway"] as? [String: Any] ?? [:]
var changed = false
@@ -541,7 +541,7 @@ final class AppState {
} else {
root["gateway"] = gateway
}
ClawdbotConfigFile.saveDict(root)
MoltbotConfigFile.saveDict(root)
}
}
@@ -685,7 +685,7 @@ extension AppState {
state.remoteTarget = "user@example.com"
state.remoteUrl = "wss://gateway.example.ts.net"
state.remoteIdentity = "~/.ssh/id_ed25519"
state.remoteProjectRoot = "~/Projects/clawdbot"
state.remoteProjectRoot = "~/Projects/moltbot"
state.remoteCliPath = ""
return state
}

View File

@@ -15,7 +15,7 @@ final class CLIInstallPrompter {
UserDefaults.standard.set(version, forKey: cliInstallPromptedVersionKey)
let alert = NSAlert()
alert.messageText = "Install Clawdbot CLI?"
alert.messageText = "Install Moltbot CLI?"
alert.informativeText = "Local mode needs the CLI so launchd can run the gateway."
alert.addButton(withTitle: "Install CLI")
alert.addButton(withTitle: "Not now")

View File

@@ -13,7 +13,7 @@ enum CLIInstaller {
fileManager: FileManager) -> String?
{
for basePath in searchPaths {
let candidate = URL(fileURLWithPath: basePath).appendingPathComponent("clawdbot").path
let candidate = URL(fileURLWithPath: basePath).appendingPathComponent("moltbot").path
var isDirectory: ObjCBool = false
guard fileManager.fileExists(atPath: candidate, isDirectory: &isDirectory),
@@ -37,14 +37,14 @@ enum CLIInstaller {
static func install(statusHandler: @escaping @MainActor @Sendable (String) async -> Void) async {
let expected = GatewayEnvironment.expectedGatewayVersionString() ?? "latest"
let prefix = Self.installPrefix()
await statusHandler("Installing clawdbot CLI…")
await statusHandler("Installing moltbot CLI…")
let cmd = self.installScriptCommand(version: expected, prefix: prefix)
let response = await ShellExecutor.runDetailed(command: cmd, cwd: nil, env: nil, timeout: 900)
if response.success {
let parsed = self.parseInstallEvents(response.stdout)
let installedVersion = parsed.last { $0.event == "done" }?.version
let summary = installedVersion.map { "Installed clawdbot \($0)." } ?? "Installed clawdbot."
let summary = installedVersion.map { "Installed moltbot \($0)." } ?? "Installed moltbot."
await statusHandler(summary)
return
}

View File

@@ -1,6 +1,6 @@
import AVFoundation
import ClawdbotIPC
import ClawdbotKit
import MoltbotIPC
import MoltbotKit
import CoreGraphics
import Foundation
import OSLog
@@ -168,7 +168,7 @@ actor CameraCaptureService {
await Self.warmUpCaptureSession()
let tmpMovURL = FileManager().temporaryDirectory
.appendingPathComponent("clawdbot-camera-\(UUID().uuidString).mov")
.appendingPathComponent("moltbot-camera-\(UUID().uuidString).mov")
defer { try? FileManager().removeItem(at: tmpMovURL) }
let outputURL: URL = {
@@ -176,7 +176,7 @@ actor CameraCaptureService {
return URL(fileURLWithPath: outPath)
}
return FileManager().temporaryDirectory
.appendingPathComponent("clawdbot-camera-\(UUID().uuidString).mp4")
.appendingPathComponent("moltbot-camera-\(UUID().uuidString).mp4")
}()
// Ensure we don't fail exporting due to an existing file.

View File

@@ -1,11 +1,11 @@
import AppKit
import ClawdbotIPC
import ClawdbotKit
import MoltbotIPC
import MoltbotKit
import Foundation
import WebKit
final class CanvasA2UIActionMessageHandler: NSObject, WKScriptMessageHandler {
static let messageName = "clawdbotCanvasA2UIAction"
static let messageName = "moltbotCanvasA2UIAction"
private let sessionKey: String
@@ -52,7 +52,7 @@ final class CanvasA2UIActionMessageHandler: NSObject, WKScriptMessageHandler {
}()
guard !userAction.isEmpty else { return }
guard let name = ClawdbotCanvasA2UIAction.extractActionName(userAction) else { return }
guard let name = MoltbotCanvasA2UIAction.extractActionName(userAction) else { return }
let actionId =
(userAction["id"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines).nonEmpty
?? UUID().uuidString
@@ -64,15 +64,15 @@ final class CanvasA2UIActionMessageHandler: NSObject, WKScriptMessageHandler {
let sourceComponentId = (userAction["sourceComponentId"] as? String)?
.trimmingCharacters(in: .whitespacesAndNewlines).nonEmpty ?? "-"
let instanceId = InstanceIdentity.instanceId.lowercased()
let contextJSON = ClawdbotCanvasA2UIAction.compactJSON(userAction["context"])
let contextJSON = MoltbotCanvasA2UIAction.compactJSON(userAction["context"])
// Token-efficient and unambiguous. The agent should treat this as a UI event and (by default) update Canvas.
let messageContext = ClawdbotCanvasA2UIAction.AgentMessageContext(
let messageContext = MoltbotCanvasA2UIAction.AgentMessageContext(
actionName: name,
session: .init(key: self.sessionKey, surfaceId: surfaceId),
component: .init(id: sourceComponentId, host: InstanceIdentity.displayName, instanceId: instanceId),
contextJSON: contextJSON)
let text = ClawdbotCanvasA2UIAction.formatAgentMessage(messageContext)
let text = MoltbotCanvasA2UIAction.formatAgentMessage(messageContext)
Task { [weak webView] in
if AppStateStore.shared.connectionMode == .local {
@@ -91,7 +91,7 @@ final class CanvasA2UIActionMessageHandler: NSObject, WKScriptMessageHandler {
await MainActor.run {
guard let webView else { return }
let js = ClawdbotCanvasA2UIAction.jsDispatchA2UIActionStatus(
let js = MoltbotCanvasA2UIAction.jsDispatchA2UIActionStatus(
actionId: actionId,
ok: result.ok,
error: result.error)
@@ -144,5 +144,5 @@ final class CanvasA2UIActionMessageHandler: NSObject, WKScriptMessageHandler {
return false
}
// Formatting helpers live in ClawdbotKit (`ClawdbotCanvasA2UIAction`).
// Formatting helpers live in MoltbotKit (`MoltbotCanvasA2UIAction`).
}

View File

@@ -1,6 +1,6 @@
import AppKit
import ClawdbotIPC
import ClawdbotKit
import MoltbotIPC
import MoltbotKit
import Foundation
import OSLog
@@ -26,7 +26,7 @@ final class CanvasManager {
private nonisolated static let canvasRoot: URL = {
let base = FileManager().urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
return base.appendingPathComponent("Clawdbot/canvas", isDirectory: true)
return base.appendingPathComponent("Moltbot/canvas", isDirectory: true)
}()
func show(sessionKey: String, path: String? = nil, placement: CanvasPlacement? = nil) throws -> String {
@@ -231,7 +231,7 @@ final class CanvasManager {
private static func resolveA2UIHostUrl(from raw: String?) -> String? {
let trimmed = raw?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
guard !trimmed.isEmpty, let base = URL(string: trimmed) else { return nil }
return base.appendingPathComponent("__clawdbot__/a2ui/").absoluteString + "?platform=macos"
return base.appendingPathComponent("__moltbot__/a2ui/").absoluteString + "?platform=macos"
}
// MARK: - Anchoring

View File

@@ -1,7 +1,7 @@
import Foundation
enum CanvasScheme {
static let scheme = "clawdbot-canvas"
static let scheme = "moltbot-canvas"
static func makeURL(session: String, path: String? = nil) -> URL? {
var comps = URLComponents()

View File

@@ -1,4 +1,4 @@
import ClawdbotKit
import MoltbotKit
import Foundation
import OSLog
import WebKit
@@ -222,7 +222,7 @@ final class CanvasSchemeHandler: NSObject, WKURLSchemeHandler {
let name = fileURL.deletingPathExtension().lastPathComponent
guard !name.isEmpty, !ext.isEmpty else { return nil }
let bundle = ClawdbotKitResources.bundle
let bundle = MoltbotKitResources.bundle
let resourceURL =
bundle.url(forResource: name, withExtension: ext, subdirectory: subdirectory)
?? bundle.url(forResource: name, withExtension: ext)

View File

@@ -23,7 +23,7 @@ extension CanvasWindowController {
}
static func storedFrameDefaultsKey(sessionKey: String) -> String {
"clawdbot.canvas.frame.\(self.sanitizeSessionKey(sessionKey))"
"moltbot.canvas.frame.\(self.sanitizeSessionKey(sessionKey))"
}
static func loadRestoredFrame(sessionKey: String) -> NSRect? {

View File

@@ -17,7 +17,7 @@ extension CanvasWindowController {
let scheme = url.scheme?.lowercased()
// Deep links: allow local Canvas content to invoke the agent without bouncing through NSWorkspace.
if scheme == "clawdbot" {
if scheme == "moltbot" {
if self.webView.url?.scheme == CanvasScheme.scheme {
Task { await DeepLinkHandler.shared.handle(url: url) }
} else {

View File

@@ -1,5 +1,5 @@
import AppKit
import ClawdbotIPC
import MoltbotIPC
extension CanvasWindowController {
// MARK: - Window
@@ -12,7 +12,7 @@ extension CanvasWindowController {
styleMask: [.titled, .closable, .resizable, .miniaturizable],
backing: .buffered,
defer: false)
window.title = "Clawdbot Canvas"
window.title = "Moltbot Canvas"
window.isReleasedWhenClosed = false
window.contentView = contentView
window.center()

View File

@@ -1,6 +1,6 @@
import AppKit
import ClawdbotIPC
import ClawdbotKit
import MoltbotIPC
import MoltbotKit
import Foundation
import WebKit
@@ -57,8 +57,8 @@ final class CanvasWindowController: NSWindowController, WKNavigationDelegate, NS
(() => {
try {
if (location.protocol !== '\(CanvasScheme.scheme):') return;
if (globalThis.__clawdbotA2UIBridgeInstalled) return;
globalThis.__clawdbotA2UIBridgeInstalled = true;
if (globalThis.__moltbotA2UIBridgeInstalled) return;
globalThis.__moltbotA2UIBridgeInstalled = true;
const deepLinkKey = \(Self.jsStringLiteral(deepLinkKey));
const sessionKey = \(Self.jsStringLiteral(injectedSessionKey));
@@ -89,7 +89,7 @@ final class CanvasWindowController: NSWindowController, WKNavigationDelegate, NS
// If the bundled A2UI shell is present, let it forward actions so we keep its richer
// context resolution (data model path lookups, surface detection, etc.).
const hasBundledA2UIHost = !!globalThis.clawdbotA2UI || !!document.querySelector('clawdbot-a2ui-host');
const hasBundledA2UIHost = !!globalThis.clawdbotA2UI || !!document.querySelector('moltbot-a2ui-host');
if (hasBundledA2UIHost && handler?.postMessage) return;
// Otherwise, forward directly when possible.
@@ -115,7 +115,7 @@ final class CanvasWindowController: NSWindowController, WKNavigationDelegate, NS
params.set('deliver', 'false');
params.set('channel', 'last');
params.set('key', deepLinkKey);
location.href = 'clawdbot://agent?' + params.toString();
location.href = 'moltbot://agent?' + params.toString();
} catch {}
}, true);
} catch {}
@@ -268,7 +268,7 @@ final class CanvasWindowController: NSWindowController, WKNavigationDelegate, NS
let js = """
(() => {
try {
const api = globalThis.__clawdbot;
const api = globalThis.__moltbot;
if (!api) return;
if (typeof api.setDebugStatusEnabled === 'function') {
api.setDebugStatusEnabled(\(enabled ? "true" : "false"));
@@ -336,7 +336,7 @@ final class CanvasWindowController: NSWindowController, WKNavigationDelegate, NS
path = outPath
} else {
let ts = Int(Date().timeIntervalSince1970)
path = "/tmp/clawdbot-canvas-\(CanvasWindowController.sanitizeSessionKey(self.sessionKey))-\(ts).png"
path = "/tmp/moltbot-canvas-\(CanvasWindowController.sanitizeSessionKey(self.sessionKey))-\(ts).png"
}
try png.write(to: URL(fileURLWithPath: path), options: [.atomic])

View File

@@ -1,4 +1,4 @@
import ClawdbotProtocol
import MoltbotProtocol
import SwiftUI
extension ChannelsSettings {

View File

@@ -1,4 +1,4 @@
import ClawdbotProtocol
import MoltbotProtocol
import Foundation
extension ChannelsStore {
@@ -28,7 +28,7 @@ extension ChannelsStore {
params: nil,
timeoutMs: 10000)
self.configStatus = snap.valid == false
? "Config invalid; fix it in ~/.clawdbot/clawdbot.json."
? "Config invalid; fix it in ~/.clawdbot/moltbot.json."
: nil
self.configRoot = snap.config?.mapValues { $0.foundationValue } ?? [:]
self.configDraft = cloneConfigValue(self.configRoot) as? [String: Any] ?? self.configRoot

View File

@@ -1,4 +1,4 @@
import ClawdbotProtocol
import MoltbotProtocol
import Foundation
extension ChannelsStore {

View File

@@ -1,4 +1,4 @@
import ClawdbotProtocol
import MoltbotProtocol
import Foundation
import Observation

View File

@@ -1,19 +1,19 @@
import ClawdbotProtocol
import MoltbotProtocol
import Foundation
enum ClawdbotConfigFile {
enum MoltbotConfigFile {
private static let logger = Logger(subsystem: "com.clawdbot", category: "config")
static func url() -> URL {
ClawdbotPaths.configURL
MoltbotPaths.configURL
}
static func stateDirURL() -> URL {
ClawdbotPaths.stateDirURL
MoltbotPaths.stateDirURL
}
static func defaultWorkspaceURL() -> URL {
ClawdbotPaths.workspaceURL
MoltbotPaths.workspaceURL
}
static func loadDict() -> [String: Any] {

View File

@@ -1,6 +1,6 @@
import Foundation
enum ClawdbotEnv {
enum MoltbotEnv {
static func path(_ key: String) -> String? {
// Normalize env overrides once so UI + file IO stay consistent.
guard let raw = getenv(key) else { return nil }
@@ -13,12 +13,12 @@ enum ClawdbotEnv {
}
}
enum ClawdbotPaths {
enum MoltbotPaths {
private static let configPathEnv = "CLAWDBOT_CONFIG_PATH"
private static let stateDirEnv = "CLAWDBOT_STATE_DIR"
static var stateDirURL: URL {
if let override = ClawdbotEnv.path(self.stateDirEnv) {
if let override = MoltbotEnv.path(self.stateDirEnv) {
return URL(fileURLWithPath: override, isDirectory: true)
}
return FileManager().homeDirectoryForCurrentUser
@@ -26,10 +26,10 @@ enum ClawdbotPaths {
}
static var configURL: URL {
if let override = ClawdbotEnv.path(self.configPathEnv) {
if let override = MoltbotEnv.path(self.configPathEnv) {
return URL(fileURLWithPath: override)
}
return self.stateDirURL.appendingPathComponent("clawdbot.json")
return self.stateDirURL.appendingPathComponent("moltbot.json")
}
static var workspaceURL: URL {

View File

@@ -1,13 +1,13 @@
import Foundation
enum CommandResolver {
private static let projectRootDefaultsKey = "clawdbot.gatewayProjectRootPath"
private static let helperName = "clawdbot"
private static let projectRootDefaultsKey = "moltbot.gatewayProjectRootPath"
private static let helperName = "moltbot"
static func gatewayEntrypoint(in root: URL) -> String? {
let distEntry = root.appendingPathComponent("dist/index.js").path
if FileManager().isReadableFile(atPath: distEntry) { return distEntry }
let binEntry = root.appendingPathComponent("bin/clawdbot.js").path
let binEntry = root.appendingPathComponent("bin/moltbot.js").path
if FileManager().isReadableFile(atPath: binEntry) { return binEntry }
return nil
}
@@ -52,7 +52,7 @@ enum CommandResolver {
return url
}
let fallback = FileManager().homeDirectoryForCurrentUser
.appendingPathComponent("Projects/clawdbot")
.appendingPathComponent("Projects/moltbot")
if FileManager().fileExists(atPath: fallback.path) {
return fallback
}
@@ -87,17 +87,17 @@ enum CommandResolver {
// Dev-only convenience. Avoid project-local PATH hijacking in release builds.
extras.insert(projectRoot.appendingPathComponent("node_modules/.bin").path, at: 0)
#endif
let clawdbotPaths = self.clawdbotManagedPaths(home: home)
if !clawdbotPaths.isEmpty {
extras.insert(contentsOf: clawdbotPaths, at: 1)
let moltbotPaths = self.clawdbotManagedPaths(home: home)
if !moltbotPaths.isEmpty {
extras.insert(contentsOf: moltbotPaths, at: 1)
}
extras.insert(contentsOf: self.nodeManagerBinPaths(home: home), at: 1 + clawdbotPaths.count)
extras.insert(contentsOf: self.nodeManagerBinPaths(home: home), at: 1 + moltbotPaths.count)
var seen = Set<String>()
// Preserve order while stripping duplicates so PATH lookups remain deterministic.
return (extras + current).filter { seen.insert($0).inserted }
}
private static func clawdbotManagedPaths(home: URL) -> [String] {
private static func moltbotManagedPaths(home: URL) -> [String] {
let base = home.appendingPathComponent(".clawdbot")
let bin = base.appendingPathComponent("bin")
let nodeBin = base.appendingPathComponent("tools/node/bin")
@@ -187,11 +187,11 @@ enum CommandResolver {
return nil
}
static func clawdbotExecutable(searchPaths: [String]? = nil) -> String? {
static func moltbotExecutable(searchPaths: [String]? = nil) -> String? {
self.findExecutable(named: self.helperName, searchPaths: searchPaths)
}
static func projectClawdbotExecutable(projectRoot: URL? = nil) -> String? {
static func projectMoltbotExecutable(projectRoot: URL? = nil) -> String? {
#if DEBUG
let root = projectRoot ?? self.projectRoot()
let candidate = root.appendingPathComponent("node_modules/.bin").appendingPathComponent(self.helperName).path
@@ -202,11 +202,11 @@ enum CommandResolver {
}
static func nodeCliPath() -> String? {
let candidate = self.projectRoot().appendingPathComponent("bin/clawdbot.js").path
let candidate = self.projectRoot().appendingPathComponent("bin/moltbot.js").path
return FileManager().isReadableFile(atPath: candidate) ? candidate : nil
}
static func hasAnyClawdbotInvoker(searchPaths: [String]? = nil) -> Bool {
static func hasAnyMoltbotInvoker(searchPaths: [String]? = nil) -> Bool {
if self.clawdbotExecutable(searchPaths: searchPaths) != nil { return true }
if self.findExecutable(named: "pnpm", searchPaths: searchPaths) != nil { return true }
if self.findExecutable(named: "node", searchPaths: searchPaths) != nil,
@@ -217,7 +217,7 @@ enum CommandResolver {
return false
}
static func clawdbotNodeCommand(
static func moltbotNodeCommand(
subcommand: String,
extraArgs: [String] = [],
defaults: UserDefaults = .standard,
@@ -238,8 +238,8 @@ enum CommandResolver {
switch runtimeResult {
case let .success(runtime):
let root = self.projectRoot()
if let clawdbotPath = self.projectClawdbotExecutable(projectRoot: root) {
return [clawdbotPath, subcommand] + extraArgs
if let moltbotPath = self.projectMoltbotExecutable(projectRoot: root) {
return [moltbotPath, subcommand] + extraArgs
}
if let entry = self.gatewayEntrypoint(in: root) {
@@ -251,14 +251,14 @@ enum CommandResolver {
}
if let pnpm = self.findExecutable(named: "pnpm", searchPaths: searchPaths) {
// Use --silent to avoid pnpm lifecycle banners that would corrupt JSON outputs.
return [pnpm, "--silent", "clawdbot", subcommand] + extraArgs
return [pnpm, "--silent", "moltbot", subcommand] + extraArgs
}
if let clawdbotPath = self.clawdbotExecutable(searchPaths: searchPaths) {
return [clawdbotPath, subcommand] + extraArgs
if let moltbotPath = self.clawdbotExecutable(searchPaths: searchPaths) {
return [moltbotPath, subcommand] + extraArgs
}
let missingEntry = """
clawdbot entrypoint missing (looked for dist/index.js or bin/clawdbot.js); run pnpm build.
moltbot entrypoint missing (looked for dist/index.js or bin/moltbot.js); run pnpm build.
"""
return self.errorCommand(with: missingEntry)
@@ -267,8 +267,8 @@ enum CommandResolver {
}
}
// Existing callers still refer to clawdbotCommand; keep it as node alias.
static func clawdbotCommand(
// Existing callers still refer to moltbotCommand; keep it as node alias.
static func moltbotCommand(
subcommand: String,
extraArgs: [String] = [],
defaults: UserDefaults = .standard,
@@ -289,7 +289,7 @@ enum CommandResolver {
guard !settings.target.isEmpty else { return nil }
guard let parsed = self.parseSSHTarget(settings.target) else { return nil }
// Run the real clawdbot CLI on the remote host.
// Run the real moltbot CLI on the remote host.
let exportedPath = [
"/opt/homebrew/bin",
"/usr/local/bin",
@@ -306,7 +306,7 @@ enum CommandResolver {
let projectSection = if userPRJ.isEmpty {
"""
DEFAULT_PRJ="$HOME/Projects/clawdbot"
DEFAULT_PRJ="$HOME/Projects/moltbot"
if [ -d "$DEFAULT_PRJ" ]; then
PRJ="$DEFAULT_PRJ"
cd "$PRJ" || { echo "Project root not found: $PRJ"; exit 127; }
@@ -345,9 +345,9 @@ enum CommandResolver {
CLI="";
\(cliSection)
\(projectSection)
if command -v clawdbot >/dev/null 2>&1; then
CLI="$(command -v clawdbot)"
clawdbot \(quotedArgs);
if command -v moltbot >/dev/null 2>&1; then
CLI="$(command -v moltbot)"
moltbot \(quotedArgs);
elif [ -n "${PRJ:-}" ] && [ -f "$PRJ/dist/index.js" ]; then
if command -v node >/dev/null 2>&1; then
CLI="node $PRJ/dist/index.js"
@@ -355,18 +355,18 @@ enum CommandResolver {
else
echo "Node >=22 required on remote host"; exit 127;
fi
elif [ -n "${PRJ:-}" ] && [ -f "$PRJ/bin/clawdbot.js" ]; then
elif [ -n "${PRJ:-}" ] && [ -f "$PRJ/bin/moltbot.js" ]; then
if command -v node >/dev/null 2>&1; then
CLI="node $PRJ/bin/clawdbot.js"
node "$PRJ/bin/clawdbot.js" \(quotedArgs);
CLI="node $PRJ/bin/moltbot.js"
node "$PRJ/bin/moltbot.js" \(quotedArgs);
else
echo "Node >=22 required on remote host"; exit 127;
fi
elif command -v pnpm >/dev/null 2>&1; then
CLI="pnpm --silent clawdbot"
pnpm --silent clawdbot \(quotedArgs);
CLI="pnpm --silent moltbot"
pnpm --silent moltbot \(quotedArgs);
else
echo "clawdbot CLI missing on remote host"; exit 127;
echo "moltbot CLI missing on remote host"; exit 127;
fi
"""
let options: [String] = [
@@ -394,7 +394,7 @@ enum CommandResolver {
defaults: UserDefaults = .standard,
configRoot: [String: Any]? = nil) -> RemoteSettings
{
let root = configRoot ?? ClawdbotConfigFile.loadDict()
let root = configRoot ?? MoltbotConfigFile.loadDict()
let mode = ConnectionModeResolver.resolve(root: root, defaults: defaults).mode
let target = defaults.string(forKey: remoteTargetKey) ?? ""
let identity = defaults.string(forKey: remoteIdentityKey) ?? ""

View File

@@ -153,7 +153,7 @@ extension ConfigSettings {
.font(.title3.weight(.semibold))
Text(self.isNixMode
? "This tab is read-only in Nix mode. Edit config via Nix and rebuild."
: "Edit ~/.clawdbot/clawdbot.json using the schema-driven form.")
: "Edit ~/.clawdbot/moltbot.json using the schema-driven form.")
.font(.callout)
.foregroundStyle(.secondary)
}

View File

@@ -1,4 +1,4 @@
import ClawdbotProtocol
import MoltbotProtocol
import Foundation
enum ConfigStore {
@@ -44,7 +44,7 @@ enum ConfigStore {
if let gateway = await self.loadFromGateway() {
return gateway
}
return ClawdbotConfigFile.loadDict()
return MoltbotConfigFile.loadDict()
}
@MainActor
@@ -63,7 +63,7 @@ enum ConfigStore {
do {
try await self.saveToGateway(root)
} catch {
ClawdbotConfigFile.saveDict(root)
MoltbotConfigFile.saveDict(root)
}
}
}

View File

@@ -43,7 +43,7 @@ enum ConnectionModeResolver {
return EffectiveConnectionMode(mode: storedMode, source: .userDefaults)
}
let seen = defaults.bool(forKey: "clawdbot.onboardingSeen")
let seen = defaults.bool(forKey: "moltbot.onboardingSeen")
return EffectiveConnectionMode(mode: seen ? .local : .unconfigured, source: .onboarding)
}
}

View File

@@ -2,43 +2,43 @@ import Foundation
let launchdLabel = "com.clawdbot.mac"
let gatewayLaunchdLabel = "com.clawdbot.gateway"
let onboardingVersionKey = "clawdbot.onboardingVersion"
let onboardingVersionKey = "moltbot.onboardingVersion"
let currentOnboardingVersion = 7
let pauseDefaultsKey = "clawdbot.pauseEnabled"
let iconAnimationsEnabledKey = "clawdbot.iconAnimationsEnabled"
let swabbleEnabledKey = "clawdbot.swabbleEnabled"
let swabbleTriggersKey = "clawdbot.swabbleTriggers"
let voiceWakeTriggerChimeKey = "clawdbot.voiceWakeTriggerChime"
let voiceWakeSendChimeKey = "clawdbot.voiceWakeSendChime"
let showDockIconKey = "clawdbot.showDockIcon"
let pauseDefaultsKey = "moltbot.pauseEnabled"
let iconAnimationsEnabledKey = "moltbot.iconAnimationsEnabled"
let swabbleEnabledKey = "moltbot.swabbleEnabled"
let swabbleTriggersKey = "moltbot.swabbleTriggers"
let voiceWakeTriggerChimeKey = "moltbot.voiceWakeTriggerChime"
let voiceWakeSendChimeKey = "moltbot.voiceWakeSendChime"
let showDockIconKey = "moltbot.showDockIcon"
let defaultVoiceWakeTriggers = ["clawd", "claude"]
let voiceWakeMaxWords = 32
let voiceWakeMaxWordLength = 64
let voiceWakeMicKey = "clawdbot.voiceWakeMicID"
let voiceWakeMicNameKey = "clawdbot.voiceWakeMicName"
let voiceWakeLocaleKey = "clawdbot.voiceWakeLocaleID"
let voiceWakeAdditionalLocalesKey = "clawdbot.voiceWakeAdditionalLocaleIDs"
let voicePushToTalkEnabledKey = "clawdbot.voicePushToTalkEnabled"
let talkEnabledKey = "clawdbot.talkEnabled"
let iconOverrideKey = "clawdbot.iconOverride"
let connectionModeKey = "clawdbot.connectionMode"
let remoteTargetKey = "clawdbot.remoteTarget"
let remoteIdentityKey = "clawdbot.remoteIdentity"
let remoteProjectRootKey = "clawdbot.remoteProjectRoot"
let remoteCliPathKey = "clawdbot.remoteCliPath"
let canvasEnabledKey = "clawdbot.canvasEnabled"
let cameraEnabledKey = "clawdbot.cameraEnabled"
let systemRunPolicyKey = "clawdbot.systemRunPolicy"
let systemRunAllowlistKey = "clawdbot.systemRunAllowlist"
let systemRunEnabledKey = "clawdbot.systemRunEnabled"
let locationModeKey = "clawdbot.locationMode"
let locationPreciseKey = "clawdbot.locationPreciseEnabled"
let peekabooBridgeEnabledKey = "clawdbot.peekabooBridgeEnabled"
let deepLinkKeyKey = "clawdbot.deepLinkKey"
let modelCatalogPathKey = "clawdbot.modelCatalogPath"
let modelCatalogReloadKey = "clawdbot.modelCatalogReload"
let cliInstallPromptedVersionKey = "clawdbot.cliInstallPromptedVersion"
let heartbeatsEnabledKey = "clawdbot.heartbeatsEnabled"
let debugFileLogEnabledKey = "clawdbot.debug.fileLogEnabled"
let appLogLevelKey = "clawdbot.debug.appLogLevel"
let voiceWakeMicKey = "moltbot.voiceWakeMicID"
let voiceWakeMicNameKey = "moltbot.voiceWakeMicName"
let voiceWakeLocaleKey = "moltbot.voiceWakeLocaleID"
let voiceWakeAdditionalLocalesKey = "moltbot.voiceWakeAdditionalLocaleIDs"
let voicePushToTalkEnabledKey = "moltbot.voicePushToTalkEnabled"
let talkEnabledKey = "moltbot.talkEnabled"
let iconOverrideKey = "moltbot.iconOverride"
let connectionModeKey = "moltbot.connectionMode"
let remoteTargetKey = "moltbot.remoteTarget"
let remoteIdentityKey = "moltbot.remoteIdentity"
let remoteProjectRootKey = "moltbot.remoteProjectRoot"
let remoteCliPathKey = "moltbot.remoteCliPath"
let canvasEnabledKey = "moltbot.canvasEnabled"
let cameraEnabledKey = "moltbot.cameraEnabled"
let systemRunPolicyKey = "moltbot.systemRunPolicy"
let systemRunAllowlistKey = "moltbot.systemRunAllowlist"
let systemRunEnabledKey = "moltbot.systemRunEnabled"
let locationModeKey = "moltbot.locationMode"
let locationPreciseKey = "moltbot.locationPreciseEnabled"
let peekabooBridgeEnabledKey = "moltbot.peekabooBridgeEnabled"
let deepLinkKeyKey = "moltbot.deepLinkKey"
let modelCatalogPathKey = "moltbot.modelCatalogPath"
let modelCatalogReloadKey = "moltbot.modelCatalogReload"
let cliInstallPromptedVersionKey = "moltbot.cliInstallPromptedVersion"
let heartbeatsEnabledKey = "moltbot.heartbeatsEnabled"
let debugFileLogEnabledKey = "moltbot.debug.fileLogEnabled"
let appLogLevelKey = "moltbot.debug.appLogLevel"
let voiceWakeSupported: Bool = ProcessInfo.processInfo.operatingSystemVersion.majorVersion >= 26

View File

@@ -1,5 +1,5 @@
import ClawdbotKit
import ClawdbotProtocol
import MoltbotKit
import MoltbotProtocol
import Foundation
import Observation
import SwiftUI
@@ -20,7 +20,7 @@ struct ControlAgentEvent: Codable, Sendable, Identifiable {
let seq: Int
let stream: String
let ts: Double
let data: [String: ClawdbotProtocol.AnyCodable]
let data: [String: MoltbotProtocol.AnyCodable]
let summary: String?
}
@@ -163,8 +163,8 @@ final class ControlChannel {
timeoutMs: Double? = nil) async throws -> Data
{
do {
let rawParams = params?.reduce(into: [String: ClawdbotKit.AnyCodable]()) {
$0[$1.key] = ClawdbotKit.AnyCodable($1.value.base)
let rawParams = params?.reduce(into: [String: MoltbotKit.AnyCodable]()) {
$0[$1.key] = MoltbotKit.AnyCodable($1.value.base)
}
let data = try await GatewayConnection.shared.request(
method: method,
@@ -400,20 +400,20 @@ final class ControlChannel {
}
private static func bridgeToProtocolArgs(
_ value: ClawdbotProtocol.AnyCodable?) -> [String: ClawdbotProtocol.AnyCodable]?
_ value: MoltbotProtocol.AnyCodable?) -> [String: MoltbotProtocol.AnyCodable]?
{
guard let value else { return nil }
if let dict = value.value as? [String: ClawdbotProtocol.AnyCodable] {
if let dict = value.value as? [String: MoltbotProtocol.AnyCodable] {
return dict
}
if let dict = value.value as? [String: ClawdbotKit.AnyCodable],
if let dict = value.value as? [String: MoltbotKit.AnyCodable],
let data = try? JSONEncoder().encode(dict),
let decoded = try? JSONDecoder().decode([String: ClawdbotProtocol.AnyCodable].self, from: data)
let decoded = try? JSONDecoder().decode([String: MoltbotProtocol.AnyCodable].self, from: data)
{
return decoded
}
if let data = try? JSONEncoder().encode(value),
let decoded = try? JSONDecoder().decode([String: ClawdbotProtocol.AnyCodable].self, from: data)
let decoded = try? JSONDecoder().decode([String: MoltbotProtocol.AnyCodable].self, from: data)
{
return decoded
}
@@ -422,6 +422,6 @@ final class ControlChannel {
}
extension Notification.Name {
static let controlHeartbeat = Notification.Name("clawdbot.control.heartbeat")
static let controlAgentEvent = Notification.Name("clawdbot.control.agent")
static let controlHeartbeat = Notification.Name("moltbot.control.heartbeat")
static let controlAgentEvent = Notification.Name("moltbot.control.agent")
}

View File

@@ -1,4 +1,4 @@
import ClawdbotProtocol
import MoltbotProtocol
import Foundation
import SwiftUI

View File

@@ -1,4 +1,4 @@
import ClawdbotProtocol
import MoltbotProtocol
import Observation
import SwiftUI

View File

@@ -1,5 +1,5 @@
import ClawdbotKit
import ClawdbotProtocol
import MoltbotKit
import MoltbotProtocol
import Foundation
import Observation
import OSLog

Some files were not shown because too many files have changed in this diff Show More