@devkitvault/recall 1.2.7 → 1.2.10
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 +35 -39
- package/dist/commands/ask.js +15 -2
- package/dist/commands/delete.js +9 -25
- package/dist/commands/import.js +155 -45
- package/dist/commands/list.js +33 -52
- package/dist/commands/pin.js +10 -16
- package/dist/commands/run.js +23 -37
- package/dist/commands/save.js +49 -52
- package/dist/commands/search.js +31 -46
- package/dist/commands/sync.js +92 -54
- package/dist/commands/upgrade.js +5 -6
- package/dist/commands/whoami.js +7 -8
- package/dist/lib/config.js +1 -1
- package/dist/lib/local-vault.js +251 -0
- package/dist/lib/shell-history.js +239 -0
- package/package.json +4 -5
package/README.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# @devkitvault/recall
|
|
2
2
|
|
|
3
|
-
**recall** is a CLI for the commands you keep losing
|
|
3
|
+
**recall** is a CLI command vault for your terminal. Save the commands you keep losing, search them, run them by name, and sync when you want cloud backup.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Product site and docs: [recall.devkitvault.com](https://recall.devkitvault.com)
|
|
6
|
+
|
|
7
|
+
Open source: [github.com/devkitvault/recall-cli](https://github.com/devkitvault/recall-cli)
|
|
6
8
|
|
|
7
9
|
## Requirements
|
|
8
10
|
|
|
@@ -14,68 +16,61 @@ Works with [recall.devkitvault.com](https://recall.devkitvault.com) and the [VS
|
|
|
14
16
|
npm install -g @devkitvault/recall
|
|
15
17
|
```
|
|
16
18
|
|
|
17
|
-
`rec` is the same
|
|
19
|
+
`rec` is the same binary as `recall`.
|
|
18
20
|
|
|
19
21
|
```sh
|
|
20
22
|
recall --version
|
|
21
23
|
recall doctor
|
|
22
24
|
```
|
|
23
25
|
|
|
24
|
-
## Quick start
|
|
26
|
+
## Quick start (local vault)
|
|
27
|
+
|
|
28
|
+
Local save / list / search / run work **without an account**. Auth is only needed for **sync**, **Ask** (Pro/Team), and team features.
|
|
25
29
|
|
|
26
30
|
```sh
|
|
27
|
-
recall
|
|
28
|
-
recall
|
|
29
|
-
|
|
30
|
-
recall
|
|
31
|
+
recall save "docker compose up -d" --name docker --tags docker
|
|
32
|
+
recall save --last -n useful -t docker
|
|
33
|
+
recall import history
|
|
34
|
+
recall list
|
|
35
|
+
recall search docker
|
|
36
|
+
recall run docker
|
|
31
37
|
```
|
|
32
38
|
|
|
33
|
-
|
|
39
|
+
`recall import history` reads your shell history file (zsh/bash/PowerShell), lets you pick commands, and saves them locally. It does **not** install a keylogger or live hook.
|
|
40
|
+
|
|
41
|
+
`recall save --last` (or `-L`) reads the last line from zsh / bash / PowerShell history. Not supported in Windows `cmd.exe`. On bash, if the command is missing: `history -a && recall save --last`.
|
|
34
42
|
|
|
35
43
|
```sh
|
|
36
44
|
recall playbook save deploy "docker compose up -d" "pnpm migrate"
|
|
37
45
|
recall playbook run deploy --dry-run
|
|
38
46
|
```
|
|
39
47
|
|
|
40
|
-
`recall playbook run` executes steps. Ask never does.
|
|
41
|
-
|
|
42
|
-
Ask prints a suggestion (`source` is `vault`, `cache`, or `recall agent` for a generated answer). Copy or save it yourself.
|
|
43
|
-
|
|
44
|
-
## Ask in the terminal
|
|
45
|
-
|
|
46
|
-
These GIFs print a command. None of them execute.
|
|
47
|
-
|
|
48
|
-
**`rec "start the project"`** (`source: project`)
|
|
48
|
+
Local playbooks follow the same idea when available; cloud playbooks need Pro. `recall playbook run` executes steps. Ask never does.
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
## Ask (Pro / Team)
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-

|
|
55
|
-
|
|
56
|
-
**`rec "free the process using port 3000"`** (`source: recall agent`)
|
|
57
|
-
|
|
58
|
-

|
|
59
|
-
|
|
60
|
-
**`rec "that docker command from last week"`** (`source: vault`)
|
|
61
|
-
|
|
62
|
-

|
|
52
|
+
Natural-language Ask prints a suggestion and never runs it. Requires a Pro or Team plan.
|
|
63
53
|
|
|
64
54
|
```sh
|
|
65
|
-
recall
|
|
66
|
-
recall
|
|
55
|
+
recall auth login
|
|
56
|
+
recall "find the 10 biggest files in this directory"
|
|
57
|
+
rec "that docker command from last week"
|
|
67
58
|
```
|
|
68
59
|
|
|
69
|
-
|
|
60
|
+
No-args `recall` opens an Ask session (`Recall >`). `/save [name]` stores the last suggestion. `/exit` leaves.
|
|
61
|
+
|
|
62
|
+
## Auth & sync (Pro / Team)
|
|
70
63
|
|
|
71
|
-
|
|
64
|
+
Account + Pro/Team syncs your **local** commands to the cloud and unlocks Ask.
|
|
72
65
|
|
|
73
66
|
```sh
|
|
74
67
|
recall auth login
|
|
75
|
-
recall
|
|
68
|
+
recall sync # pull then push
|
|
69
|
+
recall sync --pull # cloud → local
|
|
70
|
+
recall sync --push # local-only → cloud
|
|
71
|
+
recall sync --status
|
|
76
72
|
recall whoami
|
|
77
73
|
```
|
|
78
|
-
|
|
79
74
|
## Prefer a standalone binary?
|
|
80
75
|
|
|
81
76
|
- **macOS / Linux:** `curl -fsSL https://devkitvault.com/recall/install.sh | sh`
|
|
@@ -96,17 +91,18 @@ recall doctor
|
|
|
96
91
|
recall whoami
|
|
97
92
|
```
|
|
98
93
|
|
|
99
|
-
If something still fails, open an issue on [GitHub](https://github.com/devkitvault/recall/issues) or visit [recall.devkitvault.com](https://recall.devkitvault.com).
|
|
94
|
+
If something still fails, open an issue on [GitHub](https://github.com/devkitvault/recall-cli/issues) or visit [recall.devkitvault.com](https://recall.devkitvault.com).
|
|
100
95
|
|
|
101
96
|
## Links
|
|
102
97
|
|
|
103
98
|
| | |
|
|
104
99
|
| --------- | ----------------------------------------------------------------------------------------- |
|
|
105
|
-
| Website | [devkitvault.com
|
|
100
|
+
| Website | [recall.devkitvault.com](https://recall.devkitvault.com) |
|
|
106
101
|
| Docs | [recall.devkitvault.com/docs](https://recall.devkitvault.com/docs) |
|
|
107
102
|
| Dashboard | [recall.devkitvault.com](https://recall.devkitvault.com) |
|
|
103
|
+
| Source | [github.com/devkitvault/recall-cli](https://github.com/devkitvault/recall-cli) |
|
|
108
104
|
| VS Code | [Marketplace](https://marketplace.visualstudio.com/items?itemName=devkitvault.recall-cmd) |
|
|
109
|
-
| Issues | [github.com/devkitvault/recall](https://github.com/devkitvault/recall/issues)
|
|
105
|
+
| Issues | [github.com/devkitvault/recall-cli](https://github.com/devkitvault/recall-cli/issues) |
|
|
110
106
|
|
|
111
107
|
## License
|
|
112
108
|
|
package/dist/commands/ask.js
CHANGED
|
@@ -15,6 +15,7 @@ const commander_1 = require("commander");
|
|
|
15
15
|
const inquirer_1 = __importDefault(require("inquirer"));
|
|
16
16
|
const api_1 = require("../lib/api");
|
|
17
17
|
const auth_1 = require("../lib/auth");
|
|
18
|
+
const local_vault_1 = require("../lib/local-vault");
|
|
18
19
|
const events_1 = require("../lib/events");
|
|
19
20
|
const project_1 = require("../lib/project");
|
|
20
21
|
const runtime_1 = require("../lib/runtime");
|
|
@@ -91,11 +92,23 @@ async function promptSaveMeta(existingName) {
|
|
|
91
92
|
};
|
|
92
93
|
}
|
|
93
94
|
async function saveSuggestion(token, command, name, source, risk, tags) {
|
|
94
|
-
|
|
95
|
+
const local = (0, local_vault_1.saveLocalCommand)({
|
|
95
96
|
command,
|
|
96
97
|
name: name?.trim() || undefined,
|
|
97
98
|
tags,
|
|
98
|
-
}
|
|
99
|
+
});
|
|
100
|
+
try {
|
|
101
|
+
const saved = await api_1.ApiClient.post('/commands', {
|
|
102
|
+
command,
|
|
103
|
+
name: name?.trim() || undefined,
|
|
104
|
+
tags,
|
|
105
|
+
}, token);
|
|
106
|
+
if (saved?.id)
|
|
107
|
+
(0, local_vault_1.attachCloudId)(local.id, saved.id);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// Local save succeeded; cloud may require Pro
|
|
111
|
+
}
|
|
99
112
|
await (0, events_1.track)('ai_accepted', { source, risk, saved: true });
|
|
100
113
|
console.log(chalk_1.default.green(`\n ${(0, tags_1.formatSavedMessage)(name, tags)}\n`));
|
|
101
114
|
}
|
package/dist/commands/delete.js
CHANGED
|
@@ -7,38 +7,27 @@ exports.deleteCommand = void 0;
|
|
|
7
7
|
const chalk_1 = __importDefault(require("chalk"));
|
|
8
8
|
const commander_1 = require("commander");
|
|
9
9
|
const inquirer_1 = __importDefault(require("inquirer"));
|
|
10
|
-
const
|
|
11
|
-
const api_1 = require("../lib/api");
|
|
12
|
-
const auth_1 = require("../lib/auth");
|
|
10
|
+
const local_vault_1 = require("../lib/local-vault");
|
|
13
11
|
exports.deleteCommand = new commander_1.Command('delete')
|
|
14
|
-
.description('Delete a
|
|
12
|
+
.description('Delete a command from your local vault')
|
|
15
13
|
.argument('<name>', 'Command name to delete')
|
|
16
14
|
.option('-f, --force', 'Skip confirmation prompt')
|
|
17
15
|
.action(async (name, opts) => {
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
let cmd;
|
|
22
|
-
try {
|
|
23
|
-
cmd = await api_1.ApiClient.get(`/commands/by-name/${encodeURIComponent(name)}`, token);
|
|
24
|
-
spinner.stop();
|
|
25
|
-
}
|
|
26
|
-
catch {
|
|
27
|
-
spinner.fail(chalk_1.default.red(`Command "${name}" not found`));
|
|
16
|
+
const cmd = (0, local_vault_1.findLocalByName)(name);
|
|
17
|
+
if (!cmd) {
|
|
18
|
+
console.log(chalk_1.default.red(`\n Command "${name}" not found in local vault.\n`));
|
|
28
19
|
process.exit(1);
|
|
29
20
|
}
|
|
30
|
-
// Show what will be deleted
|
|
31
21
|
console.log();
|
|
32
22
|
console.log(` ${chalk_1.default.dim('Name:')} ${chalk_1.default.white(cmd.name ?? '—')}`);
|
|
33
23
|
console.log(` ${chalk_1.default.dim('Command:')} ${chalk_1.default.white(cmd.command)}`);
|
|
34
24
|
console.log();
|
|
35
|
-
// Confirm unless --force
|
|
36
25
|
if (!opts.force) {
|
|
37
26
|
const { confirm } = await inquirer_1.default.prompt([
|
|
38
27
|
{
|
|
39
28
|
type: 'confirm',
|
|
40
29
|
name: 'confirm',
|
|
41
|
-
message: chalk_1.default.red(`Delete "${name}"?`),
|
|
30
|
+
message: chalk_1.default.red(`Delete "${name}" from local vault?`),
|
|
42
31
|
default: false,
|
|
43
32
|
},
|
|
44
33
|
]);
|
|
@@ -47,12 +36,7 @@ exports.deleteCommand = new commander_1.Command('delete')
|
|
|
47
36
|
return;
|
|
48
37
|
}
|
|
49
38
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
deleteSpinner.succeed(chalk_1.default.green(`Deleted "${name}"`));
|
|
54
|
-
}
|
|
55
|
-
catch {
|
|
56
|
-
deleteSpinner.fail(chalk_1.default.red('Failed to delete'));
|
|
57
|
-
}
|
|
39
|
+
(0, local_vault_1.deleteLocalByName)(name);
|
|
40
|
+
console.log(chalk_1.default.green(` Deleted "${name}" from local vault.\n`));
|
|
41
|
+
console.log(chalk_1.default.dim(' Cloud copy (if any) is unchanged until you sync.\n'));
|
|
58
42
|
});
|
package/dist/commands/import.js
CHANGED
|
@@ -7,19 +7,22 @@ exports.importCommand = void 0;
|
|
|
7
7
|
const chalk_1 = __importDefault(require("chalk"));
|
|
8
8
|
const commander_1 = require("commander");
|
|
9
9
|
const fs_1 = __importDefault(require("fs"));
|
|
10
|
-
const
|
|
10
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
11
11
|
const path_1 = __importDefault(require("path"));
|
|
12
|
-
const
|
|
13
|
-
const
|
|
12
|
+
const local_vault_1 = require("../lib/local-vault");
|
|
13
|
+
const tags_1 = require("../lib/tags");
|
|
14
|
+
const shell_history_1 = require("../lib/shell-history");
|
|
14
15
|
function parseJSON(content) {
|
|
15
16
|
const data = JSON.parse(content);
|
|
16
17
|
if (!Array.isArray(data))
|
|
17
18
|
throw new Error('JSON must be an array of commands');
|
|
18
19
|
return data.map((item) => ({
|
|
19
|
-
command: item.command,
|
|
20
|
-
name: item.name,
|
|
21
|
-
tags: item.tags
|
|
22
|
-
|
|
20
|
+
command: String(item.command ?? ''),
|
|
21
|
+
name: typeof item.name === 'string' ? item.name : undefined,
|
|
22
|
+
tags: Array.isArray(item.tags)
|
|
23
|
+
? item.tags.filter((t) => typeof t === 'string')
|
|
24
|
+
: undefined,
|
|
25
|
+
})).filter((e) => e.command.trim());
|
|
23
26
|
}
|
|
24
27
|
function parseSh(content) {
|
|
25
28
|
const lines = content.split('\n');
|
|
@@ -28,14 +31,13 @@ function parseSh(content) {
|
|
|
28
31
|
let currentTags = [];
|
|
29
32
|
for (const raw of lines) {
|
|
30
33
|
const line = raw.trim();
|
|
31
|
-
// skip empty lines and shebang
|
|
32
34
|
if (!line || line.startsWith('#!'))
|
|
33
35
|
continue;
|
|
34
36
|
if (line.startsWith('# tags:')) {
|
|
35
37
|
currentTags = line
|
|
36
38
|
.replace('# tags:', '')
|
|
37
39
|
.split(',')
|
|
38
|
-
.map(t => t.trim())
|
|
40
|
+
.map((t) => t.trim())
|
|
39
41
|
.filter(Boolean);
|
|
40
42
|
continue;
|
|
41
43
|
}
|
|
@@ -43,24 +45,59 @@ function parseSh(content) {
|
|
|
43
45
|
currentName = line.replace('#', '').trim();
|
|
44
46
|
continue;
|
|
45
47
|
}
|
|
46
|
-
// it's a command
|
|
47
48
|
entries.push({
|
|
48
49
|
command: line,
|
|
49
50
|
name: currentName,
|
|
50
|
-
tags: currentTags,
|
|
51
|
+
tags: currentTags.length ? currentTags : undefined,
|
|
51
52
|
});
|
|
52
|
-
// reset
|
|
53
53
|
currentName = undefined;
|
|
54
54
|
currentTags = [];
|
|
55
55
|
}
|
|
56
56
|
return entries;
|
|
57
57
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
58
|
+
function commandAlreadyLocal(command, name) {
|
|
59
|
+
if (name && (0, local_vault_1.findLocalByName)(name))
|
|
60
|
+
return true;
|
|
61
|
+
return (0, local_vault_1.listLocalCommands)().some((c) => c.command === command);
|
|
62
|
+
}
|
|
63
|
+
function suggestName(command, used) {
|
|
64
|
+
const base = command
|
|
65
|
+
.replace(/[^a-zA-Z0-9]+/g, '-')
|
|
66
|
+
.replace(/^-|-$/g, '')
|
|
67
|
+
.toLowerCase()
|
|
68
|
+
.slice(0, 32) || 'cmd';
|
|
69
|
+
let name = base;
|
|
70
|
+
let n = 2;
|
|
71
|
+
while (used.has(name) || (0, local_vault_1.findLocalByName)(name)) {
|
|
72
|
+
name = `${base.slice(0, 28)}-${n}`;
|
|
73
|
+
n++;
|
|
74
|
+
}
|
|
75
|
+
used.add(name);
|
|
76
|
+
return name;
|
|
77
|
+
}
|
|
78
|
+
async function importEntries(entries, opts) {
|
|
79
|
+
let imported = 0;
|
|
80
|
+
let skipped = 0;
|
|
81
|
+
const usedNames = new Set();
|
|
82
|
+
for (const entry of entries) {
|
|
83
|
+
if (opts.skipDuplicates && commandAlreadyLocal(entry.command, entry.name)) {
|
|
84
|
+
skipped++;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const name = entry.name
|
|
88
|
+
?? (opts.nameThem ? suggestName(entry.command, usedNames) : undefined);
|
|
89
|
+
if (name)
|
|
90
|
+
usedNames.add(name);
|
|
91
|
+
(0, local_vault_1.saveLocalCommand)({
|
|
92
|
+
command: entry.command,
|
|
93
|
+
name,
|
|
94
|
+
tags: entry.tags,
|
|
95
|
+
});
|
|
96
|
+
imported++;
|
|
97
|
+
}
|
|
98
|
+
return { imported, skipped };
|
|
99
|
+
}
|
|
100
|
+
async function importFromFile(file, opts) {
|
|
64
101
|
const filePath = path_1.default.resolve(file);
|
|
65
102
|
if (!fs_1.default.existsSync(filePath)) {
|
|
66
103
|
console.error(chalk_1.default.red(`\n File not found: ${filePath}\n`));
|
|
@@ -77,7 +114,6 @@ exports.importCommand = new commander_1.Command('import')
|
|
|
77
114
|
entries = parseSh(content);
|
|
78
115
|
}
|
|
79
116
|
else {
|
|
80
|
-
// try JSON first, then sh
|
|
81
117
|
try {
|
|
82
118
|
entries = parseJSON(content);
|
|
83
119
|
}
|
|
@@ -87,40 +123,114 @@ exports.importCommand = new commander_1.Command('import')
|
|
|
87
123
|
}
|
|
88
124
|
}
|
|
89
125
|
catch (err) {
|
|
90
|
-
|
|
126
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
127
|
+
console.error(chalk_1.default.red(`\n Failed to parse file: ${message}\n`));
|
|
91
128
|
process.exit(1);
|
|
92
129
|
}
|
|
93
130
|
if (!entries.length) {
|
|
94
131
|
console.log(chalk_1.default.dim('\n No commands found in file.\n'));
|
|
95
132
|
return;
|
|
96
133
|
}
|
|
97
|
-
console.log(chalk_1.default.dim(`\n Found ${entries.length} command${entries.length === 1 ? '' : 's'}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
134
|
+
console.log(chalk_1.default.dim(`\n Found ${entries.length} command${entries.length === 1 ? '' : 's'} in file\n`));
|
|
135
|
+
const { imported, skipped } = await importEntries(entries, {
|
|
136
|
+
skipDuplicates: opts.skipDuplicates,
|
|
137
|
+
nameThem: false,
|
|
138
|
+
});
|
|
139
|
+
console.log(` ${chalk_1.default.green(`${imported} imported`)} ${chalk_1.default.yellow(`${skipped} skipped`)}`);
|
|
140
|
+
console.log(chalk_1.default.dim(` Vault: ${local_vault_1.VAULT_FILE}\n`));
|
|
141
|
+
}
|
|
142
|
+
exports.importCommand = new commander_1.Command('import')
|
|
143
|
+
.description('Import commands into your local vault')
|
|
144
|
+
.argument('[file]', 'JSON or shell script to import (or use: recall import history)')
|
|
145
|
+
.option('-s, --skip-duplicates', 'Skip commands that already exist locally', true)
|
|
146
|
+
.addHelpText('after', `
|
|
147
|
+
Examples:
|
|
148
|
+
$ recall import history
|
|
149
|
+
$ recall import history -n 20 -y
|
|
150
|
+
$ recall import ./backup.json
|
|
151
|
+
`)
|
|
152
|
+
.action(async (file, opts) => {
|
|
153
|
+
if (!file) {
|
|
154
|
+
console.log(chalk_1.default.dim('\n Usage:'));
|
|
155
|
+
console.log(chalk_1.default.dim(' recall import history # from shell history (curated)'));
|
|
156
|
+
console.log(chalk_1.default.dim(' recall import <file> # from JSON or .sh\n'));
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
await importFromFile(file, opts);
|
|
160
|
+
});
|
|
161
|
+
exports.importCommand
|
|
162
|
+
.command('history')
|
|
163
|
+
.description('Import curated commands from shell history into the local vault (not live capture)')
|
|
164
|
+
.option('-n, --limit <count>', 'Max unique recent commands to consider', '40')
|
|
165
|
+
.option('-y, --yes', 'Import all candidates without prompting')
|
|
166
|
+
.option('-i, --interactive', 'Choose which commands to import (default unless --yes)')
|
|
167
|
+
.option('--dry-run', 'Show candidates only; do not write')
|
|
168
|
+
.option('-t, --tags <tags>', 'Tags applied to imported commands')
|
|
169
|
+
.option('--min-length <n>', 'Skip commands shorter than this', '3')
|
|
170
|
+
.option('--no-names', 'Do not auto-generate names')
|
|
171
|
+
.option('--no-skip-duplicates', 'Import even if the same command text already exists')
|
|
172
|
+
.action(async (opts) => {
|
|
173
|
+
const limit = Math.max(1, parseInt(String(opts.limit), 10) || 40);
|
|
174
|
+
const minLength = Math.max(1, parseInt(String(opts.minLength), 10) || 3);
|
|
175
|
+
const tags = (0, tags_1.parseTags)(opts.tags);
|
|
176
|
+
let commands;
|
|
177
|
+
let source;
|
|
178
|
+
let shell;
|
|
179
|
+
try {
|
|
180
|
+
const result = (0, shell_history_1.readHistoryCommands)({ limit, minLength });
|
|
181
|
+
commands = result.commands;
|
|
182
|
+
source = result.source;
|
|
183
|
+
shell = result.shell;
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
if (err instanceof shell_history_1.LastCommandError) {
|
|
187
|
+
console.log(chalk_1.default.red(`\n ${err.message}\n`));
|
|
188
|
+
process.exit(1);
|
|
121
189
|
}
|
|
190
|
+
throw err;
|
|
122
191
|
}
|
|
123
192
|
console.log();
|
|
124
|
-
console.log(
|
|
193
|
+
console.log(chalk_1.default.dim(` Shell: ${shell} · ${source}`));
|
|
194
|
+
console.log(chalk_1.default.dim(` ${commands.length} unique candidate${commands.length === 1 ? '' : 's'} (newest first)\n`));
|
|
195
|
+
let selected = commands;
|
|
196
|
+
const interactive = !opts.yes && (opts.interactive || !opts.dryRun);
|
|
197
|
+
if (opts.yes) {
|
|
198
|
+
selected = commands;
|
|
199
|
+
}
|
|
200
|
+
else if (interactive && !opts.dryRun) {
|
|
201
|
+
const { picked } = await inquirer_1.default.prompt([
|
|
202
|
+
{
|
|
203
|
+
type: 'checkbox',
|
|
204
|
+
name: 'picked',
|
|
205
|
+
message: 'Select commands to save locally',
|
|
206
|
+
pageSize: 16,
|
|
207
|
+
choices: commands.map((c) => ({
|
|
208
|
+
name: c.length > 80 ? `${c.slice(0, 77)}…` : c,
|
|
209
|
+
value: c,
|
|
210
|
+
checked: false,
|
|
211
|
+
})),
|
|
212
|
+
},
|
|
213
|
+
]);
|
|
214
|
+
selected = picked;
|
|
215
|
+
if (!selected.length) {
|
|
216
|
+
console.log(chalk_1.default.dim('\n Nothing selected.\n'));
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (opts.dryRun) {
|
|
221
|
+
for (const [i, c] of selected.entries()) {
|
|
222
|
+
console.log(` ${chalk_1.default.dim(String(i + 1).padStart(2))}. ${c}`);
|
|
223
|
+
}
|
|
224
|
+
console.log(chalk_1.default.dim(`\n Dry run — nothing written. Drop --dry-run to import.\n`));
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const entries = selected.map((command) => ({ command, tags }));
|
|
228
|
+
const { imported, skipped } = await importEntries(entries, {
|
|
229
|
+
skipDuplicates: opts.skipDuplicates !== false,
|
|
230
|
+
nameThem: opts.names !== false,
|
|
231
|
+
});
|
|
125
232
|
console.log();
|
|
233
|
+
console.log(` ${chalk_1.default.green(`${imported} imported`)} ${chalk_1.default.yellow(`${skipped} skipped`)}`);
|
|
234
|
+
console.log(chalk_1.default.dim(` Vault: ${local_vault_1.VAULT_FILE}`));
|
|
235
|
+
console.log(chalk_1.default.dim(' Sync to cloud (Pro): recall sync\n'));
|
|
126
236
|
});
|
package/dist/commands/list.js
CHANGED
|
@@ -7,63 +7,44 @@ exports.listCommand = void 0;
|
|
|
7
7
|
const chalk_1 = __importDefault(require("chalk"));
|
|
8
8
|
const cli_table3_1 = __importDefault(require("cli-table3"));
|
|
9
9
|
const commander_1 = require("commander");
|
|
10
|
-
const
|
|
11
|
-
const api_1 = require("../lib/api");
|
|
12
|
-
const auth_1 = require("../lib/auth");
|
|
10
|
+
const local_vault_1 = require("../lib/local-vault");
|
|
13
11
|
exports.listCommand = new commander_1.Command('list')
|
|
14
|
-
.description('List your
|
|
12
|
+
.description('List commands in your local vault')
|
|
15
13
|
.option('-t, --tag <tag>', 'Filter by tag')
|
|
16
|
-
.option('-g, --group <group>', 'Filter by group (Pro)')
|
|
14
|
+
.option('-g, --group <group>', 'Filter by group (Pro cloud — not used locally)')
|
|
17
15
|
.option('-s, --search <q>', 'Search by keyword')
|
|
18
16
|
.option('-p, --pinned', 'Show pinned only')
|
|
19
17
|
.action(async (opts) => {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
try {
|
|
23
|
-
const params = new URLSearchParams();
|
|
24
|
-
if (opts.tag)
|
|
25
|
-
params.set('tag', opts.tag);
|
|
26
|
-
if (opts.group)
|
|
27
|
-
params.set('group', opts.group);
|
|
28
|
-
if (opts.search)
|
|
29
|
-
params.set('search', opts.search);
|
|
30
|
-
const { commands } = await api_1.ApiClient.get(`/commands?${params}`, token);
|
|
31
|
-
spinner.stop();
|
|
32
|
-
let result = commands;
|
|
33
|
-
// Filter pinned only
|
|
34
|
-
if (opts.pinned)
|
|
35
|
-
result = result.filter((c) => c.pinned);
|
|
36
|
-
// Sort: pinned first, then by createdAt
|
|
37
|
-
result.sort((a, b) => {
|
|
38
|
-
if (a.pinned && !b.pinned)
|
|
39
|
-
return -1;
|
|
40
|
-
if (!a.pinned && b.pinned)
|
|
41
|
-
return 1;
|
|
42
|
-
return 0;
|
|
43
|
-
});
|
|
44
|
-
if (!result.length) {
|
|
45
|
-
console.log(chalk_1.default.dim('\n No commands found.\n'));
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
const table = new cli_table3_1.default({
|
|
49
|
-
head: [chalk_1.default.cyan('Pin'), chalk_1.default.cyan('Name'), chalk_1.default.cyan('Command'), chalk_1.default.cyan('Tags')],
|
|
50
|
-
style: { head: [], border: ['grey'] },
|
|
51
|
-
colWidths: [5, 18, 44, 18],
|
|
52
|
-
wordWrap: true,
|
|
53
|
-
});
|
|
54
|
-
for (const cmd of result) {
|
|
55
|
-
table.push([
|
|
56
|
-
cmd.pinned ? chalk_1.default.yellow('★') : chalk_1.default.dim('·'),
|
|
57
|
-
cmd.name ?? chalk_1.default.dim('—'),
|
|
58
|
-
cmd.command.length > 42
|
|
59
|
-
? cmd.command.slice(0, 42) + '…'
|
|
60
|
-
: cmd.command,
|
|
61
|
-
(cmd.tags ?? []).join(', ') || chalk_1.default.dim('—'),
|
|
62
|
-
]);
|
|
63
|
-
}
|
|
64
|
-
console.log(table.toString());
|
|
18
|
+
if (opts.group) {
|
|
19
|
+
console.log(chalk_1.default.dim(' Local vault has no groups. Showing all local commands.\n'));
|
|
65
20
|
}
|
|
66
|
-
|
|
67
|
-
|
|
21
|
+
const result = (0, local_vault_1.listLocalCommands)({
|
|
22
|
+
tag: opts.tag,
|
|
23
|
+
search: opts.search,
|
|
24
|
+
pinned: opts.pinned,
|
|
25
|
+
});
|
|
26
|
+
if (!result.length) {
|
|
27
|
+
console.log(chalk_1.default.dim('\n No commands in local vault.'));
|
|
28
|
+
console.log(chalk_1.default.dim(` Save one: recall save "echo hi" -n hi`));
|
|
29
|
+
console.log(chalk_1.default.dim(` File: ${local_vault_1.VAULT_FILE}\n`));
|
|
30
|
+
return;
|
|
68
31
|
}
|
|
32
|
+
const table = new cli_table3_1.default({
|
|
33
|
+
head: [chalk_1.default.cyan('Pin'), chalk_1.default.cyan('Name'), chalk_1.default.cyan('Command'), chalk_1.default.cyan('Tags')],
|
|
34
|
+
style: { head: [], border: ['grey'] },
|
|
35
|
+
colWidths: [5, 18, 44, 18],
|
|
36
|
+
wordWrap: true,
|
|
37
|
+
});
|
|
38
|
+
for (const cmd of result) {
|
|
39
|
+
table.push([
|
|
40
|
+
cmd.pinned ? chalk_1.default.yellow('★') : chalk_1.default.dim('·'),
|
|
41
|
+
cmd.name ?? chalk_1.default.dim('—'),
|
|
42
|
+
cmd.command.length > 42
|
|
43
|
+
? cmd.command.slice(0, 42) + '…'
|
|
44
|
+
: cmd.command,
|
|
45
|
+
(cmd.tags ?? []).join(', ') || chalk_1.default.dim('—'),
|
|
46
|
+
]);
|
|
47
|
+
}
|
|
48
|
+
console.log(table.toString());
|
|
49
|
+
console.log(chalk_1.default.dim(`\n ${result.length} local · ${local_vault_1.VAULT_FILE}\n`));
|
|
69
50
|
});
|
package/dist/commands/pin.js
CHANGED
|
@@ -6,25 +6,19 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.pinCommand = void 0;
|
|
7
7
|
const chalk_1 = __importDefault(require("chalk"));
|
|
8
8
|
const commander_1 = require("commander");
|
|
9
|
-
const
|
|
10
|
-
const api_1 = require("../lib/api");
|
|
11
|
-
const auth_1 = require("../lib/auth");
|
|
9
|
+
const local_vault_1 = require("../lib/local-vault");
|
|
12
10
|
exports.pinCommand = new commander_1.Command('pin')
|
|
13
|
-
.description('Pin or unpin a command
|
|
11
|
+
.description('Pin or unpin a local command')
|
|
14
12
|
.argument('<name>', 'Command name')
|
|
15
13
|
.option('-u, --unpin', 'Unpin instead of pin')
|
|
16
14
|
.action(async (name, opts) => {
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
await api_1.ApiClient.patch(`/commands/${cmd.id}/pin`, { pinned }, token);
|
|
23
|
-
spinner.succeed(pinned
|
|
24
|
-
? chalk_1.default.green(`Pinned "${name}" — it will appear at the top of recall list`)
|
|
25
|
-
: chalk_1.default.green(`Unpinned "${name}"`));
|
|
26
|
-
}
|
|
27
|
-
catch {
|
|
28
|
-
spinner.fail(chalk_1.default.red(`Command "${name}" not found`));
|
|
15
|
+
const pinned = !opts.unpin;
|
|
16
|
+
const cmd = (0, local_vault_1.setLocalPinned)(name, pinned);
|
|
17
|
+
if (!cmd) {
|
|
18
|
+
console.log(chalk_1.default.red(`\n Command "${name}" not found in local vault.\n`));
|
|
19
|
+
process.exit(1);
|
|
29
20
|
}
|
|
21
|
+
console.log(pinned
|
|
22
|
+
? chalk_1.default.green(` Pinned "${name}" — it will appear at the top of recall list\n`)
|
|
23
|
+
: chalk_1.default.green(` Unpinned "${name}"\n`));
|
|
30
24
|
});
|