@devkitvault/recall 1.0.0 → 1.0.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.
Files changed (40) hide show
  1. package/README.md +14 -21
  2. package/dist/commands/alias.js +84 -0
  3. package/dist/commands/approve.js +108 -0
  4. package/dist/commands/audit.js +58 -0
  5. package/dist/commands/auth/forgot.js +33 -0
  6. package/dist/commands/auth/index.js +16 -0
  7. package/dist/commands/auth/login.js +151 -0
  8. package/dist/commands/auth/logout.js +15 -0
  9. package/dist/commands/auth/register.js +84 -0
  10. package/dist/commands/auth/reset.js +43 -0
  11. package/dist/commands/completion.js +158 -0
  12. package/dist/commands/config.js +89 -0
  13. package/dist/commands/delete.js +58 -0
  14. package/dist/commands/doctor.js +106 -0
  15. package/dist/commands/env.js +135 -0
  16. package/dist/commands/export.js +68 -0
  17. package/dist/commands/feedback.js +25 -0
  18. package/dist/commands/group.js +206 -0
  19. package/dist/commands/history.js +41 -0
  20. package/dist/commands/import.js +126 -0
  21. package/dist/commands/list.js +69 -0
  22. package/dist/commands/org.js +408 -0
  23. package/dist/commands/pin.js +30 -0
  24. package/dist/commands/run.js +55 -0
  25. package/dist/commands/save.js +73 -0
  26. package/dist/commands/search.js +82 -0
  27. package/dist/commands/share.js +88 -0
  28. package/dist/commands/snippet.js +147 -0
  29. package/dist/commands/support.js +96 -0
  30. package/dist/commands/sync.js +80 -0
  31. package/dist/commands/template.js +177 -0
  32. package/dist/commands/update.js +66 -0
  33. package/dist/commands/upgrade.js +122 -0
  34. package/dist/commands/whoami.js +70 -0
  35. package/dist/index.js +71 -0
  36. package/dist/lib/api.js +51 -0
  37. package/dist/lib/auth.js +88 -0
  38. package/dist/lib/config.js +37 -0
  39. package/package.json +12 -7
  40. package/bin/recall.js +0 -169
package/README.md CHANGED
@@ -1,15 +1,14 @@
1
1
  # @devkitvault/recall
2
2
 
