All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 1m5s
Reads %USERPROFILE%\.claude\.credentials.json (or ~/.claude/.credentials.json), extracts the access and refresh tokens, stores them encrypted in Wraith's vault. Works when Wraith's own OAuth exchange fails. If Claude Code is authenticated on the same machine, Wraith piggybacks on the existing token. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
171 lines
5.8 KiB
Vue
171 lines
5.8 KiB
Vue
<template>
|
|
<!-- Backdrop -->
|
|
<div
|
|
class="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm"
|
|
@click.self="emit('close')"
|
|
>
|
|
<!-- Modal -->
|
|
<div class="w-80 bg-[#161b22] border border-[#30363d] rounded-lg shadow-2xl overflow-hidden">
|
|
<!-- Header -->
|
|
<div class="flex items-center justify-between px-4 py-3 border-b border-[#30363d]">
|
|
<h3 class="text-sm font-semibold text-[#e0e0e0]">XO Settings</h3>
|
|
<button
|
|
class="text-[#8b949e] hover:text-[#e0e0e0] transition-colors cursor-pointer"
|
|
@click="emit('close')"
|
|
>
|
|
<svg class="w-4 h-4" viewBox="0 0 16 16" fill="currentColor">
|
|
<path d="M3.72 3.72a.75.75 0 011.06 0L8 6.94l3.22-3.22a.75.75 0 111.06 1.06L9.06 8l3.22 3.22a.75.75 0 11-1.06 1.06L8 9.06l-3.22 3.22a.75.75 0 01-1.06-1.06L6.94 8 3.72 4.78a.75.75 0 010-1.06z" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Body -->
|
|
<div class="px-4 py-4 space-y-4">
|
|
<!-- Auth section -->
|
|
<div v-if="!store.isAuthenticated">
|
|
<button
|
|
class="w-full px-3 py-2 text-sm font-medium text-white bg-[#1f6feb] hover:bg-[#388bfd] rounded transition-colors cursor-pointer"
|
|
@click="handleLogin"
|
|
>
|
|
Connect to Claude (OAuth)
|
|
</button>
|
|
|
|
<button
|
|
class="w-full mt-2 px-3 py-2 text-sm font-medium text-[#58a6ff] border border-[#30363d] hover:bg-[#1c2128] rounded transition-colors cursor-pointer"
|
|
@click="handleImportClaudeCode"
|
|
>
|
|
Use Claude Code Token
|
|
</button>
|
|
|
|
<div class="mt-3">
|
|
<label class="block text-xs text-[#8b949e] mb-1">Or enter API Key</label>
|
|
<input
|
|
v-model="apiKey"
|
|
type="password"
|
|
placeholder="sk-ant-..."
|
|
class="w-full px-2.5 py-1.5 text-xs rounded bg-[#0d1117] border border-[#30363d] text-[#e0e0e0] placeholder-[#484f58] outline-none focus:border-[#58a6ff] transition-colors"
|
|
/>
|
|
<button
|
|
class="mt-2 w-full px-3 py-1.5 text-xs font-medium text-[#e0e0e0] bg-[#21262d] hover:bg-[#30363d] border border-[#30363d] rounded transition-colors cursor-pointer"
|
|
:disabled="!apiKey.trim()"
|
|
@click="handleApiKey"
|
|
>
|
|
Authenticate
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Model selector -->
|
|
<div>
|
|
<label class="block text-xs text-[#8b949e] mb-1">Model</label>
|
|
<select
|
|
v-model="store.model"
|
|
class="w-full px-2.5 py-1.5 text-xs rounded bg-[#0d1117] border border-[#30363d] text-[#e0e0e0] outline-none focus:border-[#58a6ff] transition-colors cursor-pointer appearance-none"
|
|
>
|
|
<option value="claude-sonnet-4-5-20250514">claude-sonnet-4-5-20250514</option>
|
|
<option value="claude-opus-4-5-20250414">claude-opus-4-5-20250414</option>
|
|
<option value="claude-haiku-4-5-20251001">claude-haiku-4-5-20251001</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Token usage -->
|
|
<div>
|
|
<label class="block text-xs text-[#8b949e] mb-1">Token Usage</label>
|
|
<div class="flex items-center gap-3 text-xs text-[#e0e0e0]">
|
|
<span>
|
|
<span class="text-[#8b949e]">In:</span> {{ formatTokens(store.tokenUsage.input) }}
|
|
</span>
|
|
<span>
|
|
<span class="text-[#8b949e]">Out:</span> {{ formatTokens(store.tokenUsage.output) }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Actions -->
|
|
<div class="space-y-2 pt-2 border-t border-[#30363d]">
|
|
<button
|
|
class="w-full px-3 py-1.5 text-xs font-medium text-[#e0e0e0] bg-[#21262d] hover:bg-[#30363d] border border-[#30363d] rounded transition-colors cursor-pointer"
|
|
@click="handleClearHistory"
|
|
>
|
|
Clear History
|
|
</button>
|
|
<button
|
|
v-if="store.isAuthenticated"
|
|
class="w-full px-3 py-1.5 text-xs font-medium text-[#f85149] bg-[#21262d] hover:bg-[#30363d] border border-[#30363d] rounded transition-colors cursor-pointer"
|
|
@click="handleDisconnect"
|
|
>
|
|
Disconnect
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, watch } from "vue";
|
|
import { Call } from "@wailsio/runtime";
|
|
import { useCopilotStore } from "@/stores/copilot.store";
|
|
import { useCopilot } from "@/composables/useCopilot";
|
|
|
|
const store = useCopilotStore();
|
|
const { startLogin, logout, setModel } = useCopilot();
|
|
const apiKey = ref("");
|
|
|
|
const emit = defineEmits<{
|
|
(e: "close"): void;
|
|
}>();
|
|
|
|
const APP = "github.com/vstockwell/wraith/internal/app.WraithApp";
|
|
|
|
async function handleLogin(): Promise<void> {
|
|
await startLogin();
|
|
// Auth state will be updated when the OAuth callback completes
|
|
// and the panel re-checks on next interaction.
|
|
store.isAuthenticated = true;
|
|
emit("close");
|
|
}
|
|
|
|
async function handleImportClaudeCode(): Promise<void> {
|
|
try {
|
|
await Call.ByName(`${APP}.ImportClaudeCodeToken`);
|
|
store.isAuthenticated = true;
|
|
alert("Claude Code token imported successfully.");
|
|
emit("close");
|
|
} catch (err: any) {
|
|
alert(`Failed to import token: ${err?.message ?? err}`);
|
|
}
|
|
}
|
|
|
|
function handleApiKey(): void {
|
|
if (!apiKey.value.trim()) return;
|
|
// TODO: Wails AIService.SetApiKey(apiKey)
|
|
store.isAuthenticated = true;
|
|
apiKey.value = "";
|
|
emit("close");
|
|
}
|
|
|
|
function handleClearHistory(): void {
|
|
store.clearHistory();
|
|
emit("close");
|
|
}
|
|
|
|
async function handleDisconnect(): Promise<void> {
|
|
await logout();
|
|
emit("close");
|
|
}
|
|
|
|
function formatTokens(n: number): string {
|
|
if (n >= 1000) return (n / 1000).toFixed(1) + "K";
|
|
return String(n);
|
|
}
|
|
|
|
// Sync model changes to the Go backend
|
|
watch(
|
|
() => store.model,
|
|
(newModel) => {
|
|
setModel(newModel);
|
|
},
|
|
);
|
|
</script>
|