From 9a212cd23f8f28ae92e45fddfceb70ebc038b71f Mon Sep 17 00:00:00 2001 From: LemonCube Date: Sun, 2 Aug 2026 12:49:18 -0700 Subject: [PATCH 01/13] Update .gitignore --- .gitignore | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.gitignore b/.gitignore index e43b0f98..7c163bd7 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,9 @@ .DS_Store + +# Sensitive configuration files with real credentials +kite.toml +kite-service/.env + +# Keep example files - these should be committed +!kite.toml.example +!kite-service/.env.example From 7b99ec651f5e85dab39cb63177353a9ad3a1fd3b Mon Sep 17 00:00:00 2001 From: LemonCube Date: Sun, 2 Aug 2026 16:14:34 -0700 Subject: [PATCH 02/13] Add Command Importing --- .../components/app/CommandImportDialog.tsx | 123 ++++++++++++++++++ kite-web/src/components/app/CommandList.tsx | 4 + 2 files changed, 127 insertions(+) create mode 100644 kite-web/src/components/app/CommandImportDialog.tsx diff --git a/kite-web/src/components/app/CommandImportDialog.tsx b/kite-web/src/components/app/CommandImportDialog.tsx new file mode 100644 index 00000000..2142dbe5 --- /dev/null +++ b/kite-web/src/components/app/CommandImportDialog.tsx @@ -0,0 +1,123 @@ +import { ReactNode, useCallback, useState } from "react"; +import { Button } from "../ui/button"; +import { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "../ui/dialog"; +import { Input } from "../ui/input"; +import { useCommandsImportMutation } from "@/lib/api/mutations"; +import { useAppId } from "@/lib/hooks/params"; +import { toast } from "sonner"; +import { CommandCreateRequest } from "@/lib/types/wire.gen"; + +export function CommandImportDialog({ children }: { children: ReactNode }) { + const [dialogOpen, setDialogOpen] = useState(false); + const [shareCode, setShareCode] = useState(""); + + const appId = useAppId(); + const commandsImportMutation = useCommandsImportMutation(appId); + + const handleImport = useCallback(() => { + const trimmed = shareCode.trim(); + + if (!trimmed) { + toast.error("Please paste a share code before importing"); + return; + } + + let parsed: unknown; + try { + parsed = JSON.parse(trimmed); + } catch { + toast.error( + "Invalid share code... please check you copied it correctly" + ); + return; + } + + if ( + typeof parsed !== "object" || + parsed === null || + !("flow_source" in parsed) || + !("enabled" in parsed) + ) { + toast.error( + "This doesn't look like a valid command share code, please check you copied it correctly" + ); + return; + } + + const command = parsed as CommandCreateRequest; + + commandsImportMutation.mutate( + { commands: [command] }, + { + onSuccess: (res) => { + if (res.success) { + toast.success("Command imported successfully"); + setShareCode(""); + setDialogOpen(false); + } else { + toast.error( + `Failed to import command: ${res.error.message} (${res.error.code})` + ); + } + }, + onError: () => { + toast.error( + "Failed to import command, please check the share code and try again" + ); + }, + } + ); + }, [shareCode, commandsImportMutation]); + + return ( + { + setDialogOpen(open); + if (!open) setShareCode(""); + }} + > + {children} + + + Import Command + + Import a command using a generated share code. + + +
+
+
Share Code
+
+
+ setShareCode(e.target.value)} + placeholder="" + /> +
+
+ + + + + + +
+
+ ); +} \ No newline at end of file diff --git a/kite-web/src/components/app/CommandList.tsx b/kite-web/src/components/app/CommandList.tsx index 32cfb631..c84c3714 100644 --- a/kite-web/src/components/app/CommandList.tsx +++ b/kite-web/src/components/app/CommandList.tsx @@ -4,6 +4,7 @@ import AppEmptyPlaceholder from "./AppEmptyPlaceholder"; import { Skeleton } from "../ui/skeleton"; import AutoAnimate from "../common/AutoAnimate"; import CommandCreateDialog from "./CommandCreateDialog"; +import { CommandImportDialog } from "./CommandImportDialog"; import { useCommands } from "@/lib/hooks/api"; import { CommandDeployDialog } from "./CommandDeployDialog"; import { useState } from "react"; @@ -42,6 +43,9 @@ export default function CommandList() { + + + Date: Tue, 4 Aug 2026 18:13:00 -0700 Subject: [PATCH 03/13] Small changes for importing commands --- .../components/app/CommandImportDialog.tsx | 35 +++++++++++++++---- kite-web/src/components/app/CommandList.tsx | 14 ++++---- 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/kite-web/src/components/app/CommandImportDialog.tsx b/kite-web/src/components/app/CommandImportDialog.tsx index 2142dbe5..29b559b2 100644 --- a/kite-web/src/components/app/CommandImportDialog.tsx +++ b/kite-web/src/components/app/CommandImportDialog.tsx @@ -1,4 +1,5 @@ import { ReactNode, useCallback, useState } from "react"; +import { useRouter } from "next/router"; import { Button } from "../ui/button"; import { Dialog, @@ -20,6 +21,7 @@ export function CommandImportDialog({ children }: { children: ReactNode }) { const [dialogOpen, setDialogOpen] = useState(false); const [shareCode, setShareCode] = useState(""); + const router = useRouter(); const appId = useAppId(); const commandsImportMutation = useCommandsImportMutation(appId); @@ -27,7 +29,7 @@ export function CommandImportDialog({ children }: { children: ReactNode }) { const trimmed = shareCode.trim(); if (!trimmed) { - toast.error("Please paste a share code before importing"); + toast.error("Paste code before importing"); return; } @@ -36,7 +38,7 @@ export function CommandImportDialog({ children }: { children: ReactNode }) { parsed = JSON.parse(trimmed); } catch { toast.error( - "Invalid share code... please check you copied it correctly" + "Error importing command: invalid share code" ); return; } @@ -48,7 +50,7 @@ export function CommandImportDialog({ children }: { children: ReactNode }) { !("enabled" in parsed) ) { toast.error( - "This doesn't look like a valid command share code, please check you copied it correctly" + "Error importing command: invalid share code" ); return; } @@ -60,9 +62,29 @@ export function CommandImportDialog({ children }: { children: ReactNode }) { { onSuccess: (res) => { if (res.success) { - toast.success("Command imported successfully"); + const imported = res.data[0]; + + if (!imported) { + toast.error( + `Error importing command: command not found` + ); + setShareCode(""); + setDialogOpen(false); + return; + } + + toast.success("Command imported!"); setShareCode(""); setDialogOpen(false); + + setTimeout( + () => + router.push({ + pathname: "/apps/[appId]/commands/[cmdId]", + query: { appId, cmdId: imported.id }, + }), + 500 + ); } else { toast.error( `Failed to import command: ${res.error.message} (${res.error.code})` @@ -91,14 +113,13 @@ export function CommandImportDialog({ children }: { children: ReactNode }) { Import Command - Import a command using a generated share code. + Import a command using a share code.
Share Code
-
-
+
setShareCode(e.target.value)} diff --git a/kite-web/src/components/app/CommandList.tsx b/kite-web/src/components/app/CommandList.tsx index c84c3714..a886d311 100644 --- a/kite-web/src/components/app/CommandList.tsx +++ b/kite-web/src/components/app/CommandList.tsx @@ -40,12 +40,14 @@ export default function CommandList() { one leaves no command to compare at all. Gating on "has undeployed changes" made deleted commands unremovable. */}
- - - - - - +
+ + + + + + +
Date: Tue, 4 Aug 2026 18:13:36 -0700 Subject: [PATCH 04/13] Add command exporting --- .../components/app/CommandExportDialog.tsx | 86 +++++++++++++++++++ .../src/components/app/CommandListEntry.tsx | 8 ++ 2 files changed, 94 insertions(+) create mode 100644 kite-web/src/components/app/CommandExportDialog.tsx diff --git a/kite-web/src/components/app/CommandExportDialog.tsx b/kite-web/src/components/app/CommandExportDialog.tsx new file mode 100644 index 00000000..68c32a9c --- /dev/null +++ b/kite-web/src/components/app/CommandExportDialog.tsx @@ -0,0 +1,86 @@ +import { ReactNode, useMemo, useState } from "react"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "../ui/dialog"; +import { Button } from "../ui/button"; +import { Textarea } from "../ui/textarea"; +import { Command } from "@/lib/types/wire.gen"; +import { toast } from "sonner"; + +export function CommandExportDialog({ + command, + children, +}: { + command: Command; + children: ReactNode; +}) { + const [dialogOpen, setDialogOpen] = useState(false); + + const shareCode = useMemo(() => { + try { + return JSON.stringify({ + flow_source: command.flow_source, + enabled: command.enabled, + }); + } catch { + return null; + } + }, [command]); + + const handleCopy = () => { + if (!shareCode) { + toast.error("Failed to generate a share code for this command"); + return; + } + + if (!navigator.clipboard || !navigator.clipboard.writeText) { + toast.error("Copy failed: use ctrl + c to copy"); + return; + } + + navigator.clipboard + .writeText(shareCode) + .then(() => { + toast.success("Copied to clipboard!"); + setDialogOpen(false); + }) + .catch(() => { + toast.error("Copy failed: use ctrl + c to copy"); + }); + }; + + return ( + + {children} + + + Export Command + + Share your command using the code below. + + +
+
+
Share Code
+