fix: ctx init missing directory + add Initialize Database button to ContextPane
This commit is contained in:
parent
d903904d52
commit
957f4c20f6
5 changed files with 164 additions and 10 deletions
1
ctx
1
ctx
|
|
@ -17,6 +17,7 @@ DB_PATH = Path.home() / ".claude-context" / "context.db"
|
||||||
|
|
||||||
|
|
||||||
def get_db():
|
def get_db():
|
||||||
|
DB_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||||
db = sqlite3.connect(str(DB_PATH))
|
db = sqlite3.connect(str(DB_PATH))
|
||||||
db.row_factory = sqlite3.Row
|
db.row_factory = sqlite3.Row
|
||||||
db.execute("PRAGMA journal_mode=WAL")
|
db.execute("PRAGMA journal_mode=WAL")
|
||||||
|
|
|
||||||
|
|
@ -33,11 +33,15 @@ pub struct CtxDb {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CtxDb {
|
impl CtxDb {
|
||||||
pub fn new() -> Self {
|
fn db_path() -> std::path::PathBuf {
|
||||||
let db_path = dirs::home_dir()
|
dirs::home_dir()
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.join(".claude-context")
|
.join(".claude-context")
|
||||||
.join("context.db");
|
.join("context.db")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let db_path = Self::db_path();
|
||||||
|
|
||||||
let conn = if db_path.exists() {
|
let conn = if db_path.exists() {
|
||||||
Connection::open_with_flags(
|
Connection::open_with_flags(
|
||||||
|
|
@ -51,6 +55,92 @@ impl CtxDb {
|
||||||
Self { conn: Mutex::new(conn) }
|
Self { conn: Mutex::new(conn) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create the context database directory and schema, then open a read-only connection.
|
||||||
|
pub fn init_db(&self) -> Result<(), String> {
|
||||||
|
let db_path = Self::db_path();
|
||||||
|
|
||||||
|
// Create parent directory
|
||||||
|
if let Some(parent) = db_path.parent() {
|
||||||
|
std::fs::create_dir_all(parent)
|
||||||
|
.map_err(|e| format!("Failed to create directory: {e}"))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open read-write to create schema
|
||||||
|
let conn = Connection::open(&db_path)
|
||||||
|
.map_err(|e| format!("Failed to create database: {e}"))?;
|
||||||
|
|
||||||
|
conn.execute_batch("PRAGMA journal_mode=WAL;").map_err(|e| format!("WAL mode failed: {e}"))?;
|
||||||
|
|
||||||
|
conn.execute_batch(
|
||||||
|
"CREATE TABLE IF NOT EXISTS sessions (
|
||||||
|
name TEXT PRIMARY KEY,
|
||||||
|
description TEXT,
|
||||||
|
work_dir TEXT,
|
||||||
|
created_at TEXT DEFAULT (datetime('now'))
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS contexts (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
project TEXT NOT NULL,
|
||||||
|
key TEXT NOT NULL,
|
||||||
|
value TEXT NOT NULL,
|
||||||
|
updated_at TEXT DEFAULT (datetime('now')),
|
||||||
|
UNIQUE(project, key)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS shared (
|
||||||
|
key TEXT PRIMARY KEY,
|
||||||
|
value TEXT NOT NULL,
|
||||||
|
updated_at TEXT DEFAULT (datetime('now'))
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS summaries (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
project TEXT NOT NULL,
|
||||||
|
summary TEXT NOT NULL,
|
||||||
|
created_at TEXT DEFAULT (datetime('now'))
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE VIRTUAL TABLE IF NOT EXISTS contexts_fts USING fts5(
|
||||||
|
project, key, value, content=contexts, content_rowid=id
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE VIRTUAL TABLE IF NOT EXISTS shared_fts USING fts5(
|
||||||
|
key, value, content=shared
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TRIGGER IF NOT EXISTS contexts_ai AFTER INSERT ON contexts BEGIN
|
||||||
|
INSERT INTO contexts_fts(rowid, project, key, value)
|
||||||
|
VALUES (new.id, new.project, new.key, new.value);
|
||||||
|
END;
|
||||||
|
|
||||||
|
CREATE TRIGGER IF NOT EXISTS contexts_ad AFTER DELETE ON contexts BEGIN
|
||||||
|
INSERT INTO contexts_fts(contexts_fts, rowid, project, key, value)
|
||||||
|
VALUES ('delete', old.id, old.project, old.key, old.value);
|
||||||
|
END;
|
||||||
|
|
||||||
|
CREATE TRIGGER IF NOT EXISTS contexts_au AFTER UPDATE ON contexts BEGIN
|
||||||
|
INSERT INTO contexts_fts(contexts_fts, rowid, project, key, value)
|
||||||
|
VALUES ('delete', old.id, old.project, old.key, old.value);
|
||||||
|
INSERT INTO contexts_fts(rowid, project, key, value)
|
||||||
|
VALUES (new.id, new.project, new.key, new.value);
|
||||||
|
END;"
|
||||||
|
).map_err(|e| format!("Schema creation failed: {e}"))?;
|
||||||
|
|
||||||
|
drop(conn);
|
||||||
|
|
||||||
|
// Re-open as read-only for normal operation
|
||||||
|
let ro_conn = Connection::open_with_flags(
|
||||||
|
&db_path,
|
||||||
|
rusqlite::OpenFlags::SQLITE_OPEN_READ_ONLY | rusqlite::OpenFlags::SQLITE_OPEN_NO_MUTEX,
|
||||||
|
).map_err(|e| format!("Failed to reopen database: {e}"))?;
|
||||||
|
|
||||||
|
let mut lock = self.conn.lock().unwrap();
|
||||||
|
*lock = Some(ro_conn);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn list_projects(&self) -> Result<Vec<CtxProject>, String> {
|
pub fn list_projects(&self) -> Result<Vec<CtxProject>, String> {
|
||||||
let lock = self.conn.lock().unwrap();
|
let lock = self.conn.lock().unwrap();
|
||||||
let conn = lock.as_ref().ok_or("ctx database not found")?;
|
let conn = lock.as_ref().ok_or("ctx database not found")?;
|
||||||
|
|
|
||||||
|
|
@ -182,6 +182,11 @@ fn ssh_session_delete(state: State<'_, AppState>, id: String) -> Result<(), Stri
|
||||||
|
|
||||||
// --- ctx commands ---
|
// --- ctx commands ---
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn ctx_init_db(state: State<'_, AppState>) -> Result<(), String> {
|
||||||
|
state.ctx_db.init_db()
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn ctx_list_projects(state: State<'_, AppState>) -> Result<Vec<ctx::CtxProject>, String> {
|
fn ctx_list_projects(state: State<'_, AppState>) -> Result<Vec<ctx::CtxProject>, String> {
|
||||||
state.ctx_db.list_projects()
|
state.ctx_db.list_projects()
|
||||||
|
|
@ -539,6 +544,7 @@ pub fn run() {
|
||||||
ssh_session_list,
|
ssh_session_list,
|
||||||
ssh_session_save,
|
ssh_session_save,
|
||||||
ssh_session_delete,
|
ssh_session_delete,
|
||||||
|
ctx_init_db,
|
||||||
ctx_list_projects,
|
ctx_list_projects,
|
||||||
ctx_get_context,
|
ctx_get_context,
|
||||||
ctx_get_shared,
|
ctx_get_shared,
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,10 @@ export interface CtxSummary {
|
||||||
created_at: string;
|
created_at: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function ctxInitDb(): Promise<void> {
|
||||||
|
return invoke('ctx_init_db');
|
||||||
|
}
|
||||||
|
|
||||||
export async function ctxListProjects(): Promise<CtxProject[]> {
|
export async function ctxListProjects(): Promise<CtxProject[]> {
|
||||||
return invoke('ctx_list_projects');
|
return invoke('ctx_list_projects');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import {
|
import {
|
||||||
|
ctxInitDb,
|
||||||
ctxListProjects,
|
ctxListProjects,
|
||||||
ctxGetContext,
|
ctxGetContext,
|
||||||
ctxGetShared,
|
ctxGetShared,
|
||||||
|
|
@ -26,15 +27,34 @@
|
||||||
let searchResults = $state<CtxEntry[]>([]);
|
let searchResults = $state<CtxEntry[]>([]);
|
||||||
let error = $state('');
|
let error = $state('');
|
||||||
let loading = $state(false);
|
let loading = $state(false);
|
||||||
|
let dbMissing = $state(false);
|
||||||
|
let initializing = $state(false);
|
||||||
|
|
||||||
onMount(async () => {
|
async function loadData() {
|
||||||
try {
|
try {
|
||||||
projects = await ctxListProjects();
|
projects = await ctxListProjects();
|
||||||
sharedEntries = await ctxGetShared();
|
sharedEntries = await ctxGetShared();
|
||||||
|
error = '';
|
||||||
|
dbMissing = false;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error = `ctx database not available: ${e}`;
|
error = `${e}`;
|
||||||
|
dbMissing = error.includes('not found');
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
async function handleInitDb() {
|
||||||
|
initializing = true;
|
||||||
|
try {
|
||||||
|
await ctxInitDb();
|
||||||
|
await loadData();
|
||||||
|
} catch (e) {
|
||||||
|
error = `Failed to initialize database: ${e}`;
|
||||||
|
} finally {
|
||||||
|
initializing = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(loadData);
|
||||||
|
|
||||||
async function selectProject(name: string) {
|
async function selectProject(name: string) {
|
||||||
selectedProject = name;
|
selectedProject = name;
|
||||||
|
|
@ -86,10 +106,21 @@
|
||||||
<line x1="12" y1="16" x2="12.01" y2="16"></line>
|
<line x1="12" y1="16" x2="12.01" y2="16"></line>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="ctx-error-text">{error}</div>
|
{#if dbMissing}
|
||||||
<div class="ctx-error-hint">
|
<div class="ctx-error-text">Context database not found</div>
|
||||||
Run <code>ctx init</code> in a terminal tab to create the context database.
|
<div class="ctx-error-hint">
|
||||||
</div>
|
Create the database at <code>~/.claude-context/context.db</code> to get started.
|
||||||
|
</div>
|
||||||
|
<button class="init-btn" onclick={handleInitDb} disabled={initializing}>
|
||||||
|
{#if initializing}
|
||||||
|
Initializing...
|
||||||
|
{:else}
|
||||||
|
Initialize Database
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
{:else}
|
||||||
|
<div class="ctx-error-text">{error}</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|
@ -253,6 +284,28 @@
|
||||||
color: var(--ctp-green);
|
color: var(--ctp-green);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.init-btn {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
padding: 0.375rem 1rem;
|
||||||
|
background: var(--ctp-blue);
|
||||||
|
color: var(--ctp-base);
|
||||||
|
border: none;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: opacity 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.init-btn:hover:not(:disabled) {
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
|
||||||
|
.init-btn:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
.ctx-body {
|
.ctx-body {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue