@reminix/cli 0.1.2 → 0.1.3

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/README.md CHANGED
@@ -26,7 +26,7 @@ reminix --help
26
26
 
27
27
  ```bash
28
28
  # From the monorepo root
29
- cd reminix-cli
29
+ cd cli
30
30
 
31
31
  # Install dependencies
32
32
  pnpm install
@@ -0,0 +1,6 @@
1
+ import { Command } from 'commander';
2
+ /**
3
+ * Login command - authenticates user via browser
4
+ */
5
+ export declare const loginCommand: Command;
6
+ //# sourceMappingURL=login.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAiBpC;;GAEG;AACH,eAAO,MAAM,YAAY,SAmDrB,CAAC"}
@@ -0,0 +1,65 @@
1
+ import { Command } from 'commander';
2
+ import open from 'open';
3
+ import { createAuthServer, generateState } from '../lib/auth-server.js';
4
+ import { saveCredentials, loadCredentials } from '../lib/credentials.js';
5
+ /**
6
+ * Default web app URL - can be overridden with REMINIX_WEB_URL env var
7
+ */
8
+ const DEFAULT_WEB_URL = 'https://reminix.com';
9
+ /**
10
+ * Get the web app URL from environment or use default
11
+ */
12
+ function getWebUrl() {
13
+ return process.env.REMINIX_WEB_URL || DEFAULT_WEB_URL;
14
+ }
15
+ /**
16
+ * Login command - authenticates user via browser
17
+ */
18
+ export const loginCommand = new Command('login')
19
+ .description('Authenticate with Reminix')
20
+ .option('--no-browser', "Don't open browser automatically")
21
+ .action(async (options) => {
22
+ try {
23
+ // Check if already logged in
24
+ const existing = loadCredentials();
25
+ if (existing) {
26
+ console.log(`Already logged in as ${existing.email}`);
27
+ console.log('Run "reminix logout" first to switch accounts.');
28
+ return;
29
+ }
30
+ // Generate state for CSRF protection
31
+ const state = generateState();
32
+ // Start local auth server
33
+ console.log('Starting authentication...');
34
+ const { port, waitForCallback } = await createAuthServer(state);
35
+ // Build auth URL
36
+ const webUrl = getWebUrl();
37
+ const authUrl = `${webUrl}/cli/authorize?port=${port}&state=${state}`;
38
+ // Open browser or show URL
39
+ if (options.browser) {
40
+ console.log('Opening browser to log in...\n');
41
+ await open(authUrl);
42
+ }
43
+ else {
44
+ console.log('Open this URL in your browser to log in:\n');
45
+ console.log(` ${authUrl}\n`);
46
+ }
47
+ console.log('Waiting for authentication...');
48
+ // Wait for callback
49
+ const result = await waitForCallback();
50
+ // Save credentials
51
+ saveCredentials(result.token, result.email);
52
+ console.log(`\n✓ Successfully logged in as ${result.email}!`);
53
+ process.exit(0);
54
+ }
55
+ catch (error) {
56
+ if (error instanceof Error) {
57
+ console.error(`\n✗ Login failed: ${error.message}`);
58
+ }
59
+ else {
60
+ console.error('\n✗ Login failed: Unknown error');
61
+ }
62
+ process.exit(1);
63
+ }
64
+ });
65
+ //# sourceMappingURL=login.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACxE,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAEzE;;GAEG;AACH,MAAM,eAAe,GAAG,qBAAqB,CAAC;AAE9C;;GAEG;AACH,SAAS,SAAS;IAChB,OAAO,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,eAAe,CAAC;AACxD,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC;KAC7C,WAAW,CAAC,2BAA2B,CAAC;KACxC,MAAM,CAAC,cAAc,EAAE,kCAAkC,CAAC;KAC1D,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;IACxB,IAAI,CAAC;QACH,6BAA6B;QAC7B,MAAM,QAAQ,GAAG,eAAe,EAAE,CAAC;QACnC,IAAI,QAAQ,EAAE,CAAC;YACb,OAAO,CAAC,GAAG,CAAC,wBAAwB,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;YACtD,OAAO,CAAC,GAAG,CAAC,gDAAgD,CAAC,CAAC;YAC9D,OAAO;QACT,CAAC;QAED,qCAAqC;QACrC,MAAM,KAAK,GAAG,aAAa,EAAE,CAAC;QAE9B,0BAA0B;QAC1B,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;QAC1C,MAAM,EAAE,IAAI,EAAE,eAAe,EAAE,GAAG,MAAM,gBAAgB,CAAC,KAAK,CAAC,CAAC;QAEhE,iBAAiB;QACjB,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAC3B,MAAM,OAAO,GAAG,GAAG,MAAM,uBAAuB,IAAI,UAAU,KAAK,EAAE,CAAC;QAEtE,2BAA2B;QAC3B,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;YACpB,OAAO,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC;YAC9C,MAAM,IAAI,CAAC,OAAO,CAAC,CAAC;QACtB,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,4CAA4C,CAAC,CAAC;YAC1D,OAAO,CAAC,GAAG,CAAC,KAAK,OAAO,IAAI,CAAC,CAAC;QAChC,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,+BAA+B,CAAC,CAAC;QAE7C,oBAAoB;QACpB,MAAM,MAAM,GAAG,MAAM,eAAe,EAAE,CAAC;QAEvC,mBAAmB;QACnB,eAAe,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;QAE5C,OAAO,CAAC,GAAG,CAAC,iCAAiC,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC;QAC9D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;YAC3B,OAAO,CAAC,KAAK,CAAC,qBAAqB,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QACtD,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,KAAK,CAAC,iCAAiC,CAAC,CAAC;QACnD,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC,CAAC,CAAC"}
@@ -0,0 +1,6 @@
1
+ import { Command } from 'commander';
2
+ /**
3
+ * Logout command - clears stored credentials
4
+ */
5
+ export declare const logoutCommand: Command;
6
+ //# sourceMappingURL=logout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logout.d.ts","sourceRoot":"","sources":["../../src/commands/logout.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGpC;;GAEG;AACH,eAAO,MAAM,aAAa,SAUxB,CAAC"}
@@ -0,0 +1,15 @@
1
+ import { Command } from 'commander';
2
+ import { clearCredentials, loadCredentials } from '../lib/credentials.js';
3
+ /**
4
+ * Logout command - clears stored credentials
5
+ */
6
+ export const logoutCommand = new Command('logout').description('Log out of Reminix').action(() => {
7
+ const credentials = loadCredentials();
8
+ if (!credentials) {
9
+ console.log('Not logged in.');
10
+ return;
11
+ }
12
+ clearCredentials();
13
+ console.log(`✓ Logged out of ${credentials.email}`);
14
+ });
15
+ //# sourceMappingURL=logout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logout.js","sourceRoot":"","sources":["../../src/commands/logout.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE1E;;GAEG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,OAAO,CAAC,QAAQ,CAAC,CAAC,WAAW,CAAC,oBAAoB,CAAC,CAAC,MAAM,CAAC,GAAG,EAAE;IAC/F,MAAM,WAAW,GAAG,eAAe,EAAE,CAAC;IAEtC,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;QAC9B,OAAO;IACT,CAAC;IAED,gBAAgB,EAAE,CAAC;IACnB,OAAO,CAAC,GAAG,CAAC,mBAAmB,WAAW,CAAC,KAAK,EAAE,CAAC,CAAC;AACtD,CAAC,CAAC,CAAC"}
@@ -1,2 +1,2 @@
1
- export declare const version = "0.1.2";
1
+ export declare const version = "0.1.3";
2
2
  //# sourceMappingURL=version.d.ts.map
@@ -1,2 +1,2 @@
1
- export const version = '0.1.2';
1
+ export const version = '0.1.3';
2
2
  //# sourceMappingURL=version.js.map
@@ -0,0 +1,6 @@
1
+ import { Command } from 'commander';
2
+ /**
3
+ * Whoami command - shows current logged-in user
4
+ */
5
+ export declare const whoamiCommand: Command;
6
+ //# sourceMappingURL=whoami.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"whoami.d.ts","sourceRoot":"","sources":["../../src/commands/whoami.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGpC;;GAEG;AACH,eAAO,MAAM,aAAa,SAiBtB,CAAC"}
@@ -0,0 +1,21 @@
1
+ import { Command } from 'commander';
2
+ import { loadCredentials, getCredentialsDirPath } from '../lib/credentials.js';
3
+ /**
4
+ * Whoami command - shows current logged-in user
5
+ */
6
+ export const whoamiCommand = new Command('whoami')
7
+ .description('Show the currently logged-in user')
8
+ .option('--verbose', 'Show additional details')
9
+ .action((options) => {
10
+ const credentials = loadCredentials();
11
+ if (!credentials) {
12
+ console.log("Not logged in. Run 'reminix login' to authenticate.");
13
+ process.exit(1);
14
+ }
15
+ console.log(credentials.email);
16
+ if (options.verbose) {
17
+ console.log(`\nToken created: ${credentials.createdAt}`);
18
+ console.log(`Credentials stored in: ${getCredentialsDirPath()}`);
19
+ }
20
+ });
21
+ //# sourceMappingURL=whoami.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"whoami.js","sourceRoot":"","sources":["../../src/commands/whoami.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAE/E;;GAEG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,OAAO,CAAC,QAAQ,CAAC;KAC/C,WAAW,CAAC,mCAAmC,CAAC;KAChD,MAAM,CAAC,WAAW,EAAE,yBAAyB,CAAC;KAC9C,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE;IAClB,MAAM,WAAW,GAAG,eAAe,EAAE,CAAC;IAEtC,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,OAAO,CAAC,GAAG,CAAC,qDAAqD,CAAC,CAAC;QACnE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IAE/B,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QACpB,OAAO,CAAC,GAAG,CAAC,oBAAoB,WAAW,CAAC,SAAS,EAAE,CAAC,CAAC;QACzD,OAAO,CAAC,GAAG,CAAC,0BAA0B,qBAAqB,EAAE,EAAE,CAAC,CAAC;IACnE,CAAC;AACH,CAAC,CAAC,CAAC"}
package/dist/index.js CHANGED
@@ -1,6 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from 'commander';
3
3
  import { version } from './commands/version.js';
4
+ import { loginCommand } from './commands/login.js';
5
+ import { logoutCommand } from './commands/logout.js';
6
+ import { whoamiCommand } from './commands/whoami.js';
4
7
  const program = new Command();
5
8
  program
6
9
  .name('reminix')
@@ -10,5 +13,9 @@ program
10
13
  .action(() => {
11
14
  program.help();
12
15
  });
16
+ // Add commands
17
+ program.addCommand(loginCommand);
18
+ program.addCommand(logoutCommand);
19
+ program.addCommand(whoamiCommand);
13
20
  program.parse();
14
21
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAEhD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,SAAS,CAAC;KACf,WAAW,CAAC,kDAAkD,CAAC;KAC/D,OAAO,CAAC,OAAO,EAAE,eAAe,EAAE,wBAAwB,CAAC;KAC3D,kBAAkB,EAAE;KACpB,MAAM,CAAC,GAAG,EAAE;IACX,OAAO,CAAC,IAAI,EAAE,CAAC;AACjB,CAAC,CAAC,CAAC;AAEL,OAAO,CAAC,KAAK,EAAE,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,SAAS,CAAC;KACf,WAAW,CAAC,kDAAkD,CAAC;KAC/D,OAAO,CAAC,OAAO,EAAE,eAAe,EAAE,wBAAwB,CAAC;KAC3D,kBAAkB,EAAE;KACpB,MAAM,CAAC,GAAG,EAAE;IACX,OAAO,CAAC,IAAI,EAAE,CAAC;AACjB,CAAC,CAAC,CAAC;AAEL,eAAe;AACf,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;AACjC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;AAClC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;AAElC,OAAO,CAAC,KAAK,EAAE,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Result from the auth callback
3
+ */
4
+ export interface AuthCallbackResult {
5
+ token: string;
6
+ state: string;
7
+ email: string;
8
+ }
9
+ /**
10
+ * Start the auth server and return both the port and the result promise
11
+ * This allows the caller to get the port immediately and wait for the result separately
12
+ */
13
+ export declare function createAuthServer(expectedState: string, timeoutMs?: number): Promise<{
14
+ port: number;
15
+ waitForCallback: () => Promise<AuthCallbackResult>;
16
+ }>;
17
+ /**
18
+ * Generate a random state string for CSRF protection
19
+ */
20
+ export declare function generateState(): string;
21
+ //# sourceMappingURL=auth-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-server.d.ts","sourceRoot":"","sources":["../../src/lib/auth-server.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AA+ID;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,aAAa,EAAE,MAAM,EACrB,SAAS,GAAE,MAAsB,GAChC,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,OAAO,CAAC,kBAAkB,CAAC,CAAA;CAAE,CAAC,CA0F/E;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAItC"}
@@ -0,0 +1,234 @@
1
+ import * as http from 'node:http';
2
+ /**
3
+ * HTML page shown after successful authentication
4
+ */
5
+ const SUCCESS_HTML = `
6
+ <!DOCTYPE html>
7
+ <html lang="en">
8
+ <head>
9
+ <meta charset="UTF-8">
10
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
11
+ <title>Reminix CLI - Authenticated</title>
12
+ <style>
13
+ * { margin: 0; padding: 0; box-sizing: border-box; }
14
+ body {
15
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
16
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
17
+ min-height: 100vh;
18
+ display: flex;
19
+ align-items: center;
20
+ justify-content: center;
21
+ color: #fff;
22
+ }
23
+ .container {
24
+ text-align: center;
25
+ padding: 2rem;
26
+ }
27
+ .checkmark {
28
+ width: 80px;
29
+ height: 80px;
30
+ margin: 0 auto 1.5rem;
31
+ background: #10b981;
32
+ border-radius: 50%;
33
+ display: flex;
34
+ align-items: center;
35
+ justify-content: center;
36
+ animation: pop 0.3s ease-out;
37
+ }
38
+ .checkmark svg {
39
+ width: 40px;
40
+ height: 40px;
41
+ stroke: white;
42
+ stroke-width: 3;
43
+ fill: none;
44
+ }
45
+ h1 {
46
+ font-size: 1.5rem;
47
+ font-weight: 600;
48
+ margin-bottom: 0.5rem;
49
+ }
50
+ p {
51
+ color: #94a3b8;
52
+ font-size: 1rem;
53
+ }
54
+ @keyframes pop {
55
+ 0% { transform: scale(0); }
56
+ 80% { transform: scale(1.1); }
57
+ 100% { transform: scale(1); }
58
+ }
59
+ </style>
60
+ </head>
61
+ <body>
62
+ <div class="container">
63
+ <div class="checkmark">
64
+ <svg viewBox="0 0 24 24">
65
+ <polyline points="20 6 9 17 4 12"></polyline>
66
+ </svg>
67
+ </div>
68
+ <h1>Authentication successful!</h1>
69
+ <p>You can close this tab and return to your terminal.</p>
70
+ </div>
71
+ </body>
72
+ </html>
73
+ `;
74
+ /**
75
+ * HTML page shown when there's an error
76
+ */
77
+ const ERROR_HTML = `
78
+ <!DOCTYPE html>
79
+ <html lang="en">
80
+ <head>
81
+ <meta charset="UTF-8">
82
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
83
+ <title>Reminix CLI - Error</title>
84
+ <style>
85
+ * { margin: 0; padding: 0; box-sizing: border-box; }
86
+ body {
87
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
88
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
89
+ min-height: 100vh;
90
+ display: flex;
91
+ align-items: center;
92
+ justify-content: center;
93
+ color: #fff;
94
+ }
95
+ .container {
96
+ text-align: center;
97
+ padding: 2rem;
98
+ }
99
+ .error-icon {
100
+ width: 80px;
101
+ height: 80px;
102
+ margin: 0 auto 1.5rem;
103
+ background: #ef4444;
104
+ border-radius: 50%;
105
+ display: flex;
106
+ align-items: center;
107
+ justify-content: center;
108
+ }
109
+ .error-icon svg {
110
+ width: 40px;
111
+ height: 40px;
112
+ stroke: white;
113
+ stroke-width: 3;
114
+ fill: none;
115
+ }
116
+ h1 {
117
+ font-size: 1.5rem;
118
+ font-weight: 600;
119
+ margin-bottom: 0.5rem;
120
+ }
121
+ p {
122
+ color: #94a3b8;
123
+ font-size: 1rem;
124
+ }
125
+ </style>
126
+ </head>
127
+ <body>
128
+ <div class="container">
129
+ <div class="error-icon">
130
+ <svg viewBox="0 0 24 24">
131
+ <line x1="18" y1="6" x2="6" y2="18"></line>
132
+ <line x1="6" y1="6" x2="18" y2="18"></line>
133
+ </svg>
134
+ </div>
135
+ <h1>Authentication failed</h1>
136
+ <p>Please try again from your terminal.</p>
137
+ </div>
138
+ </body>
139
+ </html>
140
+ `;
141
+ /**
142
+ * Start the auth server and return both the port and the result promise
143
+ * This allows the caller to get the port immediately and wait for the result separately
144
+ */
145
+ export function createAuthServer(expectedState, timeoutMs = 5 * 60 * 1000) {
146
+ return new Promise((resolve, reject) => {
147
+ let callbackResolve;
148
+ let callbackReject;
149
+ const callbackPromise = new Promise((res, rej) => {
150
+ callbackResolve = res;
151
+ callbackReject = rej;
152
+ });
153
+ const server = http.createServer((req, res) => {
154
+ // Only handle GET requests to /callback
155
+ if (!req.url?.startsWith('/callback')) {
156
+ res.writeHead(404);
157
+ res.end('Not found');
158
+ return;
159
+ }
160
+ // Parse query parameters using WHATWG URL API
161
+ const parsedUrl = new URL(req.url, `http://localhost`);
162
+ const token = parsedUrl.searchParams.get('token') ?? undefined;
163
+ const state = parsedUrl.searchParams.get('state') ?? undefined;
164
+ const email = parsedUrl.searchParams.get('email') ?? undefined;
165
+ const error = parsedUrl.searchParams.get('error') ?? undefined;
166
+ // Handle error from web app
167
+ if (error) {
168
+ res.writeHead(200, { 'Content-Type': 'text/html' });
169
+ res.end(ERROR_HTML);
170
+ cleanup();
171
+ callbackReject(new Error(`Authentication failed: ${error}`));
172
+ return;
173
+ }
174
+ // Validate required parameters
175
+ if (!token || !state || !email) {
176
+ res.writeHead(200, { 'Content-Type': 'text/html' });
177
+ res.end(ERROR_HTML);
178
+ cleanup();
179
+ callbackReject(new Error('Missing token, state, or email in callback'));
180
+ return;
181
+ }
182
+ // Verify state matches (CSRF protection)
183
+ if (state !== expectedState) {
184
+ res.writeHead(200, { 'Content-Type': 'text/html' });
185
+ res.end(ERROR_HTML);
186
+ cleanup();
187
+ callbackReject(new Error('State mismatch - possible CSRF attack'));
188
+ return;
189
+ }
190
+ // Success!
191
+ res.writeHead(200, { 'Content-Type': 'text/html' });
192
+ res.end(SUCCESS_HTML);
193
+ cleanup();
194
+ callbackResolve({ token, state, email });
195
+ });
196
+ // Handle server errors
197
+ server.on('error', (err) => {
198
+ cleanup();
199
+ reject(err);
200
+ });
201
+ // Set up timeout
202
+ const timeoutId = setTimeout(() => {
203
+ cleanup();
204
+ callbackReject(new Error('Authentication timed out. Please try again.'));
205
+ }, timeoutMs);
206
+ // Cleanup function
207
+ function cleanup() {
208
+ clearTimeout(timeoutId);
209
+ server.close();
210
+ }
211
+ // Start server on random available port (port 0)
212
+ server.listen(0, '127.0.0.1', () => {
213
+ const address = server.address();
214
+ if (typeof address === 'object' && address) {
215
+ resolve({
216
+ port: address.port,
217
+ waitForCallback: () => callbackPromise,
218
+ });
219
+ }
220
+ else {
221
+ reject(new Error('Failed to start auth server'));
222
+ }
223
+ });
224
+ });
225
+ }
226
+ /**
227
+ * Generate a random state string for CSRF protection
228
+ */
229
+ export function generateState() {
230
+ const array = new Uint8Array(32);
231
+ crypto.getRandomValues(array);
232
+ return Array.from(array, (byte) => byte.toString(16).padStart(2, '0')).join('');
233
+ }
234
+ //# sourceMappingURL=auth-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-server.js","sourceRoot":"","sources":["../../src/lib/auth-server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAWlC;;GAEG;AACH,MAAM,YAAY,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoEpB,CAAC;AAEF;;GAEG;AACH,MAAM,UAAU,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+DlB,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAC9B,aAAqB,EACrB,YAAoB,CAAC,GAAG,EAAE,GAAG,IAAI;IAEjC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,IAAI,eAAqD,CAAC;QAC1D,IAAI,cAAsC,CAAC;QAE3C,MAAM,eAAe,GAAG,IAAI,OAAO,CAAqB,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;YACnE,eAAe,GAAG,GAAG,CAAC;YACtB,cAAc,GAAG,GAAG,CAAC;QACvB,CAAC,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;YAC5C,wCAAwC;YACxC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;gBACtC,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;gBACnB,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;gBACrB,OAAO;YACT,CAAC;YAED,8CAA8C;YAC9C,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAC;YACvD,MAAM,KAAK,GAAG,SAAS,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,SAAS,CAAC;YAC/D,MAAM,KAAK,GAAG,SAAS,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,SAAS,CAAC;YAC/D,MAAM,KAAK,GAAG,SAAS,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,SAAS,CAAC;YAC/D,MAAM,KAAK,GAAG,SAAS,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,SAAS,CAAC;YAE/D,4BAA4B;YAC5B,IAAI,KAAK,EAAE,CAAC;gBACV,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,WAAW,EAAE,CAAC,CAAC;gBACpD,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;gBACpB,OAAO,EAAE,CAAC;gBACV,cAAc,CAAC,IAAI,KAAK,CAAC,0BAA0B,KAAK,EAAE,CAAC,CAAC,CAAC;gBAC7D,OAAO;YACT,CAAC;YAED,+BAA+B;YAC/B,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,EAAE,CAAC;gBAC/B,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,WAAW,EAAE,CAAC,CAAC;gBACpD,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;gBACpB,OAAO,EAAE,CAAC;gBACV,cAAc,CAAC,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC,CAAC;gBACxE,OAAO;YACT,CAAC;YAED,yCAAyC;YACzC,IAAI,KAAK,KAAK,aAAa,EAAE,CAAC;gBAC5B,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,WAAW,EAAE,CAAC,CAAC;gBACpD,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;gBACpB,OAAO,EAAE,CAAC;gBACV,cAAc,CAAC,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC,CAAC;gBACnE,OAAO;YACT,CAAC;YAED,WAAW;YACX,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,WAAW,EAAE,CAAC,CAAC;YACpD,GAAG,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;YACtB,OAAO,EAAE,CAAC;YACV,eAAe,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;QAC3C,CAAC,CAAC,CAAC;QAEH,uBAAuB;QACvB,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YACzB,OAAO,EAAE,CAAC;YACV,MAAM,CAAC,GAAG,CAAC,CAAC;QACd,CAAC,CAAC,CAAC;QAEH,iBAAiB;QACjB,MAAM,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE;YAChC,OAAO,EAAE,CAAC;YACV,cAAc,CAAC,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC,CAAC;QAC3E,CAAC,EAAE,SAAS,CAAC,CAAC;QAEd,mBAAmB;QACnB,SAAS,OAAO;YACd,YAAY,CAAC,SAAS,CAAC,CAAC;YACxB,MAAM,CAAC,KAAK,EAAE,CAAC;QACjB,CAAC;QAED,iDAAiD;QACjD,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW,EAAE,GAAG,EAAE;YACjC,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;YACjC,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,EAAE,CAAC;gBAC3C,OAAO,CAAC;oBACN,IAAI,EAAE,OAAO,CAAC,IAAI;oBAClB,eAAe,EAAE,GAAG,EAAE,CAAC,eAAe;iBACvC,CAAC,CAAC;YACL,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC,CAAC;YACnD,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa;IAC3B,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC;IACjC,MAAM,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;IAC9B,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAClF,CAAC"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Credentials stored locally for CLI authentication
3
+ */
4
+ export interface Credentials {
5
+ token: string;
6
+ email: string;
7
+ createdAt: string;
8
+ }
9
+ /**
10
+ * Save credentials to the local filesystem
11
+ * Creates the config directory if it doesn't exist
12
+ */
13
+ export declare function saveCredentials(token: string, email: string): void;
14
+ /**
15
+ * Load credentials from the local filesystem
16
+ * Returns null if no credentials exist or file is invalid
17
+ */
18
+ export declare function loadCredentials(): Credentials | null;
19
+ /**
20
+ * Clear stored credentials
21
+ * Removes the credentials file if it exists
22
+ */
23
+ export declare function clearCredentials(): void;
24
+ /**
25
+ * Check if user is logged in (has valid credentials)
26
+ */
27
+ export declare function isLoggedIn(): boolean;
28
+ /**
29
+ * Get the credentials directory path (for display purposes)
30
+ */
31
+ export declare function getCredentialsDirPath(): string;
32
+ //# sourceMappingURL=credentials.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../../src/lib/credentials.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AAqBD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAmBlE;AAED;;;GAGG;AACH,wBAAgB,eAAe,IAAI,WAAW,GAAG,IAAI,CAoBpD;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAMvC;AAED;;GAEG;AACH,wBAAgB,UAAU,IAAI,OAAO,CAEpC;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,CAE9C"}
@@ -0,0 +1,86 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import * as os from 'node:os';
4
+ /**
5
+ * Get the path to the credentials directory
6
+ * Uses XDG_CONFIG_HOME if set, otherwise ~/.config/reminix
7
+ */
8
+ function getCredentialsDir() {
9
+ const xdgConfigHome = process.env.XDG_CONFIG_HOME;
10
+ if (xdgConfigHome) {
11
+ return path.join(xdgConfigHome, 'reminix');
12
+ }
13
+ return path.join(os.homedir(), '.config', 'reminix');
14
+ }
15
+ /**
16
+ * Get the path to the credentials file
17
+ */
18
+ function getCredentialsPath() {
19
+ return path.join(getCredentialsDir(), 'credentials.json');
20
+ }
21
+ /**
22
+ * Save credentials to the local filesystem
23
+ * Creates the config directory if it doesn't exist
24
+ */
25
+ export function saveCredentials(token, email) {
26
+ const dir = getCredentialsDir();
27
+ const filePath = getCredentialsPath();
28
+ // Create directory if it doesn't exist
29
+ if (!fs.existsSync(dir)) {
30
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 }); // Only owner can access
31
+ }
32
+ const credentials = {
33
+ token,
34
+ email,
35
+ createdAt: new Date().toISOString(),
36
+ };
37
+ // Write credentials file with restricted permissions
38
+ fs.writeFileSync(filePath, JSON.stringify(credentials, null, 2), {
39
+ mode: 0o600, // Only owner can read/write
40
+ });
41
+ }
42
+ /**
43
+ * Load credentials from the local filesystem
44
+ * Returns null if no credentials exist or file is invalid
45
+ */
46
+ export function loadCredentials() {
47
+ const filePath = getCredentialsPath();
48
+ if (!fs.existsSync(filePath)) {
49
+ return null;
50
+ }
51
+ try {
52
+ const content = fs.readFileSync(filePath, 'utf-8');
53
+ const credentials = JSON.parse(content);
54
+ // Basic validation
55
+ if (!credentials.token || !credentials.email) {
56
+ return null;
57
+ }
58
+ return credentials;
59
+ }
60
+ catch {
61
+ return null;
62
+ }
63
+ }
64
+ /**
65
+ * Clear stored credentials
66
+ * Removes the credentials file if it exists
67
+ */
68
+ export function clearCredentials() {
69
+ const filePath = getCredentialsPath();
70
+ if (fs.existsSync(filePath)) {
71
+ fs.unlinkSync(filePath);
72
+ }
73
+ }
74
+ /**
75
+ * Check if user is logged in (has valid credentials)
76
+ */
77
+ export function isLoggedIn() {
78
+ return loadCredentials() !== null;
79
+ }
80
+ /**
81
+ * Get the credentials directory path (for display purposes)
82
+ */
83
+ export function getCredentialsDirPath() {
84
+ return getCredentialsDir();
85
+ }
86
+ //# sourceMappingURL=credentials.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credentials.js","sourceRoot":"","sources":["../../src/lib/credentials.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAW9B;;;GAGG;AACH,SAAS,iBAAiB;IACxB,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;IAClD,IAAI,aAAa,EAAE,CAAC;QAClB,OAAO,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC;IAC7C,CAAC;IACD,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;AACvD,CAAC;AAED;;GAEG;AACH,SAAS,kBAAkB;IACzB,OAAO,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,EAAE,kBAAkB,CAAC,CAAC;AAC5D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,KAAa,EAAE,KAAa;IAC1D,MAAM,GAAG,GAAG,iBAAiB,EAAE,CAAC;IAChC,MAAM,QAAQ,GAAG,kBAAkB,EAAE,CAAC;IAEtC,uCAAuC;IACvC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,wBAAwB;IAC/E,CAAC;IAED,MAAM,WAAW,GAAgB;QAC/B,KAAK;QACL,KAAK;QACL,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACpC,CAAC;IAEF,qDAAqD;IACrD,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;QAC/D,IAAI,EAAE,KAAK,EAAE,4BAA4B;KAC1C,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe;IAC7B,MAAM,QAAQ,GAAG,kBAAkB,EAAE,CAAC;IAEtC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACnD,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAgB,CAAC;QAEvD,mBAAmB;QACnB,IAAI,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC;YAC7C,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,WAAW,CAAC;IACrB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB;IAC9B,MAAM,QAAQ,GAAG,kBAAkB,EAAE,CAAC;IAEtC,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC5B,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC1B,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU;IACxB,OAAO,eAAe,EAAE,KAAK,IAAI,CAAC;AACpC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,qBAAqB;IACnC,OAAO,iBAAiB,EAAE,CAAC;AAC7B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reminix/cli",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Reminix CLI - Command-line interface for Reminix",
5
5
  "license": "Apache-2.0",
6
6
  "author": {
@@ -18,11 +18,11 @@
18
18
  "homepage": "https://reminix.com",
19
19
  "repository": {
20
20
  "type": "git",
21
- "url": "git+https://github.com/reminix-ai/reminix-cli.git",
21
+ "url": "git+https://github.com/reminix-ai/cli.git",
22
22
  "directory": "packages/cli"
23
23
  },
24
24
  "bugs": {
25
- "url": "https://github.com/reminix-ai/reminix-cli/issues"
25
+ "url": "https://github.com/reminix-ai/cli/issues"
26
26
  },
27
27
  "engines": {
28
28
  "node": ">=20"
@@ -48,7 +48,8 @@
48
48
  "LICENSE"
49
49
  ],
50
50
  "dependencies": {
51
- "commander": "^14.0.2"
51
+ "commander": "^14.0.2",
52
+ "open": "^11.0.0"
52
53
  },
53
54
  "devDependencies": {
54
55
  "@types/node": "^25.0.9",