3
- **Install the recall CLI from npm** on first run, this package downloads the official native binary for your OS from [GitHub Releases](https://github.com/devkitvault/recall/releases) into `~/.local/bin`, then runs it. Later runs reuse the same binary.
3
+ **Install the recall CLI from npm** as a normal **Node.js** package: small download (compiled TypeScript + runtime dependencies), **no ~100MB native binary**. The published tarball contains the same logic as `packages/cli` in this repo (`tsc` output).
4
4
 
5
- recall is a **cloud-synced command vault**: save shell commands, templates, snippets, env sets, and aliases same account as [recall.devkitvault.com](https://recall.devkitvault.com) and the [VS Code extension](https://marketplace.visualstudio.com/items?itemName=devkitvault.recall-cmd).
5
+ Your auth token is stored under **`~/.recall/token`** (this npm build does not ship `keytar`; the CLI already falls back to that file when the OS keychain module is unavailable).
6
6
 
7
- ## Requirements
7
+ recall is a **cloud-synced command vault** — same account as [recall.devkitvault.com](https://recall.devkitvault.com) and the [VS Code extension](https://marketplace.visualstudio.com/items?itemName=devkitvault.recall-cmd).
8
8
 
9
- - **Node.js 18+** (only for this installer wrapper)
10
- - **Supported platforms** (prebuilt binaries): macOS (Apple Silicon + Intel), Linux x64, Windows x64
9
+ ## Requirements
11
10
 
12
- Other architectures: use the [curl / PowerShell installers](https://devkitvault.com) or build from source in `packages/cli`.
11
+ - **Node.js 18+**
13
12
 
14
13
  ## Install
15
14
 
@@ -17,8 +16,6 @@ Other architectures: use the [curl / PowerShell installers](https://devkitvault.
17
16
  npm install -g @devkitvault/recall
18
17
  ```
19
18
 
20
- Ensure `~/.local/bin` is on your `PATH` (the installer places `recall` there). On Windows, that path is `%USERPROFILE%\.local\bin`.
21
-
22
19
  ## Use
23
20
 
24
21
  ```sh
@@ -28,24 +25,20 @@ recall save "docker compose up -d" -n "start stack"
28
25
  recall search docker
29
26
  ```
30
27
 
31
- ## Re-download the binary
32
-
33
- If a release was updated or your download was corrupted:
28
+ ## Native binary instead (optional)
34
29
 
35
- ```sh
36
- RECALL_REINSTALL=1 recall --version
37
- ```
30
+ If you want a **single self-contained executable** (~100MB Bun compile) without Node, use the [PowerShell / curl installers](https://devkitvault.com) from GitHub Releases — they install to `%USERPROFILE%\.local\bin` / `~/.local/bin`.
38
31
 
39
- ## Release tag vs npm version
32
+ ## Publishing (maintainers)
40
33
 
41
- Downloads use a Git **tag** on `devkitvault/recall` (default: `v` + `version` from this package’s `package.json`). If you ship an npm **patch** without a matching GitHub tag, set either:
34
+ From the **monorepo root**, with `pnpm` installed:
42
35
 
43
- - `recall.releaseTag` in `package.json`, or
44
- - environment variable `RECALL_RELEASE_TAG` (e.g. `v1.0.0`)
45
-
46
- before running `recall`, so the wrapper points at an existing release.
36
+ ```powershell
37
+ cd packages/npm-cli
38
+ npm publish
39
+ ```
47
40
 
48
- Maintainers: after [Release CLI](https://github.com/devkitvault/recall/blob/master/.github/workflows/release.yml) publishes assets for `vX.Y.Z`, publish this npm package with the same `version` **or** set `recall.releaseTag` accordingly.
41
+ `prepublishOnly` runs **`pnpm --filter @recall/cli run build`** and copies **`packages/cli/dist`** into **`packages/npm-cli/dist`**. Ensure the CLI compiles before publishing.
49
42
 
50
43
  ## License
51
44
 
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.aliasCommand = void 0;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const cli_table3_1 = __importDefault(require("cli-table3"));
9
+ const commander_1 = require("commander");
10
+ const ora_1 = __importDefault(require("ora"));
11
+ const api_1 = require("../lib/api");
12
+ const auth_1 = require("../lib/auth");
13
+ const aliasSet = new commander_1.Command('set')
14
+ .argument('<alias>', 'Short alias')
15
+ .argument('<name>', 'Command name to alias')
16
+ .description('Create a short alias for a command')
17
+ .action(async (alias, name) => {
18
+ const token = await (0, auth_1.requireAuth)();
19
+ const spinner = (0, ora_1.default)(`Setting alias "${alias}" → "${name}"...`).start();
20
+ try {
21
+ const cmd = await api_1.ApiClient.get(`/commands/by-name/${encodeURIComponent(name)}`, token);
22
+ await api_1.ApiClient.post('/aliases', { alias, commandId: cmd.id }, token);
23
+ spinner.succeed(chalk_1.default.green(`Alias set: ${chalk_1.default.white(alias)} → ${chalk_1.default.white(name)}`));
24
+ console.log(chalk_1.default.dim(`\n Run it with: recall run ${alias}\n`));
25
+ }
26
+ catch {
27
+ spinner.fail(chalk_1.default.red(`Command "${name}" not found`));
28
+ }
29
+ });
30
+ const aliasList = new commander_1.Command('list')
31
+ .description('List all your aliases')
32
+ .action(async () => {
33
+ const token = await (0, auth_1.requireAuth)();
34
+ const spinner = (0, ora_1.default)('Fetching aliases...').start();
35
+ try {
36
+ const { aliases } = await api_1.ApiClient.get('/aliases', token);
37
+ spinner.stop();
38
+ if (!aliases.length) {
39
+ console.log(chalk_1.default.dim('\n No aliases yet. Run: recall alias set <alias> <name>\n'));
40
+ return;
41
+ }
42
+ const table = new cli_table3_1.default({
43
+ head: [chalk_1.default.cyan('Alias'), chalk_1.default.cyan('Command name'), chalk_1.default.cyan('Command')],
44
+ style: { head: [], border: ['grey'] },
45
+ colWidths: [16, 20, 46],
46
+ wordWrap: true,
47
+ });
48
+ for (const a of aliases) {
49
+ const cmdName = a.command?.name ?? '—';
50
+ const cmdCommand = a.command?.command ?? '—';
51
+ const truncated = cmdCommand.length > 44
52
+ ? cmdCommand.slice(0, 44) + '…'
53
+ : cmdCommand;
54
+ table.push([
55
+ chalk_1.default.white(a.alias),
56
+ cmdName,
57
+ truncated,
58
+ ]);
59
+ }
60
+ console.log(table.toString());
61
+ }
62
+ catch {
63
+ spinner.fail(chalk_1.default.red('Failed to fetch aliases'));
64
+ }
65
+ });
66
+ const aliasDelete = new commander_1.Command('delete')
67
+ .argument('<alias>', 'Alias to delete')
68
+ .description('Delete an alias')
69
+ .action(async (alias) => {
70
+ const token = await (0, auth_1.requireAuth)();
71
+ const spinner = (0, ora_1.default)(`Deleting alias "${alias}"...`).start();
72
+ try {
73
+ await api_1.ApiClient.delete(`/aliases/${encodeURIComponent(alias)}`, token);
74
+ spinner.succeed(chalk_1.default.green(`Deleted alias "${alias}"`));
75
+ }
76
+ catch {
77
+ spinner.fail(chalk_1.default.red(`Alias "${alias}" not found`));
78
+ }
79
+ });
80
+ exports.aliasCommand = new commander_1.Command('alias')
81
+ .description('Manage command aliases')
82
+ .addCommand(aliasSet)
83
+ .addCommand(aliasList)
84
+ .addCommand(aliasDelete);
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.approveCommand = void 0;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const child_process_1 = require("child_process");
9
+ const commander_1 = require("commander");
10
+ const ora_1 = __importDefault(require("ora"));
11
+ const api_1 = require("../lib/api");
12
+ const auth_1 = require("../lib/auth");
13
+ const approveRequest = new commander_1.Command('request')
14
+ .argument('<command-name>', 'Org command name to request approval for')
15
+ .description('Request approval to run an org command')
16
+ .action(async (commandName) => {
17
+ const token = await (0, auth_1.requireAuth)();
18
+ const spinner = (0, ora_1.default)('Creating approval request...').start();
19
+ try {
20
+ const orgRes = await api_1.ApiClient.get('/orgs/me', token);
21
+ const org = orgRes.org;
22
+ const { commands } = await api_1.ApiClient.get(`/orgs/${org.id}/commands`, token);
23
+ const cmd = commands.find((c) => c.name === commandName);
24
+ if (!cmd) {
25
+ spinner.fail(chalk_1.default.red(`Command "${commandName}" not found in org vault`));
26
+ return;
27
+ }
28
+ const result = await api_1.ApiClient.post(`/orgs/${org.id}/commands/${cmd.id}/request-approval`, {}, token);
29
+ spinner.stop();
30
+ console.log(chalk_1.default.bold('\n Approval request created\n'));
31
+ console.log(` ${chalk_1.default.dim('Command:')} ${chalk_1.default.white(cmd.command)}`);
32
+ console.log();
33
+ console.log(chalk_1.default.dim(' Share this with an org admin to approve:'));
34
+ console.log(` ${chalk_1.default.cyan(result.approveCommand)}`);
35
+ console.log();
36
+ }
37
+ catch (err) {
38
+ spinner.stop();
39
+ if (err instanceof api_1.ApiError) {
40
+ console.error(chalk_1.default.red(`\n ${err.message}\n`));
41
+ }
42
+ }
43
+ });
44
+ const approveGrant = new commander_1.Command('grant')
45
+ .argument('<token>', 'Approval token')
46
+ .description('Approve a pending request')
47
+ .option('-r, --run', 'Run the command immediately after approving')
48
+ .action(async (token, opts) => {
49
+ const authToken = await (0, auth_1.requireAuth)();
50
+ const spinner = (0, ora_1.default)('Processing approval...').start();
51
+ try {
52
+ const result = await api_1.ApiClient.post(`/orgs/approve/${token}`, {}, authToken);
53
+ spinner.succeed(chalk_1.default.green('Approved!'));
54
+ console.log();
55
+ console.log(` ${chalk_1.default.dim('Command:')} ${chalk_1.default.white(result.command)}`);
56
+ console.log();
57
+ if (opts.run) {
58
+ console.log(chalk_1.default.dim(` Running: ${result.command}\n`));
59
+ (0, child_process_1.execSync)(result.command, { stdio: 'inherit' });
60
+ }
61
+ else {
62
+ console.log(chalk_1.default.dim(' Run it with: recall org run ' + result.name));
63
+ }
64
+ console.log();
65
+ }
66
+ catch (err) {
67
+ spinner.stop();
68
+ if (err instanceof api_1.ApiError) {
69
+ console.error(chalk_1.default.red(`\n ${err.message}\n`));
70
+ }
71
+ }
72
+ });
73
+ const approveList = new commander_1.Command('list')
74
+ .description('List pending approval requests in your org')
75
+ .action(async () => {
76
+ const token = await (0, auth_1.requireAuth)();
77
+ const spinner = (0, ora_1.default)('Fetching approvals...').start();
78
+ try {
79
+ const orgRes = await api_1.ApiClient.get('/orgs/me', token);
80
+ const org = orgRes.org;
81
+ const { approvals } = await api_1.ApiClient.get(`/orgs/${org.id}/approvals`, token);
82
+ spinner.stop();
83
+ if (!approvals.length) {
84
+ console.log(chalk_1.default.dim('\n No pending approval requests.\n'));
85
+ return;
86
+ }
87
+ console.log(chalk_1.default.bold(`\n Pending approvals — ${org.name}\n`));
88
+ for (const a of approvals) {
89
+ const time = new Date(a.createdAt).toLocaleString();
90
+ console.log(` ${chalk_1.default.yellow('●')} ` +
91
+ `${chalk_1.default.dim(time)} ` +
92
+ chalk_1.default.dim(`command: ${a.commandId}`));
93
+ console.log(` Approve with: ${chalk_1.default.cyan('recall approve grant ' + a.token)}`);
94
+ console.log();
95
+ }
96
+ }
97
+ catch (err) {
98
+ spinner.stop();
99
+ if (err instanceof api_1.ApiError) {
100
+ console.error(chalk_1.default.red(`\n ${err.message}\n`));
101
+ }
102
+ }
103
+ });
104
+ exports.approveCommand = new commander_1.Command('approve')
105
+ .description('Manage command approval requests (Team plan)')
106
+ .addCommand(approveRequest)
107
+ .addCommand(approveGrant)
108
+ .addCommand(approveList);
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.auditCommand = void 0;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const commander_1 = require("commander");
9
+ const ora_1 = __importDefault(require("ora"));
10
+ const api_1 = require("../lib/api");
11
+ const auth_1 = require("../lib/auth");
12
+ const ACTION_COLORS = {
13
+ approval_requested: 'yellow',
14
+ approval_granted: 'green',
15
+ require_approval_on: 'cyan',
16
+ require_approval_off: 'dim',
17
+ command_run: 'white',
18
+ member_joined: 'green',
19
+ member_removed: 'red',
20
+ };
21
+ exports.auditCommand = new commander_1.Command('audit')
22
+ .description('Show org audit log (Team plan)')
23
+ .option('-l, --limit <n>', 'Number of entries', '30')
24
+ .action(async (opts) => {
25
+ const token = await (0, auth_1.requireAuth)();
26
+ const spinner = (0, ora_1.default)('Fetching audit log...').start();
27
+ try {
28
+ const { user } = await api_1.ApiClient.get('/auth/me', token);
29
+ const orgRes = await api_1.ApiClient.get('/orgs/me', token);
30
+ const org = orgRes.org;
31
+ const { audit } = await api_1.ApiClient.get(`/orgs/${org.id}/audit?limit=${opts.limit}`, token);
32
+ spinner.stop();
33
+ if (!audit.length) {
34
+ console.log(chalk_1.default.dim('\n No audit events yet.\n'));
35
+ return;
36
+ }
37
+ console.log(chalk_1.default.bold(`\n Audit log — ${org.name}\n`));
38
+ for (const log of audit) {
39
+ const time = new Date(log.createdAt).toLocaleString();
40
+ const color = ACTION_COLORS[log.action] ?? 'white';
41
+ const action = chalk_1.default[color]?.(log.action) ?? log.action;
42
+ const username = chalk_1.default.cyan(log.username);
43
+ const details = log.details ? chalk_1.default.dim(` — ${log.details}`) : '';
44
+ console.log(` ${chalk_1.default.dim(time)} ${username} ${action}${details}`);
45
+ }
46
+ console.log();
47
+ }
48
+ catch (err) {
49
+ spinner.stop();
50
+ if (err instanceof api_1.ApiError && err.status === 403) {
51
+ console.log(chalk_1.default.yellow('\n Audit log requires a Team plan.'));
52
+ console.log(chalk_1.default.dim(' Run: recall upgrade\n'));
53
+ }
54
+ else {
55
+ console.error(chalk_1.default.red('\n Failed to fetch audit log\n'));
56
+ }
57
+ }
58
+ });
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.forgotCommand = void 0;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const commander_1 = require("commander");
9
+ const ora_1 = __importDefault(require("ora"));
10
+ const api_1 = require("../../lib/api");
11
+ const config_1 = require("../../lib/config");
12
+ exports.forgotCommand = new commander_1.Command('forgot')
13
+ .description('Request a password reset token')
14
+ .argument('<email>', 'Your account email')
15
+ .action(async (email) => {
16
+ const spinner = (0, ora_1.default)('Sending reset request...').start();
17
+ try {
18
+ await api_1.ApiClient.post('/auth/forgot-password', { email });
19
+ spinner.stop();
20
+ console.log(chalk_1.default.green('\n Reset request sent.\n'));
21
+ console.log(chalk_1.default.dim(' Check your email for the reset token.'));
22
+ console.log(chalk_1.default.dim(' Then run: recall auth reset <token>\n'));
23
+ // Dev hint — API logs token to console
24
+ const { apiUrl } = (0, config_1.getConfig)();
25
+ if (apiUrl.includes('127.0.0.1') || apiUrl.includes('localhost')) {
26
+ console.log(chalk_1.default.dim(' (Dev mode: check the API terminal for the token)\n'));
27
+ }
28
+ }
29
+ catch (err) {
30
+ spinner.fail(chalk_1.default.red('Request failed'));
31
+ console.error(chalk_1.default.dim(` ${err.message}\n`));
32
+ }
33
+ });
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.authCommand = void 0;
4
+ const commander_1 = require("commander");
5
+ const forgot_1 = require("./forgot");
6
+ const login_1 = require("./login");
7
+ const logout_1 = require("./logout");
8
+ const register_1 = require("./register");
9
+ const reset_1 = require("./reset");
10
+ exports.authCommand = new commander_1.Command('auth')
11
+ .description('Manage authentication')
12
+ .addCommand(register_1.registerCommand)
13
+ .addCommand(login_1.loginCommand)
14
+ .addCommand(logout_1.logoutCommand)
15
+ .addCommand(forgot_1.forgotCommand)
16
+ .addCommand(reset_1.resetCommand);
@@ -0,0 +1,151 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.loginCommand = void 0;
40
+ const chalk_1 = __importDefault(require("chalk"));
41
+ const commander_1 = require("commander");
42
+ const inquirer_1 = __importDefault(require("inquirer"));
43
+ const ora_1 = __importDefault(require("ora"));
44
+ const api_1 = require("../../lib/api");
45
+ const auth_1 = require("../../lib/auth");
46
+ const config_1 = require("../../lib/config");
47
+ async function loginWithCredentials() {
48
+ const answers = await inquirer_1.default.prompt([
49
+ {
50
+ type: 'input',
51
+ name: 'username',
52
+ message: 'Username:',
53
+ validate: (v) => v.length > 0 || 'Required',
54
+ },
55
+ {
56
+ type: 'password',
57
+ name: 'password',
58
+ message: 'Password:',
59
+ mask: '*',
60
+ validate: (v) => v.length > 0 || 'Required',
61
+ },
62
+ ]);
63
+ const spinner = (0, ora_1.default)('Authenticating...').start();
64
+ try {
65
+ const { token, username } = await api_1.ApiClient.post('/auth/login', {
66
+ username: answers.username,
67
+ password: answers.password,
68
+ });
69
+ await (0, auth_1.setToken)(token);
70
+ (0, config_1.saveConfig)({ username });
71
+ spinner.succeed(chalk_1.default.green('Authenticated successfully!'));
72
+ console.log(chalk_1.default.dim(`\n Logged in as ${chalk_1.default.white(username)}\n`));
73
+ }
74
+ catch (err) {
75
+ spinner.fail(chalk_1.default.red('Authentication failed'));
76
+ console.error(chalk_1.default.dim(` ${err.message}\n`));
77
+ process.exit(1);
78
+ }
79
+ }
80
+ async function loginWithBrowser() {
81
+ const http = require('http');
82
+ const crypto = require('crypto');
83
+ const open = (await Promise.resolve().then(() => __importStar(require('open')))).default;
84
+ const state = crypto.randomBytes(16).toString('hex');
85
+ const spinner = (0, ora_1.default)('Opening browser...').start();
86
+ const { token, username } = await new Promise((resolve, reject) => {
87
+ const server = http.createServer((req, res) => {
88
+ const url = new URL(req.url, 'http://localhost:9876');
89
+ if (url.pathname !== '/callback') {
90
+ res.end();
91
+ return;
92
+ }
93
+ const returnedState = url.searchParams.get('state');
94
+ const returnedToken = url.searchParams.get('token');
95
+ const returnedUser = url.searchParams.get('username') ?? '';
96
+ if (returnedState !== state || !returnedToken) {
97
+ res.writeHead(400);
98
+ res.end('Invalid callback.');
99
+ reject(new Error('Invalid state'));
100
+ return;
101
+ }
102
+ // Return simple response — browser already shows success UI
103
+ res.writeHead(200, { 'Content-Type': 'text/html' });
104
+ res.end(`<!DOCTYPE html>
105
+ <html>
106
+ <head><meta charset="utf-8"></head>
107
+ <body style="font-family:system-ui;max-width:480px;margin:80px auto;text-align:center;background:#080b0f;color:#e2e8f0;">
108
+ <h2 style="color:#4ade80;margin-bottom:12px;">✓ Authenticated!</h2>
109
+ <p style="color:#6b7e94;font-family:monospace;">You can close this tab and return to your terminal.</p>
110
+ </body>
111
+ </html>`);
112
+ server.close();
113
+ resolve({ token: returnedToken, username: returnedUser });
114
+ });
115
+ server.listen(9876, () => {
116
+ const authUrl = `https://recall.devkitvault.com/cli-login?state=${state}&redirect=http://localhost:9876/callback`;
117
+ open(authUrl);
118
+ spinner.text = 'Waiting for browser authentication...';
119
+ });
120
+ setTimeout(() => {
121
+ server.close();
122
+ reject(new Error('Timed out after 5 minutes'));
123
+ }, 5 * 60 * 1000);
124
+ });
125
+ await (0, auth_1.setToken)(token);
126
+ (0, config_1.saveConfig)({ username });
127
+ spinner.succeed(chalk_1.default.green('Authenticated successfully!'));
128
+ console.log(chalk_1.default.dim(`\n Logged in as ${chalk_1.default.white(username)}\n`));
129
+ }
130
+ exports.loginCommand = new commander_1.Command('login')
131
+ .description('Log in to recall')
132
+ .action(async () => {
133
+ console.log(chalk_1.default.bold('\n recall auth\n'));
134
+ const { method } = await inquirer_1.default.prompt([
135
+ {
136
+ type: 'list',
137
+ name: 'method',
138
+ message: 'How would you like to log in?',
139
+ choices: [
140
+ { name: 'Login with credentials (username + password)', value: 'credentials' },
141
+ { name: 'Login with browser (opens devkitvault.com)', value: 'browser' },
142
+ ],
143
+ },
144
+ ]);
145
+ if (method === 'credentials') {
146
+ await loginWithCredentials();
147
+ }
148
+ else {
149
+ await loginWithBrowser();
150
+ }
151
+ });
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.logoutCommand = void 0;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const commander_1 = require("commander");
9
+ const auth_1 = require("../../lib/auth");
10
+ exports.logoutCommand = new commander_1.Command('logout')
11
+ .description('Log out of recall')
12
+ .action(async () => {
13
+ await (0, auth_1.deleteToken)();
14
+ console.log(chalk_1.default.green('\n Logged out successfully.\n'));
15
+ });
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.registerCommand = void 0;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const commander_1 = require("commander");
9
+ const inquirer_1 = __importDefault(require("inquirer"));
10
+ const ora_1 = __importDefault(require("ora"));
11
+ const api_1 = require("../../lib/api");
12
+ const auth_1 = require("../../lib/auth");
13
+ const config_1 = require("../../lib/config");
14
+ exports.registerCommand = new commander_1.Command('register')
15
+ .description('Create a new recall account')
16
+ .action(async () => {
17
+ console.log(chalk_1.default.bold('\n Create your recall account\n'));
18
+ const answers = await inquirer_1.default.prompt([
19
+ {
20
+ type: 'input',
21
+ name: 'username',
22
+ message: 'Username:',
23
+ validate: (v) => {
24
+ if (v.length < 3)
25
+ return 'Username must be at least 3 characters';
26
+ if (v.length > 30)
27
+ return 'Username must be under 30 characters';
28
+ if (!/^[a-zA-Z0-9_-]+$/.test(v))
29
+ return 'Only letters, numbers, - and _ allowed';
30
+ return true;
31
+ },
32
+ },
33
+ {
34
+ type: 'input',
35
+ name: 'email',
36
+ message: 'Email:',
37
+ validate: (v) => {
38
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v))
39
+ return 'Enter a valid email';
40
+ return true;
41
+ },
42
+ },
43
+ {
44
+ type: 'password',
45
+ name: 'password',
46
+ message: 'Password:',
47
+ mask: '*',
48
+ validate: (v) => {
49
+ if (v.length < 8)
50
+ return 'Password must be at least 8 characters';
51
+ return true;
52
+ },
53
+ },
54
+ {
55
+ type: 'password',
56
+ name: 'confirm',
57
+ message: 'Confirm password:',
58
+ mask: '*',
59
+ validate: (v, answers) => {
60
+ if (v !== answers.password)
61
+ return 'Passwords do not match';
62
+ return true;
63
+ },
64
+ },
65
+ ]);
66
+ const spinner = (0, ora_1.default)('Creating account...').start();
67
+ try {
68
+ const { token, username } = await api_1.ApiClient.post('/auth/register', {
69
+ username: answers.username,
70
+ email: answers.email,
71
+ password: answers.password,
72
+ });
73
+ await (0, auth_1.setToken)(token);
74
+ (0, config_1.saveConfig)({ username });
75
+ spinner.succeed(chalk_1.default.green('Account created successfully!'));
76
+ console.log(chalk_1.default.dim(`\n Logged in as ${chalk_1.default.white(username)}`));
77
+ console.log(chalk_1.default.dim(` Plan: ${chalk_1.default.white('free')}\n`));
78
+ }
79
+ catch (err) {
80
+ spinner.fail(chalk_1.default.red('Registration failed'));
81
+ console.error(chalk_1.default.dim(` ${err.message}\n`));
82
+ process.exit(1);
83
+ }
84
+ });
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.resetCommand = void 0;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const commander_1 = require("commander");
9
+ const inquirer_1 = __importDefault(require("inquirer"));
10
+ const ora_1 = __importDefault(require("ora"));
11
+ const api_1 = require("../../lib/api");
12
+ exports.resetCommand = new commander_1.Command('reset')
13
+ .description('Reset your password using a reset token')
14
+ .argument('<token>', 'Reset token from email')
15
+ .action(async (token) => {
16
+ console.log(chalk_1.default.bold('\n Reset your password\n'));
17
+ const { newPassword, confirm } = await inquirer_1.default.prompt([
18
+ {
19
+ type: 'password',
20
+ name: 'newPassword',
21
+ message: 'New password:',
22
+ mask: '*',
23
+ validate: (v) => v.length >= 8 || 'Password must be at least 8 characters',
24
+ },
25
+ {
26
+ type: 'password',
27
+ name: 'confirm',
28
+ message: 'Confirm new password:',
29
+ mask: '*',
30
+ validate: (v, answers) => v === answers.newPassword || 'Passwords do not match',
31
+ },
32
+ ]);
33
+ const spinner = (0, ora_1.default)('Resetting password...').start();
34
+ try {
35
+ await api_1.ApiClient.post('/auth/reset-password', { token, newPassword });
36
+ spinner.succeed(chalk_1.default.green('Password reset successfully!'));
37
+ console.log(chalk_1.default.dim('\n Run: recall auth login\n'));
38
+ }
39
+ catch (err) {
40
+ spinner.fail(chalk_1.default.red('Reset failed'));
41
+ console.error(chalk_1.default.dim(` ${err.message}\n`));
42
+ }
43
+ });