@nikitadmitrieff/feedback-chat 0.1.3 → 0.2.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/dist/cli/deploy-agent.js +177 -0
- package/dist/cli/init.js +183 -34
- package/package.json +3 -2
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli/deploy-agent.ts
|
|
4
|
+
import prompts from "prompts";
|
|
5
|
+
import { existsSync, readFileSync } from "fs";
|
|
6
|
+
import { join, resolve } from "path";
|
|
7
|
+
import { execSync } from "child_process";
|
|
8
|
+
function checkCommand(cmd) {
|
|
9
|
+
try {
|
|
10
|
+
execSync(`which ${cmd}`, { stdio: "ignore" });
|
|
11
|
+
return true;
|
|
12
|
+
} catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function readEnvFile(cwd) {
|
|
17
|
+
const envPath = join(cwd, ".env.local");
|
|
18
|
+
if (!existsSync(envPath)) return {};
|
|
19
|
+
const content = readFileSync(envPath, "utf-8");
|
|
20
|
+
const vars = {};
|
|
21
|
+
for (const line of content.split("\n")) {
|
|
22
|
+
const match = line.match(/^([A-Z_]+)=(.+)$/);
|
|
23
|
+
if (match) vars[match[1]] = match[2];
|
|
24
|
+
}
|
|
25
|
+
return vars;
|
|
26
|
+
}
|
|
27
|
+
async function main() {
|
|
28
|
+
const cwd = resolve(process.cwd());
|
|
29
|
+
console.error();
|
|
30
|
+
console.error(" feedback-chat agent deployment script generator");
|
|
31
|
+
console.error(" \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
32
|
+
console.error();
|
|
33
|
+
const hasRailway = checkCommand("railway");
|
|
34
|
+
const hasGh = checkCommand("gh");
|
|
35
|
+
if (!hasRailway) {
|
|
36
|
+
console.error(" \u2717 Railway CLI not found. Install: npm install -g @railway/cli");
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
if (!hasGh) {
|
|
40
|
+
console.error(" \u2717 GitHub CLI not found. Install: https://cli.github.com/");
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
const envVars = readEnvFile(cwd);
|
|
44
|
+
const githubToken = envVars["GITHUB_TOKEN"] || "";
|
|
45
|
+
const githubRepo = envVars["GITHUB_REPO"] || "";
|
|
46
|
+
const anthropicKey = envVars["ANTHROPIC_API_KEY"] || "";
|
|
47
|
+
if (!githubToken || !githubRepo) {
|
|
48
|
+
console.error(" \u2717 GITHUB_TOKEN and GITHUB_REPO must be set in .env.local");
|
|
49
|
+
console.error(" Run `npx feedback-chat init` first with the +GitHub or +Pipeline tier.");
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
const { authMethod } = await prompts({
|
|
53
|
+
type: "select",
|
|
54
|
+
name: "authMethod",
|
|
55
|
+
message: "Claude authentication for the agent",
|
|
56
|
+
choices: [
|
|
57
|
+
{
|
|
58
|
+
title: "Claude Max subscription ($0/run)",
|
|
59
|
+
description: "Uses OAuth credentials from your local keychain",
|
|
60
|
+
value: "max"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
title: "API key (pay per token)",
|
|
64
|
+
description: "Uses ANTHROPIC_API_KEY from .env.local",
|
|
65
|
+
value: "api"
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
});
|
|
69
|
+
if (!authMethod) {
|
|
70
|
+
console.error(" Cancelled.");
|
|
71
|
+
process.exit(0);
|
|
72
|
+
}
|
|
73
|
+
let credentialsJson = "";
|
|
74
|
+
if (authMethod === "max") {
|
|
75
|
+
console.error();
|
|
76
|
+
console.error(" To get your Claude credentials, run:");
|
|
77
|
+
console.error(' security find-generic-password -s "Claude Code-credentials" -a "$USER" -w');
|
|
78
|
+
console.error();
|
|
79
|
+
console.error(" Copy the JSON output (the claudeAiOauth portion).");
|
|
80
|
+
console.error();
|
|
81
|
+
const { creds } = await prompts({
|
|
82
|
+
type: "text",
|
|
83
|
+
name: "creds",
|
|
84
|
+
message: "Paste your Claude credentials JSON (or press Enter to include a placeholder)"
|
|
85
|
+
});
|
|
86
|
+
credentialsJson = creds || "${CLAUDE_CREDENTIALS_JSON}";
|
|
87
|
+
}
|
|
88
|
+
const webhookSecret = "$(openssl rand -hex 32)";
|
|
89
|
+
const envLocalPath = join(cwd, ".env.local");
|
|
90
|
+
const authLine = authMethod === "max" ? `
|
|
91
|
+
CLAUDE_CREDENTIALS_JSON='${credentialsJson.replace(/'/g, "'\\''")}'` : anthropicKey ? `
|
|
92
|
+
ANTHROPIC_API_KEY="${anthropicKey}"` : "";
|
|
93
|
+
const script = `#!/usr/bin/env bash
|
|
94
|
+
set -euo pipefail
|
|
95
|
+
|
|
96
|
+
# \u2500\u2500 feedback-chat agent deployment script \u2500\u2500
|
|
97
|
+
# Generated by: npx feedback-chat deploy-agent
|
|
98
|
+
# Review this script before running it.
|
|
99
|
+
|
|
100
|
+
echo "==> Cloning feedback-chat agent..."
|
|
101
|
+
TMPDIR=$(mktemp -d)
|
|
102
|
+
trap 'rm -rf "$TMPDIR"' EXIT
|
|
103
|
+
git clone --depth 1 https://github.com/NikitaDmitrieff/feedback-chat "$TMPDIR/feedback-chat"
|
|
104
|
+
cd "$TMPDIR/feedback-chat/packages/agent"
|
|
105
|
+
|
|
106
|
+
echo "==> Creating Railway project..."
|
|
107
|
+
railway init
|
|
108
|
+
|
|
109
|
+
echo "==> First deploy (creates the service)..."
|
|
110
|
+
DEPLOY_OUTPUT=$(railway up --detach 2>&1)
|
|
111
|
+
echo "$DEPLOY_OUTPUT"
|
|
112
|
+
|
|
113
|
+
echo "==> Waiting for service to register..."
|
|
114
|
+
sleep 5
|
|
115
|
+
|
|
116
|
+
echo "==> Finding and linking service..."
|
|
117
|
+
SERVICE_NAME=$(railway service status --all 2>&1 | grep -oE '^[a-z][-a-z0-9]*' | head -1)
|
|
118
|
+
if [ -z "$SERVICE_NAME" ]; then
|
|
119
|
+
echo "\u2717 Could not detect service name. Run 'railway service status --all' manually."
|
|
120
|
+
exit 1
|
|
121
|
+
fi
|
|
122
|
+
echo " Linking service: $SERVICE_NAME"
|
|
123
|
+
railway service link "$SERVICE_NAME"
|
|
124
|
+
|
|
125
|
+
echo "==> Setting environment variables..."
|
|
126
|
+
WEBHOOK_SECRET=${webhookSecret}
|
|
127
|
+
railway variables set \\
|
|
128
|
+
GITHUB_TOKEN="${githubToken}" \\
|
|
129
|
+
GITHUB_REPO="${githubRepo}" \\
|
|
130
|
+
WEBHOOK_SECRET="$WEBHOOK_SECRET"${authLine ? ` \\${authLine}` : ""}
|
|
131
|
+
|
|
132
|
+
echo "==> Getting public domain..."
|
|
133
|
+
DOMAIN_OUTPUT=$(railway domain 2>&1)
|
|
134
|
+
AGENT_URL=$(echo "$DOMAIN_OUTPUT" | grep -oE 'https://[^ ]+')
|
|
135
|
+
if [ -z "$AGENT_URL" ]; then
|
|
136
|
+
echo "\u2717 Could not extract domain URL. Output was:"
|
|
137
|
+
echo "$DOMAIN_OUTPUT"
|
|
138
|
+
exit 1
|
|
139
|
+
fi
|
|
140
|
+
echo " Agent URL: $AGENT_URL"
|
|
141
|
+
|
|
142
|
+
echo "==> Setting up GitHub webhook..."
|
|
143
|
+
REPO_OWNER=$(echo "${githubRepo}" | cut -d/ -f1)
|
|
144
|
+
REPO_NAME=$(echo "${githubRepo}" | cut -d/ -f2)
|
|
145
|
+
gh api "repos/$REPO_OWNER/$REPO_NAME/hooks" \\
|
|
146
|
+
-f name=web -f active=true \\
|
|
147
|
+
-f "config[url]=$AGENT_URL/webhook/github" \\
|
|
148
|
+
-f "config[content_type]=json" \\
|
|
149
|
+
-f "config[secret]=$WEBHOOK_SECRET" \\
|
|
150
|
+
-f 'events[]=issues'
|
|
151
|
+
|
|
152
|
+
echo "==> Updating consumer .env.local..."
|
|
153
|
+
echo "AGENT_URL=$AGENT_URL" >> "${envLocalPath}"
|
|
154
|
+
|
|
155
|
+
echo ""
|
|
156
|
+
echo "==> Done! Summary:"
|
|
157
|
+
echo " Agent URL: $AGENT_URL"
|
|
158
|
+
echo " Webhook: $AGENT_URL/webhook/github"
|
|
159
|
+
echo " Webhook secret: $WEBHOOK_SECRET"
|
|
160
|
+
echo ""
|
|
161
|
+
echo " Next steps:"
|
|
162
|
+
echo " 1. Wait for Railway deploy to finish"
|
|
163
|
+
echo " 2. Verify: curl $AGENT_URL/health"
|
|
164
|
+
echo " 3. Test by submitting feedback through the widget"
|
|
165
|
+
`;
|
|
166
|
+
process.stdout.write(script);
|
|
167
|
+
console.error();
|
|
168
|
+
console.error(" Script generated! To run it:");
|
|
169
|
+
console.error(" npx feedback-chat deploy-agent | bash");
|
|
170
|
+
console.error(" Or save and review first:");
|
|
171
|
+
console.error(" npx feedback-chat deploy-agent > deploy.sh && chmod +x deploy.sh && cat deploy.sh");
|
|
172
|
+
console.error();
|
|
173
|
+
}
|
|
174
|
+
main().catch((err) => {
|
|
175
|
+
console.error(err);
|
|
176
|
+
process.exit(1);
|
|
177
|
+
});
|
package/dist/cli/init.js
CHANGED
|
@@ -4,27 +4,79 @@
|
|
|
4
4
|
import prompts from "prompts";
|
|
5
5
|
import { existsSync, writeFileSync, appendFileSync, readFileSync, mkdirSync } from "fs";
|
|
6
6
|
import { join, resolve } from "path";
|
|
7
|
-
|
|
7
|
+
import { execSync } from "child_process";
|
|
8
|
+
var CHAT_ROUTE_TEMPLATE = (tier) => `import { createFeedbackHandler } from '@nikitadmitrieff/feedback-chat/server'
|
|
8
9
|
|
|
9
10
|
const handler = createFeedbackHandler({
|
|
10
|
-
password: process.env.FEEDBACK_PASSWORD!,${
|
|
11
|
+
password: process.env.FEEDBACK_PASSWORD!,${tier !== "chat" ? `
|
|
11
12
|
github: {
|
|
12
13
|
token: process.env.GITHUB_TOKEN!,
|
|
13
14
|
repo: process.env.GITHUB_REPO!,
|
|
14
15
|
},` : ""}
|
|
16
|
+
// projectContext: 'Describe your app here so the AI gives better responses',
|
|
15
17
|
})
|
|
16
18
|
|
|
17
19
|
export const POST = handler.POST
|
|
18
20
|
`;
|
|
19
|
-
var STATUS_ROUTE_TEMPLATE =
|
|
21
|
+
var STATUS_ROUTE_TEMPLATE = (tier) => {
|
|
22
|
+
if (tier === "chat") {
|
|
23
|
+
return `import { createStatusHandler } from '@nikitadmitrieff/feedback-chat/server'
|
|
20
24
|
|
|
21
25
|
const handler = createStatusHandler({
|
|
22
26
|
password: process.env.FEEDBACK_PASSWORD!,
|
|
23
27
|
})
|
|
24
28
|
|
|
25
29
|
export const { GET, POST } = handler
|
|
30
|
+
`;
|
|
31
|
+
}
|
|
32
|
+
if (tier === "github") {
|
|
33
|
+
return `import { createStatusHandler } from '@nikitadmitrieff/feedback-chat/server'
|
|
34
|
+
|
|
35
|
+
const handler = createStatusHandler({
|
|
36
|
+
password: process.env.FEEDBACK_PASSWORD!,
|
|
37
|
+
github: {
|
|
38
|
+
token: process.env.GITHUB_TOKEN!,
|
|
39
|
+
repo: process.env.GITHUB_REPO!,
|
|
40
|
+
},
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
export const { GET, POST } = handler
|
|
44
|
+
`;
|
|
45
|
+
}
|
|
46
|
+
return `import { createStatusHandler } from '@nikitadmitrieff/feedback-chat/server'
|
|
47
|
+
|
|
48
|
+
const handler = createStatusHandler({
|
|
49
|
+
password: process.env.FEEDBACK_PASSWORD!,
|
|
50
|
+
github: {
|
|
51
|
+
token: process.env.GITHUB_TOKEN!,
|
|
52
|
+
repo: process.env.GITHUB_REPO!,
|
|
53
|
+
},
|
|
54
|
+
agentUrl: process.env.AGENT_URL,
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
export const { GET, POST } = handler
|
|
58
|
+
`;
|
|
59
|
+
};
|
|
60
|
+
var FEEDBACK_BUTTON_TEMPLATE = `'use client'
|
|
61
|
+
|
|
62
|
+
import { useState } from 'react'
|
|
63
|
+
import { FeedbackPanel } from '@nikitadmitrieff/feedback-chat'
|
|
64
|
+
import '@nikitadmitrieff/feedback-chat/styles.css'
|
|
65
|
+
|
|
66
|
+
export function FeedbackButton() {
|
|
67
|
+
const [open, setOpen] = useState(false)
|
|
68
|
+
return <FeedbackPanel isOpen={open} onToggle={() => setOpen(!open)} />
|
|
69
|
+
}
|
|
26
70
|
`;
|
|
27
71
|
var SOURCE_DIRECTIVE = '@source "../node_modules/@nikitadmitrieff/feedback-chat/dist/**/*.js";';
|
|
72
|
+
var GITHUB_LABELS = [
|
|
73
|
+
{ name: "feedback-bot", color: "0E8A16", description: "Created by feedback widget" },
|
|
74
|
+
{ name: "auto-implement", color: "1D76DB", description: "Agent should implement this" },
|
|
75
|
+
{ name: "in-progress", color: "FBCA04", description: "Agent is working on this" },
|
|
76
|
+
{ name: "agent-failed", color: "D93F0B", description: "Agent build/lint failed" },
|
|
77
|
+
{ name: "preview-pending", color: "C5DEF5", description: "PR ready, preview deploying" },
|
|
78
|
+
{ name: "rejected", color: "E4E669", description: "User rejected changes" }
|
|
79
|
+
];
|
|
28
80
|
function findAppDir(cwd) {
|
|
29
81
|
const candidates = [
|
|
30
82
|
join(cwd, "src", "app"),
|
|
@@ -84,12 +136,65 @@ function appendEnvVar(envPath, key, value) {
|
|
|
84
136
|
appendFileSync(envPath, `${key}=${value}
|
|
85
137
|
`);
|
|
86
138
|
}
|
|
139
|
+
function checkReactVersion(cwd) {
|
|
140
|
+
const reactPkgPath = join(cwd, "node_modules", "react", "package.json");
|
|
141
|
+
if (!existsSync(reactPkgPath)) return;
|
|
142
|
+
try {
|
|
143
|
+
const pkg = JSON.parse(readFileSync(reactPkgPath, "utf-8"));
|
|
144
|
+
const version = pkg.version;
|
|
145
|
+
if (version === "19.1.0" || version === "19.1.1") {
|
|
146
|
+
console.error(` \u2717 react@${version} detected \u2014 @ai-sdk/react excludes this version.`);
|
|
147
|
+
console.error(" Fix: npm install react@latest react-dom@latest");
|
|
148
|
+
console.error();
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
|
151
|
+
} catch {
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function hasGhCli() {
|
|
155
|
+
try {
|
|
156
|
+
execSync("which gh", { stdio: "ignore" });
|
|
157
|
+
return true;
|
|
158
|
+
} catch {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function createGitHubLabels(cwd) {
|
|
163
|
+
if (!hasGhCli()) {
|
|
164
|
+
console.log(" \u26A0 GitHub CLI (gh) not found. Create these labels manually:");
|
|
165
|
+
console.log();
|
|
166
|
+
for (const label of GITHUB_LABELS) {
|
|
167
|
+
console.log(` gh label create ${label.name} --color ${label.color} --description "${label.description}" --force`);
|
|
168
|
+
}
|
|
169
|
+
console.log();
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
console.log(" Creating GitHub labels...");
|
|
173
|
+
for (const label of GITHUB_LABELS) {
|
|
174
|
+
try {
|
|
175
|
+
execSync(
|
|
176
|
+
`gh label create ${label.name} --color ${label.color} --description "${label.description}" --force`,
|
|
177
|
+
{ cwd, stdio: "ignore" }
|
|
178
|
+
);
|
|
179
|
+
console.log(` Created label: ${label.name}`);
|
|
180
|
+
} catch {
|
|
181
|
+
console.log(` \u26A0 Could not create label: ${label.name}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function detectComponentsDir(cwd) {
|
|
186
|
+
if (existsSync(join(cwd, "src", "app"))) {
|
|
187
|
+
return join(cwd, "src", "components");
|
|
188
|
+
}
|
|
189
|
+
return join(cwd, "components");
|
|
190
|
+
}
|
|
87
191
|
async function main() {
|
|
88
192
|
const cwd = resolve(process.cwd());
|
|
89
193
|
console.log();
|
|
90
194
|
console.log(" feedback-chat setup wizard");
|
|
91
195
|
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");
|
|
92
196
|
console.log();
|
|
197
|
+
checkReactVersion(cwd);
|
|
93
198
|
const appDir = findAppDir(cwd);
|
|
94
199
|
if (!appDir) {
|
|
95
200
|
console.error(" Could not find app/ or src/app/ directory.");
|
|
@@ -98,7 +203,26 @@ async function main() {
|
|
|
98
203
|
}
|
|
99
204
|
console.log(` Found Next.js app directory: ${appDir}`);
|
|
100
205
|
console.log();
|
|
101
|
-
console.log(" \u2500\u2500
|
|
206
|
+
console.log(" \u2500\u2500 Tier Selection \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\u2500");
|
|
207
|
+
console.log();
|
|
208
|
+
const tierAnswer = await prompts({
|
|
209
|
+
type: "select",
|
|
210
|
+
name: "tier",
|
|
211
|
+
message: "Choose your tier",
|
|
212
|
+
choices: [
|
|
213
|
+
{ title: "Chat only (AI conversations, localStorage persistence)", value: "chat" },
|
|
214
|
+
{ title: "+ GitHub (Chat + auto-creates GitHub issues)", value: "github" },
|
|
215
|
+
{ title: "+ Pipeline (Chat + GitHub + agent \u2192 PR \u2192 preview \u2192 approve)", value: "pipeline" }
|
|
216
|
+
],
|
|
217
|
+
initial: 0
|
|
218
|
+
});
|
|
219
|
+
if (tierAnswer.tier === void 0) {
|
|
220
|
+
console.log(" Cancelled.");
|
|
221
|
+
process.exit(0);
|
|
222
|
+
}
|
|
223
|
+
const tier = tierAnswer.tier;
|
|
224
|
+
console.log();
|
|
225
|
+
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\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
102
226
|
console.log();
|
|
103
227
|
const widgetAnswers = await prompts([
|
|
104
228
|
{
|
|
@@ -116,23 +240,17 @@ async function main() {
|
|
|
116
240
|
console.log(" Cancelled.");
|
|
117
241
|
process.exit(0);
|
|
118
242
|
}
|
|
119
|
-
console.log();
|
|
120
|
-
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");
|
|
121
|
-
console.log();
|
|
122
|
-
const githubAnswer = await prompts({
|
|
123
|
-
type: "confirm",
|
|
124
|
-
name: "enabled",
|
|
125
|
-
message: "Enable GitHub issues? (feedback creates issues for tracking)",
|
|
126
|
-
initial: true
|
|
127
|
-
});
|
|
128
243
|
let githubToken = "";
|
|
129
244
|
let githubRepo = "";
|
|
130
|
-
if (
|
|
245
|
+
if (tier !== "chat") {
|
|
246
|
+
console.log();
|
|
247
|
+
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\u2500\u2500\u2500\u2500");
|
|
248
|
+
console.log();
|
|
131
249
|
const ghAnswers = await prompts([
|
|
132
250
|
{
|
|
133
251
|
type: "password",
|
|
134
252
|
name: "token",
|
|
135
|
-
message: "GitHub token (needs repo scope)"
|
|
253
|
+
message: "GitHub token (needs repo scope, must start with ghp_)"
|
|
136
254
|
},
|
|
137
255
|
{
|
|
138
256
|
type: "text",
|
|
@@ -143,7 +261,18 @@ async function main() {
|
|
|
143
261
|
githubToken = ghAnswers.token || "";
|
|
144
262
|
githubRepo = ghAnswers.repo || "";
|
|
145
263
|
}
|
|
146
|
-
|
|
264
|
+
let agentUrl = "";
|
|
265
|
+
if (tier === "pipeline") {
|
|
266
|
+
console.log();
|
|
267
|
+
console.log(" \u2500\u2500 Agent 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\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
268
|
+
console.log();
|
|
269
|
+
const agentAnswer = await prompts({
|
|
270
|
+
type: "text",
|
|
271
|
+
name: "url",
|
|
272
|
+
message: "Agent URL (e.g., https://your-agent.railway.app)"
|
|
273
|
+
});
|
|
274
|
+
agentUrl = agentAnswer.url || "";
|
|
275
|
+
}
|
|
147
276
|
let overwrite = false;
|
|
148
277
|
const chatRoutePath = join(appDir, "api", "feedback", "chat", "route.ts");
|
|
149
278
|
const statusRoutePath = join(appDir, "api", "feedback", "status", "route.ts");
|
|
@@ -157,12 +286,17 @@ async function main() {
|
|
|
157
286
|
overwrite = overwriteAnswer.overwrite ?? false;
|
|
158
287
|
}
|
|
159
288
|
console.log();
|
|
160
|
-
if (safeWriteFile(chatRoutePath, CHAT_ROUTE_TEMPLATE(
|
|
289
|
+
if (safeWriteFile(chatRoutePath, CHAT_ROUTE_TEMPLATE(tier), overwrite)) {
|
|
161
290
|
console.log(` Created ${chatRoutePath}`);
|
|
162
291
|
}
|
|
163
|
-
if (safeWriteFile(statusRoutePath, STATUS_ROUTE_TEMPLATE, overwrite)) {
|
|
292
|
+
if (safeWriteFile(statusRoutePath, STATUS_ROUTE_TEMPLATE(tier), overwrite)) {
|
|
164
293
|
console.log(` Created ${statusRoutePath}`);
|
|
165
294
|
}
|
|
295
|
+
const componentsDir = detectComponentsDir(cwd);
|
|
296
|
+
const feedbackButtonPath = join(componentsDir, "FeedbackButton.tsx");
|
|
297
|
+
if (safeWriteFile(feedbackButtonPath, FEEDBACK_BUTTON_TEMPLATE, overwrite)) {
|
|
298
|
+
console.log(` Created ${feedbackButtonPath}`);
|
|
299
|
+
}
|
|
166
300
|
const globalsCss = findGlobalsCss(cwd);
|
|
167
301
|
if (globalsCss) {
|
|
168
302
|
if (injectSourceDirective(globalsCss)) {
|
|
@@ -178,28 +312,43 @@ async function main() {
|
|
|
178
312
|
const envPath = join(cwd, ".env.local");
|
|
179
313
|
appendEnvVar(envPath, "ANTHROPIC_API_KEY", widgetAnswers.apiKey);
|
|
180
314
|
appendEnvVar(envPath, "FEEDBACK_PASSWORD", widgetAnswers.password);
|
|
181
|
-
if (
|
|
182
|
-
appendEnvVar(envPath, "GITHUB_TOKEN", githubToken);
|
|
183
|
-
appendEnvVar(envPath, "GITHUB_REPO", githubRepo);
|
|
315
|
+
if (tier !== "chat") {
|
|
316
|
+
if (githubToken) appendEnvVar(envPath, "GITHUB_TOKEN", githubToken);
|
|
317
|
+
if (githubRepo) appendEnvVar(envPath, "GITHUB_REPO", githubRepo);
|
|
318
|
+
}
|
|
319
|
+
if (tier === "pipeline" && agentUrl) {
|
|
320
|
+
appendEnvVar(envPath, "AGENT_URL", agentUrl);
|
|
184
321
|
}
|
|
185
322
|
console.log(` Updated ${envPath}`);
|
|
323
|
+
if (tier !== "chat") {
|
|
324
|
+
console.log();
|
|
325
|
+
createGitHubLabels(cwd);
|
|
326
|
+
}
|
|
186
327
|
console.log();
|
|
187
|
-
console.log(" \u2500\u2500
|
|
328
|
+
console.log(" \u2500\u2500 Next Steps \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\u2500\u2500\u2500\u2500\u2500");
|
|
188
329
|
console.log();
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
console.log("
|
|
192
|
-
console.log(" import { FeedbackPanel } from '@nikitadmitrieff/feedback-chat'");
|
|
193
|
-
console.log(" import '@nikitadmitrieff/feedback-chat/styles.css'");
|
|
330
|
+
const usesSrc = existsSync(join(cwd, "src", "app"));
|
|
331
|
+
const importPath = usesSrc ? "@/components/FeedbackButton" : "@/components/FeedbackButton";
|
|
332
|
+
console.log(" 1. Add to your layout.tsx:");
|
|
194
333
|
console.log();
|
|
195
|
-
console.log(
|
|
196
|
-
console.log(" const [open, setOpen] = useState(false)");
|
|
197
|
-
console.log(" return <FeedbackPanel isOpen={open} onToggle={() => setOpen(!open)} />");
|
|
198
|
-
console.log(" }");
|
|
334
|
+
console.log(` import { FeedbackButton } from '${importPath}'`);
|
|
199
335
|
console.log();
|
|
200
|
-
console.log("
|
|
201
|
-
console.log("
|
|
202
|
-
console.log(
|
|
336
|
+
console.log(" // Inside <body>:");
|
|
337
|
+
console.log(" <FeedbackButton />");
|
|
338
|
+
console.log();
|
|
339
|
+
if (tier === "chat") {
|
|
340
|
+
console.log(" 2. Run npm run dev and open the app.");
|
|
341
|
+
console.log(" Click the feedback bar at the bottom, enter your password, and chat.");
|
|
342
|
+
} else if (tier === "github") {
|
|
343
|
+
console.log(" 2. Run npm run dev and open the app.");
|
|
344
|
+
console.log(" Submit feedback to see issues created on your repo.");
|
|
345
|
+
} else {
|
|
346
|
+
console.log(" 2. Run npm run dev and open the app.");
|
|
347
|
+
console.log(" Submit feedback to see issues created on your repo.");
|
|
348
|
+
console.log();
|
|
349
|
+
console.log(" 3. Deploy the agent service:");
|
|
350
|
+
console.log(" See docs/agent-deployment.md or run: npx feedback-chat deploy-agent");
|
|
351
|
+
}
|
|
203
352
|
console.log();
|
|
204
353
|
console.log(" Done.");
|
|
205
354
|
console.log();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nikitadmitrieff/feedback-chat",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/client/index.js",
|
|
6
6
|
"types": "./dist/client/index.d.ts",
|
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
}
|
|
22
22
|
},
|
|
23
23
|
"bin": {
|
|
24
|
-
"feedback-chat": "./dist/cli/init.js"
|
|
24
|
+
"feedback-chat": "./dist/cli/init.js",
|
|
25
|
+
"feedback-chat-deploy-agent": "./dist/cli/deploy-agent.js"
|
|
25
26
|
},
|
|
26
27
|
"files": ["dist", "README.md"],
|
|
27
28
|
"scripts": {
|