kanna-code 0.40.1 → 0.41.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.
@@ -0,0 +1,67 @@
1
+ import { mkdir } from "node:fs/promises"
2
+ import path from "node:path"
3
+ import type { ProjectQuickAction } from "../shared/protocol"
4
+ import { resolveLocalPath } from "./paths"
5
+
6
+ const QUICK_ACTIONS_FILE_NAME = "quick-actions.json"
7
+ const MAX_QUICK_ACTIONS = 50
8
+ const MAX_LABEL_LENGTH = 80
9
+ const MAX_COMMAND_LENGTH = 2_000
10
+
11
+ function getProjectQuickActionsPath(projectPath: string) {
12
+ return path.join(resolveLocalPath(projectPath), ".kanna", QUICK_ACTIONS_FILE_NAME)
13
+ }
14
+
15
+ function normalizeQuickAction(value: unknown): ProjectQuickAction | null {
16
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null
17
+ const record = value as Record<string, unknown>
18
+ const id = typeof record.id === "string" ? record.id.trim() : ""
19
+ const label = typeof record.label === "string" ? record.label.trim() : ""
20
+ const command = typeof record.command === "string" ? record.command.trim() : ""
21
+ if (!id || !command) return null
22
+
23
+ return {
24
+ id,
25
+ label: (label || command).slice(0, MAX_LABEL_LENGTH),
26
+ command: command.slice(0, MAX_COMMAND_LENGTH),
27
+ }
28
+ }
29
+
30
+ export function normalizeQuickActions(value: unknown): ProjectQuickAction[] {
31
+ const rawActions = Array.isArray(value)
32
+ ? value
33
+ : value && typeof value === "object" && Array.isArray((value as { quickActions?: unknown }).quickActions)
34
+ ? (value as { quickActions: unknown[] }).quickActions
35
+ : []
36
+ const seenIds = new Set<string>()
37
+ const actions: ProjectQuickAction[] = []
38
+
39
+ for (const rawAction of rawActions) {
40
+ const action = normalizeQuickAction(rawAction)
41
+ if (!action || seenIds.has(action.id)) continue
42
+ seenIds.add(action.id)
43
+ actions.push(action)
44
+ if (actions.length >= MAX_QUICK_ACTIONS) break
45
+ }
46
+
47
+ return actions
48
+ }
49
+
50
+ export async function readProjectQuickActions(projectPath: string): Promise<ProjectQuickAction[]> {
51
+ const file = Bun.file(getProjectQuickActionsPath(projectPath))
52
+ if (!(await file.exists())) return []
53
+
54
+ try {
55
+ return normalizeQuickActions(JSON.parse(await file.text()))
56
+ } catch {
57
+ return []
58
+ }
59
+ }
60
+
61
+ export async function writeProjectQuickActions(projectPath: string, quickActions: ProjectQuickAction[]): Promise<ProjectQuickAction[]> {
62
+ const filePath = getProjectQuickActionsPath(projectPath)
63
+ const normalized = normalizeQuickActions(quickActions)
64
+ await mkdir(path.dirname(filePath), { recursive: true })
65
+ await Bun.write(filePath, `${JSON.stringify({ quickActions: normalized }, null, 2)}\n`)
66
+ return normalized
67
+ }
@@ -327,6 +327,18 @@ export class TerminalManager {
327
327
  }
328
328
  }
329
329
 
330
+ getRootPidsByCwd(cwd: string) {
331
+ const pids: number[] = []
332
+ for (const session of this.sessions.values()) {
333
+ if (session.cwd !== cwd || session.status !== "running") continue
334
+ const pid = session.process?.pid
335
+ if (typeof pid === "number") {
336
+ pids.push(pid)
337
+ }
338
+ }
339
+ return pids
340
+ }
341
+
330
342
  private snapshotOf(session: TerminalSession): TerminalSnapshot {
331
343
  return {
332
344
  terminalId: session.terminalId,
@@ -14,7 +14,9 @@ import { DiffStore } from "./diff-store"
14
14
  import { EventStore } from "./event-store"
15
15
  import { openExternal } from "./external-open"
16
16
  import { KeybindingsManager } from "./keybindings"
17
+ import { killLocalHttpServer, listLocalHttpServers } from "./local-http-servers"
17
18
  import { ensureProjectDirectory, resolveLocalPath } from "./paths"
19
+ import { readProjectQuickActions, writeProjectQuickActions } from "./project-quick-actions"
18
20
  import { writeStandaloneTranscriptExport } from "./standalone-export"
19
21
  import { TerminalManager } from "./terminal-manager"
20
22
  import type { UpdateManager } from "./update-manager"
@@ -1037,6 +1039,38 @@ export function createWsRouter({
1037
1039
  send(ws, { v: PROTOCOL_VERSION, type: "ack", id })
1038
1040
  return
1039
1041
  }
1042
+ case "browser.listLocalHttpServers": {
1043
+ const project = command.projectId ? store.getProject(command.projectId) : null
1044
+ const result = await listLocalHttpServers({
1045
+ projectPath: project?.localPath,
1046
+ projectTerminalRootPids: project ? terminals.getRootPidsByCwd(project.localPath) : [],
1047
+ })
1048
+ send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result })
1049
+ return
1050
+ }
1051
+ case "browser.killLocalHttpServer": {
1052
+ const result = await killLocalHttpServer(command.port)
1053
+ send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result })
1054
+ return
1055
+ }
1056
+ case "project.readQuickActions": {
1057
+ const project = store.getProject(command.projectId)
1058
+ if (!project) {
1059
+ throw new Error("Project not found")
1060
+ }
1061
+ const result = await readProjectQuickActions(project.localPath)
1062
+ send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result })
1063
+ return
1064
+ }
1065
+ case "project.writeQuickActions": {
1066
+ const project = store.getProject(command.projectId)
1067
+ if (!project) {
1068
+ throw new Error("Project not found")
1069
+ }
1070
+ const result = await writeProjectQuickActions(project.localPath, command.quickActions)
1071
+ send(ws, { v: PROTOCOL_VERSION, type: "ack", id, result })
1072
+ return
1073
+ }
1040
1074
  case "update.check": {
1041
1075
  const snapshot = updateManager
1042
1076
  ? await updateManager.checkForUpdates({ force: command.force })
@@ -25,6 +25,22 @@ export interface EditorOpenSettings {
25
25
  commandTemplate: string
26
26
  }
27
27
 
28
+ export interface LocalHttpServerInfo {
29
+ title: string
30
+ address: string
31
+ port: number
32
+ status: number
33
+ ownerPath?: string
34
+ processName?: string
35
+ sameProject?: boolean
36
+ }
37
+
38
+ export interface ProjectQuickAction {
39
+ id: string
40
+ label: string
41
+ command: string
42
+ }
43
+
28
44
  export type SubscriptionTopic =
29
45
  | { type: "sidebar" }
30
46
  | { type: "local-projects" }
@@ -61,6 +77,10 @@ export type ClientCommand =
61
77
  | { type: "sidebar.reorderProjectGroups"; projectIds: string[] }
62
78
  | { type: "project.readDiffPatch"; projectId: string; path: string }
63
79
  | { type: "system.ping" }
80
+ | { type: "browser.listLocalHttpServers"; projectId?: string }
81
+ | { type: "browser.killLocalHttpServer"; port: number }
82
+ | { type: "project.readQuickActions"; projectId: string }
83
+ | { type: "project.writeQuickActions"; projectId: string; quickActions: ProjectQuickAction[] }
64
84
  | { type: "update.check"; force?: boolean }
65
85
  | { type: "update.install" }
66
86
  | { type: "settings.readKeybindings" }