@embeddables/cli 0.8.1 → 0.8.2

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.
@@ -1,267 +0,0 @@
1
- import fs from 'node:fs';
2
- import os from 'node:os';
3
- import path from 'node:path';
4
- import { execSync } from 'node:child_process';
5
- import * as Sentry from '@sentry/node';
6
- import { createLogger } from '../logger.js';
7
- import * as stdout from '../stdout.js';
8
- import { prompt } from '../helpers/prompt.js';
9
- import { readProjectConfig } from '../config/index.js';
10
- import { getLastHistoryEntry } from '../command-history.js';
11
- import { getSentryContextFromEmbeddableConfig, setSentryContext } from '../sentry-context.js';
12
- const logger = createLogger('runFeedback');
13
- const VALID_CATEGORIES = new Set(['cli', 'ai', 'compiler', 'other']);
14
- export async function runFeedback(opts) {
15
- // 1. Resolve message
16
- let message = opts.message;
17
- if (!message) {
18
- const res = await prompt({
19
- type: 'text',
20
- name: 'value',
21
- message: "What's your feedback?",
22
- });
23
- message = res.value?.trim();
24
- if (!message) {
25
- stdout.warn('No feedback provided.');
26
- return;
27
- }
28
- }
29
- // 2. Resolve sentiment
30
- let sentiment;
31
- if (opts.positive) {
32
- sentiment = 'positive';
33
- }
34
- else if (opts.negative) {
35
- sentiment = 'negative';
36
- }
37
- else if (!opts.message) {
38
- const res = await prompt({
39
- type: 'select',
40
- name: 'value',
41
- message: 'Was this positive or negative?',
42
- choices: [
43
- { title: '👍 Positive', value: 'positive' },
44
- { title: '👎 Negative', value: 'negative' },
45
- ],
46
- });
47
- sentiment = res.value === 'positive' ? 'positive' : 'negative';
48
- }
49
- else {
50
- sentiment = 'negative';
51
- }
52
- // 3. Resolve category
53
- let category = opts.category;
54
- if (category && !VALID_CATEGORIES.has(category)) {
55
- stdout.warn(`Invalid category "${category}". Must be one of: cli, ai, compiler, other`);
56
- category = undefined;
57
- }
58
- if (!category && !opts.message) {
59
- const res = await prompt({
60
- type: 'select',
61
- name: 'value',
62
- message: 'Which area does this relate to?',
63
- choices: [
64
- { title: 'CLI command', value: 'cli' },
65
- { title: 'AI prompts', value: 'ai' },
66
- { title: 'Compiler', value: 'compiler' },
67
- { title: 'Other', value: 'other' },
68
- ],
69
- });
70
- category = res.value || undefined;
71
- }
72
- // 4. Collect context
73
- const context = collectFeedbackContext();
74
- // 5. Set Sentry context and send
75
- Sentry.setTag('feedback.sentiment', sentiment);
76
- if (category)
77
- Sentry.setTag('feedback.category', category);
78
- Sentry.setContext('feedback', {
79
- sentiment,
80
- category: category ?? null,
81
- });
82
- if (context.cliVersion)
83
- Sentry.setTag('cli.version', context.cliVersion);
84
- if (context.nodeVersion)
85
- Sentry.setTag('node.version', context.nodeVersion);
86
- if (context.platform)
87
- Sentry.setTag('os.platform', context.platform);
88
- if (context.arch)
89
- Sentry.setTag('os.arch', context.arch);
90
- Sentry.setContext('environment', {
91
- cliVersion: context.cliVersion ?? null,
92
- nodeVersion: context.nodeVersion ?? null,
93
- platform: context.platform ?? null,
94
- osRelease: context.osRelease ?? null,
95
- arch: context.arch ?? null,
96
- });
97
- if (context.projectId || context.orgId) {
98
- setSentryContext({
99
- project: context.projectId
100
- ? { id: context.projectId, title: context.projectName ?? null }
101
- : undefined,
102
- org: context.orgId ? { id: context.orgId, title: context.orgName ?? null } : undefined,
103
- });
104
- }
105
- if (context.embeddableId) {
106
- const embCtx = getSentryContextFromEmbeddableConfig(context.embeddableId);
107
- setSentryContext({
108
- embeddable: { id: context.embeddableId },
109
- ...embCtx,
110
- });
111
- }
112
- if (context.lastCommand) {
113
- Sentry.setContext('lastCommand', context.lastCommand);
114
- }
115
- Sentry.setContext('git', {
116
- branch: context.gitBranch ?? null,
117
- dirty: context.gitDirty ?? null,
118
- });
119
- if (context.aiTool) {
120
- Sentry.setTag('ai.tool', context.aiTool);
121
- Sentry.setContext('ai', { tool: context.aiTool });
122
- }
123
- Sentry.captureFeedback({
124
- message,
125
- email: context.userEmail ?? undefined,
126
- name: context.userName ?? undefined,
127
- });
128
- await Sentry.flush(5000);
129
- // 6. Show confirmation
130
- stdout.gap();
131
- const icon = sentiment === 'positive' ? '👍' : '👎';
132
- stdout.success(`Feedback sent ${icon}`);
133
- stdout.dim('Thanks for helping us improve!');
134
- logger.info('feedback sent', {
135
- sentiment,
136
- category: category ?? 'none',
137
- hasEmail: !!context.userEmail,
138
- messageLength: message.length,
139
- });
140
- }
141
- export function collectFeedbackContext() {
142
- const ctx = {};
143
- // User email from auth (best effort, no await — read sync from file)
144
- try {
145
- const authPath = path.join(os.homedir(), '.embeddables', 'auth.json');
146
- if (fs.existsSync(authPath)) {
147
- const raw = JSON.parse(fs.readFileSync(authPath, 'utf8'));
148
- if (raw.access_token && typeof raw.access_token === 'string') {
149
- const payload = JSON.parse(Buffer.from(raw.access_token.split('.')[1], 'base64').toString());
150
- if (typeof payload.email === 'string')
151
- ctx.userEmail = payload.email;
152
- if (typeof payload.name === 'string')
153
- ctx.userName = payload.name;
154
- }
155
- }
156
- }
157
- catch {
158
- // auth not available — fine
159
- }
160
- // CLI version
161
- try {
162
- const pkgPath = path.join(path.dirname(new URL(import.meta.url).pathname), '..', 'package.json');
163
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
164
- if (typeof pkg.version === 'string')
165
- ctx.cliVersion = pkg.version;
166
- }
167
- catch {
168
- // ignore
169
- }
170
- // Node / OS
171
- ctx.nodeVersion = process.version;
172
- ctx.platform = process.platform;
173
- ctx.osRelease = os.release();
174
- ctx.arch = process.arch;
175
- // Project config
176
- try {
177
- const config = readProjectConfig();
178
- if (config?.project_id)
179
- ctx.projectId = config.project_id;
180
- if (config?.project_name)
181
- ctx.projectName = config.project_name;
182
- if (config?.org_id)
183
- ctx.orgId = config.org_id;
184
- if (config?.org_title)
185
- ctx.orgName = config.org_title;
186
- }
187
- catch {
188
- // ignore
189
- }
190
- // Embeddable ID (first local embeddable found)
191
- try {
192
- const embeddablesDir = 'embeddables';
193
- if (fs.existsSync(embeddablesDir)) {
194
- const entries = fs.readdirSync(embeddablesDir, { withFileTypes: true });
195
- const firstDir = entries.find((e) => e.isDirectory());
196
- if (firstDir)
197
- ctx.embeddableId = firstDir.name;
198
- }
199
- }
200
- catch {
201
- // ignore
202
- }
203
- // Last CLI command
204
- try {
205
- const last = getLastHistoryEntry();
206
- if (last) {
207
- ctx.lastCommand = { ...last };
208
- }
209
- }
210
- catch {
211
- // ignore
212
- }
213
- // Git branch
214
- try {
215
- ctx.gitBranch = execSync('git rev-parse --abbrev-ref HEAD', {
216
- encoding: 'utf8',
217
- timeout: 3000,
218
- stdio: ['pipe', 'pipe', 'pipe'],
219
- }).trim();
220
- }
221
- catch {
222
- // not in a git repo
223
- }
224
- // Git dirty
225
- try {
226
- const status = execSync('git status --porcelain', {
227
- encoding: 'utf8',
228
- timeout: 3000,
229
- stdio: ['pipe', 'pipe', 'pipe'],
230
- }).trim();
231
- ctx.gitDirty = status.length > 0;
232
- }
233
- catch {
234
- // not in a git repo
235
- }
236
- // AI tool detection
237
- ctx.aiTool = detectAiTool();
238
- return ctx;
239
- }
240
- export function detectAiTool() {
241
- const envChecks = [
242
- ['CURSOR_', 'cursor'],
243
- ['CLAUDE_', 'claude-code'],
244
- ['WINDSURF_', 'windsurf'],
245
- ];
246
- for (const [prefix, name] of envChecks) {
247
- for (const key of Object.keys(process.env)) {
248
- if (key.startsWith(prefix))
249
- return name;
250
- }
251
- }
252
- const dirChecks = [
253
- ['.cursor', 'cursor'],
254
- ['.claude', 'claude-code'],
255
- ['.windsurf', 'windsurf'],
256
- ];
257
- for (const [dir, name] of dirChecks) {
258
- try {
259
- if (fs.existsSync(path.join(process.cwd(), dir)))
260
- return name;
261
- }
262
- catch {
263
- // ignore
264
- }
265
- }
266
- return undefined;
267
- }
@@ -1 +0,0 @@
1
- "use strict";
@@ -1 +0,0 @@
1
- //# sourceMappingURL=TEMP%20helpers%20file.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"TEMP helpers file.d.ts","sourceRoot":"","sources":["../../src/helpers/TEMP helpers file.ts"],"names":[],"mappings":""}
@@ -1 +0,0 @@
1
- "use strict";
@@ -1,31 +0,0 @@
1
- # Embeddables Workbench - Cloudflare Worker
2
-
3
- ## Deploy
4
-
5
- ```bash
6
- npx wrangler login # One-time
7
- npx wrangler deploy
8
- ```
9
-
10
- ## Assets
11
-
12
- - `public/workbench.js` - Main bundle
13
- - `public/workbench.css` - Styles
14
-
15
- ## Usage
16
-
17
- Add `?workbench=true` to any engine preview URL:
18
-
19
- ```
20
- https://engine.embeddables.com/preview/<id>?workbench=true
21
- ```
22
-
23
- The engine will inject the workbench from this CDN.
24
-
25
- ## Update
26
-
27
- ```bash
28
- npx embeddables build-workbench
29
- cd dist/workbench/cloudflare-worker
30
- npx wrangler deploy
31
- ```