@siteboon/claude-code-ui 1.21.0 → 1.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +82 -213
- package/dist/assets/index-7_J3n3lH.js +1204 -0
- package/dist/assets/index-BFyod1Qa.css +32 -0
- package/dist/assets/{vendor-codemirror-BMLq5tLB.js → vendor-codemirror-C8f1vU1x.js} +9 -9
- package/dist/assets/{vendor-react-DIN4KjD2.js → vendor-react-CdSTmIF1.js} +1 -1
- package/dist/index.html +6 -6
- package/package.json +23 -2
- package/server/claude-sdk.js +63 -11
- package/server/database/db.js +68 -0
- package/server/database/init.sql +14 -1
- package/server/index.js +486 -6
- package/server/projects.js +53 -14
- package/server/routes/cli-auth.js +23 -4
- package/server/routes/codex.js +3 -0
- package/server/routes/cursor.js +5 -2
- package/server/routes/gemini.js +2 -0
- package/shared/modelConstants.js +6 -2
- package/dist/assets/index-Cxnz_sny.css +0 -32
- package/dist/assets/index-DN2ZJcRJ.js +0 -1381
|
@@ -56,4 +56,4 @@ Error generating stack: `+u.message+`
|
|
|
56
56
|
* LICENSE.md file in the root directory of this source tree.
|
|
57
57
|
*
|
|
58
58
|
* @license MIT
|
|
59
|
-
*/const up="6";try{window.__reactRouterVersion=up}catch{}const ip="startTransition",Ha=od[ip];function dp(o){let{basename:f,children:a,future:y,window:g}=o,C=j.useRef();C.current==null&&(C.current=pd({window:g,v5Compat:!0}));let _=C.current,[T,P]=j.useState({action:_.action,location:_.location}),{v7_startTransition:U}=y||{},$=j.useCallback(N=>{U&&Ha?Ha(()=>P(N)):P(N)},[P,U]);return j.useLayoutEffect(()=>_.listen($),[_,$]),j.useEffect(()=>np(y),[y]),j.createElement(lp,{basename:f,children:a,location:T.location,navigationType:T.action,navigator:_,future:y})}var Qa;(function(o){o.UseScrollRestoration="useScrollRestoration",o.UseSubmit="useSubmit",o.UseSubmitFetcher="useSubmitFetcher",o.UseFetcher="useFetcher",o.useViewTransitionState="useViewTransitionState"})(Qa||(Qa={}));var Ka;(function(o){o.UseFetcher="useFetcher",o.UseFetchers="useFetchers",o.UseScrollRestoration="useScrollRestoration"})(Ka||(Ka={}));export{dp as B,
|
|
59
|
+
*/const up="6";try{window.__reactRouterVersion=up}catch{}const ip="startTransition",Ha=od[ip];function dp(o){let{basename:f,children:a,future:y,window:g}=o,C=j.useRef();C.current==null&&(C.current=pd({window:g,v5Compat:!0}));let _=C.current,[T,P]=j.useState({action:_.action,location:_.location}),{v7_startTransition:U}=y||{},$=j.useCallback(N=>{U&&Ha?Ha(()=>P(N)):P(N)},[P,U]);return j.useLayoutEffect(()=>_.listen($),[_,$]),j.useEffect(()=>np(y),[y]),j.createElement(lp,{basename:f,children:a,location:T.location,navigationType:T.action,navigator:_,future:y})}var Qa;(function(o){o.UseScrollRestoration="useScrollRestoration",o.UseSubmit="useSubmit",o.UseSubmitFetcher="useSubmitFetcher",o.UseFetcher="useFetcher",o.useViewTransitionState="useViewTransitionState"})(Qa||(Qa={}));var Ka;(function(o){o.UseFetcher="useFetcher",o.UseFetchers="useFetchers",o.UseScrollRestoration="useScrollRestoration"})(Ka||(Ka={}));export{dp as B,sp as R,j as a,fd as b,id as c,op as d,cp as e,fp as f,Ya as g,rp as h,Xa as r,ap as u};
|
package/dist/index.html
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<title>CloudCLI UI</title>
|
|
9
9
|
|
|
10
10
|
<!-- PWA Manifest -->
|
|
11
|
-
<link rel="manifest" href="/manifest.json" />
|
|
11
|
+
<link rel="manifest" href="/manifest.json" crossorigin="use-credentials" />
|
|
12
12
|
|
|
13
13
|
<!-- iOS Safari PWA Meta Tags -->
|
|
14
14
|
<meta name="mobile-web-app-capable" content="yes" />
|
|
@@ -25,11 +25,11 @@
|
|
|
25
25
|
|
|
26
26
|
<!-- Prevent zoom on iOS -->
|
|
27
27
|
<meta name="format-detection" content="telephone=no" />
|
|
28
|
-
<script type="module" crossorigin src="/assets/index-
|
|
29
|
-
<link rel="modulepreload" crossorigin href="/assets/vendor-react-
|
|
30
|
-
<link rel="modulepreload" crossorigin href="/assets/vendor-codemirror-
|
|
28
|
+
<script type="module" crossorigin src="/assets/index-7_J3n3lH.js"></script>
|
|
29
|
+
<link rel="modulepreload" crossorigin href="/assets/vendor-react-CdSTmIF1.js">
|
|
30
|
+
<link rel="modulepreload" crossorigin href="/assets/vendor-codemirror-C8f1vU1x.js">
|
|
31
31
|
<link rel="modulepreload" crossorigin href="/assets/vendor-xterm-CJZjLICi.js">
|
|
32
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
32
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BFyod1Qa.css">
|
|
33
33
|
</head>
|
|
34
34
|
<body>
|
|
35
35
|
<div id="root"></div>
|
|
@@ -49,4 +49,4 @@
|
|
|
49
49
|
}
|
|
50
50
|
</script>
|
|
51
51
|
</body>
|
|
52
|
-
</html>
|
|
52
|
+
</html>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@siteboon/claude-code-ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.23.0",
|
|
4
4
|
"description": "A web-based UI for Claude Code CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "server/index.js",
|
|
@@ -30,10 +30,13 @@
|
|
|
30
30
|
"build": "vite build",
|
|
31
31
|
"preview": "vite preview",
|
|
32
32
|
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
33
|
+
"lint": "eslint src/",
|
|
34
|
+
"lint:fix": "eslint src/ --fix",
|
|
33
35
|
"start": "npm run build && npm run server",
|
|
34
36
|
"release": "./release.sh",
|
|
35
37
|
"prepublishOnly": "npm run build",
|
|
36
|
-
"postinstall": "node scripts/fix-node-pty.js"
|
|
38
|
+
"postinstall": "node scripts/fix-node-pty.js",
|
|
39
|
+
"prepare": "husky"
|
|
37
40
|
},
|
|
38
41
|
"keywords": [
|
|
39
42
|
"claude code",
|
|
@@ -78,6 +81,7 @@
|
|
|
78
81
|
"i18next": "^25.7.4",
|
|
79
82
|
"i18next-browser-languagedetector": "^8.2.0",
|
|
80
83
|
"jsonwebtoken": "^9.0.2",
|
|
84
|
+
"jszip": "^3.10.1",
|
|
81
85
|
"katex": "^0.16.25",
|
|
82
86
|
"lucide-react": "^0.515.0",
|
|
83
87
|
"mime-types": "^3.0.1",
|
|
@@ -102,6 +106,9 @@
|
|
|
102
106
|
"ws": "^8.14.2"
|
|
103
107
|
},
|
|
104
108
|
"devDependencies": {
|
|
109
|
+
"@commitlint/cli": "^20.4.3",
|
|
110
|
+
"@commitlint/config-conventional": "^20.4.3",
|
|
111
|
+
"@eslint/js": "^9.39.3",
|
|
105
112
|
"@release-it/conventional-changelog": "^10.0.5",
|
|
106
113
|
"@types/node": "^22.19.7",
|
|
107
114
|
"@types/react": "^18.2.43",
|
|
@@ -110,12 +117,26 @@
|
|
|
110
117
|
"auto-changelog": "^2.5.0",
|
|
111
118
|
"autoprefixer": "^10.4.16",
|
|
112
119
|
"concurrently": "^8.2.2",
|
|
120
|
+
"eslint": "^9.39.3",
|
|
121
|
+
"eslint-plugin-import-x": "^4.16.1",
|
|
122
|
+
"eslint-plugin-react": "^7.37.5",
|
|
123
|
+
"eslint-plugin-react-hooks": "^7.0.1",
|
|
124
|
+
"eslint-plugin-react-refresh": "^0.5.2",
|
|
125
|
+
"eslint-plugin-tailwindcss": "^3.18.2",
|
|
126
|
+
"eslint-plugin-unused-imports": "^4.4.1",
|
|
127
|
+
"globals": "^17.4.0",
|
|
128
|
+
"husky": "^9.1.7",
|
|
129
|
+
"lint-staged": "^16.3.2",
|
|
113
130
|
"node-gyp": "^10.0.0",
|
|
114
131
|
"postcss": "^8.4.32",
|
|
115
132
|
"release-it": "^19.0.5",
|
|
116
133
|
"sharp": "^0.34.2",
|
|
117
134
|
"tailwindcss": "^3.4.0",
|
|
118
135
|
"typescript": "^5.9.3",
|
|
136
|
+
"typescript-eslint": "^8.56.1",
|
|
119
137
|
"vite": "^7.0.4"
|
|
138
|
+
},
|
|
139
|
+
"lint-staged": {
|
|
140
|
+
"src/**/*.{ts,tsx,js,jsx}": "eslint"
|
|
120
141
|
}
|
|
121
142
|
}
|
package/server/claude-sdk.js
CHANGED
|
@@ -34,7 +34,7 @@ function createRequestId() {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
function waitForToolApproval(requestId, options = {}) {
|
|
37
|
-
const { timeoutMs = TOOL_APPROVAL_TIMEOUT_MS, signal, onCancel } = options;
|
|
37
|
+
const { timeoutMs = TOOL_APPROVAL_TIMEOUT_MS, signal, onCancel, metadata } = options;
|
|
38
38
|
|
|
39
39
|
return new Promise(resolve => {
|
|
40
40
|
let settled = false;
|
|
@@ -78,9 +78,14 @@ function waitForToolApproval(requestId, options = {}) {
|
|
|
78
78
|
signal.addEventListener('abort', abortHandler, { once: true });
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
const resolver = (decision) => {
|
|
82
82
|
finalize(decision);
|
|
83
|
-
}
|
|
83
|
+
};
|
|
84
|
+
// Attach metadata for getPendingApprovalsForSession lookup
|
|
85
|
+
if (metadata) {
|
|
86
|
+
Object.assign(resolver, metadata);
|
|
87
|
+
}
|
|
88
|
+
pendingToolApprovals.set(requestId, resolver);
|
|
84
89
|
});
|
|
85
90
|
}
|
|
86
91
|
|
|
@@ -209,13 +214,14 @@ function mapCliOptionsToSDK(options = {}) {
|
|
|
209
214
|
* @param {Array<string>} tempImagePaths - Temp image file paths for cleanup
|
|
210
215
|
* @param {string} tempDir - Temp directory for cleanup
|
|
211
216
|
*/
|
|
212
|
-
function addSession(sessionId, queryInstance, tempImagePaths = [], tempDir = null) {
|
|
217
|
+
function addSession(sessionId, queryInstance, tempImagePaths = [], tempDir = null, writer = null) {
|
|
213
218
|
activeSessions.set(sessionId, {
|
|
214
219
|
instance: queryInstance,
|
|
215
220
|
startTime: Date.now(),
|
|
216
221
|
status: 'active',
|
|
217
222
|
tempImagePaths,
|
|
218
|
-
tempDir
|
|
223
|
+
tempDir,
|
|
224
|
+
writer
|
|
219
225
|
});
|
|
220
226
|
}
|
|
221
227
|
|
|
@@ -512,6 +518,12 @@ async function queryClaudeSDK(command, options = {}, ws) {
|
|
|
512
518
|
const decision = await waitForToolApproval(requestId, {
|
|
513
519
|
timeoutMs: requiresInteraction ? 0 : undefined,
|
|
514
520
|
signal: context?.signal,
|
|
521
|
+
metadata: {
|
|
522
|
+
_sessionId: capturedSessionId || sessionId || null,
|
|
523
|
+
_toolName: toolName,
|
|
524
|
+
_input: input,
|
|
525
|
+
_receivedAt: new Date(),
|
|
526
|
+
},
|
|
515
527
|
onCancel: (reason) => {
|
|
516
528
|
ws.send({
|
|
517
529
|
type: 'claude-permission-cancelled',
|
|
@@ -562,7 +574,7 @@ async function queryClaudeSDK(command, options = {}, ws) {
|
|
|
562
574
|
|
|
563
575
|
// Track the query instance for abort capability
|
|
564
576
|
if (capturedSessionId) {
|
|
565
|
-
addSession(capturedSessionId, queryInstance, tempImagePaths, tempDir);
|
|
577
|
+
addSession(capturedSessionId, queryInstance, tempImagePaths, tempDir, ws);
|
|
566
578
|
}
|
|
567
579
|
|
|
568
580
|
// Process streaming messages
|
|
@@ -572,7 +584,7 @@ async function queryClaudeSDK(command, options = {}, ws) {
|
|
|
572
584
|
if (message.session_id && !capturedSessionId) {
|
|
573
585
|
|
|
574
586
|
capturedSessionId = message.session_id;
|
|
575
|
-
addSession(capturedSessionId, queryInstance, tempImagePaths, tempDir);
|
|
587
|
+
addSession(capturedSessionId, queryInstance, tempImagePaths, tempDir, ws);
|
|
576
588
|
|
|
577
589
|
// Set session ID on writer
|
|
578
590
|
if (ws.setSessionId && typeof ws.setSessionId === 'function') {
|
|
@@ -593,9 +605,6 @@ async function queryClaudeSDK(command, options = {}, ws) {
|
|
|
593
605
|
console.log('No session_id in message or already captured. message.session_id:', message.session_id, 'capturedSessionId:', capturedSessionId);
|
|
594
606
|
}
|
|
595
607
|
|
|
596
|
-
// logs which model was used in the message
|
|
597
|
-
console.log("---> Model was sent using:", Object.keys(message.modelUsage || {}));
|
|
598
|
-
|
|
599
608
|
// Transform and send message to WebSocket
|
|
600
609
|
const transformedMessage = transformMessage(message);
|
|
601
610
|
ws.send({
|
|
@@ -606,6 +615,10 @@ async function queryClaudeSDK(command, options = {}, ws) {
|
|
|
606
615
|
|
|
607
616
|
// Extract and send token budget updates from result messages
|
|
608
617
|
if (message.type === 'result') {
|
|
618
|
+
const models = Object.keys(message.modelUsage || {});
|
|
619
|
+
if (models.length > 0) {
|
|
620
|
+
console.log("---> Model was sent using:", models);
|
|
621
|
+
}
|
|
609
622
|
const tokenBudget = extractTokenBudget(message);
|
|
610
623
|
if (tokenBudget) {
|
|
611
624
|
console.log('Token budget from modelUsage:', tokenBudget);
|
|
@@ -711,11 +724,50 @@ function getActiveClaudeSDKSessions() {
|
|
|
711
724
|
return getAllSessions();
|
|
712
725
|
}
|
|
713
726
|
|
|
727
|
+
/**
|
|
728
|
+
* Get pending tool approvals for a specific session.
|
|
729
|
+
* @param {string} sessionId - The session ID
|
|
730
|
+
* @returns {Array} Array of pending permission request objects
|
|
731
|
+
*/
|
|
732
|
+
function getPendingApprovalsForSession(sessionId) {
|
|
733
|
+
const pending = [];
|
|
734
|
+
for (const [requestId, resolver] of pendingToolApprovals.entries()) {
|
|
735
|
+
if (resolver._sessionId === sessionId) {
|
|
736
|
+
pending.push({
|
|
737
|
+
requestId,
|
|
738
|
+
toolName: resolver._toolName || 'UnknownTool',
|
|
739
|
+
input: resolver._input,
|
|
740
|
+
context: resolver._context,
|
|
741
|
+
sessionId,
|
|
742
|
+
receivedAt: resolver._receivedAt || new Date(),
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
return pending;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/**
|
|
750
|
+
* Reconnect a session's WebSocketWriter to a new raw WebSocket.
|
|
751
|
+
* Called when client reconnects (e.g. page refresh) while SDK is still running.
|
|
752
|
+
* @param {string} sessionId - The session ID
|
|
753
|
+
* @param {Object} newRawWs - The new raw WebSocket connection
|
|
754
|
+
* @returns {boolean} True if writer was successfully reconnected
|
|
755
|
+
*/
|
|
756
|
+
function reconnectSessionWriter(sessionId, newRawWs) {
|
|
757
|
+
const session = getSession(sessionId);
|
|
758
|
+
if (!session?.writer?.updateWebSocket) return false;
|
|
759
|
+
session.writer.updateWebSocket(newRawWs);
|
|
760
|
+
console.log(`[RECONNECT] Writer swapped for session ${sessionId}`);
|
|
761
|
+
return true;
|
|
762
|
+
}
|
|
763
|
+
|
|
714
764
|
// Export public API
|
|
715
765
|
export {
|
|
716
766
|
queryClaudeSDK,
|
|
717
767
|
abortClaudeSDKSession,
|
|
718
768
|
isClaudeSDKSessionActive,
|
|
719
769
|
getActiveClaudeSDKSessions,
|
|
720
|
-
resolveToolApproval
|
|
770
|
+
resolveToolApproval,
|
|
771
|
+
getPendingApprovalsForSession,
|
|
772
|
+
reconnectSessionWriter
|
|
721
773
|
};
|
package/server/database/db.js
CHANGED
|
@@ -91,6 +91,18 @@ const runMigrations = () => {
|
|
|
91
91
|
db.exec('ALTER TABLE users ADD COLUMN has_completed_onboarding BOOLEAN DEFAULT 0');
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
// Create session_names table if it doesn't exist (for existing installations)
|
|
95
|
+
db.exec(`CREATE TABLE IF NOT EXISTS session_names (
|
|
96
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
97
|
+
session_id TEXT NOT NULL,
|
|
98
|
+
provider TEXT NOT NULL DEFAULT 'claude',
|
|
99
|
+
custom_name TEXT NOT NULL,
|
|
100
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
101
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
102
|
+
UNIQUE(session_id, provider)
|
|
103
|
+
)`);
|
|
104
|
+
db.exec('CREATE INDEX IF NOT EXISTS idx_session_names_lookup ON session_names(session_id, provider)');
|
|
105
|
+
|
|
94
106
|
console.log('Database migrations completed successfully');
|
|
95
107
|
} catch (error) {
|
|
96
108
|
console.error('Error running migrations:', error.message);
|
|
@@ -348,6 +360,60 @@ const credentialsDb = {
|
|
|
348
360
|
}
|
|
349
361
|
};
|
|
350
362
|
|
|
363
|
+
// Session custom names database operations
|
|
364
|
+
const sessionNamesDb = {
|
|
365
|
+
// Set (insert or update) a custom session name
|
|
366
|
+
setName: (sessionId, provider, customName) => {
|
|
367
|
+
db.prepare(`
|
|
368
|
+
INSERT INTO session_names (session_id, provider, custom_name)
|
|
369
|
+
VALUES (?, ?, ?)
|
|
370
|
+
ON CONFLICT(session_id, provider)
|
|
371
|
+
DO UPDATE SET custom_name = excluded.custom_name, updated_at = CURRENT_TIMESTAMP
|
|
372
|
+
`).run(sessionId, provider, customName);
|
|
373
|
+
},
|
|
374
|
+
|
|
375
|
+
// Get a single custom session name
|
|
376
|
+
getName: (sessionId, provider) => {
|
|
377
|
+
const row = db.prepare(
|
|
378
|
+
'SELECT custom_name FROM session_names WHERE session_id = ? AND provider = ?'
|
|
379
|
+
).get(sessionId, provider);
|
|
380
|
+
return row?.custom_name || null;
|
|
381
|
+
},
|
|
382
|
+
|
|
383
|
+
// Batch lookup — returns Map<sessionId, customName>
|
|
384
|
+
getNames: (sessionIds, provider) => {
|
|
385
|
+
if (!sessionIds.length) return new Map();
|
|
386
|
+
const placeholders = sessionIds.map(() => '?').join(',');
|
|
387
|
+
const rows = db.prepare(
|
|
388
|
+
`SELECT session_id, custom_name FROM session_names
|
|
389
|
+
WHERE session_id IN (${placeholders}) AND provider = ?`
|
|
390
|
+
).all(...sessionIds, provider);
|
|
391
|
+
return new Map(rows.map(r => [r.session_id, r.custom_name]));
|
|
392
|
+
},
|
|
393
|
+
|
|
394
|
+
// Delete a custom session name
|
|
395
|
+
deleteName: (sessionId, provider) => {
|
|
396
|
+
return db.prepare(
|
|
397
|
+
'DELETE FROM session_names WHERE session_id = ? AND provider = ?'
|
|
398
|
+
).run(sessionId, provider).changes > 0;
|
|
399
|
+
},
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
// Apply custom session names from the database (overrides CLI-generated summaries)
|
|
403
|
+
function applyCustomSessionNames(sessions, provider) {
|
|
404
|
+
if (!sessions?.length) return;
|
|
405
|
+
try {
|
|
406
|
+
const ids = sessions.map(s => s.id);
|
|
407
|
+
const customNames = sessionNamesDb.getNames(ids, provider);
|
|
408
|
+
for (const session of sessions) {
|
|
409
|
+
const custom = customNames.get(session.id);
|
|
410
|
+
if (custom) session.summary = custom;
|
|
411
|
+
}
|
|
412
|
+
} catch (error) {
|
|
413
|
+
console.warn(`[DB] Failed to apply custom session names for ${provider}:`, error.message);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
351
417
|
// Backward compatibility - keep old names pointing to new system
|
|
352
418
|
const githubTokensDb = {
|
|
353
419
|
createGithubToken: (userId, tokenName, githubToken, description = null) => {
|
|
@@ -373,5 +439,7 @@ export {
|
|
|
373
439
|
userDb,
|
|
374
440
|
apiKeysDb,
|
|
375
441
|
credentialsDb,
|
|
442
|
+
sessionNamesDb,
|
|
443
|
+
applyCustomSessionNames,
|
|
376
444
|
githubTokensDb // Backward compatibility
|
|
377
445
|
};
|
package/server/database/init.sql
CHANGED
|
@@ -49,4 +49,17 @@ CREATE TABLE IF NOT EXISTS user_credentials (
|
|
|
49
49
|
|
|
50
50
|
CREATE INDEX IF NOT EXISTS idx_user_credentials_user_id ON user_credentials(user_id);
|
|
51
51
|
CREATE INDEX IF NOT EXISTS idx_user_credentials_type ON user_credentials(credential_type);
|
|
52
|
-
CREATE INDEX IF NOT EXISTS idx_user_credentials_active ON user_credentials(is_active);
|
|
52
|
+
CREATE INDEX IF NOT EXISTS idx_user_credentials_active ON user_credentials(is_active);
|
|
53
|
+
|
|
54
|
+
-- Session custom names (provider-agnostic display name overrides)
|
|
55
|
+
CREATE TABLE IF NOT EXISTS session_names (
|
|
56
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
57
|
+
session_id TEXT NOT NULL,
|
|
58
|
+
provider TEXT NOT NULL DEFAULT 'claude',
|
|
59
|
+
custom_name TEXT NOT NULL,
|
|
60
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
61
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
62
|
+
UNIQUE(session_id, provider)
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
CREATE INDEX IF NOT EXISTS idx_session_names_lookup ON session_names(session_id, provider);
|