@devkitvault/recall 1.0.5 → 1.2.0

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/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) devkitvault
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) devkitvault
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,81 +1,93 @@
1
- # @devkitvault/recall
2
-
3
- **recall** is a cloud-synced CLI command vault save terminal commands once, search and run them from any machine.
4
-
5
- Works with [recall.devkitvault.com](https://recall.devkitvault.com) and the [VS Code extension](https://marketplace.visualstudio.com/items?itemName=devkitvault.recall-cmd).
6
-
7
- ## Requirements
8
-
9
- - [Node.js](https://nodejs.org/) **18 or newer**
10
-
11
- ## Install
12
-
13
- ```sh
14
- npm install -g @devkitvault/recall
15
- ```
16
-
17
- Verify the install:
18
-
19
- ```sh
20
- recall --version
21
- recall doctor
22
- ```
23
-
24
- ## Quick start
25
-
26
- ```sh
27
- recall auth login
28
- recall save "docker compose up -d" --name start-stack --tags docker
29
- recall search docker
30
- recall list
31
- recall run start-stack
32
- ```
33
-
34
- Run `recall --help` for the full command list.
35
-
36
- ## Authentication
37
-
38
- After `recall auth login`, your session is stored locally so you stay signed in between runs.
39
-
40
- ```sh
41
- recall auth login # sign in (browser or email/password)
42
- recall auth logout # sign out on this machine
43
- recall auth whoami # show the signed-in account
44
- ```
45
-
46
- ## Prefer a standalone binary?
47
-
48
- If you do not want Node.js, use the native installer instead — same commands, no runtime required:
49
-
50
- - **macOS / Linux:** `curl -fsSL https://devkitvault.com/recall/install.sh | sh`
51
- - **Windows:** `irm https://devkitvault.com/recall/install.ps1 | iex`
52
-
53
- See [devkitvault.com/recall](https://devkitvault.com/recall) for downloads and docs.
54
-
55
- ## Update
56
-
57
- ```sh
58
- npm update -g @devkitvault/recall
59
- ```
60
-
61
- ## Troubleshooting
62
-
63
- ```sh
64
- recall doctor # check install, auth, and API connectivity
65
- recall auth whoami # confirm you are signed in
66
- ```
67
-
68
- If something still fails, open an issue on [GitHub](https://github.com/devkitvault/recall/issues) or visit [recall.devkitvault.com](https://recall.devkitvault.com).
69
-
70
- ## Links
71
-
72
- | | |
73
- | --------- | ----------------------------------------------------------------------------------------- |
74
- | Website | [devkitvault.com/recall](https://devkitvault.com) |
75
- | Dashboard | [recall.devkitvault.com](https://recall.devkitvault.com) |
76
- | VS Code | [Marketplace](https://marketplace.visualstudio.com/items?itemName=devkitvault.recall-cmd) |
77
- | Issues | [github.com/devkitvault/recall](https://github.com/devkitvault/recall/issues) |
78
-
79
- ## License
80
-
81
- MIT — see [LICENSE](./LICENSE). © [devkitvault](https://devkitvault.com)
1
+ # @devkitvault/recall
2
+
3
+ **recall** is an AI terminal copilot with a personal command vault. Ask in English. It prints a command. It does not run it. Saved commands can answer first so you do not spend an AI request.
4
+
5
+ Works with [recall.devkitvault.com](https://recall.devkitvault.com) and the [VS Code extension](https://marketplace.visualstudio.com/items?itemName=devkitvault.recall-cmd).
6
+
7
+ ## Requirements
8
+
9
+ - [Node.js](https://nodejs.org/) **18 or newer**
10
+
11
+ ## Install
12
+
13
+ ```sh
14
+ npm install -g @devkitvault/recall
15
+ ```
16
+
17
+ `rec` is the same command as `recall`.
18
+
19
+ ```sh
20
+ recall --version
21
+ recall doctor
22
+ ```
23
+
24
+ ## Quick start
25
+
26
+ ```sh
27
+ recall auth login
28
+ recall "find the 10 biggest files in this directory"
29
+ rec "that docker command from last week"
30
+ recall
31
+ ```
32
+
33
+ No-args `recall` opens an Ask session (`Recall >`). `/save [name]` stores the last suggestion. `/exit` leaves.
34
+
35
+ ```sh
36
+ recall playbook save deploy "docker compose up -d" "pnpm migrate"
37
+ recall playbook run deploy --dry-run
38
+ ```
39
+
40
+ `recall playbook run` executes steps. Ask never does.
41
+
42
+ Ask prints a suggestion (`source` is `vault`, `cache`, or `llm`). Copy or save it yourself.
43
+
44
+ ```sh
45
+ recall save "docker compose up -d" --name docker --tags docker
46
+ recall run docker
47
+ ```
48
+
49
+ Run `recall --help` for the full command list. Free plans include **20 AI asks / month**; Pro includes **500**.
50
+
51
+ ## Authentication
52
+
53
+ ```sh
54
+ recall auth login
55
+ recall auth logout
56
+ recall whoami
57
+ ```
58
+
59
+ ## Prefer a standalone binary?
60
+
61
+ - **macOS / Linux:** `curl -fsSL https://devkitvault.com/recall/install.sh | sh`
62
+ - **Windows:** `irm https://devkitvault.com/recall/install.ps1 | iex`
63
+
64
+ See [recall.devkitvault.com/docs/install](https://recall.devkitvault.com/docs/install).
65
+
66
+ ## Update
67
+
68
+ ```sh
69
+ npm update -g @devkitvault/recall
70
+ ```
71
+
72
+ ## Troubleshooting
73
+
74
+ ```sh
75
+ recall doctor
76
+ recall whoami
77
+ ```
78
+
79
+ If something still fails, open an issue on [GitHub](https://github.com/devkitvault/recall/issues) or visit [recall.devkitvault.com](https://recall.devkitvault.com).
80
+
81
+ ## Links
82
+
83
+ | | |
84
+ | --------- | ----------------------------------------------------------------------------------------- |
85
+ | Website | [devkitvault.com/recall](https://devkitvault.com) |
86
+ | Docs | [recall.devkitvault.com/docs](https://recall.devkitvault.com/docs) |
87
+ | Dashboard | [recall.devkitvault.com](https://recall.devkitvault.com) |
88
+ | VS Code | [Marketplace](https://marketplace.visualstudio.com/items?itemName=devkitvault.recall-cmd) |
89
+ | Issues | [github.com/devkitvault/recall](https://github.com/devkitvault/recall/issues) |
90
+
91
+ ## License
92
+
93
+ MIT — see [LICENSE](./LICENSE). © [devkitvault](https://devkitvault.com)
@@ -0,0 +1,100 @@
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.askCommand = void 0;
7
+ exports.runAsk = runAsk;
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const commander_1 = require("commander");
10
+ const inquirer_1 = __importDefault(require("inquirer"));
11
+ const ora_1 = __importDefault(require("ora"));
12
+ const api_1 = require("../lib/api");
13
+ const auth_1 = require("../lib/auth");
14
+ const events_1 = require("../lib/events");
15
+ const runtime_1 = require("../lib/runtime");
16
+ function riskColor(risk) {
17
+ if (risk === 'DANGEROUS')
18
+ return chalk_1.default.red(risk);
19
+ if (risk === 'CAUTION')
20
+ return chalk_1.default.yellow(risk);
21
+ return chalk_1.default.green(risk);
22
+ }
23
+ async function runAsk(query) {
24
+ const token = await (0, auth_1.requireAuth)();
25
+ const runtime = (0, runtime_1.detectRuntime)();
26
+ const spinner = (0, ora_1.default)('Looking up...').start();
27
+ try {
28
+ const result = await api_1.ApiClient.post('/ai/resolve', {
29
+ query,
30
+ os: runtime.os,
31
+ shell: runtime.shell,
32
+ mode: 'ask',
33
+ }, token);
34
+ spinner.stop();
35
+ console.log();
36
+ console.log(` ${chalk_1.default.white(result.command)}`);
37
+ if (result.explanation) {
38
+ console.log(` ${chalk_1.default.dim(result.explanation)}`);
39
+ }
40
+ console.log();
41
+ console.log(` ${chalk_1.default.dim('shell:')} ${result.shell} ${chalk_1.default.dim('risk:')} ${riskColor(result.risk)} ${chalk_1.default.dim('source:')} ${result.source}`);
42
+ if (result.source === 'vault') {
43
+ console.log(chalk_1.default.dim(' from your vault'));
44
+ }
45
+ if (result.remaining !== null) {
46
+ console.log(` ${chalk_1.default.dim(`ai left this month: ${result.remaining}`)}`);
47
+ }
48
+ if (result.alternatives?.length) {
49
+ console.log();
50
+ for (const alt of result.alternatives) {
51
+ console.log(` ${chalk_1.default.dim('alt')} ${alt}`);
52
+ }
53
+ }
54
+ console.log();
55
+ console.log(chalk_1.default.dim(' Ask mode does not run commands. Copy or save this yourself.'));
56
+ console.log();
57
+ if (result.source === 'vault') {
58
+ await (0, events_1.track)('ai_accepted', { source: result.source, risk: result.risk, saved: false });
59
+ return;
60
+ }
61
+ const { save } = await inquirer_1.default.prompt([{
62
+ type: 'confirm',
63
+ name: 'save',
64
+ message: 'Save this command?',
65
+ default: false,
66
+ }]);
67
+ if (!save)
68
+ return;
69
+ const { name } = await inquirer_1.default.prompt([{
70
+ type: 'input',
71
+ name: 'name',
72
+ message: 'Name (optional):',
73
+ }]);
74
+ await api_1.ApiClient.post('/commands', {
75
+ command: result.command,
76
+ name: name?.trim() || undefined,
77
+ }, token);
78
+ await (0, events_1.track)('ai_accepted', { source: result.source, risk: result.risk, saved: true });
79
+ console.log(chalk_1.default.green('\n Saved.\n'));
80
+ }
81
+ catch (err) {
82
+ spinner.stop();
83
+ if (err instanceof api_1.ApiError && err.status === 403 && err.body?.upgrade) {
84
+ console.log(chalk_1.default.yellow(`\n ${err.message}`));
85
+ console.log(chalk_1.default.dim(` Upgrade at ${chalk_1.default.white(err.body.upgrade)}\n`));
86
+ return;
87
+ }
88
+ if (err instanceof api_1.ApiError) {
89
+ console.error(chalk_1.default.red(`\n ${err.message}\n`));
90
+ return;
91
+ }
92
+ console.error(chalk_1.default.red('\n Failed to resolve that request.\n'));
93
+ }
94
+ }
95
+ exports.askCommand = new commander_1.Command('ask')
96
+ .description('Suggest a command from natural language (does not run it)')
97
+ .argument('<query...>', 'What you want to do')
98
+ .action(async (parts) => {
99
+ await runAsk(parts.join(' '));
100
+ });
@@ -70,6 +70,8 @@ async function loginWithCredentials() {
70
70
  (0, config_1.saveConfig)({ username });
71
71
  spinner.succeed(chalk_1.default.green('Authenticated successfully!'));
72
72
  console.log(chalk_1.default.dim(`\n Logged in as ${chalk_1.default.white(username)}\n`));
73
+ const { track } = await Promise.resolve().then(() => __importStar(require('../../lib/events')));
74
+ await track('cli_login', { method: 'credentials' });
73
75
  }
74
76
  catch (err) {
75
77
  spinner.fail(chalk_1.default.red('Authentication failed'));
@@ -115,13 +117,13 @@ async function loginWithBrowser() {
115
117
  }
116
118
  // Loopback callback: tell the browser to close the socket so server.close() can finish promptly.
117
119
  res.writeHead(200, { 'Content-Type': 'text/html', ...closeHeaders });
118
- res.end(`<!DOCTYPE html>
119
- <html>
120
- <head><meta charset="utf-8"></head>
121
- <body style="font-family:system-ui;max-width:480px;margin:80px auto;text-align:center;background:#080b0f;color:#e2e8f0;">
122
- <h2 style="color:#4ade80;margin-bottom:12px;">✓ Authenticated!</h2>
123
- <p style="color:#6b7e94;font-family:monospace;">You can close this tab and return to your terminal.</p>
124
- </body>
120
+ res.end(`<!DOCTYPE html>
121
+ <html>
122
+ <head><meta charset="utf-8"></head>
123
+ <body style="font-family:system-ui;max-width:480px;margin:80px auto;text-align:center;background:#080b0f;color:#e2e8f0;">
124
+ <h2 style="color:#4ade80;margin-bottom:12px;">✓ Authenticated!</h2>
125
+ <p style="color:#6b7e94;font-family:monospace;">You can close this tab and return to your terminal.</p>
126
+ </body>
125
127
  </html>`);
126
128
  const srv = server;
127
129
  srv.closeAllConnections?.();
@@ -145,6 +147,8 @@ async function loginWithBrowser() {
145
147
  (0, config_1.saveConfig)({ username });
146
148
  spinner.succeed(chalk_1.default.green('Authenticated successfully!'));
147
149
  console.log(chalk_1.default.dim(`\n Logged in as ${chalk_1.default.white(username)}\n`));
150
+ const { track } = await Promise.resolve().then(() => __importStar(require('../../lib/events')));
151
+ await track('cli_login', { method: 'browser' });
148
152
  }
149
153
  exports.loginCommand = new commander_1.Command('login')
150
154
  .description('Log in to recall')
@@ -11,7 +11,7 @@ const ALL_COMMANDS = [
11
11
  'export', 'import', 'sync', 'whoami', 'upgrade', 'config',
12
12
  'pin', 'alias', 'history', 'share', 'snippet', 'env',
13
13
  'group', 'org', 'audit', 'approve', 'template', 'doctor',
14
- 'completion',
14
+ 'completion', 'ask',
15
15
  ];
16
16
  const AUTH_CMDS = ['register', 'login', 'logout', 'forgot', 'reset'];
17
17
  const GROUP_CMDS = ['create', 'list', 'show', 'add', 'remove', 'delete', 'rename'];
@@ -24,109 +24,109 @@ const TEMPLATE_CMDS = ['save', 'list', 'show', 'run', 'delete'];
24
24
  const APPROVE_CMDS = ['request', 'grant', 'list'];
25
25
  const CONFIG_CMDS = ['show', 'set', 'switch', 'reset'];
26
26
  function bashCompletion() {
27
- return `# recall bash completion
28
- # Add this to ~/.bashrc or ~/.bash_profile:
29
- # eval "$(recall completion bash)"
30
-
31
- _recall_completion() {
32
- local cur prev words
33
- COMPREPLY=()
34
- cur="\${COMP_WORDS[COMP_CWORD]}"
35
- prev="\${COMP_WORDS[COMP_CWORD-1]}"
36
- words=("\${COMP_WORDS[@]}")
37
-
38
- local commands="${ALL_COMMANDS.join(' ')}"
39
-
40
- case "\${words[1]}" in
41
- auth) COMPREPLY=($(compgen -W "${AUTH_CMDS.join(' ')}" -- "$cur")) ;;
42
- group) COMPREPLY=($(compgen -W "${GROUP_CMDS.join(' ')}" -- "$cur")) ;;
43
- org) COMPREPLY=($(compgen -W "${ORG_CMDS.join(' ')}" -- "$cur")) ;;
44
- alias) COMPREPLY=($(compgen -W "${ALIAS_CMDS.join(' ')}" -- "$cur")) ;;
45
- share) COMPREPLY=($(compgen -W "${SHARE_CMDS.join(' ')}" -- "$cur")) ;;
46
- snippet) COMPREPLY=($(compgen -W "${SNIPPET_CMDS.join(' ')}" -- "$cur")) ;;
47
- env) COMPREPLY=($(compgen -W "${ENV_CMDS.join(' ')}" -- "$cur")) ;;
48
- template) COMPREPLY=($(compgen -W "${TEMPLATE_CMDS.join(' ')}" -- "$cur")) ;;
49
- approve) COMPREPLY=($(compgen -W "${APPROVE_CMDS.join(' ')}" -- "$cur")) ;;
50
- config) COMPREPLY=($(compgen -W "${CONFIG_CMDS.join(' ')}" -- "$cur")) ;;
51
- *) COMPREPLY=($(compgen -W "$commands" -- "$cur")) ;;
52
- esac
53
-
54
- return 0
55
- }
56
-
27
+ return `# recall bash completion
28
+ # Add this to ~/.bashrc or ~/.bash_profile:
29
+ # eval "$(recall completion bash)"
30
+
31
+ _recall_completion() {
32
+ local cur prev words
33
+ COMPREPLY=()
34
+ cur="\${COMP_WORDS[COMP_CWORD]}"
35
+ prev="\${COMP_WORDS[COMP_CWORD-1]}"
36
+ words=("\${COMP_WORDS[@]}")
37
+
38
+ local commands="${ALL_COMMANDS.join(' ')}"
39
+
40
+ case "\${words[1]}" in
41
+ auth) COMPREPLY=($(compgen -W "${AUTH_CMDS.join(' ')}" -- "$cur")) ;;
42
+ group) COMPREPLY=($(compgen -W "${GROUP_CMDS.join(' ')}" -- "$cur")) ;;
43
+ org) COMPREPLY=($(compgen -W "${ORG_CMDS.join(' ')}" -- "$cur")) ;;
44
+ alias) COMPREPLY=($(compgen -W "${ALIAS_CMDS.join(' ')}" -- "$cur")) ;;
45
+ share) COMPREPLY=($(compgen -W "${SHARE_CMDS.join(' ')}" -- "$cur")) ;;
46
+ snippet) COMPREPLY=($(compgen -W "${SNIPPET_CMDS.join(' ')}" -- "$cur")) ;;
47
+ env) COMPREPLY=($(compgen -W "${ENV_CMDS.join(' ')}" -- "$cur")) ;;
48
+ template) COMPREPLY=($(compgen -W "${TEMPLATE_CMDS.join(' ')}" -- "$cur")) ;;
49
+ approve) COMPREPLY=($(compgen -W "${APPROVE_CMDS.join(' ')}" -- "$cur")) ;;
50
+ config) COMPREPLY=($(compgen -W "${CONFIG_CMDS.join(' ')}" -- "$cur")) ;;
51
+ *) COMPREPLY=($(compgen -W "$commands" -- "$cur")) ;;
52
+ esac
53
+
54
+ return 0
55
+ }
56
+
57
57
  complete -F _recall_completion recall`;
58
58
  }
59
59
  function zshCompletion() {
60
- return `# recall zsh completion
61
- # Add this to ~/.zshrc:
62
- # eval "$(recall completion zsh)"
63
-
64
- _recall() {
65
- local state
66
-
67
- _arguments \\
68
- '1: :->command' \\
69
- '*: :->args'
70
-
71
- case $state in
72
- command)
73
- local commands=(${ALL_COMMANDS.map(c => `'${c}'`).join(' ')})
74
- _describe 'command' commands
75
- ;;
76
- args)
77
- case $words[2] in
78
- auth) local sub=(${AUTH_CMDS.map(c => `'${c}'`).join(' ')}) && _describe 'subcommand' sub ;;
79
- group) local sub=(${GROUP_CMDS.map(c => `'${c}'`).join(' ')}) && _describe 'subcommand' sub ;;
80
- org) local sub=(${ORG_CMDS.map(c => `'${c}'`).join(' ')}) && _describe 'subcommand' sub ;;
81
- alias) local sub=(${ALIAS_CMDS.map(c => `'${c}'`).join(' ')}) && _describe 'subcommand' sub ;;
82
- share) local sub=(${SHARE_CMDS.map(c => `'${c}'`).join(' ')}) && _describe 'subcommand' sub ;;
83
- snippet) local sub=(${SNIPPET_CMDS.map(c => `'${c}'`).join(' ')}) && _describe 'subcommand' sub ;;
84
- env) local sub=(${ENV_CMDS.map(c => `'${c}'`).join(' ')}) && _describe 'subcommand' sub ;;
85
- template) local sub=(${TEMPLATE_CMDS.map(c => `'${c}'`).join(' ')}) && _describe 'subcommand' sub ;;
86
- approve) local sub=(${APPROVE_CMDS.map(c => `'${c}'`).join(' ')}) && _describe 'subcommand' sub ;;
87
- config) local sub=(${CONFIG_CMDS.map(c => `'${c}'`).join(' ')}) && _describe 'subcommand' sub ;;
88
- esac
89
- ;;
90
- esac
91
- }
92
-
60
+ return `# recall zsh completion
61
+ # Add this to ~/.zshrc:
62
+ # eval "$(recall completion zsh)"
63
+
64
+ _recall() {
65
+ local state
66
+
67
+ _arguments \\
68
+ '1: :->command' \\
69
+ '*: :->args'
70
+
71
+ case $state in
72
+ command)
73
+ local commands=(${ALL_COMMANDS.map(c => `'${c}'`).join(' ')})
74
+ _describe 'command' commands
75
+ ;;
76
+ args)
77
+ case $words[2] in
78
+ auth) local sub=(${AUTH_CMDS.map(c => `'${c}'`).join(' ')}) && _describe 'subcommand' sub ;;
79
+ group) local sub=(${GROUP_CMDS.map(c => `'${c}'`).join(' ')}) && _describe 'subcommand' sub ;;
80
+ org) local sub=(${ORG_CMDS.map(c => `'${c}'`).join(' ')}) && _describe 'subcommand' sub ;;
81
+ alias) local sub=(${ALIAS_CMDS.map(c => `'${c}'`).join(' ')}) && _describe 'subcommand' sub ;;
82
+ share) local sub=(${SHARE_CMDS.map(c => `'${c}'`).join(' ')}) && _describe 'subcommand' sub ;;
83
+ snippet) local sub=(${SNIPPET_CMDS.map(c => `'${c}'`).join(' ')}) && _describe 'subcommand' sub ;;
84
+ env) local sub=(${ENV_CMDS.map(c => `'${c}'`).join(' ')}) && _describe 'subcommand' sub ;;
85
+ template) local sub=(${TEMPLATE_CMDS.map(c => `'${c}'`).join(' ')}) && _describe 'subcommand' sub ;;
86
+ approve) local sub=(${APPROVE_CMDS.map(c => `'${c}'`).join(' ')}) && _describe 'subcommand' sub ;;
87
+ config) local sub=(${CONFIG_CMDS.map(c => `'${c}'`).join(' ')}) && _describe 'subcommand' sub ;;
88
+ esac
89
+ ;;
90
+ esac
91
+ }
92
+
93
93
  compdef _recall recall`;
94
94
  }
95
95
  function psCompletion() {
96
- return `# recall PowerShell completion
97
- # Add this to your PowerShell profile ($PROFILE):
98
- # recall completion ps | Out-String | Invoke-Expression
99
-
100
- Register-ArgumentCompleter -Native -CommandName recall -ScriptBlock {
101
- param($wordToComplete, $commandAst, $cursorPosition)
102
-
103
- $commands = @(${ALL_COMMANDS.map(c => `'${c}'`).join(', ')})
104
- $tokens = $commandAst.CommandElements
105
-
106
- $subCommands = @{
107
- 'auth' = @(${AUTH_CMDS.map(c => `'${c}'`).join(', ')})
108
- 'group' = @(${GROUP_CMDS.map(c => `'${c}'`).join(', ')})
109
- 'org' = @(${ORG_CMDS.map(c => `'${c}'`).join(', ')})
110
- 'alias' = @(${ALIAS_CMDS.map(c => `'${c}'`).join(', ')})
111
- 'share' = @(${SHARE_CMDS.map(c => `'${c}'`).join(', ')})
112
- 'snippet' = @(${SNIPPET_CMDS.map(c => `'${c}'`).join(', ')})
113
- 'env' = @(${ENV_CMDS.map(c => `'${c}'`).join(', ')})
114
- 'template' = @(${TEMPLATE_CMDS.map(c => `'${c}'`).join(', ')})
115
- 'approve' = @(${APPROVE_CMDS.map(c => `'${c}'`).join(', ')})
116
- 'config' = @(${CONFIG_CMDS.map(c => `'${c}'`).join(', ')})
117
- }
118
-
119
- if ($tokens.Count -ge 2) {
120
- $mainCmd = $tokens[1].Value
121
- if ($subCommands.ContainsKey($mainCmd)) {
122
- $subCommands[$mainCmd] | Where-Object { $_ -like "$wordToComplete*" } |
123
- ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }
124
- return
125
- }
126
- }
127
-
128
- $commands | Where-Object { $_ -like "$wordToComplete*" } |
129
- ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }
96
+ return `# recall PowerShell completion
97
+ # Add this to your PowerShell profile ($PROFILE):
98
+ # recall completion ps | Out-String | Invoke-Expression
99
+
100
+ Register-ArgumentCompleter -Native -CommandName recall -ScriptBlock {
101
+ param($wordToComplete, $commandAst, $cursorPosition)
102
+
103
+ $commands = @(${ALL_COMMANDS.map(c => `'${c}'`).join(', ')})
104
+ $tokens = $commandAst.CommandElements
105
+
106
+ $subCommands = @{
107
+ 'auth' = @(${AUTH_CMDS.map(c => `'${c}'`).join(', ')})
108
+ 'group' = @(${GROUP_CMDS.map(c => `'${c}'`).join(', ')})
109
+ 'org' = @(${ORG_CMDS.map(c => `'${c}'`).join(', ')})
110
+ 'alias' = @(${ALIAS_CMDS.map(c => `'${c}'`).join(', ')})
111
+ 'share' = @(${SHARE_CMDS.map(c => `'${c}'`).join(', ')})
112
+ 'snippet' = @(${SNIPPET_CMDS.map(c => `'${c}'`).join(', ')})
113
+ 'env' = @(${ENV_CMDS.map(c => `'${c}'`).join(', ')})
114
+ 'template' = @(${TEMPLATE_CMDS.map(c => `'${c}'`).join(', ')})
115
+ 'approve' = @(${APPROVE_CMDS.map(c => `'${c}'`).join(', ')})
116
+ 'config' = @(${CONFIG_CMDS.map(c => `'${c}'`).join(', ')})
117
+ }
118
+
119
+ if ($tokens.Count -ge 2) {
120
+ $mainCmd = $tokens[1].Value
121
+ if ($subCommands.ContainsKey($mainCmd)) {
122
+ $subCommands[$mainCmd] | Where-Object { $_ -like "$wordToComplete*" } |
123
+ ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }
124
+ return
125
+ }
126
+ }
127
+
128
+ $commands | Where-Object { $_ -like "$wordToComplete*" } |
129
+ ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }
130
130
  }`;
