@vellumai/cli 0.8.8-dev.202606081143.f600053 → 0.8.9-staging.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/cli",
3
- "version": "0.8.8-dev.202606081143.f600053",
3
+ "version": "0.8.9-staging.1",
4
4
  "description": "CLI tools for vellum-assistant",
5
5
  "type": "module",
6
6
  "exports": {
@@ -32,6 +32,131 @@ import { syncCloudAssistants } from "../lib/sync-cloud-assistants";
32
32
 
33
33
  const LOGIN_TIMEOUT_MS = 120_000; // 2 minutes
34
34
 
35
+ function escapeHtml(s: string): string {
36
+ return s
37
+ .replace(/&/g, "&")
38
+ .replace(/</g, "&lt;")
39
+ .replace(/>/g, "&gt;")
40
+ .replace(/"/g, "&quot;")
41
+ .replace(/'/g, "&#39;");
42
+ }
43
+
44
+ function renderLoginPage(title: string, subtitle: string, success: boolean): string {
45
+ const checkmarkSvg = `<svg class="icon" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg">
46
+ <circle cx="28" cy="28" r="28" fill="var(--positive-bg)"/>
47
+ <path class="check" d="M17 28.5L24.5 36L39 21" stroke="var(--positive-fg)" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
48
+ </svg>`;
49
+
50
+ const errorSvg = `<svg class="icon" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg">
51
+ <circle cx="28" cy="28" r="28" fill="var(--negative-bg)"/>
52
+ <path class="cross cross-1" d="M20 20L36 36" stroke="var(--negative-fg)" stroke-width="3.5" stroke-linecap="round" fill="none"/>
53
+ <path class="cross cross-2" d="M36 20L20 36" stroke="var(--negative-fg)" stroke-width="3.5" stroke-linecap="round" fill="none"/>
54
+ </svg>`;
55
+
56
+ return `<!DOCTYPE html>
57
+ <html lang="en">
58
+ <head>
59
+ <meta charset="utf-8">
60
+ <meta name="viewport" content="width=device-width, initial-scale=1">
61
+ <title>${escapeHtml(title)}</title>
62
+ <style>
63
+ :root {
64
+ --surface: #F5F3EB;
65
+ --surface-card: #FFFFFF;
66
+ --card-border: #E8E6DA;
67
+ --text-primary: #2A2A28;
68
+ --text-secondary: #4A4A46;
69
+ --positive-bg: #D4DFD0;
70
+ --positive-fg: #516748;
71
+ --negative-bg: #F7DAC9;
72
+ --negative-fg: #DA491A;
73
+ --shadow: 0 1px 3px rgba(0,0,0,0.04), 0 4px 12px rgba(0,0,0,0.06);
74
+ --font: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", sans-serif;
75
+ }
76
+ @media (prefers-color-scheme: dark) {
77
+ :root {
78
+ --surface: #1A1A18;
79
+ --surface-card: #2A2A28;
80
+ --card-border: #3A3A37;
81
+ --text-primary: #F5F3EB;
82
+ --text-secondary: #BDB9A9;
83
+ --positive-bg: #1A2316;
84
+ --positive-fg: #7A8B6F;
85
+ --negative-bg: #4E281D;
86
+ --negative-fg: #E86B40;
87
+ --shadow: 0 1px 3px rgba(0,0,0,0.2), 0 4px 12px rgba(0,0,0,0.3);
88
+ }
89
+ }
90
+ * { margin: 0; padding: 0; box-sizing: border-box; }
91
+ body {
92
+ font-family: var(--font);
93
+ background: var(--surface);
94
+ color: var(--text-primary);
95
+ display: flex;
96
+ align-items: center;
97
+ justify-content: center;
98
+ min-height: 100vh;
99
+ -webkit-font-smoothing: antialiased;
100
+ }
101
+ .card {
102
+ text-align: center;
103
+ padding: 48px 40px 40px;
104
+ background: var(--surface-card);
105
+ border: 1px solid var(--card-border);
106
+ border-radius: 16px;
107
+ box-shadow: var(--shadow);
108
+ max-width: 380px;
109
+ width: 100%;
110
+ opacity: 0;
111
+ transform: translateY(8px) scale(0.98);
112
+ animation: cardIn 0.5s cubic-bezier(0.16, 1, 0.3, 1) 0.1s forwards;
113
+ }
114
+ @keyframes cardIn {
115
+ to { opacity: 1; transform: translateY(0) scale(1); }
116
+ }
117
+ .icon {
118
+ width: 56px;
119
+ height: 56px;
120
+ margin-bottom: 20px;
121
+ }
122
+ .check {
123
+ stroke-dasharray: 32;
124
+ stroke-dashoffset: 32;
125
+ animation: draw 0.4s ease-out 0.45s forwards;
126
+ }
127
+ .cross {
128
+ stroke-dasharray: 22;
129
+ stroke-dashoffset: 22;
130
+ }
131
+ .cross-1 { animation: draw 0.3s ease-out 0.45s forwards; }
132
+ .cross-2 { animation: draw 0.3s ease-out 0.55s forwards; }
133
+ @keyframes draw {
134
+ to { stroke-dashoffset: 0; }
135
+ }
136
+ h1 {
137
+ font-size: 18px;
138
+ font-weight: 600;
139
+ letter-spacing: -0.2px;
140
+ color: var(--text-primary);
141
+ margin-bottom: 6px;
142
+ }
143
+ p {
144
+ font-size: 13px;
145
+ line-height: 1.5;
146
+ color: var(--text-secondary);
147
+ }
148
+ </style>
149
+ </head>
150
+ <body>
151
+ <div class="card">
152
+ ${success ? checkmarkSvg : errorSvg}
153
+ <h1>${escapeHtml(title)}</h1>
154
+ <p>${escapeHtml(subtitle)}</p>
155
+ </div>
156
+ </body>
157
+ </html>`;
158
+ }
159
+
35
160
  /**
36
161
  * Open a URL in the user's default browser.
37
162
  */
@@ -72,26 +197,20 @@ function browserLogin(webUrl: string): Promise<string> {
72
197
 
73
198
  if (receivedState !== state) {
74
199
  res.writeHead(400, { "Content-Type": "text/html" });
75
- res.end(
76
- "<html><body><h2>Login failed</h2><p>State mismatch. Please try again.</p></body></html>",
77
- );
200
+ res.end(renderLoginPage("Login Failed", "State mismatch. Please try again.", false));
78
201
  cleanup("State mismatch — possible CSRF attack.");
79
202
  return;
80
203
  }
81
204
 
82
205
  if (!sessionToken) {
83
206
  res.writeHead(400, { "Content-Type": "text/html" });
84
- res.end(
85
- "<html><body><h2>Login failed</h2><p>No session token received. Please try again.</p></body></html>",
86
- );
207
+ res.end(renderLoginPage("Login Failed", "No session token received. Please try again.", false));
87
208
  cleanup("No session token received from platform.");
88
209
  return;
89
210
  }
90
211
 
91
212
  res.writeHead(200, { "Content-Type": "text/html" });
92
- res.end(
93
- "<html><body><h2>Login successful!</h2><p>You can close this window and return to your terminal.</p></body></html>",
94
- );
213
+ res.end(renderLoginPage("Login Successful", "You can close this window and return to your terminal.", true));
95
214
  cleanup(null, sessionToken);
96
215
  });
97
216