hearback 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Agent Team Foundation
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,23 @@
1
+ # hearback
2
+
3
+ Add [hearback](https://github.com/agent-team-foundation/hearback) — user feedback → GitHub Issues → email notification loop — to any product with one command.
4
+
5
+ ```bash
6
+ npx hearback init
7
+ ```
8
+
9
+ Auto-detects your framework (Next.js, Express, Hono, or AI Agent), injects the middleware / widget / env vars, and optionally sets up the notification GitHub Action.
10
+
11
+ ## What it does
12
+
13
+ - **Next.js**: generates `app/api/feedback/[...path]/route.ts`, adds `<Script>` to `layout.tsx`
14
+ - **Express / Hono**: injects `feedbackHandler` middleware + widget `<script>` tag into your HTML
15
+ - **AI Agent (OpenAI / Anthropic / LangChain)**: injects `feedbackSkill` setup into your agent entry file
16
+
17
+ Writes `FEEDBACK_REPO` and `GITHUB_TOKEN` to `.env`. Optionally creates `.github/workflows/feedback-notify.yml` for email notifications on issue close.
18
+
19
+ See the [main repo](https://github.com/agent-team-foundation/hearback) for architecture, manual setup, and integration examples.
20
+
21
+ ## License
22
+
23
+ MIT
@@ -0,0 +1,489 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+ import { createInterface } from 'node:readline';
6
+ import { execSync } from 'node:child_process';
7
+
8
+ const RESET = '\x1b[0m';
9
+ const BOLD = '\x1b[1m';
10
+ const DIM = '\x1b[2m';
11
+ const GREEN = '\x1b[32m';
12
+ const YELLOW = '\x1b[33m';
13
+ const CYAN = '\x1b[36m';
14
+
15
+ function log(msg) { console.log(msg); }
16
+ function step(n, msg) { log(`\n${CYAN}[${n}]${RESET} ${BOLD}${msg}${RESET}`); }
17
+ function done(msg) { log(` ${GREEN}✓${RESET} ${msg}`); }
18
+ function info(msg) { log(` ${DIM}${msg}${RESET}`); }
19
+ function warn(msg) { log(` ${YELLOW}⚠${RESET} ${msg}`); }
20
+
21
+ async function ask(question) {
22
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
23
+ return new Promise((resolve) => {
24
+ rl.question(` ${question} `, (answer) => {
25
+ rl.close();
26
+ resolve(answer.trim());
27
+ });
28
+ });
29
+ }
30
+
31
+ async function confirm(question) {
32
+ const answer = await ask(`${question} (Y/n)`);
33
+ return answer === '' || answer.toLowerCase().startsWith('y');
34
+ }
35
+
36
+ // --- File finders ---
37
+
38
+ function findServerEntry(cwd) {
39
+ // Common server entry file patterns
40
+ const candidates = [
41
+ 'src/server.ts', 'src/server.js',
42
+ 'src/app.ts', 'src/app.js',
43
+ 'src/index.ts', 'src/index.js',
44
+ 'server.ts', 'server.js',
45
+ 'app.ts', 'app.js',
46
+ 'index.ts', 'index.js',
47
+ ];
48
+
49
+ for (const candidate of candidates) {
50
+ const fullPath = join(cwd, candidate);
51
+ if (!existsSync(fullPath)) continue;
52
+ const content = readFileSync(fullPath, 'utf-8');
53
+ // Must look like a server file (imports express/hono/fastify or has listen/app.use)
54
+ if (content.includes('express') || content.includes('hono') || content.includes('fastify') ||
55
+ content.includes('.listen(') || content.includes('app.use(') || content.includes('createServer')) {
56
+ return { path: candidate, fullPath, content };
57
+ }
58
+ }
59
+ return null;
60
+ }
61
+
62
+ function findHtmlLayout(cwd) {
63
+ // Look for HTML files that have </body>
64
+ const candidates = [
65
+ 'src/index.html', 'public/index.html', 'index.html',
66
+ 'views/layout.ejs', 'views/layout.hbs',
67
+ 'templates/base.html', 'templates/layout.html',
68
+ ];
69
+
70
+ for (const candidate of candidates) {
71
+ const fullPath = join(cwd, candidate);
72
+ if (!existsSync(fullPath)) continue;
73
+ const content = readFileSync(fullPath, 'utf-8');
74
+ if (content.includes('</body>')) {
75
+ return { path: candidate, fullPath, content };
76
+ }
77
+ }
78
+ return null;
79
+ }
80
+
81
+ function findNextjsLayout(cwd) {
82
+ const candidates = [
83
+ 'src/app/layout.tsx', 'src/app/layout.jsx', 'src/app/layout.js',
84
+ 'app/layout.tsx', 'app/layout.jsx', 'app/layout.js',
85
+ ];
86
+
87
+ for (const candidate of candidates) {
88
+ const fullPath = join(cwd, candidate);
89
+ if (!existsSync(fullPath)) continue;
90
+ return { path: candidate, fullPath, content: readFileSync(fullPath, 'utf-8') };
91
+ }
92
+ return null;
93
+ }
94
+
95
+ function findAgentEntry(cwd) {
96
+ const candidates = [
97
+ 'src/agent.ts', 'src/agent.js',
98
+ 'src/index.ts', 'src/index.js',
99
+ 'agent.ts', 'agent.js',
100
+ 'index.ts', 'index.js',
101
+ ];
102
+
103
+ for (const candidate of candidates) {
104
+ const fullPath = join(cwd, candidate);
105
+ if (!existsSync(fullPath)) continue;
106
+ const content = readFileSync(fullPath, 'utf-8');
107
+ if (content.includes('openai') || content.includes('anthropic') || content.includes('langchain') ||
108
+ content.includes('ChatOpenAI') || content.includes('Anthropic')) {
109
+ return { path: candidate, fullPath, content };
110
+ }
111
+ }
112
+ return null;
113
+ }
114
+
115
+ // --- Injectors ---
116
+
117
+ function injectImportAndMiddleware(content, isTs) {
118
+ const importLine = `import { feedbackHandler } from 'hearback-server';`;
119
+ const middlewareBlock = [
120
+ '',
121
+ '// hearback feedback widget',
122
+ `app.use('/feedback', feedbackHandler({`,
123
+ ` repo: process.env.FEEDBACK_REPO${isTs ? '!' : ''},`,
124
+ ` githubToken: process.env.GITHUB_TOKEN${isTs ? '!' : ''},`,
125
+ `}));`,
126
+ ].join('\n');
127
+
128
+ if (content.includes('hearback-server')) return content;
129
+
130
+ const lines = content.split('\n');
131
+ const result = [];
132
+ let importInserted = false;
133
+ let middlewareInserted = false;
134
+
135
+ for (let i = 0; i < lines.length; i++) {
136
+ const line = lines[i];
137
+
138
+ // Insert import after the last import/require line
139
+ if (!importInserted && (line.startsWith('import ') || line.includes('require('))) {
140
+ // Check if next line is NOT an import — insert after this one
141
+ const nextLine = lines[i + 1] ?? '';
142
+ if (!nextLine.startsWith('import ') && !nextLine.includes('require(')) {
143
+ result.push(line);
144
+ result.push(importLine);
145
+ importInserted = true;
146
+ continue;
147
+ }
148
+ }
149
+
150
+ // Insert middleware before .listen()
151
+ if (!middlewareInserted && line.includes('.listen(')) {
152
+ result.push(middlewareBlock);
153
+ middlewareInserted = true;
154
+ }
155
+
156
+ result.push(line);
157
+ }
158
+
159
+ // Fallback: add import at top if no imports found
160
+ if (!importInserted) {
161
+ result.unshift(importLine);
162
+ }
163
+
164
+ // Fallback: add middleware at end if no .listen() found
165
+ if (!middlewareInserted) {
166
+ result.push(middlewareBlock);
167
+ }
168
+
169
+ return result.join('\n');
170
+ }
171
+
172
+ function injectWidgetScript(content, endpoint) {
173
+ const scriptTag = `<script src="https://unpkg.com/hearback-widget/dist/widget.js" data-endpoint="${endpoint}"></script>`;
174
+
175
+ if (content.includes('hearback-widget')) return content;
176
+
177
+ // Insert before </body>
178
+ return content.replace('</body>', ` ${scriptTag}\n</body>`);
179
+ }
180
+
181
+ function injectNextjsWidget(content, endpoint) {
182
+ const scriptImport = `import Script from 'next/script';`;
183
+ const scriptTag = ` <Script src="https://unpkg.com/hearback-widget/dist/widget.js" data-endpoint="${endpoint}" strategy="lazyOnload" />`;
184
+
185
+ if (content.includes('hearback-widget')) return content;
186
+
187
+ // Add Script import if not present — find the LAST import statement and
188
+ // insert after it. If no imports exist, prepend to the file.
189
+ let result = content;
190
+ if (!result.includes("from 'next/script'") && !result.includes('from "next/script"')) {
191
+ const lines = result.split('\n');
192
+ let lastImportIdx = -1;
193
+ for (let i = 0; i < lines.length; i++) {
194
+ if (/^import\s/.test(lines[i])) lastImportIdx = i;
195
+ }
196
+ if (lastImportIdx >= 0) {
197
+ lines.splice(lastImportIdx + 1, 0, scriptImport);
198
+ } else {
199
+ lines.unshift(scriptImport);
200
+ }
201
+ result = lines.join('\n');
202
+ }
203
+
204
+ // Add before </body>. Put on its own line (preserve surrounding whitespace).
205
+ result = result.replace(/(\s*)<\/body>/, `\n${scriptTag}$1</body>`);
206
+
207
+ return result;
208
+ }
209
+
210
+ // --- Detect project type ---
211
+
212
+ function detectProject(cwd) {
213
+ const pkgPath = join(cwd, 'package.json');
214
+ if (!existsSync(pkgPath)) return { type: 'unknown' };
215
+
216
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
217
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
218
+
219
+ if (deps['next']) {
220
+ return { type: 'nextjs', pkg, deps };
221
+ }
222
+ if (deps['express']) return { type: 'express', pkg, deps };
223
+ if (deps['hono']) return { type: 'hono', pkg, deps };
224
+ if (deps['openai'] || deps['@anthropic-ai/sdk'] || deps['langchain'] || deps['@langchain/core']) {
225
+ return { type: 'agent', pkg, deps };
226
+ }
227
+ if (deps['fastify']) return { type: 'fastify', pkg, deps };
228
+
229
+ return { type: 'node', pkg, deps };
230
+ }
231
+
232
+ // --- Templates ---
233
+
234
+ const NEXTJS_ROUTE = `import { NextRequest, NextResponse } from 'next/server';
235
+ import { createFeedbackHandler } from 'hearback-server';
236
+
237
+ const handler = createFeedbackHandler({
238
+ repo: process.env.FEEDBACK_REPO!,
239
+ githubToken: process.env.GITHUB_TOKEN!,
240
+ llm: process.env.OPENAI_API_KEY ? { apiKey: process.env.OPENAI_API_KEY } : undefined,
241
+ });
242
+
243
+ async function handleRequest(req: NextRequest) {
244
+ const url = new URL(req.url);
245
+ const path = url.pathname.replace('/api/feedback', '') || '/';
246
+ const body = req.method === 'POST' ? await req.json() : undefined;
247
+
248
+ const result = await handler.handle({
249
+ method: req.method,
250
+ path,
251
+ body,
252
+ ip: req.headers.get('x-forwarded-for')?.split(',')[0] ?? '127.0.0.1',
253
+ headers: Object.fromEntries(req.headers.entries()),
254
+ });
255
+
256
+ if (result.body && typeof result.body === 'object' && 'getReader' in (result.body as object)) {
257
+ return new Response(result.body as ReadableStream, {
258
+ status: result.status,
259
+ headers: result.headers,
260
+ });
261
+ }
262
+
263
+ return NextResponse.json(result.body, {
264
+ status: result.status,
265
+ headers: result.headers,
266
+ });
267
+ }
268
+
269
+ export const GET = handleRequest;
270
+ export const POST = handleRequest;
271
+ export const OPTIONS = handleRequest;
272
+ `;
273
+
274
+ const NOTIFY_WORKFLOW = `name: Notify Reporter
275
+ on:
276
+ issues:
277
+ types: [closed, labeled]
278
+ jobs:
279
+ notify:
280
+ if: |
281
+ (github.event.action == 'closed' && contains(github.event.issue.labels.*.name, 'hearback')) ||
282
+ (github.event.action == 'labeled' && github.event.label.name == 'feedback-responded')
283
+ runs-on: ubuntu-latest
284
+ steps:
285
+ - uses: actions/checkout@v4
286
+ with:
287
+ repository: agent-team-foundation/hearback
288
+ ref: v0.1.0
289
+ path: .hearback
290
+ - uses: ./.hearback/.github/actions/notify
291
+ with:
292
+ github-token: \${{ secrets.GITHUB_TOKEN }}
293
+ resend-api-key: \${{ secrets.RESEND_API_KEY }}
294
+ `;
295
+
296
+ // --- Main ---
297
+
298
+ async function main() {
299
+ const args = process.argv.slice(2);
300
+ const command = args[0];
301
+
302
+ if (!command || command === 'help' || command === '--help') {
303
+ log(`
304
+ ${BOLD}hearback${RESET} — add user feedback loop to any product
305
+
306
+ ${BOLD}Usage:${RESET}
307
+ npx hearback init Set up hearback in the current project
308
+ npx hearback help Show this help
309
+ `);
310
+ return;
311
+ }
312
+
313
+ if (command !== 'init') {
314
+ log(`Unknown command: ${command}. Run "npx hearback help" for usage.`);
315
+ process.exit(1);
316
+ }
317
+
318
+ const cwd = process.cwd();
319
+
320
+ log(`\n${BOLD}🎯 hearback init${RESET}`);
321
+ log(`${DIM}Add user feedback → GitHub Issues → email notification${RESET}`);
322
+
323
+ // Step 1: Detect
324
+ step(1, 'Detecting project...');
325
+ const project = detectProject(cwd);
326
+
327
+ if (project.type === 'unknown') {
328
+ warn('No package.json found. Run this from your project root.');
329
+ process.exit(1);
330
+ }
331
+
332
+ const typeLabels = {
333
+ nextjs: 'Next.js',
334
+ express: 'Express',
335
+ hono: 'Hono',
336
+ fastify: 'Fastify',
337
+ agent: 'AI Agent',
338
+ node: 'Node.js',
339
+ };
340
+ done(`${typeLabels[project.type] ?? project.type} project`);
341
+
342
+ // Step 2: Collect info
343
+ step(2, 'Configuration');
344
+ const repo = await ask('GitHub repo for issues (owner/name):');
345
+ if (!repo || !repo.includes('/')) {
346
+ warn('Invalid repo format. Expected: owner/name');
347
+ process.exit(1);
348
+ }
349
+
350
+ // Step 3: Install
351
+ step(3, 'Installing packages...');
352
+
353
+ const pkgToInstall = project.type === 'agent' ? 'hearback-agent-skill' : 'hearback-server';
354
+ try {
355
+ execSync(`npm install ${pkgToInstall}`, { cwd, stdio: 'pipe' });
356
+ done(`${pkgToInstall} installed`);
357
+ } catch {
358
+ warn(`Could not install ${pkgToInstall} (not yet published to npm)`);
359
+ info(`When available, run: npm install ${pkgToInstall}`);
360
+ }
361
+
362
+ // Step 4: Generate & inject
363
+ step(4, 'Injecting code...');
364
+
365
+ let widgetEndpoint = '/feedback';
366
+
367
+ if (project.type === 'nextjs') {
368
+ // Create API route
369
+ const appBase = existsSync(join(cwd, 'src/app')) ? 'src/app' : 'app';
370
+ const routeDir = join(cwd, appBase, 'api/feedback/[...path]');
371
+ mkdirSync(routeDir, { recursive: true });
372
+ writeFileSync(join(routeDir, 'route.ts'), NEXTJS_ROUTE);
373
+ done(`Created ${appBase}/api/feedback/[...path]/route.ts`);
374
+
375
+ widgetEndpoint = '/api/feedback';
376
+
377
+ // Inject widget into layout
378
+ const layout = findNextjsLayout(cwd);
379
+ if (layout) {
380
+ const updated = injectNextjsWidget(layout.content, widgetEndpoint);
381
+ if (updated !== layout.content) {
382
+ writeFileSync(layout.fullPath, updated);
383
+ done(`Added widget to ${layout.path}`);
384
+ }
385
+ } else {
386
+ warn('Could not find layout file — add the widget script manually:');
387
+ info(`<Script src="https://unpkg.com/hearback-widget/dist/widget.js" data-endpoint="${widgetEndpoint}" strategy="lazyOnload" />`);
388
+ }
389
+
390
+ } else if (project.type === 'agent') {
391
+ // Agent project — inject skill setup
392
+ const entry = findAgentEntry(cwd);
393
+ if (entry) {
394
+ const isTs = entry.path.endsWith('.ts');
395
+ const importLine = `import { feedbackSkill } from 'hearback-agent-skill';\n`;
396
+ const setupBlock = `\n// hearback feedback skill\nconst hearbackSkill = feedbackSkill({\n repo: process.env.FEEDBACK_REPO${isTs ? '!' : ''},\n githubToken: process.env.GITHUB_TOKEN${isTs ? '!' : ''},\n});\n// hearbackSkill.instructions → add to system prompt\n// hearbackSkill.tools.openai → register as tools\n// hearbackSkill.handlers → execute tool calls\n`;
397
+
398
+ let content = entry.content;
399
+ if (!content.includes('hearback-agent-skill')) {
400
+ const lastImportIdx = Math.max(content.lastIndexOf('import '), content.lastIndexOf('require('));
401
+ if (lastImportIdx !== -1) {
402
+ const lineEnd = content.indexOf('\n', lastImportIdx);
403
+ content = content.slice(0, lineEnd + 1) + importLine + content.slice(lineEnd + 1);
404
+ } else {
405
+ content = importLine + content;
406
+ }
407
+ content += setupBlock;
408
+ writeFileSync(entry.fullPath, content);
409
+ done(`Injected skill setup into ${entry.path}`);
410
+ } else {
411
+ info(`${entry.path} already has hearback-agent-skill`);
412
+ }
413
+ } else {
414
+ warn('Could not find agent entry file');
415
+ info('Add to your agent setup:');
416
+ log(`\n ${DIM}import { feedbackSkill } from 'hearback-agent-skill';${RESET}\n`);
417
+ }
418
+
419
+ } else {
420
+ // Express / Hono / Fastify / generic Node
421
+ const serverFile = findServerEntry(cwd);
422
+ if (serverFile) {
423
+ const isTs = serverFile.path.endsWith('.ts');
424
+ const updated = injectImportAndMiddleware(serverFile.content, isTs);
425
+ if (updated !== serverFile.content) {
426
+ writeFileSync(serverFile.fullPath, updated);
427
+ done(`Injected middleware into ${serverFile.path}`);
428
+ } else {
429
+ info(`${serverFile.path} already has hearback middleware`);
430
+ }
431
+ } else {
432
+ warn('Could not find server entry file');
433
+ info('Add to your server:');
434
+ log(`\n ${DIM}import { feedbackHandler } from 'hearback-server';`);
435
+ log(` app.use('/feedback', feedbackHandler({ repo: process.env.FEEDBACK_REPO, githubToken: process.env.GITHUB_TOKEN }));${RESET}\n`);
436
+ }
437
+
438
+ // Inject widget into HTML
439
+ const html = findHtmlLayout(cwd);
440
+ if (html) {
441
+ const updated = injectWidgetScript(html.content, widgetEndpoint);
442
+ if (updated !== html.content) {
443
+ writeFileSync(html.fullPath, updated);
444
+ done(`Added widget to ${html.path}`);
445
+ }
446
+ } else {
447
+ info('No HTML file found — add before </body>:');
448
+ log(`\n ${DIM}<script src="https://unpkg.com/hearback-widget/dist/widget.js" data-endpoint="${widgetEndpoint}"></script>${RESET}\n`);
449
+ }
450
+ }
451
+
452
+ // Env vars
453
+ const envFile = existsSync(join(cwd, '.env.local')) ? '.env.local' : '.env';
454
+ const envPath = join(cwd, envFile);
455
+ const envContent = existsSync(envPath) ? readFileSync(envPath, 'utf-8') : '';
456
+
457
+ const newVars = [];
458
+ if (!envContent.includes('FEEDBACK_REPO')) newVars.push(`FEEDBACK_REPO=${repo}`);
459
+ if (!envContent.includes('GITHUB_TOKEN')) newVars.push(`GITHUB_TOKEN= # Create at https://github.com/settings/tokens?type=beta (needs issues:write + contents:write)`);
460
+
461
+ if (newVars.length > 0) {
462
+ const addition = '\n# hearback\n' + newVars.join('\n') + '\n';
463
+ writeFileSync(envPath, envContent + addition);
464
+ done(`Added env vars to ${envFile}`);
465
+ }
466
+
467
+ // Step 5: Notification workflow
468
+ step(5, 'Notification loop');
469
+ const wantNotify = await confirm('Set up email notifications when issues are fixed?');
470
+
471
+ if (wantNotify) {
472
+ const workflowDir = join(cwd, '.github/workflows');
473
+ mkdirSync(workflowDir, { recursive: true });
474
+ writeFileSync(join(workflowDir, 'feedback-notify.yml'), NOTIFY_WORKFLOW);
475
+ done('Created .github/workflows/feedback-notify.yml');
476
+ info('Add RESEND_API_KEY to your repo secrets (free at resend.com)');
477
+ }
478
+
479
+ // Done
480
+ log(`\n${GREEN}${BOLD}✓ hearback is ready!${RESET}\n`);
481
+ log(`${DIM}Next steps:`);
482
+ log(` 1. Add your GITHUB_TOKEN to ${envFile}`);
483
+ log(` 2. Run your dev server and test the feedback button${RESET}\n`);
484
+ }
485
+
486
+ main().catch((err) => {
487
+ console.error(err);
488
+ process.exit(1);
489
+ });
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "hearback",
3
+ "version": "0.1.0",
4
+ "description": "Add user feedback → GitHub Issues → email notification loop to any product with one command. Works with Next.js, Express, Hono, and AI agents.",
5
+ "type": "module",
6
+ "bin": {
7
+ "hearback": "./bin/hearback.js"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "README.md",
12
+ "LICENSE"
13
+ ],
14
+ "scripts": {
15
+ "build": "echo 'no build needed'",
16
+ "clean": "echo 'nothing to clean'"
17
+ },
18
+ "keywords": [
19
+ "hearback",
20
+ "feedback",
21
+ "bug-report",
22
+ "github-issues",
23
+ "cli",
24
+ "init",
25
+ "scaffold",
26
+ "widget",
27
+ "agent",
28
+ "nextjs",
29
+ "express"
30
+ ],
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/agent-team-foundation/hearback.git",
34
+ "directory": "packages/cli"
35
+ },
36
+ "bugs": {
37
+ "url": "https://github.com/agent-team-foundation/hearback/issues"
38
+ },
39
+ "homepage": "https://github.com/agent-team-foundation/hearback#readme",
40
+ "license": "MIT"
41
+ }