@devkitvault/recall 1.0.0 → 1.0.2
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 +11 -22
- package/dist/commands/alias.js +84 -0
- package/dist/commands/approve.js +108 -0
- package/dist/commands/audit.js +58 -0
- package/dist/commands/auth/forgot.js +33 -0
- package/dist/commands/auth/index.js +16 -0
- package/dist/commands/auth/login.js +151 -0
- package/dist/commands/auth/logout.js +15 -0
- package/dist/commands/auth/register.js +84 -0
- package/dist/commands/auth/reset.js +43 -0
- package/dist/commands/completion.js +158 -0
- package/dist/commands/config.js +89 -0
- package/dist/commands/delete.js +58 -0
- package/dist/commands/doctor.js +106 -0
- package/dist/commands/env.js +135 -0
- package/dist/commands/export.js +68 -0
- package/dist/commands/feedback.js +25 -0
- package/dist/commands/group.js +206 -0
- package/dist/commands/history.js +41 -0
- package/dist/commands/import.js +126 -0
- package/dist/commands/list.js +69 -0
- package/dist/commands/org.js +408 -0
- package/dist/commands/pin.js +30 -0
- package/dist/commands/run.js +55 -0
- package/dist/commands/save.js +73 -0
- package/dist/commands/search.js +82 -0
- package/dist/commands/share.js +88 -0
- package/dist/commands/snippet.js +147 -0
- package/dist/commands/support.js +96 -0
- package/dist/commands/sync.js +80 -0
- package/dist/commands/template.js +177 -0
- package/dist/commands/update.js +66 -0
- package/dist/commands/upgrade.js +122 -0
- package/dist/commands/whoami.js +70 -0
- package/dist/index.js +71 -0
- package/dist/lib/api.js +51 -0
- package/dist/lib/auth.js +88 -0
- package/dist/lib/config.js +37 -0
- package/package.json +12 -7
- package/bin/recall.js +0 -169
|
@@ -0,0 +1,88 @@
|
|
|
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.shareCommand = 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 shareCreate = new commander_1.Command('create')
|
|
13
|
+
.argument('<name>', 'Command name to share')
|
|
14
|
+
.option('-e, --expires <days>', 'Expire after N days')
|
|
15
|
+
.description('Generate a shareable link for a command')
|
|
16
|
+
.action(async (name, opts) => {
|
|
17
|
+
const token = await (0, auth_1.requireAuth)();
|
|
18
|
+
const spinner = (0, ora_1.default)('Looking up command...').start();
|
|
19
|
+
try {
|
|
20
|
+
const cmd = await api_1.ApiClient.get(`/commands/by-name/${encodeURIComponent(name)}`, token);
|
|
21
|
+
spinner.text = 'Creating share link...';
|
|
22
|
+
const result = await api_1.ApiClient.post(`/commands/${cmd.id}/share`, {
|
|
23
|
+
expiresIn: opts.expires ? parseInt(opts.expires) : undefined,
|
|
24
|
+
}, token);
|
|
25
|
+
spinner.stop();
|
|
26
|
+
console.log();
|
|
27
|
+
console.log(` ${chalk_1.default.bold('Share link created')}\n`);
|
|
28
|
+
console.log(` ${chalk_1.default.dim('URL:')} ${chalk_1.default.cyan(result.url)}`);
|
|
29
|
+
console.log(` ${chalk_1.default.dim('Command:')} ${chalk_1.default.white(cmd.command)}`);
|
|
30
|
+
if (result.expiresAt) {
|
|
31
|
+
console.log(` ${chalk_1.default.dim('Expires:')} ${new Date(result.expiresAt).toLocaleDateString()}`);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
console.log(` ${chalk_1.default.dim('Expires:')} never`);
|
|
35
|
+
}
|
|
36
|
+
console.log();
|
|
37
|
+
console.log(chalk_1.default.dim(' Anyone with this link can view and copy the command.'));
|
|
38
|
+
console.log();
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
spinner.fail(chalk_1.default.red(`Command "${name}" not found`));
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
const shareList = new commander_1.Command('list')
|
|
45
|
+
.description('List all your shared links')
|
|
46
|
+
.action(async () => {
|
|
47
|
+
const token = await (0, auth_1.requireAuth)();
|
|
48
|
+
const spinner = (0, ora_1.default)('Fetching shared links...').start();
|
|
49
|
+
try {
|
|
50
|
+
const { shared } = await api_1.ApiClient.get('/commands/shared', token);
|
|
51
|
+
spinner.stop();
|
|
52
|
+
if (!shared.length) {
|
|
53
|
+
console.log(chalk_1.default.dim('\n No shared links yet. Run: recall share create <name>\n'));
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
console.log();
|
|
57
|
+
for (const s of shared) {
|
|
58
|
+
const url = `https://devkitvault.com/recall/s/${s.slug}`;
|
|
59
|
+
const expired = s.expiresAt && new Date() > new Date(s.expiresAt);
|
|
60
|
+
console.log(` ${expired ? chalk_1.default.red('✗') : chalk_1.default.green('✓')} ` +
|
|
61
|
+
`${chalk_1.default.cyan(url)} ` +
|
|
62
|
+
chalk_1.default.dim(`${s.views ?? 0} views`));
|
|
63
|
+
}
|
|
64
|
+
console.log();
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
spinner.fail(chalk_1.default.red('Failed to fetch shared links'));
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
const shareDelete = new commander_1.Command('delete')
|
|
71
|
+
.argument('<slug>', 'Share link slug to delete')
|
|
72
|
+
.description('Delete a shared link')
|
|
73
|
+
.action(async (slug) => {
|
|
74
|
+
const token = await (0, auth_1.requireAuth)();
|
|
75
|
+
const spinner = (0, ora_1.default)('Deleting...').start();
|
|
76
|
+
try {
|
|
77
|
+
await api_1.ApiClient.delete(`/commands/shared/${slug}`, token);
|
|
78
|
+
spinner.succeed(chalk_1.default.green('Share link deleted'));
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
spinner.fail(chalk_1.default.red('Failed to delete share link'));
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
exports.shareCommand = new commander_1.Command('share')
|
|
85
|
+
.description('Share commands via public links')
|
|
86
|
+
.addCommand(shareCreate)
|
|
87
|
+
.addCommand(shareList)
|
|
88
|
+
.addCommand(shareDelete);
|
|
@@ -0,0 +1,147 @@
|
|
|
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.snippetCommand = void 0;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const child_process_1 = require("child_process");
|
|
9
|
+
const cli_table3_1 = __importDefault(require("cli-table3"));
|
|
10
|
+
const commander_1 = require("commander");
|
|
11
|
+
const fs_1 = __importDefault(require("fs"));
|
|
12
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
13
|
+
const ora_1 = __importDefault(require("ora"));
|
|
14
|
+
const path_1 = __importDefault(require("path"));
|
|
15
|
+
const api_1 = require("../lib/api");
|
|
16
|
+
const auth_1 = require("../lib/auth");
|
|
17
|
+
const snippetSave = new commander_1.Command('save')
|
|
18
|
+
.argument('<name>', 'Snippet name')
|
|
19
|
+
.option('-f, --file <path>', 'Load content from file')
|
|
20
|
+
.option('-t, --tags <tags>', 'Comma-separated tags')
|
|
21
|
+
.description('Save a multi-line script as a snippet')
|
|
22
|
+
.action(async (name, opts) => {
|
|
23
|
+
const token = await (0, auth_1.requireAuth)();
|
|
24
|
+
let content;
|
|
25
|
+
if (opts.file) {
|
|
26
|
+
const filePath = path_1.default.resolve(opts.file);
|
|
27
|
+
if (!fs_1.default.existsSync(filePath)) {
|
|
28
|
+
console.error(chalk_1.default.red(`\n File not found: ${filePath}\n`));
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
content = fs_1.default.readFileSync(filePath, 'utf-8');
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
const { text } = await inquirer_1.default.prompt([{
|
|
35
|
+
type: 'editor',
|
|
36
|
+
name: 'text',
|
|
37
|
+
message: 'Write your snippet (opens editor):',
|
|
38
|
+
}]);
|
|
39
|
+
content = text;
|
|
40
|
+
}
|
|
41
|
+
const spinner = (0, ora_1.default)('Saving snippet...').start();
|
|
42
|
+
try {
|
|
43
|
+
await api_1.ApiClient.post('/snippets', {
|
|
44
|
+
name,
|
|
45
|
+
content,
|
|
46
|
+
tags: opts.tags?.split(',').map((t) => t.trim()) ?? [],
|
|
47
|
+
}, token);
|
|
48
|
+
spinner.succeed(chalk_1.default.green(`Saved snippet "${name}"`));
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
spinner.fail(chalk_1.default.red(err.message ?? 'Failed to save snippet'));
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
const snippetList = new commander_1.Command('list')
|
|
55
|
+
.description('List all your snippets')
|
|
56
|
+
.action(async () => {
|
|
57
|
+
const token = await (0, auth_1.requireAuth)();
|
|
58
|
+
const spinner = (0, ora_1.default)('Fetching snippets...').start();
|
|
59
|
+
try {
|
|
60
|
+
const { snippets } = await api_1.ApiClient.get('/snippets', token);
|
|
61
|
+
spinner.stop();
|
|
62
|
+
if (!snippets.length) {
|
|
63
|
+
console.log(chalk_1.default.dim('\n No snippets yet. Run: recall snippet save <name>\n'));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const table = new cli_table3_1.default({
|
|
67
|
+
head: [chalk_1.default.cyan('Name'), chalk_1.default.cyan('Tags'), chalk_1.default.cyan('Created')],
|
|
68
|
+
style: { head: [], border: ['grey'] },
|
|
69
|
+
colWidths: [24, 24, 16],
|
|
70
|
+
});
|
|
71
|
+
for (const s of snippets) {
|
|
72
|
+
table.push([
|
|
73
|
+
chalk_1.default.white(s.name),
|
|
74
|
+
(s.tags ?? []).join(', ') || chalk_1.default.dim('—'),
|
|
75
|
+
new Date(s.createdAt).toLocaleDateString(),
|
|
76
|
+
]);
|
|
77
|
+
}
|
|
78
|
+
console.log(table.toString());
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
spinner.fail(chalk_1.default.red('Failed to fetch snippets'));
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
const snippetShow = new commander_1.Command('show')
|
|
85
|
+
.argument('<name>', 'Snippet name')
|
|
86
|
+
.description('Show snippet content')
|
|
87
|
+
.action(async (name) => {
|
|
88
|
+
const token = await (0, auth_1.requireAuth)();
|
|
89
|
+
const spinner = (0, ora_1.default)('Fetching...').start();
|
|
90
|
+
try {
|
|
91
|
+
const snippet = await api_1.ApiClient.get(`/snippets/by-name/${encodeURIComponent(name)}`, token);
|
|
92
|
+
spinner.stop();
|
|
93
|
+
console.log(chalk_1.default.bold(`\n ${snippet.name}\n`));
|
|
94
|
+
console.log(chalk_1.default.dim('─'.repeat(50)));
|
|
95
|
+
console.log(snippet.content);
|
|
96
|
+
console.log(chalk_1.default.dim('─'.repeat(50)));
|
|
97
|
+
console.log();
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
spinner.fail(chalk_1.default.red(`Snippet "${name}" not found`));
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
const snippetRun = new commander_1.Command('run')
|
|
104
|
+
.argument('<name>', 'Snippet name')
|
|
105
|
+
.option('-d, --dry-run', 'Print without running')
|
|
106
|
+
.description('Run a snippet')
|
|
107
|
+
.action(async (name, opts) => {
|
|
108
|
+
const token = await (0, auth_1.requireAuth)();
|
|
109
|
+
const spinner = (0, ora_1.default)('Fetching snippet...').start();
|
|
110
|
+
try {
|
|
111
|
+
const snippet = await api_1.ApiClient.get(`/snippets/by-name/${encodeURIComponent(name)}`, token);
|
|
112
|
+
spinner.stop();
|
|
113
|
+
console.log(chalk_1.default.dim(`\n Running snippet: ${chalk_1.default.white(snippet.name)}\n`));
|
|
114
|
+
if (!opts.dryRun) {
|
|
115
|
+
(0, child_process_1.execSync)(snippet.content, { stdio: 'inherit', shell: 'bash' });
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
console.log(snippet.content);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
spinner.fail(chalk_1.default.red(`Snippet "${name}" not found`));
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
const snippetDelete = new commander_1.Command('delete')
|
|
126
|
+
.argument('<name>', 'Snippet name')
|
|
127
|
+
.description('Delete a snippet')
|
|
128
|
+
.action(async (name) => {
|
|
129
|
+
const token = await (0, auth_1.requireAuth)();
|
|
130
|
+
const spinner = (0, ora_1.default)('Looking up...').start();
|
|
131
|
+
try {
|
|
132
|
+
const snippet = await api_1.ApiClient.get(`/snippets/by-name/${encodeURIComponent(name)}`, token);
|
|
133
|
+
spinner.text = 'Deleting...';
|
|
134
|
+
await api_1.ApiClient.delete(`/snippets/${snippet.id}`, token);
|
|
135
|
+
spinner.succeed(chalk_1.default.green(`Deleted snippet "${name}"`));
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
spinner.fail(chalk_1.default.red(`Snippet "${name}" not found`));
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
exports.snippetCommand = new commander_1.Command('snippet')
|
|
142
|
+
.description('Save and run multi-line shell scripts')
|
|
143
|
+
.addCommand(snippetSave)
|
|
144
|
+
.addCommand(snippetList)
|
|
145
|
+
.addCommand(snippetShow)
|
|
146
|
+
.addCommand(snippetRun)
|
|
147
|
+
.addCommand(snippetDelete);
|
|
@@ -0,0 +1,96 @@
|
|
|
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.supportCommand = void 0;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const commander_1 = require("commander");
|
|
9
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
10
|
+
const open_1 = __importDefault(require("open"));
|
|
11
|
+
const ora_1 = __importDefault(require("ora"));
|
|
12
|
+
const api_1 = require("../lib/api");
|
|
13
|
+
const auth_1 = require("../lib/auth");
|
|
14
|
+
const supportTicket = new commander_1.Command('ticket')
|
|
15
|
+
.description('Submit a support ticket')
|
|
16
|
+
.action(async () => {
|
|
17
|
+
const token = await (0, auth_1.getToken)();
|
|
18
|
+
if (!token) {
|
|
19
|
+
console.log(chalk_1.default.dim('\n Not logged in — opening support page instead.\n'));
|
|
20
|
+
await (0, open_1.default)('https://devkitvault.com/recall/support');
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
console.log(chalk_1.default.bold('\n Submit a support ticket\n'));
|
|
24
|
+
const { subject, message } = await inquirer_1.default.prompt([
|
|
25
|
+
{
|
|
26
|
+
type: 'input',
|
|
27
|
+
name: 'subject',
|
|
28
|
+
message: 'Subject:',
|
|
29
|
+
validate: (v) => v.length > 0 || 'Subject is required',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
type: 'editor',
|
|
33
|
+
name: 'message',
|
|
34
|
+
message: 'Describe your issue (opens editor):',
|
|
35
|
+
validate: (v) => v.length > 0 || 'Message is required',
|
|
36
|
+
},
|
|
37
|
+
]);
|
|
38
|
+
const spinner = (0, ora_1.default)('Submitting ticket...').start();
|
|
39
|
+
try {
|
|
40
|
+
await api_1.ApiClient.post('/support/tickets', { subject, message }, token);
|
|
41
|
+
spinner.succeed(chalk_1.default.green('Ticket submitted!'));
|
|
42
|
+
console.log(chalk_1.default.dim('\n We will reply to your email within 24 hours.\n'));
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
spinner.fail(chalk_1.default.red('Failed to submit ticket'));
|
|
46
|
+
console.log(chalk_1.default.dim(' Try: https://devkitvault.com/recall/support\n'));
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
const supportList = new commander_1.Command('list')
|
|
50
|
+
.description('List your support tickets')
|
|
51
|
+
.action(async () => {
|
|
52
|
+
const token = await (0, auth_1.getToken)();
|
|
53
|
+
if (!token) {
|
|
54
|
+
console.error(chalk_1.default.red('\n Not logged in. Run: recall auth login\n'));
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const spinner = (0, ora_1.default)('Fetching tickets...').start();
|
|
58
|
+
try {
|
|
59
|
+
const { tickets } = await api_1.ApiClient.get('/support/tickets', token);
|
|
60
|
+
spinner.stop();
|
|
61
|
+
if (!tickets.length) {
|
|
62
|
+
console.log(chalk_1.default.dim('\n No tickets yet.\n'));
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
console.log();
|
|
66
|
+
for (const t of tickets) {
|
|
67
|
+
const statusColor = t.status === 'resolved'
|
|
68
|
+
? chalk_1.default.green(t.status)
|
|
69
|
+
: t.status === 'in-progress'
|
|
70
|
+
? chalk_1.default.yellow(t.status)
|
|
71
|
+
: chalk_1.default.red(t.status);
|
|
72
|
+
console.log(` ${statusColor} ${chalk_1.default.white(t.subject)}`);
|
|
73
|
+
console.log(` ${chalk_1.default.dim(new Date(t.createdAt).toLocaleDateString())}`);
|
|
74
|
+
if (t.reply) {
|
|
75
|
+
console.log(` ${chalk_1.default.green('Reply:')} ${chalk_1.default.dim(t.reply.slice(0, 60))}...`);
|
|
76
|
+
}
|
|
77
|
+
console.log();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
spinner.fail(chalk_1.default.red('Failed to fetch tickets'));
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
exports.supportCommand = new commander_1.Command('support')
|
|
85
|
+
.description('Get help and submit support tickets')
|
|
86
|
+
.addCommand(supportTicket)
|
|
87
|
+
.addCommand(supportList)
|
|
88
|
+
.action(async () => {
|
|
89
|
+
console.log();
|
|
90
|
+
console.log(chalk_1.default.bold(' recall support\n'));
|
|
91
|
+
console.log(` ${chalk_1.default.dim('Documentation:')} https://devkitvault.com/recall/docs`);
|
|
92
|
+
console.log(` ${chalk_1.default.dim('Submit ticket:')} recall support ticket`);
|
|
93
|
+
console.log(` ${chalk_1.default.dim('View tickets:')} recall support list`);
|
|
94
|
+
console.log(` ${chalk_1.default.dim('Email:')} support@devkitvault.com`);
|
|
95
|
+
console.log();
|
|
96
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
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.syncCommand = void 0;
|
|
7
|
+
exports.getCachedCommands = getCachedCommands;
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const commander_1 = require("commander");
|
|
10
|
+
const fs_1 = __importDefault(require("fs"));
|
|
11
|
+
const ora_1 = __importDefault(require("ora"));
|
|
12
|
+
const os_1 = __importDefault(require("os"));
|
|
13
|
+
const path_1 = __importDefault(require("path"));
|
|
14
|
+
const api_1 = require("../lib/api");
|
|
15
|
+
const auth_1 = require("../lib/auth");
|
|
16
|
+
const CACHE_DIR = path_1.default.join(os_1.default.homedir(), '.recall');
|
|
17
|
+
const CACHE_FILE = path_1.default.join(CACHE_DIR, 'commands.json');
|
|
18
|
+
function writeCache(commands) {
|
|
19
|
+
fs_1.default.mkdirSync(CACHE_DIR, { recursive: true });
|
|
20
|
+
fs_1.default.writeFileSync(CACHE_FILE, JSON.stringify({
|
|
21
|
+
syncedAt: new Date().toISOString(),
|
|
22
|
+
commands,
|
|
23
|
+
}, null, 2));
|
|
24
|
+
}
|
|
25
|
+
function readCache() {
|
|
26
|
+
try {
|
|
27
|
+
if (!fs_1.default.existsSync(CACHE_FILE))
|
|
28
|
+
return null;
|
|
29
|
+
return JSON.parse(fs_1.default.readFileSync(CACHE_FILE, 'utf-8'));
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function getCachedCommands() {
|
|
36
|
+
const cache = readCache();
|
|
37
|
+
return cache?.commands ?? [];
|
|
38
|
+
}
|
|
39
|
+
exports.syncCommand = new commander_1.Command('sync')
|
|
40
|
+
.description('Sync commands from server to local cache')
|
|
41
|
+
.option('-s, --status', 'Show last sync status without syncing')
|
|
42
|
+
.action(async (opts) => {
|
|
43
|
+
// Just show status
|
|
44
|
+
if (opts.status) {
|
|
45
|
+
const cache = readCache();
|
|
46
|
+
if (!cache) {
|
|
47
|
+
console.log(chalk_1.default.dim('\n Never synced. Run: recall sync\n'));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const ago = Math.round((Date.now() - new Date(cache.syncedAt).getTime()) / 1000 / 60);
|
|
51
|
+
console.log();
|
|
52
|
+
console.log(` Last synced: ${chalk_1.default.white(ago < 1 ? 'just now' : `${ago} minutes ago`)}`);
|
|
53
|
+
console.log(` Commands: ${chalk_1.default.white(cache.commands.length)} cached locally`);
|
|
54
|
+
console.log(` Cache file: ${chalk_1.default.dim(CACHE_FILE)}`);
|
|
55
|
+
console.log();
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const token = await (0, auth_1.requireAuth)();
|
|
59
|
+
const spinner = (0, ora_1.default)('Syncing from server...').start();
|
|
60
|
+
try {
|
|
61
|
+
const { commands } = await api_1.ApiClient.get('/commands', token);
|
|
62
|
+
writeCache(commands);
|
|
63
|
+
spinner.succeed(chalk_1.default.green(`Synced ${commands.length} command${commands.length === 1 ? '' : 's'}`));
|
|
64
|
+
console.log();
|
|
65
|
+
console.log(` ${chalk_1.default.dim('Cached to:')} ${chalk_1.default.dim(CACHE_FILE)}`);
|
|
66
|
+
if (commands.length) {
|
|
67
|
+
console.log();
|
|
68
|
+
console.log(chalk_1.default.dim(' Commands available offline:'));
|
|
69
|
+
for (const cmd of commands) {
|
|
70
|
+
const name = cmd.name ? chalk_1.default.white(cmd.name) : chalk_1.default.dim('unnamed');
|
|
71
|
+
console.log(` · ${name} ${chalk_1.default.dim(cmd.command.slice(0, 50))}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
console.log();
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
spinner.fail(chalk_1.default.red('Sync failed'));
|
|
78
|
+
console.log(chalk_1.default.dim('\n Make sure you are logged in and the API is reachable.\n'));
|
|
79
|
+
}
|
|
80
|
+
});
|
|
@@ -0,0 +1,177 @@
|
|
|
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.templateCommand = void 0;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const child_process_1 = require("child_process");
|
|
9
|
+
const cli_table3_1 = __importDefault(require("cli-table3"));
|
|
10
|
+
const commander_1 = require("commander");
|
|
11
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
12
|
+
const ora_1 = __importDefault(require("ora"));
|
|
13
|
+
const api_1 = require("../lib/api");
|
|
14
|
+
const auth_1 = require("../lib/auth");
|
|
15
|
+
function extractVariables(template) {
|
|
16
|
+
const matches = template.match(/\{([^}]+)\}/g) ?? [];
|
|
17
|
+
return [...new Set(matches.map((m) => m.slice(1, -1)))];
|
|
18
|
+
}
|
|
19
|
+
const templateSave = new commander_1.Command('save')
|
|
20
|
+
.argument('<name>', 'Template name')
|
|
21
|
+
.argument('<template>', 'Command template with {variable} placeholders')
|
|
22
|
+
.option('-t, --tags <tags>', 'Comma-separated tags')
|
|
23
|
+
.description('Save a command template with variables')
|
|
24
|
+
.action(async (name, template, opts) => {
|
|
25
|
+
const token = await (0, auth_1.requireAuth)();
|
|
26
|
+
const variables = extractVariables(template);
|
|
27
|
+
if (!variables.length) {
|
|
28
|
+
console.log(chalk_1.default.yellow('\n No variables found in template.'));
|
|
29
|
+
console.log(chalk_1.default.dim(' Use {variable} syntax. Example: kubectl logs {pod} -n {namespace}\n'));
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const spinner = (0, ora_1.default)('Saving template...').start();
|
|
33
|
+
try {
|
|
34
|
+
await api_1.ApiClient.post('/templates', {
|
|
35
|
+
name,
|
|
36
|
+
template,
|
|
37
|
+
tags: opts.tags?.split(',').map((t) => t.trim()) ?? [],
|
|
38
|
+
}, token);
|
|
39
|
+
spinner.succeed(chalk_1.default.green(`Template "${name}" saved`));
|
|
40
|
+
console.log(chalk_1.default.dim(`\n Variables: ${variables.map(v => chalk_1.default.cyan(`{${v}}`)).join(', ')}\n`));
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
spinner.fail(chalk_1.default.red(err.message ?? 'Failed to save template'));
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
const templateList = new commander_1.Command('list')
|
|
47
|
+
.description('List all your templates')
|
|
48
|
+
.action(async () => {
|
|
49
|
+
const token = await (0, auth_1.requireAuth)();
|
|
50
|
+
const spinner = (0, ora_1.default)('Fetching templates...').start();
|
|
51
|
+
try {
|
|
52
|
+
const { templates } = await api_1.ApiClient.get('/templates', token);
|
|
53
|
+
spinner.stop();
|
|
54
|
+
if (!templates.length) {
|
|
55
|
+
console.log(chalk_1.default.dim('\n No templates yet.'));
|
|
56
|
+
console.log(chalk_1.default.dim(' Run: recall template save <name> "<command with {vars}>"\n'));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const table = new cli_table3_1.default({
|
|
60
|
+
head: [chalk_1.default.cyan('Name'), chalk_1.default.cyan('Template'), chalk_1.default.cyan('Variables')],
|
|
61
|
+
style: { head: [], border: ['grey'] },
|
|
62
|
+
colWidths: [20, 36, 24],
|
|
63
|
+
wordWrap: true,
|
|
64
|
+
});
|
|
65
|
+
for (const t of templates) {
|
|
66
|
+
table.push([
|
|
67
|
+
chalk_1.default.white(t.name),
|
|
68
|
+
t.template.length > 34 ? t.template.slice(0, 34) + '…' : t.template,
|
|
69
|
+
(t.variables ?? []).map((v) => chalk_1.default.cyan(`{${v}}`)).join(' ') || chalk_1.default.dim('none'),
|
|
70
|
+
]);
|
|
71
|
+
}
|
|
72
|
+
console.log(table.toString());
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
spinner.fail(chalk_1.default.red('Failed to fetch templates'));
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
const templateShow = new commander_1.Command('show')
|
|
79
|
+
.argument('<name>', 'Template name')
|
|
80
|
+
.description('Show template details')
|
|
81
|
+
.action(async (name) => {
|
|
82
|
+
const token = await (0, auth_1.requireAuth)();
|
|
83
|
+
const spinner = (0, ora_1.default)('Fetching...').start();
|
|
84
|
+
try {
|
|
85
|
+
const tmpl = await api_1.ApiClient.get(`/templates/by-name/${encodeURIComponent(name)}`, token);
|
|
86
|
+
spinner.stop();
|
|
87
|
+
console.log(chalk_1.default.bold(`\n ${tmpl.name}\n`));
|
|
88
|
+
console.log(` ${chalk_1.default.dim('Template:')} ${chalk_1.default.white(tmpl.template)}`);
|
|
89
|
+
console.log(` ${chalk_1.default.dim('Variables:')} ${(tmpl.variables ?? []).map((v) => chalk_1.default.cyan(`{${v}}`)).join(', ')}`);
|
|
90
|
+
if (tmpl.tags?.length) {
|
|
91
|
+
console.log(` ${chalk_1.default.dim('Tags:')} ${tmpl.tags.join(', ')}`);
|
|
92
|
+
}
|
|
93
|
+
console.log();
|
|
94
|
+
console.log(chalk_1.default.dim(` Run with: recall template run ${tmpl.name}`));
|
|
95
|
+
console.log();
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
spinner.fail(chalk_1.default.red(`Template "${name}" not found`));
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
const templateRun = new commander_1.Command('run')
|
|
102
|
+
.argument('<name>', 'Template name')
|
|
103
|
+
.option('-d, --dry-run', 'Print without running')
|
|
104
|
+
.description('Run a template — prompts for each variable')
|
|
105
|
+
.action(async (name, opts) => {
|
|
106
|
+
const token = await (0, auth_1.requireAuth)();
|
|
107
|
+
const spinner = (0, ora_1.default)('Fetching template...').start();
|
|
108
|
+
try {
|
|
109
|
+
const tmpl = await api_1.ApiClient.get(`/templates/by-name/${encodeURIComponent(name)}`, token);
|
|
110
|
+
spinner.stop();
|
|
111
|
+
const variables = tmpl.variables ?? [];
|
|
112
|
+
if (!variables.length) {
|
|
113
|
+
console.error(chalk_1.default.red('\n Template has no variables\n'));
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
console.log(chalk_1.default.bold(`\n ${tmpl.name}`));
|
|
117
|
+
console.log(chalk_1.default.dim(` ${tmpl.template}\n`));
|
|
118
|
+
// Prompt for each variable
|
|
119
|
+
const answers = {};
|
|
120
|
+
for (const variable of variables) {
|
|
121
|
+
const { value } = await inquirer_1.default.prompt([{
|
|
122
|
+
type: 'input',
|
|
123
|
+
name: 'value',
|
|
124
|
+
message: `${chalk_1.default.cyan(variable)}:`,
|
|
125
|
+
validate: (v) => v.length > 0 || `${variable} is required`,
|
|
126
|
+
}]);
|
|
127
|
+
answers[variable] = value;
|
|
128
|
+
}
|
|
129
|
+
// Render the template
|
|
130
|
+
const { rendered } = await api_1.ApiClient.post(`/templates/${tmpl.id}/render`, { vars: answers }, token);
|
|
131
|
+
console.log();
|
|
132
|
+
console.log(chalk_1.default.dim(' Rendered command:'));
|
|
133
|
+
console.log(` ${chalk_1.default.white(rendered)}`);
|
|
134
|
+
console.log();
|
|
135
|
+
if (opts.dryRun)
|
|
136
|
+
return;
|
|
137
|
+
const { confirm } = await inquirer_1.default.prompt([{
|
|
138
|
+
type: 'confirm',
|
|
139
|
+
name: 'confirm',
|
|
140
|
+
message: 'Run this command?',
|
|
141
|
+
default: true,
|
|
142
|
+
}]);
|
|
143
|
+
if (!confirm) {
|
|
144
|
+
console.log(chalk_1.default.dim('\n Cancelled.\n'));
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
console.log();
|
|
148
|
+
(0, child_process_1.execSync)(rendered, { stdio: 'inherit' });
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
spinner.stop();
|
|
152
|
+
console.error(chalk_1.default.red(`\n ${err.message ?? 'Failed'}\n`));
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
const templateDelete = new commander_1.Command('delete')
|
|
156
|
+
.argument('<name>', 'Template name')
|
|
157
|
+
.description('Delete a template')
|
|
158
|
+
.action(async (name) => {
|
|
159
|
+
const token = await (0, auth_1.requireAuth)();
|
|
160
|
+
const spinner = (0, ora_1.default)('Looking up...').start();
|
|
161
|
+
try {
|
|
162
|
+
const tmpl = await api_1.ApiClient.get(`/templates/by-name/${encodeURIComponent(name)}`, token);
|
|
163
|
+
spinner.text = 'Deleting...';
|
|
164
|
+
await api_1.ApiClient.delete(`/templates/${tmpl.id}`, token);
|
|
165
|
+
spinner.succeed(chalk_1.default.green(`Deleted template "${name}"`));
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
spinner.fail(chalk_1.default.red(`Template "${name}" not found`));
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
exports.templateCommand = new commander_1.Command('template')
|
|
172
|
+
.description('Save and run command templates with variables')
|
|
173
|
+
.addCommand(templateSave)
|
|
174
|
+
.addCommand(templateList)
|
|
175
|
+
.addCommand(templateShow)
|
|
176
|
+
.addCommand(templateRun)
|
|
177
|
+
.addCommand(templateDelete);
|
|
@@ -0,0 +1,66 @@
|
|
|
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.updateCommand = 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
|
+
exports.updateCommand = new commander_1.Command('update')
|
|
14
|
+
.description('Update a saved command name, command, or tags')
|
|
15
|
+
.argument('<name>', 'Current command name')
|
|
16
|
+
.action(async (name) => {
|
|
17
|
+
const token = await (0, auth_1.requireAuth)();
|
|
18
|
+
const spinner = (0, ora_1.default)('Looking up...').start();
|
|
19
|
+
let cmd;
|
|
20
|
+
try {
|
|
21
|
+
cmd = await api_1.ApiClient.get(`/commands/by-name/${encodeURIComponent(name)}`, token);
|
|
22
|
+
spinner.stop();
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
spinner.fail(chalk_1.default.red(`Command "${name}" not found`));
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
// Show current values
|
|
29
|
+
console.log();
|
|
30
|
+
console.log(chalk_1.default.dim(' Current values — press Enter to keep, or type to update\n'));
|
|
31
|
+
const answers = await inquirer_1.default.prompt([
|
|
32
|
+
{
|
|
33
|
+
type: 'input',
|
|
34
|
+
name: 'name',
|
|
35
|
+
message: 'Name:',
|
|
36
|
+
default: cmd.name ?? '',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
type: 'input',
|
|
40
|
+
name: 'command',
|
|
41
|
+
message: 'Command:',
|
|
42
|
+
default: cmd.command,
|
|
43
|
+
validate: (v) => v.length > 0 || 'Command cannot be empty',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
type: 'input',
|
|
47
|
+
name: 'tags',
|
|
48
|
+
message: 'Tags (comma-separated):',
|
|
49
|
+
default: (cmd.tags ?? []).join(', '),
|
|
50
|
+
},
|
|
51
|
+
]);
|
|
52
|
+
const updateSpinner = (0, ora_1.default)('Updating...').start();
|
|
53
|
+
try {
|
|
54
|
+
await api_1.ApiClient.patch(`/commands/${cmd.id}`, {
|
|
55
|
+
name: answers.name || undefined,
|
|
56
|
+
command: answers.command,
|
|
57
|
+
tags: answers.tags
|
|
58
|
+
? answers.tags.split(',').map((t) => t.trim()).filter(Boolean)
|
|
59
|
+
: [],
|
|
60
|
+
}, token);
|
|
61
|
+
updateSpinner.succeed(chalk_1.default.green(`Updated "${answers.name || name}"`));
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
updateSpinner.fail(chalk_1.default.red('Failed to update'));
|
|
65
|
+
}
|
|
66
|
+
});
|