@nikitadmitrieff/feedback-chat 0.1.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,158 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli/init.ts
4
+ import prompts from "prompts";
5
+ import { existsSync, writeFileSync, appendFileSync, readFileSync, mkdirSync } from "fs";
6
+ import { join, resolve } from "path";
7
+ var CHAT_ROUTE_TEMPLATE = (hasGithub) => `import { createFeedbackHandler } from '@nikitadmitrieff/feedback-chat/server'
8
+
9
+ const handler = createFeedbackHandler({
10
+ password: process.env.FEEDBACK_PASSWORD!,${hasGithub ? `
11
+ github: {
12
+ token: process.env.GITHUB_TOKEN!,
13
+ repo: process.env.GITHUB_REPO!,
14
+ },` : ""}
15
+ })
16
+
17
+ export const POST = handler.POST
18
+ `;
19
+ var STATUS_ROUTE_TEMPLATE = `import { createStatusHandler } from '@nikitadmitrieff/feedback-chat/server'
20
+
21
+ const handler = createStatusHandler({
22
+ password: process.env.FEEDBACK_PASSWORD!,
23
+ })
24
+
25
+ export const { GET, POST } = handler
26
+ `;
27
+ function findAppDir(cwd) {
28
+ const candidates = [
29
+ join(cwd, "src", "app"),
30
+ join(cwd, "app")
31
+ ];
32
+ for (const dir of candidates) {
33
+ if (existsSync(dir)) return dir;
34
+ }
35
+ return null;
36
+ }
37
+ function safeWriteFile(filePath, content, overwrite) {
38
+ if (existsSync(filePath) && !overwrite) {
39
+ console.log(` Skipped ${filePath} (already exists)`);
40
+ return false;
41
+ }
42
+ const dir = filePath.substring(0, filePath.lastIndexOf("/"));
43
+ mkdirSync(dir, { recursive: true });
44
+ writeFileSync(filePath, content);
45
+ return true;
46
+ }
47
+ function appendEnvVar(envPath, key, value) {
48
+ if (existsSync(envPath)) {
49
+ const existing = readFileSync(envPath, "utf-8");
50
+ if (existing.includes(`${key}=`)) return;
51
+ }
52
+ appendFileSync(envPath, `${key}=${value}
53
+ `);
54
+ }
55
+ async function main() {
56
+ const cwd = resolve(process.cwd());
57
+ console.log();
58
+ console.log(" feedback-chat setup wizard");
59
+ console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
60
+ console.log();
61
+ const appDir = findAppDir(cwd);
62
+ if (!appDir) {
63
+ console.error(" Could not find app/ or src/app/ directory.");
64
+ console.error(" Make sure you run this from a Next.js project root.");
65
+ process.exit(1);
66
+ }
67
+ console.log(` Found Next.js app directory: ${appDir}`);
68
+ console.log();
69
+ console.log(" \u2500\u2500 Widget Setup \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
70
+ console.log();
71
+ const widgetAnswers = await prompts([
72
+ {
73
+ type: "password",
74
+ name: "apiKey",
75
+ message: "Anthropic API key (for chat, uses Haiku)"
76
+ },
77
+ {
78
+ type: "password",
79
+ name: "password",
80
+ message: "Feedback password (gates access to the chatbot)"
81
+ }
82
+ ]);
83
+ if (!widgetAnswers.apiKey || !widgetAnswers.password) {
84
+ console.log(" Cancelled.");
85
+ process.exit(0);
86
+ }
87
+ console.log();
88
+ console.log(" \u2500\u2500 GitHub Integration \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
89
+ console.log();
90
+ const githubAnswer = await prompts({
91
+ type: "confirm",
92
+ name: "enabled",
93
+ message: "Enable GitHub issues? (feedback creates issues for tracking)",
94
+ initial: true
95
+ });
96
+ let githubToken = "";
97
+ let githubRepo = "";
98
+ if (githubAnswer.enabled) {
99
+ const ghAnswers = await prompts([
100
+ {
101
+ type: "password",
102
+ name: "token",
103
+ message: "GitHub token (needs repo scope)"
104
+ },
105
+ {
106
+ type: "text",
107
+ name: "repo",
108
+ message: "GitHub repo (owner/name)"
109
+ }
110
+ ]);
111
+ githubToken = ghAnswers.token || "";
112
+ githubRepo = ghAnswers.repo || "";
113
+ }
114
+ const hasGithub = !!(githubToken && githubRepo);
115
+ let overwrite = false;
116
+ const chatRoutePath = join(appDir, "api", "feedback", "chat", "route.ts");
117
+ const statusRoutePath = join(appDir, "api", "feedback", "status", "route.ts");
118
+ if (existsSync(chatRoutePath) || existsSync(statusRoutePath)) {
119
+ const overwriteAnswer = await prompts({
120
+ type: "confirm",
121
+ name: "overwrite",
122
+ message: "Route files already exist. Overwrite?",
123
+ initial: false
124
+ });
125
+ overwrite = overwriteAnswer.overwrite ?? false;
126
+ }
127
+ console.log();
128
+ if (safeWriteFile(chatRoutePath, CHAT_ROUTE_TEMPLATE(hasGithub), overwrite)) {
129
+ console.log(` Created ${chatRoutePath}`);
130
+ }
131
+ if (safeWriteFile(statusRoutePath, STATUS_ROUTE_TEMPLATE, overwrite)) {
132
+ console.log(` Created ${statusRoutePath}`);
133
+ }
134
+ const envPath = join(cwd, ".env.local");
135
+ appendEnvVar(envPath, "ANTHROPIC_API_KEY", widgetAnswers.apiKey);
136
+ appendEnvVar(envPath, "FEEDBACK_PASSWORD", widgetAnswers.password);
137
+ if (hasGithub) {
138
+ appendEnvVar(envPath, "GITHUB_TOKEN", githubToken);
139
+ appendEnvVar(envPath, "GITHUB_REPO", githubRepo);
140
+ }
141
+ console.log(` Updated ${envPath}`);
142
+ console.log();
143
+ console.log(" \u2500\u2500 Add to your layout \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
144
+ console.log();
145
+ console.log(" import { FeedbackPanel } from '@nikitadmitrieff/feedback-chat'");
146
+ console.log(" import '@nikitadmitrieff/feedback-chat/styles.css'");
147
+ console.log();
148
+ console.log(" // In your component:");
149
+ console.log(" const [open, setOpen] = useState(false)");
150
+ console.log(" <FeedbackPanel isOpen={open} onToggle={() => setOpen(v => !v)} />");
151
+ console.log();
152
+ console.log(" Done.");
153
+ console.log();
154
+ }
155
+ main().catch((err) => {
156
+ console.error(err);
157
+ process.exit(1);
158
+ });
@@ -0,0 +1,43 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ type Stage = 'created' | 'queued' | 'running' | 'validating' | 'preview_ready' | 'deployed' | 'failed' | 'rejected';
4
+ type StatusResponse = {
5
+ stage: Stage;
6
+ issueNumber: number;
7
+ issueUrl: string;
8
+ failReason?: string;
9
+ previewUrl?: string;
10
+ prNumber?: number;
11
+ prUrl?: string;
12
+ };
13
+ type Conversation = {
14
+ id: string;
15
+ title: string;
16
+ createdAt: string;
17
+ updatedAt: string;
18
+ };
19
+ type FeedbackPanelProps = {
20
+ isOpen: boolean;
21
+ onToggle: () => void;
22
+ };
23
+
24
+ declare function FeedbackPanel({ isOpen, onToggle, apiUrl }: FeedbackPanelProps & {
25
+ apiUrl?: string;
26
+ }): react_jsx_runtime.JSX.Element;
27
+
28
+ declare function useConversations(): {
29
+ conversations: Conversation[];
30
+ activeId: string;
31
+ switchTo: (id: string) => void;
32
+ create: () => void;
33
+ remove: (id: string) => void;
34
+ save: () => void;
35
+ };
36
+
37
+ type PipelineTrackerProps = {
38
+ issueUrl: string;
39
+ statusEndpoint?: string;
40
+ };
41
+ declare function PipelineTracker({ issueUrl, statusEndpoint, }: PipelineTrackerProps): react_jsx_runtime.JSX.Element;
42
+
43
+ export { type Conversation, FeedbackPanel, type FeedbackPanelProps, PipelineTracker, type Stage, type StatusResponse, useConversations };