131
131
  }
132
132
  exports.completionCommand = new commander_1.Command('completion')
@@ -16,7 +16,8 @@ const PLANS = {
16
16
  label: 'Free',
17
17
  price: '$0/month',
18
18
  features: [
19
- 'Unlimited command saves',
19
+ '20 AI asks / month',
20
+ '100 command saves',
20
21
  'Search and filter',
21
22
  'Export as JSON or shell script',
22
23
  'Cross-machine sync',
@@ -24,22 +25,21 @@ const PLANS = {
24
25
  },
25
26
  pro: {
26
27
  label: 'Pro',
27
- price: '$9/month',
28
+ price: '$6/month',
28
29
  features: [
29
- 'Everything in Free',
30
+ '500 AI asks / month',
31
+ 'Unlimited commands',
30
32
  'Command groups',
31
33
  'Share commands via link',
32
- 'Priority support',
33
34
  ],
34
35
  },
35
36
  team: {
36
37
  label: 'Team',
37
- price: '$19/month per seat',
38
+ price: '$12/month per seat',
38
39
  features: [
39
40
  'Everything in Pro',
40
41
  'Org-level vaults',
41
42
  'Role-based access',
42
- 'SSO / SAML',
43
43
  'Audit log',
44
44
  ],
45
45
  },
@@ -81,12 +81,12 @@ exports.upgradeCommand = new commander_1.Command('upgrade')
81
81
  }
82
82
  const upgradeTo = currentPlan === 'free'
83
83
  ? [
84
- { name: 'Upgrade to Pro — $9/month', value: 'pro' },
85
- { name: 'Upgrade to Team — $19/month per seat', value: 'team' },
84
+ { name: 'Upgrade to Pro — $6/month', value: 'pro' },
85
+ { name: 'Upgrade to Team — $12/month per seat', value: 'team' },
86
86
  { name: 'Maybe later', value: 'exit' },
87
87
  ]
88
88
  : [
89
- { name: 'Upgrade to Team — $19/month per seat', value: 'team' },
89
+ { name: 'Upgrade to Team — $12/month per seat', value: 'team' },
90
90
  { name: 'Maybe later', value: 'exit' },
91
91
  ];
92
92
  const { choice } = await inquirer_1.default.prompt([
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@ const chalk_1 = __importDefault(require("chalk"));
8
8
  const commander_1 = require("commander");
9
9
  const alias_1 = require("./commands/alias");
10
10
  const approve_1 = require("./commands/approve");
11
+ const ask_1 = require("./commands/ask");
11
12
  const audit_1 = require("./commands/audit");
12
13
  const index_1 = require("./commands/auth/index");
13
14
  const completion_1 = require("./commands/completion");
@@ -35,11 +36,26 @@ const update_1 = require("./commands/update");
35
36
  const upgrade_1 = require("./commands/upgrade");
36
37
  const whoami_1 = require("./commands/whoami");
37
38
  const config_2 = require("./lib/config");
39
+ const events_1 = require("./lib/events");
40
+ const KNOWN = new Set([
41
+ 'auth', 'save', 'list', 'run', 'delete', 'search', 'update',
42
+ 'export', 'import', 'whoami', 'upgrade', 'sync', 'group', 'org',
43
+ 'config', 'pin', 'alias', 'history', 'share', 'snippet', 'env',
44
+ 'audit', 'approve', 'template', 'doctor', 'completion', 'feedback',
45
+ 'support', 'ask', 'help',
46
+ ]);
47
+ const rawArgs = process.argv.slice(2);
48
+ if (rawArgs.length > 0
49
+ && !rawArgs[0].startsWith('-')
50
+ && !KNOWN.has(rawArgs[0])) {
51
+ process.argv = [process.argv[0], process.argv[1], 'ask', ...rawArgs];
52
+ }
38
53
  const program = new commander_1.Command();
39
54
  program
40
55
  .name('recall')
41
- .description(chalk_1.default.cyan('Save and retrieve your CLI commands from anywhere'))
56
+ .description(chalk_1.default.cyan('Your terminal remembers how you work'))
42
57
  .version(config_2.APP_VERSION);
58
+ program.addCommand(ask_1.askCommand);
43
59
  program.addCommand(index_1.authCommand);
44
60
  program.addCommand(save_1.saveCommand);
45
61
  program.addCommand(list_1.listCommand);
@@ -68,4 +84,12 @@ program.addCommand(doctor_1.doctorCommand);
68
84
  program.addCommand(completion_1.completionCommand);
69
85
  program.addCommand(feedback_1.feedbackCommand);
70
86
  program.addCommand(support_1.supportCommand);
71
- program.parse();
87
+ async function main() {
88
+ void (0, events_1.trackInstalledOnce)();
89
+ await program.parseAsync();
90
+ }
91
+ main().catch((err) => {
92
+ const message = err instanceof Error ? err.message : 'Unknown error';
93
+ console.error(chalk_1.default.red(`\n ${message}\n`));
94
+ process.exit(1);
95
+ });
package/dist/lib/auth.js CHANGED
@@ -40,42 +40,81 @@ exports.getToken = getToken;
40
40
  exports.setToken = setToken;
41
41
  exports.deleteToken = deleteToken;
42
42
  exports.requireAuth = requireAuth;
43
+ const node_fs_1 = __importDefault(require("node:fs"));
44
+ const node_os_1 = __importDefault(require("node:os"));
45
+ const node_path_1 = __importDefault(require("node:path"));
43
46
  const chalk_1 = __importDefault(require("chalk"));
44
- const TOKEN_FILE = require('path').join(require('os').homedir(), '.recall', 'token');
45
- async function getToken() {
47
+ const TOKEN_FILE = node_path_1.default.join(node_os_1.default.homedir(), '.recall', 'token');
48
+ async function loadKeytar() {
49
+ try {
50
+ const loaded = await Promise.resolve().then(() => __importStar(require('keytar')));
51
+ const api = loaded.default ?? loaded;
52
+ if (typeof api.getPassword !== 'function' || typeof api.setPassword !== 'function') {
53
+ return null;
54
+ }
55
+ return api;
56
+ }
57
+ catch {
58
+ return null;
59
+ }
60
+ }
61
+ function readTokenFile() {
46
62
  try {
47
- const keytar = await Promise.resolve().then(() => __importStar(require('keytar')));
48
- return keytar.getPassword('recall', 'token');
63
+ if (!node_fs_1.default.existsSync(TOKEN_FILE))
64
+ return null;
65
+ const token = node_fs_1.default.readFileSync(TOKEN_FILE, 'utf-8').trim();
66
+ return token || null;
49
67
  }
50
68
  catch {
51
- // fallback to file if keytar not available
52
- const fs = require('fs');
53
- if (fs.existsSync(TOKEN_FILE))
54
- return fs.readFileSync(TOKEN_FILE, 'utf-8').trim();
55
69
  return null;
56
70
  }
57
71
  }
72
+ function writeTokenFile(token) {
73
+ node_fs_1.default.mkdirSync(node_path_1.default.dirname(TOKEN_FILE), { recursive: true });
74
+ node_fs_1.default.writeFileSync(TOKEN_FILE, token, { mode: 0o600 });
75
+ }
76
+ async function getToken() {
77
+ const keytar = await loadKeytar();
78
+ if (keytar) {
79
+ try {
80
+ const fromKeytar = await keytar.getPassword('recall', 'token');
81
+ if (fromKeytar)
82
+ return fromKeytar;
83
+ }
84
+ catch {
85
+ // fall through to file
86
+ }
87
+ }
88
+ return readTokenFile();
89
+ }
58
90
  async function setToken(token) {
91
+ writeTokenFile(token);
92
+ const keytar = await loadKeytar();
93
+ if (!keytar)
94
+ return;
59
95
  try {
60
- const keytar = await Promise.resolve().then(() => __importStar(require('keytar')));
61
96
  await keytar.setPassword('recall', 'token', token);
62
97
  }
63
98
  catch {
64
- const fs = require('fs');
65
- const path = require('path');
66
- fs.mkdirSync(path.dirname(TOKEN_FILE), { recursive: true });
67
- fs.writeFileSync(TOKEN_FILE, token, { mode: 0o600 });
99
+ // file is already written
68
100
  }
69
101
  }
70
102
  async function deleteToken() {
71
103
  try {
72
- const keytar = await Promise.resolve().then(() => __importStar(require('keytar')));
104
+ if (node_fs_1.default.existsSync(TOKEN_FILE))
105
+ node_fs_1.default.unlinkSync(TOKEN_FILE);
106
+ }
107
+ catch {
108
+ // ignore
109
+ }
110
+ const keytar = await loadKeytar();
111
+ if (!keytar)
112
+ return;
113
+ try {
73
114
  await keytar.deletePassword('recall', 'token');
74
115
  }
75
116
  catch {
76
- const fs = require('fs');
77
- if (fs.existsSync(TOKEN_FILE))
78
- fs.unlinkSync(TOKEN_FILE);
117
+ // ignore
79
118
  }
80
119
  }
81
120
  async function requireAuth() {
@@ -11,7 +11,7 @@ const os_1 = __importDefault(require("os"));
11
11
  const path_1 = __importDefault(require("path"));
12
12
  const CONFIG_DIR = path_1.default.join(os_1.default.homedir(), '.recall');
13
13
  const CONFIG_FILE = path_1.default.join(CONFIG_DIR, 'config.json');
14
- exports.APP_VERSION = "1.0.1";
14
+ exports.APP_VERSION = "2.5.0";
15
15
  exports.ENVIRONMENTS = {
16
16
  production: 'https://api.devkitvault.com',
17
17
  local: 'http://127.0.0.1:3001',
@@ -0,0 +1,40 @@
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.track = track;
7
+ exports.trackInstalledOnce = trackInstalledOnce;
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const os_1 = __importDefault(require("os"));
10
+ const path_1 = __importDefault(require("path"));
11
+ const api_1 = require("./api");
12
+ const auth_1 = require("./auth");
13
+ const config_1 = require("./config");
14
+ const FLAG_FILE = path_1.default.join(os_1.default.homedir(), '.recall', 'installed');
15
+ function markInstalled() {
16
+ try {
17
+ if (fs_1.default.existsSync(FLAG_FILE))
18
+ return false;
19
+ fs_1.default.mkdirSync(path_1.default.dirname(FLAG_FILE), { recursive: true });
20
+ fs_1.default.writeFileSync(FLAG_FILE, new Date().toISOString(), { mode: 0o600 });
21
+ return true;
22
+ }
23
+ catch {
24
+ return false;
25
+ }
26
+ }
27
+ async function track(name, properties) {
28
+ try {
29
+ const token = await (0, auth_1.getToken)();
30
+ await api_1.ApiClient.post('/events', { name, properties: properties ?? {} }, token ?? undefined);
31
+ }
32
+ catch {
33
+ // never block the CLI on analytics
34
+ }
35
+ }
36
+ async function trackInstalledOnce() {
37
+ if (!markInstalled())
38
+ return;
39
+ await track('cli_installed', { version: config_1.APP_VERSION });
40
+ }
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.detectRuntime = detectRuntime;
4
+ function detectRuntime() {
5
+ const os = process.platform === 'win32'
6
+ ? 'windows'
7
+ : process.platform === 'darwin'
8
+ ? 'macos'
9
+ : 'linux';
10
+ if (os === 'windows') {
11
+ const shellEnv = (process.env.SHELL ?? '').toLowerCase();
12
+ if (shellEnv.includes('zsh'))
13
+ return { os, shell: 'zsh' };
14
+ if (shellEnv.includes('bash'))
15
+ return { os, shell: 'bash' };
16
+ if (process.env.PSModulePath)
17
+ return { os, shell: 'powershell' };
18
+ const comspec = (process.env.ComSpec ?? '').toLowerCase();
19
+ if (comspec.includes('cmd'))
20
+ return { os, shell: 'cmd' };
21
+ return { os, shell: 'powershell' };
22
+ }
23
+ const s = process.env.SHELL ?? '/bin/bash';
24
+ if (s.includes('zsh'))
25
+ return { os, shell: 'zsh' };
26
+ if (s.includes('fish'))
27
+ return { os, shell: 'fish' };
28
+ if (s.includes('pwsh') || s.includes('powershell'))
29
+ return { os, shell: 'powershell' };
30
+ return { os, shell: 'bash' };
31
+ }
package/package.json CHANGED
@@ -1,56 +1,57 @@
1
- {
2
- "name": "@devkitvault/recall",
3
- "version": "1.0.5",
4
- "description": "recall CLI — cloud-synced command vault (JavaScript build for npm; no bundled native binary)",
5
- "keywords": [
6
- "cli",
7
- "shell",
8
- "commands",
9
- "devtools",
10
- "recall",
11
- "vault",
12
- "sync",
13
- "kubernetes",
14
- "docker",
15
- "terminal",
16
- "devkitvault"
17
- ],
18
- "author": "devkitvault",
19
- "license": "MIT",
20
- "homepage": "https://recall.devkitvault.com",
21
- "bugs": {
22
- "url": "https://github.com/devkitvault/recall/issues"
23
- },
24
- "repository": {
25
- "type": "git",
26
- "url": "git+https://github.com/devkitvault/recall.git",
27
- "directory": "packages/npm-cli"
28
- },
29
- "bin": {
30
- "recall": "dist/index.js"
31
- },
32
- "files": [
33
- "dist",
34
- "README.md",
35
- "LICENSE"
36
- ],
37
- "engines": {
38
- "node": ">=18.0.0"
39
- },
40
- "publishConfig": {
41
- "access": "public"
42
- },
43
- "scripts": {
44
- "prepublishOnly": "node scripts/prepublish.cjs && node --check dist/index.js",
45
- "check:registry": "npm view @devkitvault/recall version",
46
- "try:published": "npx --yes --package=@devkitvault/recall@latest recall --version"
47
- },
48
- "dependencies": {
49
- "chalk": "^4.1.2",
50
- "cli-table3": "^0.6.3",
51
- "commander": "^12.0.0",
52
- "inquirer": "^8.2.7",
53
- "open": "^8.4.2",
54
- "ora": "^5.4.1"
55
- }
56
- }
1
+ {
2
+ "name": "@devkitvault/recall",
3
+ "version": "1.2.0",
4
+ "description": "recall CLI — AI terminal copilot with a personal command vault",
5
+ "keywords": [
6
+ "cli",
7
+ "shell",
8
+ "commands",
9
+ "devtools",
10
+ "recall",
11
+ "vault",
12
+ "sync",
13
+ "kubernetes",
14
+ "docker",
15
+ "terminal",
16
+ "devkitvault"
17
+ ],
18
+ "author": "devkitvault",
19
+ "license": "MIT",
20
+ "homepage": "https://recall.devkitvault.com",
21
+ "bugs": {
22
+ "url": "https://github.com/devkitvault/recall/issues"
23
+ },
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/devkitvault/recall.git",
27
+ "directory": "packages/npm-cli"
28
+ },
29
+ "bin": {
30
+ "recall": "dist/index.js",
31
+ "rec": "dist/index.js"
32
+ },
33
+ "files": [
34
+ "dist",
35
+ "README.md",
36
+ "LICENSE"
37
+ ],
38
+ "engines": {
39
+ "node": ">=18.0.0"
40
+ },
41
+ "publishConfig": {
42
+ "access": "public"
43
+ },
44
+ "scripts": {
45
+ "prepublishOnly": "node scripts/prepublish.cjs && node --check dist/index.js",
46
+ "check:registry": "npm view @devkitvault/recall version",
47
+ "try:published": "npx --yes --package=@devkitvault/recall@latest recall --version"
48
+ },
49
+ "dependencies": {
50
+ "chalk": "^4.1.2",
51
+ "cli-table3": "^0.6.3",
52
+ "commander": "^12.0.0",
53
+ "inquirer": "^8.2.7",
54
+ "open": "^8.4.2",
55
+ "ora": "^5.4.1"
56
+ }
57
+ }