@gramatr/client 0.6.5 → 0.6.7

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.
Files changed (2) hide show
  1. package/bin/gmtr-login.ts +118 -56
  2. package/package.json +1 -1
package/bin/gmtr-login.ts CHANGED
@@ -48,76 +48,125 @@ const DASHBOARD_BASE = process.env.GMTR_DASHBOARD_URL || (() => {
48
48
 
49
49
  // ── HTML Templates ──
50
50
 
51
+ // Brand tokens lifted directly from staging.gramatr.com BaseLayout.css.
52
+ // DO NOT introduce gradients, glassmorphism, or cyan accents — the real
53
+ // brand is flat near-black surfaces, #3B82F6 blue, Inter + Outfit.
51
54
  const BRAND_CSS = `
55
+ @import url('https://fonts.googleapis.com/css2?family=Outfit:wght@600&family=Inter:wght@400;500;600;700&display=swap');
52
56
  * { margin: 0; padding: 0; box-sizing: border-box; }
57
+ :root {
58
+ --gmtr-bg: #0E0E0E;
59
+ --gmtr-surface: #1A1A1A;
60
+ --gmtr-border: #333333;
61
+ --gmtr-text: #ECECEC;
62
+ --gmtr-text-secondary: #A0A0A0;
63
+ --gmtr-text-muted: #707070;
64
+ --gmtr-text-faint: #4A4A4A;
65
+ --gmtr-primary: #3B82F6;
66
+ --gmtr-accent: #60A5FA;
67
+ --gmtr-success: #4ADE80;
68
+ --gmtr-error: #F87171;
69
+ --font-wordmark: 'Outfit', -apple-system, sans-serif;
70
+ --font-heading: 'Inter', -apple-system, sans-serif;
71
+ --font-body: 'Inter', -apple-system, sans-serif;
72
+ --font-mono: 'JetBrains Mono', ui-monospace, Menlo, monospace;
73
+ }
74
+ html, body { height: 100%; }
53
75
  body {
54
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
55
- background: #0a0e17;
56
- color: #e0e6ed;
76
+ background: var(--gmtr-bg);
77
+ color: var(--gmtr-text);
78
+ font-family: var(--font-body);
79
+ font-weight: 400;
80
+ line-height: 1.7;
57
81
  min-height: 100vh;
58
82
  display: flex;
59
83
  align-items: center;
60
84
  justify-content: center;
85
+ padding: 24px;
86
+ -webkit-font-smoothing: antialiased;
87
+ -moz-osx-font-smoothing: grayscale;
61
88
  }
62
89
  .card {
63
- background: #141b2d;
64
- border: 1px solid #1e2940;
65
- border-radius: 16px;
66
- padding: 48px;
67
- max-width: 440px;
68
- width: 90%;
90
+ background: var(--gmtr-surface);
91
+ border: 1px solid var(--gmtr-border);
92
+ border-radius: 12px;
93
+ padding: 2.5rem;
94
+ max-width: 480px;
95
+ width: 100%;
69
96
  text-align: center;
70
- box-shadow: 0 8px 32px rgba(0,0,0,0.4);
71
97
  }
72
- .logo {
73
- font-size: 28px;
74
- font-weight: 700;
75
- letter-spacing: -0.5px;
76
- margin-bottom: 8px;
98
+ .wordmark {
99
+ font-family: var(--font-wordmark);
100
+ font-weight: 600;
101
+ font-size: 1.5rem;
102
+ color: var(--gmtr-text);
103
+ letter-spacing: -0.02em;
104
+ margin-bottom: 0.25rem;
77
105
  }
78
- .logo .accent { color: #00b4d8; }
79
- .logo .dim { color: #5a6a8a; }
80
- .subtitle {
81
- color: #5a6a8a;
82
- font-size: 13px;
83
- margin-bottom: 32px;
106
+ .tagline {
107
+ color: var(--gmtr-text-muted);
108
+ font-size: 0.75rem;
109
+ font-weight: 500;
110
+ text-transform: uppercase;
111
+ letter-spacing: 0.1em;
112
+ margin-bottom: 2rem;
84
113
  }
85
- .status {
86
- font-size: 48px;
87
- margin-bottom: 16px;
114
+ .status-icon {
115
+ width: 56px;
116
+ height: 56px;
117
+ margin: 0 auto 1.25rem;
118
+ display: flex;
119
+ align-items: center;
120
+ justify-content: center;
88
121
  }
122
+ .status-icon svg { width: 56px; height: 56px; }
89
123
  h2 {
90
- font-size: 20px;
91
- font-weight: 600;
92
- margin-bottom: 12px;
124
+ font-family: var(--font-heading);
125
+ font-size: 1.5rem;
126
+ font-weight: 700;
127
+ line-height: 1.2;
128
+ color: var(--gmtr-text);
129
+ margin: 0 0 0.5rem;
93
130
  }
94
- h2.success { color: #00b4d8; }
95
- h2.error { color: #e74c3c; }
96
131
  p {
97
- color: #7a8aaa;
98
- font-size: 14px;
132
+ color: var(--gmtr-text-secondary);
133
+ font-size: 0.9375rem;
99
134
  line-height: 1.6;
135
+ margin: 0;
100
136
  }
101
137
  .hint {
102
- margin-top: 24px;
103
- padding-top: 24px;
104
- border-top: 1px solid #1e2940;
105
- font-size: 12px;
106
- color: #4a5a7a;
138
+ margin-top: 1.75rem;
139
+ padding-top: 1.25rem;
140
+ border-top: 1px solid var(--gmtr-border);
141
+ font-size: 0.8125rem;
142
+ color: var(--gmtr-text-muted);
143
+ }
144
+ .hint code {
145
+ font-family: var(--font-mono);
146
+ font-size: 0.8125rem;
147
+ background: var(--gmtr-bg);
148
+ border: 1px solid var(--gmtr-border);
149
+ color: var(--gmtr-accent);
150
+ padding: 0.15em 0.4em;
151
+ border-radius: 4px;
107
152
  }
108
153
  `;
109
154
 
155
+ // Flat, no pulse, no glow — matches the rest of gramatr.com.
156
+ const CHECK_SVG = `<svg viewBox="0 0 24 24" fill="none" stroke="#4ADE80" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polyline points="16 10 11 15 8 12"></polyline></svg>`;
157
+ const X_SVG = `<svg viewBox="0 0 24 24" fill="none" stroke="#F87171" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="15" y1="9" x2="9" y2="15"></line><line x1="9" y1="9" x2="15" y2="15"></line></svg>`;
158
+
110
159
  function htmlPage(title: string, body: string): string {
111
160
  return `<!DOCTYPE html>
112
161
  <html lang="en"><head>
113
162
  <meta charset="utf-8">
114
163
  <meta name="viewport" content="width=device-width, initial-scale=1">
115
- <title>${title} — gramatr</title>
164
+ <title>${title} — grāmatr</title>
116
165
  <style>${BRAND_CSS}</style>
117
166
  </head><body>
118
167
  <div class="card">
119
- <div class="logo"><span class="accent">gr</span>āma<span class="accent">tr</span></div>
120
- <div class="subtitle">your cross-agent AI brain</div>
168
+ <div class="wordmark">grāmatr</div>
169
+ <div class="tagline">Your AI gets smarter</div>
121
170
  ${body}
122
171
  </div>
123
172
  </body></html>`;
@@ -125,19 +174,19 @@ function htmlPage(title: string, body: string): string {
125
174
 
126
175
  function successPage(): string {
127
176
  return htmlPage('Authenticated', `
128
- <div class="status">✓</div>
129
- <h2 class="success">Authenticated</h2>
130
- <p>Token saved. You can close this tab and return to your terminal.</p>
131
- <div class="hint">gramatr intelligence is now active across all your AI tools.</div>
177
+ <div class="status-icon">${CHECK_SVG}</div>
178
+ <h2>You're signed in</h2>
179
+ <p>Your token is saved on this machine. You can close this tab and return to your terminal.</p>
180
+ <div class="hint">grāmatr intelligence is now active across every AI tool you use.</div>
132
181
  `);
133
182
  }
134
183
 
135
184
  function errorPage(title: string, detail: string): string {
136
185
  return htmlPage('Error', `
137
- <div class="status">✗</div>
138
- <h2 class="error">${title}</h2>
186
+ <div class="status-icon">${X_SVG}</div>
187
+ <h2>${title}</h2>
139
188
  <p>${detail}</p>
140
- <div class="hint">Return to your terminal and try again, or use <code>gmtr-login --token</code> to paste a token directly.</div>
189
+ <div class="hint">Return to your terminal and try again, or run <code>gmtr-login --token</code> to paste a token directly.</div>
141
190
  `);
142
191
  }
143
192
 
@@ -148,10 +197,11 @@ function isHeadless(forceFlag: boolean = false): boolean {
148
197
  if (forceFlag) return true;
149
198
  if (process.env.GRAMATR_LOGIN_HEADLESS === '1') return true;
150
199
 
151
- // SSH session without display forwarding
152
- if (process.env.SSH_CONNECTION || process.env.SSH_TTY) {
153
- if (!process.env.DISPLAY && process.platform !== 'darwin') return true;
154
- }
200
+ // SSH session always go headless. Even on macOS (which has a local
201
+ // display), `open` would launch Safari on the Mac's *physical* screen,
202
+ // not on the remote terminal where the user actually is. Device flow
203
+ // lets them paste the code on whichever machine they're sitting at.
204
+ if (process.env.SSH_CONNECTION || process.env.SSH_TTY) return true;
155
205
  // Docker / CI / no TTY
156
206
  if (process.env.CI || process.env.DOCKER) return true;
157
207
  // Linux without display
@@ -493,23 +543,35 @@ async function loginBrowser(opts: { forceHeadless?: boolean } = {}): Promise<voi
493
543
  const code = url.searchParams.get('code');
494
544
  const returnedState = url.searchParams.get('state');
495
545
  const error = url.searchParams.get('error');
546
+ // v0.6.6: `server.close()` alone does not terminate keep-alive
547
+ // sockets — the browser holds the connection open and the Node
548
+ // event loop never exits, so the CLI prints "Authenticated" and
549
+ // then hangs until Ctrl+C. Send `Connection: close` on the
550
+ // response and call `closeAllConnections()` to forcibly drop
551
+ // lingering sockets after we respond.
552
+ const shutdown = () => {
553
+ callbackServer.close();
554
+ if (typeof (callbackServer as any).closeAllConnections === 'function') {
555
+ (callbackServer as any).closeAllConnections();
556
+ }
557
+ };
496
558
  if (error) {
497
- res.writeHead(200, { 'Content-Type': 'text/html' });
559
+ res.writeHead(200, { 'Content-Type': 'text/html', Connection: 'close' });
498
560
  res.end(errorPage('Authentication Failed', error));
499
- callbackServer.close();
561
+ shutdown();
500
562
  reject(new Error(`OAuth error: ${error}`));
501
563
  return;
502
564
  }
503
565
  if (!code || returnedState !== state) {
504
- res.writeHead(400, { 'Content-Type': 'text/html' });
566
+ res.writeHead(400, { 'Content-Type': 'text/html', Connection: 'close' });
505
567
  res.end(errorPage('Invalid Callback', 'Missing code or state mismatch. Please try again.'));
506
- callbackServer.close();
568
+ shutdown();
507
569
  reject(new Error('Invalid callback'));
508
570
  return;
509
571
  }
510
- res.writeHead(200, { 'Content-Type': 'text/html' });
572
+ res.writeHead(200, { 'Content-Type': 'text/html', Connection: 'close' });
511
573
  res.end(successPage());
512
- callbackServer.close();
574
+ shutdown();
513
575
  resolve(code);
514
576
  });
515
577
  codeTimeoutHandle = setTimeout(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gramatr/client",
3
- "version": "0.6.5",
3
+ "version": "0.6.7",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },