kanna-code 0.33.9 → 0.34.1
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/dist/client/assets/{index-BldnkLtN.js → index-BiI7fqiX.js} +192 -192
- package/dist/client/assets/index-ZfBgbTjt.css +32 -0
- package/dist/client/index.html +2 -2
- package/package.json +1 -1
- package/src/server/agent.test.ts +65 -0
- package/src/server/agent.ts +10 -0
- package/src/server/analytics.test.ts +305 -0
- package/src/server/analytics.ts +126 -0
- package/src/server/app-settings.test.ts +90 -0
- package/src/server/app-settings.ts +263 -0
- package/src/server/server.ts +28 -1
- package/src/server/update-manager.test.ts +35 -0
- package/src/server/update-manager.ts +19 -0
- package/src/server/ws-router.test.ts +262 -1
- package/src/server/ws-router.ts +57 -4
- package/src/shared/analytics.ts +30 -0
- package/src/shared/branding.ts +8 -0
- package/src/shared/protocol.ts +4 -0
- package/src/shared/types.ts +6 -0
- package/dist/client/assets/index-FQ1dOuTJ.css +0 -32
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test"
|
|
2
|
-
import
|
|
2
|
+
import { mkdtemp, rm } from "node:fs/promises"
|
|
3
|
+
import { tmpdir } from "node:os"
|
|
4
|
+
import path from "node:path"
|
|
5
|
+
import type { AppSettingsSnapshot, KeybindingsSnapshot, LlmProviderSnapshot, UpdateSnapshot } from "../shared/types"
|
|
3
6
|
import { PROTOCOL_VERSION } from "../shared/types"
|
|
4
7
|
import { createEmptyState } from "./events"
|
|
5
8
|
import { createWsRouter } from "./ws-router"
|
|
@@ -56,6 +59,12 @@ const DEFAULT_KEYBINDINGS_SNAPSHOT: KeybindingsSnapshot = {
|
|
|
56
59
|
filePathDisplay: "~/.kanna/keybindings.json",
|
|
57
60
|
}
|
|
58
61
|
|
|
62
|
+
const DEFAULT_APP_SETTINGS_SNAPSHOT: AppSettingsSnapshot = {
|
|
63
|
+
analyticsEnabled: true,
|
|
64
|
+
warning: null,
|
|
65
|
+
filePathDisplay: "~/.kanna/data/settings.json",
|
|
66
|
+
}
|
|
67
|
+
|
|
59
68
|
const DEFAULT_UPDATE_SNAPSHOT: UpdateSnapshot = {
|
|
60
69
|
currentVersion: "0.12.0",
|
|
61
70
|
latestVersion: null,
|
|
@@ -211,6 +220,258 @@ describe("ws-router", () => {
|
|
|
211
220
|
}])
|
|
212
221
|
})
|
|
213
222
|
|
|
223
|
+
test("reads and writes app settings via commands", async () => {
|
|
224
|
+
const writes: Array<{ analyticsEnabled: boolean }> = []
|
|
225
|
+
let analyticsEnabled = DEFAULT_APP_SETTINGS_SNAPSHOT.analyticsEnabled
|
|
226
|
+
const router = createWsRouter({
|
|
227
|
+
store: { state: createEmptyState() } as never,
|
|
228
|
+
agent: { getActiveStatuses: () => new Map(), getDrainingChatIds: () => new Set() } as never,
|
|
229
|
+
terminals: {
|
|
230
|
+
getSnapshot: () => null,
|
|
231
|
+
onEvent: () => () => {},
|
|
232
|
+
} as never,
|
|
233
|
+
keybindings: {
|
|
234
|
+
getSnapshot: () => DEFAULT_KEYBINDINGS_SNAPSHOT,
|
|
235
|
+
onChange: () => () => {},
|
|
236
|
+
} as never,
|
|
237
|
+
appSettings: {
|
|
238
|
+
getSnapshot: () => ({
|
|
239
|
+
...DEFAULT_APP_SETTINGS_SNAPSHOT,
|
|
240
|
+
analyticsEnabled,
|
|
241
|
+
}),
|
|
242
|
+
write: async (value) => {
|
|
243
|
+
writes.push(value)
|
|
244
|
+
analyticsEnabled = value.analyticsEnabled
|
|
245
|
+
return {
|
|
246
|
+
...DEFAULT_APP_SETTINGS_SNAPSHOT,
|
|
247
|
+
analyticsEnabled: value.analyticsEnabled,
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
refreshDiscovery: async () => [],
|
|
252
|
+
getDiscoveredProjects: () => [],
|
|
253
|
+
machineDisplayName: "Local Machine",
|
|
254
|
+
updateManager: null,
|
|
255
|
+
})
|
|
256
|
+
const ws = new FakeWebSocket()
|
|
257
|
+
|
|
258
|
+
await router.handleMessage(
|
|
259
|
+
ws as never,
|
|
260
|
+
JSON.stringify({
|
|
261
|
+
v: 1,
|
|
262
|
+
type: "command",
|
|
263
|
+
id: "settings-read-1",
|
|
264
|
+
command: { type: "settings.readAppSettings" },
|
|
265
|
+
})
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
await router.handleMessage(
|
|
269
|
+
ws as never,
|
|
270
|
+
JSON.stringify({
|
|
271
|
+
v: 1,
|
|
272
|
+
type: "command",
|
|
273
|
+
id: "settings-write-1",
|
|
274
|
+
command: {
|
|
275
|
+
type: "settings.writeAppSettings",
|
|
276
|
+
analyticsEnabled: false,
|
|
277
|
+
},
|
|
278
|
+
})
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
expect(ws.sent).toEqual([
|
|
282
|
+
{
|
|
283
|
+
v: PROTOCOL_VERSION,
|
|
284
|
+
type: "ack",
|
|
285
|
+
id: "settings-read-1",
|
|
286
|
+
result: DEFAULT_APP_SETTINGS_SNAPSHOT,
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
v: PROTOCOL_VERSION,
|
|
290
|
+
type: "ack",
|
|
291
|
+
id: "settings-write-1",
|
|
292
|
+
result: {
|
|
293
|
+
...DEFAULT_APP_SETTINGS_SNAPSHOT,
|
|
294
|
+
analyticsEnabled: false,
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
])
|
|
298
|
+
expect(writes).toEqual([{ analyticsEnabled: false }])
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
test("tracks analytics preference transitions in the correct order", async () => {
|
|
302
|
+
const analyticsEvents: string[] = []
|
|
303
|
+
let analyticsEnabled = true
|
|
304
|
+
const router = createWsRouter({
|
|
305
|
+
store: { state: createEmptyState() } as never,
|
|
306
|
+
agent: { getActiveStatuses: () => new Map(), getDrainingChatIds: () => new Set() } as never,
|
|
307
|
+
terminals: {
|
|
308
|
+
getSnapshot: () => null,
|
|
309
|
+
onEvent: () => () => {},
|
|
310
|
+
} as never,
|
|
311
|
+
keybindings: {
|
|
312
|
+
getSnapshot: () => DEFAULT_KEYBINDINGS_SNAPSHOT,
|
|
313
|
+
onChange: () => () => {},
|
|
314
|
+
} as never,
|
|
315
|
+
appSettings: {
|
|
316
|
+
getSnapshot: () => ({
|
|
317
|
+
...DEFAULT_APP_SETTINGS_SNAPSHOT,
|
|
318
|
+
analyticsEnabled,
|
|
319
|
+
}),
|
|
320
|
+
write: async (value) => {
|
|
321
|
+
analyticsEnabled = value.analyticsEnabled
|
|
322
|
+
return {
|
|
323
|
+
...DEFAULT_APP_SETTINGS_SNAPSHOT,
|
|
324
|
+
analyticsEnabled: value.analyticsEnabled,
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
analytics: {
|
|
329
|
+
track: (eventName: string) => {
|
|
330
|
+
analyticsEvents.push(eventName)
|
|
331
|
+
},
|
|
332
|
+
trackLaunch: () => {},
|
|
333
|
+
},
|
|
334
|
+
refreshDiscovery: async () => [],
|
|
335
|
+
getDiscoveredProjects: () => [],
|
|
336
|
+
machineDisplayName: "Local Machine",
|
|
337
|
+
updateManager: null,
|
|
338
|
+
})
|
|
339
|
+
const ws = new FakeWebSocket()
|
|
340
|
+
|
|
341
|
+
await router.handleMessage(
|
|
342
|
+
ws as never,
|
|
343
|
+
JSON.stringify({
|
|
344
|
+
v: 1,
|
|
345
|
+
type: "command",
|
|
346
|
+
id: "settings-disable-1",
|
|
347
|
+
command: {
|
|
348
|
+
type: "settings.writeAppSettings",
|
|
349
|
+
analyticsEnabled: false,
|
|
350
|
+
},
|
|
351
|
+
})
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
await router.handleMessage(
|
|
355
|
+
ws as never,
|
|
356
|
+
JSON.stringify({
|
|
357
|
+
v: 1,
|
|
358
|
+
type: "command",
|
|
359
|
+
id: "settings-enable-1",
|
|
360
|
+
command: {
|
|
361
|
+
type: "settings.writeAppSettings",
|
|
362
|
+
analyticsEnabled: true,
|
|
363
|
+
},
|
|
364
|
+
})
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
await router.handleMessage(
|
|
368
|
+
ws as never,
|
|
369
|
+
JSON.stringify({
|
|
370
|
+
v: 1,
|
|
371
|
+
type: "command",
|
|
372
|
+
id: "settings-enable-2",
|
|
373
|
+
command: {
|
|
374
|
+
type: "settings.writeAppSettings",
|
|
375
|
+
analyticsEnabled: true,
|
|
376
|
+
},
|
|
377
|
+
})
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
expect(analyticsEvents).toEqual([
|
|
381
|
+
"analytics_disabled",
|
|
382
|
+
"analytics_enabled",
|
|
383
|
+
])
|
|
384
|
+
})
|
|
385
|
+
|
|
386
|
+
test("tracks project lifecycle analytics", async () => {
|
|
387
|
+
const analyticsEvents: string[] = []
|
|
388
|
+
const state = createEmptyState()
|
|
389
|
+
const projectPath = await mkdtemp(path.join(tmpdir(), "kanna-router-project-"))
|
|
390
|
+
|
|
391
|
+
try {
|
|
392
|
+
const router = createWsRouter({
|
|
393
|
+
store: {
|
|
394
|
+
state,
|
|
395
|
+
openProject: async (localPath: string, title?: string) => {
|
|
396
|
+
const project = {
|
|
397
|
+
id: "project-1",
|
|
398
|
+
localPath,
|
|
399
|
+
title: title ?? "Project",
|
|
400
|
+
createdAt: Date.now(),
|
|
401
|
+
updatedAt: Date.now(),
|
|
402
|
+
deletedAt: null,
|
|
403
|
+
}
|
|
404
|
+
state.projectsById.set(project.id, project as never)
|
|
405
|
+
state.projectIdsByPath.set(localPath, project.id)
|
|
406
|
+
return project
|
|
407
|
+
},
|
|
408
|
+
getProject: () => ({
|
|
409
|
+
id: "project-1",
|
|
410
|
+
localPath: projectPath,
|
|
411
|
+
}),
|
|
412
|
+
listChatsByProject: () => [{ id: "chat-1" }, { id: "chat-2" }],
|
|
413
|
+
removeProject: async () => {},
|
|
414
|
+
} as never,
|
|
415
|
+
agent: {
|
|
416
|
+
cancel: async () => {},
|
|
417
|
+
closeChat: async () => {},
|
|
418
|
+
getActiveStatuses: () => new Map(),
|
|
419
|
+
getDrainingChatIds: () => new Set(),
|
|
420
|
+
} as never,
|
|
421
|
+
analytics: {
|
|
422
|
+
track: (eventName: string) => {
|
|
423
|
+
analyticsEvents.push(eventName)
|
|
424
|
+
},
|
|
425
|
+
trackLaunch: () => {},
|
|
426
|
+
},
|
|
427
|
+
terminals: {
|
|
428
|
+
closeByCwd: () => {},
|
|
429
|
+
getSnapshot: () => null,
|
|
430
|
+
onEvent: () => () => {},
|
|
431
|
+
} as never,
|
|
432
|
+
keybindings: {
|
|
433
|
+
getSnapshot: () => DEFAULT_KEYBINDINGS_SNAPSHOT,
|
|
434
|
+
onChange: () => () => {},
|
|
435
|
+
} as never,
|
|
436
|
+
refreshDiscovery: async () => [],
|
|
437
|
+
getDiscoveredProjects: () => [],
|
|
438
|
+
machineDisplayName: "Local Machine",
|
|
439
|
+
updateManager: null,
|
|
440
|
+
})
|
|
441
|
+
const ws = new FakeWebSocket()
|
|
442
|
+
|
|
443
|
+
await router.handleMessage(
|
|
444
|
+
ws as never,
|
|
445
|
+
JSON.stringify({
|
|
446
|
+
v: 1,
|
|
447
|
+
type: "command",
|
|
448
|
+
id: "project-create-1",
|
|
449
|
+
command: { type: "project.create", localPath: projectPath, title: "Project" },
|
|
450
|
+
})
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
await router.handleMessage(
|
|
454
|
+
ws as never,
|
|
455
|
+
JSON.stringify({
|
|
456
|
+
v: 1,
|
|
457
|
+
type: "command",
|
|
458
|
+
id: "project-remove-1",
|
|
459
|
+
command: { type: "project.remove", projectId: "project-1" },
|
|
460
|
+
})
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
expect(analyticsEvents).toEqual([
|
|
464
|
+
"project_opened",
|
|
465
|
+
"project_created",
|
|
466
|
+
"project_removed",
|
|
467
|
+
"chat_deleted",
|
|
468
|
+
"chat_deleted",
|
|
469
|
+
])
|
|
470
|
+
} finally {
|
|
471
|
+
await rm(projectPath, { recursive: true, force: true })
|
|
472
|
+
}
|
|
473
|
+
})
|
|
474
|
+
|
|
214
475
|
test("acks terminal.input without rebroadcasting terminal snapshots", async () => {
|
|
215
476
|
const router = createWsRouter({
|
|
216
477
|
store: { state: createEmptyState() } as never,
|
package/src/server/ws-router.ts
CHANGED
|
@@ -3,17 +3,19 @@ import { PROTOCOL_VERSION } from "../shared/types"
|
|
|
3
3
|
import type { ClientEnvelope, ServerEnvelope, SubscriptionTopic } from "../shared/protocol"
|
|
4
4
|
import { isClientEnvelope } from "../shared/protocol"
|
|
5
5
|
import type { AgentCoordinator } from "./agent"
|
|
6
|
+
import type { AnalyticsReporter } from "./analytics"
|
|
7
|
+
import { NoopAnalyticsReporter } from "./analytics"
|
|
8
|
+
import type { AppSettingsManager } from "./app-settings"
|
|
6
9
|
import type { DiscoveredProject } from "./discovery"
|
|
7
10
|
import { DiffStore } from "./diff-store"
|
|
8
11
|
import { EventStore } from "./event-store"
|
|
9
12
|
import { openExternal } from "./external-open"
|
|
10
13
|
import { KeybindingsManager } from "./keybindings"
|
|
11
|
-
import { ensureProjectDirectory } from "./paths"
|
|
14
|
+
import { ensureProjectDirectory, resolveLocalPath } from "./paths"
|
|
12
15
|
import { TerminalManager } from "./terminal-manager"
|
|
13
16
|
import type { UpdateManager } from "./update-manager"
|
|
14
17
|
import { deriveChatSnapshot, deriveLocalProjectsSnapshot, deriveSidebarData } from "./read-models"
|
|
15
|
-
import type { LlmProviderSnapshot } from "../shared/types"
|
|
16
|
-
import type { LlmProviderValidationResult } from "../shared/types"
|
|
18
|
+
import type { AppSettingsSnapshot, LlmProviderSnapshot, LlmProviderValidationResult } from "../shared/types"
|
|
17
19
|
|
|
18
20
|
const DEFAULT_CHAT_RECENT_LIMIT = 200
|
|
19
21
|
|
|
@@ -98,6 +100,8 @@ interface CreateWsRouterArgs {
|
|
|
98
100
|
agent: AgentCoordinator
|
|
99
101
|
terminals: TerminalManager
|
|
100
102
|
keybindings: KeybindingsManager
|
|
103
|
+
appSettings?: Pick<AppSettingsManager, "getSnapshot" | "write">
|
|
104
|
+
analytics?: AnalyticsReporter
|
|
101
105
|
llmProvider?: {
|
|
102
106
|
read: () => Promise<LlmProviderSnapshot>
|
|
103
107
|
write: (value: Pick<LlmProviderSnapshot, "provider" | "apiKey" | "model" | "baseUrl">) => Promise<LlmProviderSnapshot>
|
|
@@ -152,6 +156,8 @@ export function createWsRouter({
|
|
|
152
156
|
agent,
|
|
153
157
|
terminals,
|
|
154
158
|
keybindings,
|
|
159
|
+
appSettings,
|
|
160
|
+
analytics,
|
|
155
161
|
llmProvider,
|
|
156
162
|
refreshDiscovery,
|
|
157
163
|
getDiscoveredProjects,
|
|
@@ -219,6 +225,19 @@ export function createWsRouter({
|
|
|
219
225
|
},
|
|
220
226
|
}),
|
|
221
227
|
}
|
|
228
|
+
const resolvedAppSettings = appSettings ?? {
|
|
229
|
+
getSnapshot: () => ({
|
|
230
|
+
analyticsEnabled: true,
|
|
231
|
+
warning: null,
|
|
232
|
+
filePathDisplay: "~/.kanna/data/settings.json",
|
|
233
|
+
} satisfies AppSettingsSnapshot),
|
|
234
|
+
write: async ({ analyticsEnabled }: { analyticsEnabled: boolean }) => ({
|
|
235
|
+
analyticsEnabled,
|
|
236
|
+
warning: null,
|
|
237
|
+
filePathDisplay: "~/.kanna/data/settings.json",
|
|
238
|
+
} satisfies AppSettingsSnapshot),
|
|
239
|
+
}
|
|
240
|
+
const resolvedAnalytics = analytics ?? NoopAnalyticsReporter
|
|
222
241
|
|
|
223
242
|
function getProtectedChatIds() {
|
|
224
243
|
const activeStatuses = agent.getActiveStatuses()
|
|
@@ -734,6 +753,22 @@ export function createWsRouter({
|
|
|
734
753
|
send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result: snapshot })
|
|
735
754
|
return
|
|
736
755
|
}
|
|
756
|
+
case "settings.readAppSettings": {
|
|
757
|
+
send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result: resolvedAppSettings.getSnapshot() })
|
|
758
|
+
return
|
|
759
|
+
}
|
|
760
|
+
case "settings.writeAppSettings": {
|
|
761
|
+
const previousAnalyticsEnabled = resolvedAppSettings.getSnapshot().analyticsEnabled
|
|
762
|
+
if (previousAnalyticsEnabled && !command.analyticsEnabled) {
|
|
763
|
+
resolvedAnalytics.track("analytics_disabled")
|
|
764
|
+
}
|
|
765
|
+
const snapshot = await resolvedAppSettings.write({ analyticsEnabled: command.analyticsEnabled })
|
|
766
|
+
send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result: snapshot })
|
|
767
|
+
if (!previousAnalyticsEnabled && command.analyticsEnabled) {
|
|
768
|
+
resolvedAnalytics.track("analytics_enabled")
|
|
769
|
+
}
|
|
770
|
+
return
|
|
771
|
+
}
|
|
737
772
|
case "settings.readLlmProvider": {
|
|
738
773
|
send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result: await resolvedLlmProvider.read() })
|
|
739
774
|
return
|
|
@@ -760,21 +795,33 @@ export function createWsRouter({
|
|
|
760
795
|
}
|
|
761
796
|
case "project.open": {
|
|
762
797
|
await ensureProjectDirectory(command.localPath)
|
|
798
|
+
const normalizedPath = resolveLocalPath(command.localPath)
|
|
799
|
+
const existingProjectId = store.state.projectIdsByPath.get(normalizedPath)
|
|
763
800
|
const project = await store.openProject(command.localPath)
|
|
764
801
|
await refreshDiscovery()
|
|
765
802
|
send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result: { projectId: project.id } })
|
|
803
|
+
if (!existingProjectId) {
|
|
804
|
+
resolvedAnalytics.track("project_opened")
|
|
805
|
+
}
|
|
766
806
|
break
|
|
767
807
|
}
|
|
768
808
|
case "project.create": {
|
|
769
809
|
await ensureProjectDirectory(command.localPath)
|
|
810
|
+
const normalizedPath = resolveLocalPath(command.localPath)
|
|
811
|
+
const existingProjectId = store.state.projectIdsByPath.get(normalizedPath)
|
|
770
812
|
const project = await store.openProject(command.localPath, command.title)
|
|
771
813
|
await refreshDiscovery()
|
|
772
814
|
send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result: { projectId: project.id } })
|
|
815
|
+
if (!existingProjectId) {
|
|
816
|
+
resolvedAnalytics.track("project_opened")
|
|
817
|
+
resolvedAnalytics.track("project_created")
|
|
818
|
+
}
|
|
773
819
|
break
|
|
774
820
|
}
|
|
775
821
|
case "project.remove": {
|
|
776
822
|
const project = store.getProject(command.projectId)
|
|
777
|
-
|
|
823
|
+
const chats = store.listChatsByProject(command.projectId)
|
|
824
|
+
for (const chat of chats) {
|
|
778
825
|
await agent.cancel(chat.id)
|
|
779
826
|
await agent.closeChat(chat.id)
|
|
780
827
|
}
|
|
@@ -783,6 +830,10 @@ export function createWsRouter({
|
|
|
783
830
|
}
|
|
784
831
|
await store.removeProject(command.projectId)
|
|
785
832
|
send(ws, { v: PROTOCOL_VERSION, type: "ack", id })
|
|
833
|
+
resolvedAnalytics.track("project_removed")
|
|
834
|
+
for (const _chat of chats) {
|
|
835
|
+
resolvedAnalytics.track("chat_deleted")
|
|
836
|
+
}
|
|
786
837
|
break
|
|
787
838
|
}
|
|
788
839
|
case "sidebar.reorderProjectGroups": {
|
|
@@ -811,6 +862,7 @@ export function createWsRouter({
|
|
|
811
862
|
case "chat.create": {
|
|
812
863
|
const chat = await store.createChat(command.projectId)
|
|
813
864
|
send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result: { chatId: chat.id } })
|
|
865
|
+
resolvedAnalytics.track("chat_created")
|
|
814
866
|
await broadcastChatAndSidebar(chat.id)
|
|
815
867
|
return
|
|
816
868
|
}
|
|
@@ -831,6 +883,7 @@ export function createWsRouter({
|
|
|
831
883
|
await agent.closeChat(command.chatId)
|
|
832
884
|
await store.deleteChat(command.chatId)
|
|
833
885
|
send(ws, { v: PROTOCOL_VERSION, type: "ack", id })
|
|
886
|
+
resolvedAnalytics.track("chat_deleted")
|
|
834
887
|
await broadcastFilteredSnapshots({ includeSidebar: true })
|
|
835
888
|
return
|
|
836
889
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export const ANALYTICS_ENDPOINT = "https://kanna.sh/api/t"
|
|
2
|
+
|
|
3
|
+
export const ANALYTICS_STATIC_EVENT_NAMES = [
|
|
4
|
+
"app_launch",
|
|
5
|
+
"project_opened",
|
|
6
|
+
"project_created",
|
|
7
|
+
"project_removed",
|
|
8
|
+
"chat_created",
|
|
9
|
+
"chat_deleted",
|
|
10
|
+
"message_sent",
|
|
11
|
+
"update_checked",
|
|
12
|
+
"update_installed",
|
|
13
|
+
"update_failed",
|
|
14
|
+
"analytics_enabled",
|
|
15
|
+
"analytics_disabled",
|
|
16
|
+
] as const
|
|
17
|
+
|
|
18
|
+
export const ANALYTICS_STATIC_PROPERTY_NAMES = [
|
|
19
|
+
"current_version",
|
|
20
|
+
"environment",
|
|
21
|
+
"latest_version",
|
|
22
|
+
"custom_port_enabled",
|
|
23
|
+
"no_open_enabled",
|
|
24
|
+
"password_enabled",
|
|
25
|
+
"strict_port_enabled",
|
|
26
|
+
"remote_enabled",
|
|
27
|
+
"host_enabled",
|
|
28
|
+
"share_quick_enabled",
|
|
29
|
+
"share_token_enabled",
|
|
30
|
+
] as const
|
package/src/shared/branding.ts
CHANGED
|
@@ -47,6 +47,14 @@ export function getDataDirDisplay(env: RuntimeEnv = getRuntimeEnv()) {
|
|
|
47
47
|
return `${getDataRootDirDisplay(env)}/data`
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
export function getSettingsFilePath(homeDir: string, env: RuntimeEnv = getRuntimeEnv()) {
|
|
51
|
+
return `${getDataDir(homeDir, env)}/settings.json`
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function getSettingsFilePathDisplay(env: RuntimeEnv = getRuntimeEnv()) {
|
|
55
|
+
return `${getDataDirDisplay(env)}/settings.json`
|
|
56
|
+
}
|
|
57
|
+
|
|
50
58
|
export function getKeybindingsFilePath(homeDir: string, env: RuntimeEnv = getRuntimeEnv()) {
|
|
51
59
|
return `${getDataRootDir(homeDir, env)}/keybindings.json`
|
|
52
60
|
}
|
package/src/shared/protocol.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
AppSettingsSnapshot,
|
|
2
3
|
AgentProvider,
|
|
3
4
|
ChatAttachment,
|
|
4
5
|
ChatDiffSnapshot,
|
|
@@ -58,6 +59,8 @@ export type ClientCommand =
|
|
|
58
59
|
| { type: "update.install" }
|
|
59
60
|
| { type: "settings.readKeybindings" }
|
|
60
61
|
| { type: "settings.writeKeybindings"; bindings: KeybindingsSnapshot["bindings"] }
|
|
62
|
+
| { type: "settings.readAppSettings" }
|
|
63
|
+
| { type: "settings.writeAppSettings"; analyticsEnabled: boolean }
|
|
61
64
|
| { type: "settings.readLlmProvider" }
|
|
62
65
|
| {
|
|
63
66
|
type: "settings.writeLlmProvider"
|
|
@@ -207,6 +210,7 @@ export type ServerSnapshot =
|
|
|
207
210
|
| { type: "local-projects"; data: LocalProjectsSnapshot }
|
|
208
211
|
| { type: "update"; data: UpdateSnapshot }
|
|
209
212
|
| { type: "keybindings"; data: KeybindingsSnapshot }
|
|
213
|
+
| { type: "app-settings"; data: AppSettingsSnapshot }
|
|
210
214
|
| { type: "llm-provider"; data: LlmProviderSnapshot }
|
|
211
215
|
| { type: "chat"; data: ChatSnapshot | null }
|
|
212
216
|
| { type: "project-git"; data: ChatDiffSnapshot | null }
|
package/src/shared/types.ts
CHANGED
|
@@ -308,6 +308,12 @@ export interface LocalProjectsSnapshot {
|
|
|
308
308
|
projects: LocalProjectSummary[]
|
|
309
309
|
}
|
|
310
310
|
|
|
311
|
+
export interface AppSettingsSnapshot {
|
|
312
|
+
analyticsEnabled: boolean
|
|
313
|
+
warning: string | null
|
|
314
|
+
filePathDisplay: string
|
|
315
|
+
}
|
|
316
|
+
|
|
311
317
|
export interface LlmProviderFile {
|
|
312
318
|
provider?: LlmProviderKind
|
|
313
319
|
apiKey?: string
|