@devkitvault/recall 1.2.10 → 1.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,10 +1,13 @@
1
1
  # @devkitvault/recall
2
2
 
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.
3
+ I kept losing useful shell commands. So I built a small vault for the ones I actually reuse.
4
4
 
5
- Product site and docs: [recall.devkitvault.com](https://recall.devkitvault.com)
5
+ **recall** saves commands locally, lets you search and run them by name, and optionally syncs when you want the same vault on another machine.
6
6
 
7
- Open source: [github.com/devkitvault/recall-cli](https://github.com/devkitvault/recall-cli)
7
+ - **Free:** local vault only — no account, nothing uploads (`~/.recall/commands.json`)
8
+ - **Pro / Team:** `recall sync` + Ask (prints a suggestion; never runs it)
9
+
10
+ Product: [recall.devkitvault.com](https://recall.devkitvault.com) · Docs: [docs](https://recall.devkitvault.com/docs)
8
11
 
9
12
  ## Requirements
10
13
 
@@ -23,45 +26,43 @@ recall --version
23
26
  recall doctor
24
27
  ```
25
28
 
26
- ## Quick start (local vault)
29
+ ## Quick start (Free — local)
27
30
 
28
- Local save / list / search / run work **without an account**. Auth is only needed for **sync**, **Ask** (Pro/Team), and team features.
31
+ No account needed.
29
32
 
30
33
  ```sh
31
- recall save "docker compose up -d" --name docker --tags docker
34
+ recall save "docker compose up -d" -n up -t docker
32
35
  recall save --last -n useful -t docker
33
- recall import history
36
+ recall import history -n 20
34
37
  recall list
35
38
  recall search docker
36
- recall run docker
39
+ recall run up
37
40
  ```
38
41
 
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.
42
+ `recall import history` reads your shell history file (zsh / bash / PowerShell) and lets you pick what to keep. It does not sit in the background recording keystrokes.
40
43
 
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`.
44
+ `recall save --last` (or `-L`) takes the last history line. Not supported in Windows `cmd.exe`. On bash, if it’s empty: `history -a && recall save --last`.
42
45
 
43
46
  ```sh
44
47
  recall playbook save deploy "docker compose up -d" "pnpm migrate"
45
48
  recall playbook run deploy --dry-run
46
49
  ```
47
50
 
48
- Local playbooks follow the same idea when available; cloud playbooks need Pro. `recall playbook run` executes steps. Ask never does.
51
+ `recall playbook run` executes steps. Ask never does.
49
52
 
50
53
  ## Ask (Pro / Team)
51
54
 
52
- Natural-language Ask prints a suggestion and never runs it. Requires a Pro or Team plan.
53
-
54
55
  ```sh
55
56
  recall auth login
56
57
  recall "find the 10 biggest files in this directory"
57
58
  rec "that docker command from last week"
58
59
  ```
59
60
 
60
- No-args `recall` opens an Ask session (`Recall >`). `/save [name]` stores the last suggestion. `/exit` leaves.
61
+ Ask only prints. You run the command yourself.
61
62
 
62
- ## Auth & sync (Pro / Team)
63
+ No-args `recall` opens an Ask session (`Recall >`). `/save [name]` stores the last suggestion. `/exit` leaves.
63
64
 
64
- Account + Pro/Team syncs your **local** commands to the cloud and unlocks Ask.
65
+ ## Sync (Pro / Team)
65
66
 
66
67
  ```sh
67
68
  recall auth login
@@ -71,12 +72,13 @@ recall sync --push # local-only → cloud
71
72
  recall sync --status
72
73
  recall whoami
73
74
  ```
74
- ## Prefer a standalone binary?
75
+
76
+ ## Native binary (optional)
75
77
 
76
78
  - **macOS / Linux:** `curl -fsSL https://devkitvault.com/recall/install.sh | sh`
77
79
  - **Windows:** `irm https://devkitvault.com/recall/install.ps1 | iex`
78
80
 
79
- See [recall.devkitvault.com/docs/install](https://recall.devkitvault.com/docs/install).
81
+ See [install docs](https://recall.devkitvault.com/docs/install).
80
82
 
81
83
  ## Update
82
84
 
@@ -91,18 +93,17 @@ recall doctor
91
93
  recall whoami
92
94
  ```
93
95
 
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).
96
+ Issues: [github.com/devkitvaultorg/recall-cli/issues](https://github.com/devkitvaultorg/recall-cli/issues)
95
97
 
96
98
  ## Links
97
99
 
98
- | | |
100
+ | | |
99
101
  | --------- | ----------------------------------------------------------------------------------------- |
100
- | Website | [recall.devkitvault.com](https://recall.devkitvault.com) |
101
- | Docs | [recall.devkitvault.com/docs](https://recall.devkitvault.com/docs) |
102
- | Dashboard | [recall.devkitvault.com](https://recall.devkitvault.com) |
103
- | Source | [github.com/devkitvault/recall-cli](https://github.com/devkitvault/recall-cli) |
104
- | VS Code | [Marketplace](https://marketplace.visualstudio.com/items?itemName=devkitvault.recall-cmd) |
105
- | Issues | [github.com/devkitvault/recall-cli](https://github.com/devkitvault/recall-cli/issues) |
102
+ | Website | [recall.devkitvault.com](https://recall.devkitvault.com) |
103
+ | Docs | [recall.devkitvault.com/docs](https://recall.devkitvault.com/docs) |
104
+ | Source | [github.com/devkitvaultorg/recall-cli](https://github.com/devkitvaultorg/recall-cli) |
105
+ | npm | [@devkitvault/recall](https://www.npmjs.com/package/@devkitvault/recall) |
106
+ | VS Code | [Marketplace](https://marketplace.visualstudio.com/items?itemName=devkitvault.recall-cmd) |
106
107
 
107
108
  ## License
108
109
 
@@ -10,29 +10,47 @@ const commander_1 = require("commander");
10
10
  const ora_1 = __importDefault(require("ora"));
11
11
  const api_1 = require("../lib/api");
12
12
  const auth_1 = require("../lib/auth");
13
+ const local_collections_1 = require("../lib/local-collections");
13
14
  const local_vault_1 = require("../lib/local-vault");
14
15
  exports.syncCommand = new commander_1.Command('sync')
15
16
  .description('Sync local vault ↔ cloud (Pro/Team)')
16
17
  .option('-s, --status', 'Show local vault + last sync status')
17
18
  .option('--pull', 'Pull cloud → local only')
18
- .option('--push', 'Push local-only commands → cloud only')
19
+ .option('--push', 'Push local-only items → cloud only')
19
20
  .action(async (opts) => {
20
21
  const vault = (0, local_vault_1.readVault)();
21
22
  if (opts.status) {
23
+ const groups = (0, local_collections_1.listLocalGroups)().length;
24
+ const templates = (0, local_collections_1.listLocalTemplates)().length;
25
+ const snippets = (0, local_collections_1.listLocalSnippets)().length;
26
+ const envSets = (0, local_collections_1.listLocalEnvSets)().length;
27
+ const aliases = (0, local_collections_1.listLocalAliases)().length;
22
28
  console.log();
23
- console.log(` Local commands: ${chalk_1.default.white(vault.commands.length)}`);
24
- console.log(` Vault file: ${chalk_1.default.dim(local_vault_1.VAULT_FILE)}`);
29
+ console.log(chalk_1.default.white(' Local vault (Free soft caps)'));
30
+ console.log(` Commands: ${chalk_1.default.white(vault.commands.length)}/${local_collections_1.LOCAL_SOFT_CAPS.commands}`);
31
+ console.log(` Groups: ${chalk_1.default.white(groups)}/${local_collections_1.LOCAL_SOFT_CAPS.groups}`);
32
+ console.log(` Templates: ${chalk_1.default.white(templates)}/${local_collections_1.LOCAL_SOFT_CAPS.templates}`);
33
+ console.log(` Snippets: ${chalk_1.default.white(snippets)}/${local_collections_1.LOCAL_SOFT_CAPS.snippets}`);
34
+ console.log(` Env sets: ${chalk_1.default.white(envSets)}/${local_collections_1.LOCAL_SOFT_CAPS.envSets}`);
35
+ console.log(` Aliases: ${chalk_1.default.white(aliases)}/${local_collections_1.LOCAL_SOFT_CAPS.aliases}`);
36
+ console.log(` Vault file: ${chalk_1.default.dim(local_vault_1.VAULT_FILE)}`);
25
37
  if (!vault.syncedAt) {
26
38
  console.log(chalk_1.default.dim(' Never synced to cloud.'));
27
39
  }
28
40
  else {
29
41
  const ago = Math.round((Date.now() - new Date(vault.syncedAt).getTime()) / 1000 / 60);
30
- console.log(` Last synced: ${chalk_1.default.white(ago < 1 ? 'just now' : `${ago} minutes ago`)}`);
42
+ console.log(` Last synced: ${chalk_1.default.white(ago < 1 ? 'just now' : `${ago} minutes ago`)}`);
31
43
  }
32
- const pending = (0, local_vault_1.localCommandsNeedingPush)().length;
44
+ const pending = (0, local_vault_1.localCommandsNeedingPush)().length +
45
+ (0, local_collections_1.groupsNeedingPush)().length +
46
+ (0, local_collections_1.templatesNeedingPush)().length +
47
+ (0, local_collections_1.snippetsNeedingPush)().length +
48
+ (0, local_collections_1.envSetsNeedingPush)().length +
49
+ (0, local_collections_1.aliasesNeedingPush)().length;
33
50
  if (pending) {
34
- console.log(` Not on cloud: ${chalk_1.default.yellow(String(pending))} (recall sync --push)`);
51
+ console.log(` Not on cloud: ${chalk_1.default.yellow(String(pending))} (recall sync --push)`);
35
52
  }
53
+ console.log(chalk_1.default.dim(' Sync requires Pro/Team. Free cloud quota is always 0.'));
36
54
  console.log();
37
55
  return;
38
56
  }
@@ -44,9 +62,11 @@ exports.syncCommand = new commander_1.Command('sync')
44
62
  const { user } = await api_1.ApiClient.get('/auth/me', token);
45
63
  const plan = (user.plan ?? 'free');
46
64
  spinner.stop();
65
+ // Anti-scam: never sync on Free — cloud Free quotas are 0
47
66
  if (plan !== 'pro' && plan !== 'team') {
48
67
  console.log(chalk_1.default.yellow('\n Cloud sync requires Pro or Team.'));
49
68
  console.log(chalk_1.default.dim(' Local vault still works without sync.'));
69
+ console.log(chalk_1.default.dim(' Free accounts cannot push to cloud (anti-abuse).'));
50
70
  console.log(chalk_1.default.dim(' Upgrade: recall upgrade\n'));
51
71
  process.exit(1);
52
72
  }
@@ -64,9 +84,56 @@ exports.syncCommand = new commander_1.Command('sync')
64
84
  const pullSpinner = (0, ora_1.default)('Pulling from cloud...').start();
65
85
  try {
66
86
  const { commands } = await api_1.ApiClient.get('/commands', token);
67
- const { added, updated } = (0, local_vault_1.mergeCloudIntoLocal)(commands);
68
- pulled = added + updated;
69
- pullSpinner.succeed(chalk_1.default.green(`Pulled cloud vault (${added} new, ${updated} updated)`));
87
+ const cmdMerge = (0, local_vault_1.mergeCloudIntoLocal)(commands);
88
+ pulled += cmdMerge.added + cmdMerge.updated;
89
+ try {
90
+ const { groups } = await api_1.ApiClient.get('/groups', token);
91
+ const g = (0, local_collections_1.mergeCloudGroups)(groups ?? []);
92
+ pulled += g.added + g.updated;
93
+ }
94
+ catch {
95
+ /* Free/legacy — ignore */
96
+ }
97
+ try {
98
+ const { templates } = await api_1.ApiClient.get('/templates', token);
99
+ const t = (0, local_collections_1.mergeCloudTemplates)(templates ?? []);
100
+ pulled += t.added + t.updated;
101
+ }
102
+ catch {
103
+ /* ignore */
104
+ }
105
+ try {
106
+ const { snippets } = await api_1.ApiClient.get('/snippets', token);
107
+ const s = (0, local_collections_1.mergeCloudSnippets)(snippets ?? []);
108
+ pulled += s.added + s.updated;
109
+ }
110
+ catch {
111
+ /* ignore */
112
+ }
113
+ // Env list omits vars — fetch each by name
114
+ try {
115
+ const { envSets } = await api_1.ApiClient.get('/env', token);
116
+ const full = [];
117
+ for (const e of envSets ?? []) {
118
+ try {
119
+ const row = await api_1.ApiClient.get(`/env/${encodeURIComponent(e.name)}`, token);
120
+ full.push({
121
+ id: row.id,
122
+ name: row.name,
123
+ vars: row.vars ?? {},
124
+ });
125
+ }
126
+ catch {
127
+ /* skip */
128
+ }
129
+ }
130
+ const em = (0, local_collections_1.mergeCloudEnvSets)(full);
131
+ pulled += em.added + em.updated;
132
+ }
133
+ catch {
134
+ /* ignore */
135
+ }
136
+ pullSpinner.succeed(chalk_1.default.green(`Pulled cloud vault (≈${pulled} changes)`));
70
137
  }
71
138
  catch (err) {
72
139
  pullSpinner.fail(chalk_1.default.red('Pull failed'));
@@ -77,32 +144,76 @@ exports.syncCommand = new commander_1.Command('sync')
77
144
  }
78
145
  }
79
146
  if (doPush) {
80
- const pending = (0, local_vault_1.localCommandsNeedingPush)();
81
- if (!pending.length) {
82
- console.log(chalk_1.default.dim(' Nothing new to push.'));
83
- }
84
- else {
85
- const pushSpinner = (0, ora_1.default)(`Pushing ${pending.length} local command(s)...`).start();
86
- try {
87
- for (const cmd of pending) {
88
- const saved = await api_1.ApiClient.post('/commands', {
89
- command: cmd.command,
90
- name: cmd.name,
91
- tags: cmd.tags,
92
- }, token);
93
- if (saved?.id)
94
- (0, local_vault_1.attachCloudId)(cmd.id, saved.id);
95
- pushed++;
96
- }
97
- pushSpinner.succeed(chalk_1.default.green(`Pushed ${pushed} command${pushed === 1 ? '' : 's'} to cloud`));
147
+ const pushSpinner = (0, ora_1.default)('Pushing local items...').start();
148
+ try {
149
+ for (const cmd of (0, local_vault_1.localCommandsNeedingPush)()) {
150
+ const saved = await api_1.ApiClient.post('/commands', {
151
+ command: cmd.command,
152
+ name: cmd.name,
153
+ tags: cmd.tags,
154
+ }, token);
155
+ if (saved?.id)
156
+ (0, local_vault_1.attachCloudId)(cmd.id, saved.id);
157
+ pushed++;
158
+ }
159
+ for (const g of (0, local_collections_1.groupsNeedingPush)()) {
160
+ const saved = await api_1.ApiClient.post('/groups', { name: g.name }, token);
161
+ if (saved?.id)
162
+ (0, local_collections_1.attachGroupCloudId)(g.id, saved.id);
163
+ pushed++;
98
164
  }
99
- catch (err) {
100
- pushSpinner.fail(chalk_1.default.red('Push failed'));
101
- if (err instanceof api_1.ApiError && err.status === 403) {
102
- console.log(chalk_1.default.yellow('\n Cloud save requires Pro. Local vault is unchanged.\n'));
165
+ for (const t of (0, local_collections_1.templatesNeedingPush)()) {
166
+ const saved = await api_1.ApiClient.post('/templates', { name: t.name, template: t.template, tags: t.tags ?? [] }, token);
167
+ if (saved?.id)
168
+ (0, local_collections_1.attachTemplateCloudId)(t.id, saved.id);
169
+ pushed++;
170
+ }
171
+ for (const s of (0, local_collections_1.snippetsNeedingPush)()) {
172
+ const saved = await api_1.ApiClient.post('/snippets', { name: s.name, content: s.content, tags: s.tags ?? [] }, token);
173
+ if (saved?.id)
174
+ (0, local_collections_1.attachSnippetCloudId)(s.id, saved.id);
175
+ pushed++;
176
+ }
177
+ for (const e of (0, local_collections_1.envSetsNeedingPush)()) {
178
+ const saved = await api_1.ApiClient.post('/env', { name: e.name, vars: e.vars }, token);
179
+ // Env upsert may return without id — treat name as synced marker via cloudId if present
180
+ if (saved?.id)
181
+ (0, local_collections_1.attachEnvCloudId)(e.id, saved.id);
182
+ else if (saved?.name)
183
+ (0, local_collections_1.attachEnvCloudId)(e.id, saved.name);
184
+ pushed++;
185
+ }
186
+ // Aliases need cloud commandId — resolve via local command's cloudId
187
+ for (const a of (0, local_collections_1.aliasesNeedingPush)()) {
188
+ const localCmd = (0, local_vault_1.listLocalCommands)().find((c) => c.id === a.localCommandId);
189
+ const cloudCommandId = localCmd?.cloudId ??
190
+ (localCmd?.name ? (0, local_vault_1.findLocalByName)(localCmd.name)?.cloudId : undefined);
191
+ if (!cloudCommandId) {
192
+ continue; // skip until command is synced
103
193
  }
104
- process.exit(1);
194
+ const saved = await api_1.ApiClient.post('/aliases', { alias: a.alias, commandId: cloudCommandId }, token);
195
+ if (saved?.id)
196
+ (0, local_collections_1.attachAliasCloudId)(a.id, saved.id);
197
+ else
198
+ (0, local_collections_1.attachAliasCloudId)(a.id, a.alias);
199
+ pushed++;
200
+ }
201
+ if (!pushed) {
202
+ pushSpinner.succeed(chalk_1.default.dim('Nothing new to push'));
105
203
  }
204
+ else {
205
+ pushSpinner.succeed(chalk_1.default.green(`Pushed ${pushed} item${pushed === 1 ? '' : 's'} to cloud`));
206
+ }
207
+ }
208
+ catch (err) {
209
+ pushSpinner.fail(chalk_1.default.red('Push failed'));
210
+ if (err instanceof api_1.ApiError && err.status === 403) {
211
+ console.log(chalk_1.default.yellow('\n Cloud save blocked (plan limit or Free). Local vault unchanged.\n'));
212
+ }
213
+ else if (err instanceof Error) {
214
+ console.log(chalk_1.default.red(`\n ${err.message}\n`));
215
+ }
216
+ process.exit(1);
106
217
  }
107
218
  }
108
219
  console.log();
@@ -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.2.10';
14
+ exports.APP_VERSION = '1.2.11';
15
15
  exports.ENVIRONMENTS = {
16
16
  production: 'https://api.devkitvault.com',
17
17
  local: 'http://127.0.0.1:3001',
@@ -0,0 +1,617 @@
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.LocalVaultSizeError = exports.LocalVaultLimitError = exports.LOCAL_SIZE_LIMITS = exports.LOCAL_SOFT_CAPS = void 0;
7
+ exports.listLocalGroups = listLocalGroups;
8
+ exports.saveLocalGroup = saveLocalGroup;
9
+ exports.deleteLocalGroup = deleteLocalGroup;
10
+ exports.renameLocalGroup = renameLocalGroup;
11
+ exports.groupsNeedingPush = groupsNeedingPush;
12
+ exports.attachGroupCloudId = attachGroupCloudId;
13
+ exports.mergeCloudGroups = mergeCloudGroups;
14
+ exports.listLocalTemplates = listLocalTemplates;
15
+ exports.saveLocalTemplate = saveLocalTemplate;
16
+ exports.updateLocalTemplate = updateLocalTemplate;
17
+ exports.deleteLocalTemplate = deleteLocalTemplate;
18
+ exports.templatesNeedingPush = templatesNeedingPush;
19
+ exports.attachTemplateCloudId = attachTemplateCloudId;
20
+ exports.mergeCloudTemplates = mergeCloudTemplates;
21
+ exports.listLocalSnippets = listLocalSnippets;
22
+ exports.saveLocalSnippet = saveLocalSnippet;
23
+ exports.updateLocalSnippet = updateLocalSnippet;
24
+ exports.deleteLocalSnippet = deleteLocalSnippet;
25
+ exports.snippetsNeedingPush = snippetsNeedingPush;
26
+ exports.attachSnippetCloudId = attachSnippetCloudId;
27
+ exports.mergeCloudSnippets = mergeCloudSnippets;
28
+ exports.listLocalEnvSets = listLocalEnvSets;
29
+ exports.saveLocalEnvSet = saveLocalEnvSet;
30
+ exports.deleteLocalEnvSet = deleteLocalEnvSet;
31
+ exports.updateLocalEnvSet = updateLocalEnvSet;
32
+ exports.envSetsNeedingPush = envSetsNeedingPush;
33
+ exports.attachEnvCloudId = attachEnvCloudId;
34
+ exports.mergeCloudEnvSets = mergeCloudEnvSets;
35
+ exports.listLocalAliases = listLocalAliases;
36
+ exports.saveLocalAlias = saveLocalAlias;
37
+ exports.deleteLocalAlias = deleteLocalAlias;
38
+ exports.aliasesNeedingPush = aliasesNeedingPush;
39
+ exports.attachAliasCloudId = attachAliasCloudId;
40
+ exports.localCollectionCounts = localCollectionCounts;
41
+ /**
42
+ * Free local collections under ~/.recall/ (separate files).
43
+ * Soft caps + size limits prevent vault abuse; cloud sync still requires Pro/Team
44
+ * and server-side plan gates.
45
+ */
46
+ const node_crypto_1 = require("node:crypto");
47
+ const node_fs_1 = __importDefault(require("node:fs"));
48
+ const node_os_1 = __importDefault(require("node:os"));
49
+ const node_path_1 = __importDefault(require("node:path"));
50
+ const VAULT_DIR = node_path_1.default.join(process.env.RECALL_HOME?.trim() || node_os_1.default.homedir(), '.recall');
51
+ exports.LOCAL_SOFT_CAPS = {
52
+ commands: 500,
53
+ groups: 50,
54
+ templates: 50,
55
+ snippets: 50,
56
+ envSets: 50,
57
+ aliases: 100,
58
+ };
59
+ exports.LOCAL_SIZE_LIMITS = {
60
+ commandChars: 10_000,
61
+ templateChars: 10_000,
62
+ snippetContentChars: 100_000,
63
+ envVarsJsonChars: 50_000,
64
+ nameChars: 200,
65
+ aliasChars: 64,
66
+ };
67
+ class LocalVaultLimitError extends Error {
68
+ kind;
69
+ limit;
70
+ current;
71
+ constructor(kind, limit, current) {
72
+ super(`Local ${kind} limit reached (${current}/${limit}). Delete some or upgrade to Pro to sync a subset.`);
73
+ this.kind = kind;
74
+ this.limit = limit;
75
+ this.current = current;
76
+ this.name = 'LocalVaultLimitError';
77
+ }
78
+ }
79
+ exports.LocalVaultLimitError = LocalVaultLimitError;
80
+ class LocalVaultSizeError extends Error {
81
+ constructor(message) {
82
+ super(message);
83
+ this.name = 'LocalVaultSizeError';
84
+ }
85
+ }
86
+ exports.LocalVaultSizeError = LocalVaultSizeError;
87
+ function nowIso() {
88
+ return new Date().toISOString();
89
+ }
90
+ function ensureDir() {
91
+ node_fs_1.default.mkdirSync(VAULT_DIR, { recursive: true });
92
+ }
93
+ function collectionPath(file) {
94
+ return node_path_1.default.join(VAULT_DIR, file);
95
+ }
96
+ function readCollection(file) {
97
+ const p = collectionPath(file);
98
+ try {
99
+ if (!node_fs_1.default.existsSync(p)) {
100
+ return { version: 1, updatedAt: nowIso(), syncedAt: null, items: [] };
101
+ }
102
+ const parsed = JSON.parse(node_fs_1.default.readFileSync(p, 'utf-8'));
103
+ const items = Array.isArray(parsed.items)
104
+ ? parsed.items
105
+ : Array.isArray(parsed.groups)
106
+ ? parsed.groups
107
+ : Array.isArray(parsed.templates)
108
+ ? parsed.templates
109
+ : Array.isArray(parsed.snippets)
110
+ ? parsed.snippets
111
+ : Array.isArray(parsed.envSets)
112
+ ? parsed.envSets
113
+ : Array.isArray(parsed.aliases)
114
+ ? parsed.aliases
115
+ : [];
116
+ return {
117
+ version: 1,
118
+ updatedAt: typeof parsed.updatedAt === 'string' ? parsed.updatedAt : nowIso(),
119
+ syncedAt: typeof parsed.syncedAt === 'string' ? parsed.syncedAt : null,
120
+ items,
121
+ };
122
+ }
123
+ catch {
124
+ return { version: 1, updatedAt: nowIso(), syncedAt: null, items: [] };
125
+ }
126
+ }
127
+ function writeCollection(file, key, items, syncedAt) {
128
+ ensureDir();
129
+ const payload = {
130
+ version: 1,
131
+ updatedAt: nowIso(),
132
+ syncedAt: syncedAt ?? null,
133
+ [key]: items,
134
+ items, // dual-write for forward readers
135
+ };
136
+ node_fs_1.default.writeFileSync(collectionPath(file), JSON.stringify(payload, null, 2), { mode: 0o600 });
137
+ }
138
+ function assertUnderCap(kind, current) {
139
+ const limit = exports.LOCAL_SOFT_CAPS[kind];
140
+ if (current >= limit) {
141
+ throw new LocalVaultLimitError(kind, limit, current);
142
+ }
143
+ }
144
+ function assertName(name) {
145
+ const t = name.trim();
146
+ if (!t)
147
+ throw new LocalVaultSizeError('Name is required');
148
+ if (t.length > exports.LOCAL_SIZE_LIMITS.nameChars) {
149
+ throw new LocalVaultSizeError(`Name too long (max ${exports.LOCAL_SIZE_LIMITS.nameChars})`);
150
+ }
151
+ return t;
152
+ }
153
+ function listLocalGroups() {
154
+ return readCollection('groups.json').items.sort((a, b) => a.name.localeCompare(b.name));
155
+ }
156
+ function saveLocalGroup(name) {
157
+ const n = assertName(name);
158
+ const file = readCollection('groups.json');
159
+ const existing = file.items.find((g) => g.name.toLowerCase() === n.toLowerCase());
160
+ if (existing) {
161
+ existing.name = n;
162
+ existing.updatedAt = nowIso();
163
+ writeCollection('groups.json', 'groups', file.items, file.syncedAt);
164
+ return existing;
165
+ }
166
+ assertUnderCap('groups', file.items.length);
167
+ const row = {
168
+ id: (0, node_crypto_1.randomUUID)(),
169
+ name: n,
170
+ createdAt: nowIso(),
171
+ updatedAt: nowIso(),
172
+ };
173
+ file.items.push(row);
174
+ writeCollection('groups.json', 'groups', file.items, file.syncedAt);
175
+ return row;
176
+ }
177
+ function deleteLocalGroup(id) {
178
+ const file = readCollection('groups.json');
179
+ const idx = file.items.findIndex((g) => g.id === id);
180
+ if (idx < 0)
181
+ return undefined;
182
+ const [removed] = file.items.splice(idx, 1);
183
+ writeCollection('groups.json', 'groups', file.items, file.syncedAt);
184
+ return removed;
185
+ }
186
+ function renameLocalGroup(id, name) {
187
+ const n = assertName(name);
188
+ const file = readCollection('groups.json');
189
+ const row = file.items.find((g) => g.id === id);
190
+ if (!row)
191
+ return undefined;
192
+ row.name = n;
193
+ row.updatedAt = nowIso();
194
+ writeCollection('groups.json', 'groups', file.items, file.syncedAt);
195
+ return row;
196
+ }
197
+ function groupsNeedingPush() {
198
+ return listLocalGroups().filter((g) => !g.cloudId);
199
+ }
200
+ function attachGroupCloudId(localId, cloudId) {
201
+ const file = readCollection('groups.json');
202
+ const row = file.items.find((g) => g.id === localId);
203
+ if (!row)
204
+ return;
205
+ row.cloudId = cloudId;
206
+ row.updatedAt = nowIso();
207
+ writeCollection('groups.json', 'groups', file.items, file.syncedAt);
208
+ }
209
+ function mergeCloudGroups(cloud) {
210
+ const file = readCollection('groups.json');
211
+ const stamp = nowIso();
212
+ let added = 0;
213
+ let updated = 0;
214
+ for (const c of cloud) {
215
+ const byCloud = file.items.find((g) => g.cloudId === c.id);
216
+ const byName = file.items.find((g) => g.name.toLowerCase() === c.name.toLowerCase());
217
+ const target = byCloud ?? byName;
218
+ if (target) {
219
+ target.cloudId = c.id;
220
+ target.name = c.name;
221
+ target.updatedAt = stamp;
222
+ updated++;
223
+ }
224
+ else {
225
+ file.items.push({
226
+ id: (0, node_crypto_1.randomUUID)(),
227
+ cloudId: c.id,
228
+ name: c.name,
229
+ createdAt: c.createdAt ?? stamp,
230
+ updatedAt: stamp,
231
+ });
232
+ added++;
233
+ }
234
+ }
235
+ writeCollection('groups.json', 'groups', file.items, stamp);
236
+ return { added, updated };
237
+ }
238
+ function listLocalTemplates() {
239
+ return readCollection('templates.json').items.sort((a, b) => a.name.localeCompare(b.name));
240
+ }
241
+ function saveLocalTemplate(input) {
242
+ const name = assertName(input.name);
243
+ const template = input.template.trim();
244
+ if (!template)
245
+ throw new LocalVaultSizeError('Template body is required');
246
+ if (template.length > exports.LOCAL_SIZE_LIMITS.templateChars) {
247
+ throw new LocalVaultSizeError(`Template too long (max ${exports.LOCAL_SIZE_LIMITS.templateChars})`);
248
+ }
249
+ const file = readCollection('templates.json');
250
+ const existing = file.items.find((t) => t.name.toLowerCase() === name.toLowerCase());
251
+ if (existing) {
252
+ existing.template = template;
253
+ existing.tags = input.tags?.length ? input.tags : undefined;
254
+ existing.updatedAt = nowIso();
255
+ writeCollection('templates.json', 'templates', file.items, file.syncedAt);
256
+ return existing;
257
+ }
258
+ assertUnderCap('templates', file.items.length);
259
+ const row = {
260
+ id: (0, node_crypto_1.randomUUID)(),
261
+ name,
262
+ template,
263
+ tags: input.tags?.length ? input.tags : undefined,
264
+ createdAt: nowIso(),
265
+ updatedAt: nowIso(),
266
+ };
267
+ file.items.push(row);
268
+ writeCollection('templates.json', 'templates', file.items, file.syncedAt);
269
+ return row;
270
+ }
271
+ function updateLocalTemplate(id, patch) {
272
+ const file = readCollection('templates.json');
273
+ const row = file.items.find((t) => t.id === id);
274
+ if (!row)
275
+ return undefined;
276
+ if (patch.name !== undefined)
277
+ row.name = assertName(patch.name);
278
+ if (patch.template !== undefined) {
279
+ const t = patch.template.trim();
280
+ if (t.length > exports.LOCAL_SIZE_LIMITS.templateChars) {
281
+ throw new LocalVaultSizeError(`Template too long (max ${exports.LOCAL_SIZE_LIMITS.templateChars})`);
282
+ }
283
+ row.template = t;
284
+ }
285
+ if (patch.tags !== undefined)
286
+ row.tags = patch.tags.length ? patch.tags : undefined;
287
+ row.updatedAt = nowIso();
288
+ writeCollection('templates.json', 'templates', file.items, file.syncedAt);
289
+ return row;
290
+ }
291
+ function deleteLocalTemplate(id) {
292
+ const file = readCollection('templates.json');
293
+ const idx = file.items.findIndex((t) => t.id === id);
294
+ if (idx < 0)
295
+ return undefined;
296
+ const [removed] = file.items.splice(idx, 1);
297
+ writeCollection('templates.json', 'templates', file.items, file.syncedAt);
298
+ return removed;
299
+ }
300
+ function templatesNeedingPush() {
301
+ return listLocalTemplates().filter((t) => !t.cloudId);
302
+ }
303
+ function attachTemplateCloudId(localId, cloudId) {
304
+ const file = readCollection('templates.json');
305
+ const row = file.items.find((t) => t.id === localId);
306
+ if (!row)
307
+ return;
308
+ row.cloudId = cloudId;
309
+ row.updatedAt = nowIso();
310
+ writeCollection('templates.json', 'templates', file.items, file.syncedAt);
311
+ }
312
+ function mergeCloudTemplates(cloud) {
313
+ const file = readCollection('templates.json');
314
+ const stamp = nowIso();
315
+ let added = 0;
316
+ let updated = 0;
317
+ for (const c of cloud) {
318
+ const byCloud = file.items.find((t) => t.cloudId === c.id);
319
+ const byName = file.items.find((t) => t.name.toLowerCase() === c.name.toLowerCase());
320
+ const target = byCloud ?? byName;
321
+ if (target) {
322
+ target.cloudId = c.id;
323
+ target.name = c.name;
324
+ target.template = c.template;
325
+ target.tags = c.tags?.length ? c.tags : undefined;
326
+ target.updatedAt = stamp;
327
+ updated++;
328
+ }
329
+ else {
330
+ file.items.push({
331
+ id: (0, node_crypto_1.randomUUID)(),
332
+ cloudId: c.id,
333
+ name: c.name,
334
+ template: c.template,
335
+ tags: c.tags?.length ? c.tags : undefined,
336
+ createdAt: stamp,
337
+ updatedAt: stamp,
338
+ });
339
+ added++;
340
+ }
341
+ }
342
+ writeCollection('templates.json', 'templates', file.items, stamp);
343
+ return { added, updated };
344
+ }
345
+ function listLocalSnippets() {
346
+ return readCollection('snippets.json').items.sort((a, b) => a.name.localeCompare(b.name));
347
+ }
348
+ function saveLocalSnippet(input) {
349
+ const name = assertName(input.name);
350
+ if (input.content.length > exports.LOCAL_SIZE_LIMITS.snippetContentChars) {
351
+ throw new LocalVaultSizeError(`Snippet too large (max ${exports.LOCAL_SIZE_LIMITS.snippetContentChars} chars)`);
352
+ }
353
+ const file = readCollection('snippets.json');
354
+ const existing = file.items.find((s) => s.name.toLowerCase() === name.toLowerCase());
355
+ if (existing) {
356
+ existing.content = input.content;
357
+ existing.tags = input.tags?.length ? input.tags : undefined;
358
+ existing.updatedAt = nowIso();
359
+ writeCollection('snippets.json', 'snippets', file.items, file.syncedAt);
360
+ return existing;
361
+ }
362
+ assertUnderCap('snippets', file.items.length);
363
+ const row = {
364
+ id: (0, node_crypto_1.randomUUID)(),
365
+ name,
366
+ content: input.content,
367
+ tags: input.tags?.length ? input.tags : undefined,
368
+ createdAt: nowIso(),
369
+ updatedAt: nowIso(),
370
+ };
371
+ file.items.push(row);
372
+ writeCollection('snippets.json', 'snippets', file.items, file.syncedAt);
373
+ return row;
374
+ }
375
+ function updateLocalSnippet(id, patch) {
376
+ const file = readCollection('snippets.json');
377
+ const row = file.items.find((s) => s.id === id);
378
+ if (!row)
379
+ return undefined;
380
+ if (patch.name !== undefined)
381
+ row.name = assertName(patch.name);
382
+ if (patch.content !== undefined) {
383
+ if (patch.content.length > exports.LOCAL_SIZE_LIMITS.snippetContentChars) {
384
+ throw new LocalVaultSizeError(`Snippet too large (max ${exports.LOCAL_SIZE_LIMITS.snippetContentChars} chars)`);
385
+ }
386
+ row.content = patch.content;
387
+ }
388
+ if (patch.tags !== undefined)
389
+ row.tags = patch.tags.length ? patch.tags : undefined;
390
+ row.updatedAt = nowIso();
391
+ writeCollection('snippets.json', 'snippets', file.items, file.syncedAt);
392
+ return row;
393
+ }
394
+ function deleteLocalSnippet(id) {
395
+ const file = readCollection('snippets.json');
396
+ const idx = file.items.findIndex((s) => s.id === id);
397
+ if (idx < 0)
398
+ return undefined;
399
+ const [removed] = file.items.splice(idx, 1);
400
+ writeCollection('snippets.json', 'snippets', file.items, file.syncedAt);
401
+ return removed;
402
+ }
403
+ function snippetsNeedingPush() {
404
+ return listLocalSnippets().filter((s) => !s.cloudId);
405
+ }
406
+ function attachSnippetCloudId(localId, cloudId) {
407
+ const file = readCollection('snippets.json');
408
+ const row = file.items.find((s) => s.id === localId);
409
+ if (!row)
410
+ return;
411
+ row.cloudId = cloudId;
412
+ row.updatedAt = nowIso();
413
+ writeCollection('snippets.json', 'snippets', file.items, file.syncedAt);
414
+ }
415
+ function mergeCloudSnippets(cloud) {
416
+ const file = readCollection('snippets.json');
417
+ const stamp = nowIso();
418
+ let added = 0;
419
+ let updated = 0;
420
+ for (const c of cloud) {
421
+ const byCloud = file.items.find((s) => s.cloudId === c.id);
422
+ const byName = file.items.find((s) => s.name.toLowerCase() === c.name.toLowerCase());
423
+ const target = byCloud ?? byName;
424
+ if (target) {
425
+ target.cloudId = c.id;
426
+ target.name = c.name;
427
+ target.content = c.content;
428
+ target.tags = c.tags?.length ? c.tags : undefined;
429
+ target.updatedAt = stamp;
430
+ updated++;
431
+ }
432
+ else {
433
+ file.items.push({
434
+ id: (0, node_crypto_1.randomUUID)(),
435
+ cloudId: c.id,
436
+ name: c.name,
437
+ content: c.content,
438
+ tags: c.tags?.length ? c.tags : undefined,
439
+ createdAt: stamp,
440
+ updatedAt: stamp,
441
+ });
442
+ added++;
443
+ }
444
+ }
445
+ writeCollection('snippets.json', 'snippets', file.items, stamp);
446
+ return { added, updated };
447
+ }
448
+ function listLocalEnvSets() {
449
+ return readCollection('env.json').items.sort((a, b) => a.name.localeCompare(b.name));
450
+ }
451
+ function saveLocalEnvSet(input) {
452
+ const name = assertName(input.name);
453
+ const json = JSON.stringify(input.vars);
454
+ if (json.length > exports.LOCAL_SIZE_LIMITS.envVarsJsonChars) {
455
+ throw new LocalVaultSizeError(`Env set too large (max ${exports.LOCAL_SIZE_LIMITS.envVarsJsonChars} chars)`);
456
+ }
457
+ // Reject obvious secret-stuffing patterns in keys only (values stay local; sync still Pro-gated)
458
+ for (const k of Object.keys(input.vars)) {
459
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(k)) {
460
+ throw new LocalVaultSizeError(`Invalid env var name: ${k}`);
461
+ }
462
+ }
463
+ const file = readCollection('env.json');
464
+ const existing = file.items.find((e) => e.name.toLowerCase() === name.toLowerCase());
465
+ if (existing) {
466
+ existing.vars = input.vars;
467
+ existing.updatedAt = nowIso();
468
+ writeCollection('env.json', 'envSets', file.items, file.syncedAt);
469
+ return existing;
470
+ }
471
+ assertUnderCap('envSets', file.items.length);
472
+ const row = {
473
+ id: (0, node_crypto_1.randomUUID)(),
474
+ name,
475
+ vars: input.vars,
476
+ createdAt: nowIso(),
477
+ updatedAt: nowIso(),
478
+ };
479
+ file.items.push(row);
480
+ writeCollection('env.json', 'envSets', file.items, file.syncedAt);
481
+ return row;
482
+ }
483
+ function deleteLocalEnvSet(id) {
484
+ const file = readCollection('env.json');
485
+ const idx = file.items.findIndex((e) => e.id === id);
486
+ if (idx < 0)
487
+ return undefined;
488
+ const [removed] = file.items.splice(idx, 1);
489
+ writeCollection('env.json', 'envSets', file.items, file.syncedAt);
490
+ return removed;
491
+ }
492
+ function updateLocalEnvSet(id, patch) {
493
+ const file = readCollection('env.json');
494
+ const row = file.items.find((e) => e.id === id);
495
+ if (!row)
496
+ return undefined;
497
+ if (patch.name !== undefined)
498
+ row.name = assertName(patch.name);
499
+ if (patch.vars !== undefined) {
500
+ const json = JSON.stringify(patch.vars);
501
+ if (json.length > exports.LOCAL_SIZE_LIMITS.envVarsJsonChars) {
502
+ throw new LocalVaultSizeError(`Env set too large (max ${exports.LOCAL_SIZE_LIMITS.envVarsJsonChars} chars)`);
503
+ }
504
+ row.vars = patch.vars;
505
+ }
506
+ row.updatedAt = nowIso();
507
+ writeCollection('env.json', 'envSets', file.items, file.syncedAt);
508
+ return row;
509
+ }
510
+ function envSetsNeedingPush() {
511
+ return listLocalEnvSets().filter((e) => !e.cloudId);
512
+ }
513
+ function attachEnvCloudId(localId, cloudId) {
514
+ const file = readCollection('env.json');
515
+ const row = file.items.find((e) => e.id === localId);
516
+ if (!row)
517
+ return;
518
+ row.cloudId = cloudId;
519
+ row.updatedAt = nowIso();
520
+ writeCollection('env.json', 'envSets', file.items, file.syncedAt);
521
+ }
522
+ function mergeCloudEnvSets(cloud) {
523
+ const file = readCollection('env.json');
524
+ const stamp = nowIso();
525
+ let added = 0;
526
+ let updated = 0;
527
+ for (const c of cloud) {
528
+ if (!c.vars)
529
+ continue; // list endpoint may omit vars — skip incomplete
530
+ const byCloud = c.id ? file.items.find((e) => e.cloudId === c.id) : undefined;
531
+ const byName = file.items.find((e) => e.name.toLowerCase() === c.name.toLowerCase());
532
+ const target = byCloud ?? byName;
533
+ if (target) {
534
+ if (c.id)
535
+ target.cloudId = c.id;
536
+ target.name = c.name;
537
+ target.vars = c.vars;
538
+ target.updatedAt = stamp;
539
+ updated++;
540
+ }
541
+ else {
542
+ file.items.push({
543
+ id: (0, node_crypto_1.randomUUID)(),
544
+ cloudId: c.id,
545
+ name: c.name,
546
+ vars: c.vars,
547
+ createdAt: stamp,
548
+ updatedAt: stamp,
549
+ });
550
+ added++;
551
+ }
552
+ }
553
+ writeCollection('env.json', 'envSets', file.items, stamp);
554
+ return { added, updated };
555
+ }
556
+ function listLocalAliases() {
557
+ return readCollection('aliases.json').items.sort((a, b) => a.alias.localeCompare(b.alias));
558
+ }
559
+ function saveLocalAlias(input) {
560
+ const alias = input.alias.trim();
561
+ if (!alias || /\s/.test(alias)) {
562
+ throw new LocalVaultSizeError('Alias must be a single token without spaces');
563
+ }
564
+ if (alias.length > exports.LOCAL_SIZE_LIMITS.aliasChars) {
565
+ throw new LocalVaultSizeError(`Alias too long (max ${exports.LOCAL_SIZE_LIMITS.aliasChars})`);
566
+ }
567
+ const file = readCollection('aliases.json');
568
+ const existing = file.items.find((a) => a.alias.toLowerCase() === alias.toLowerCase());
569
+ if (existing) {
570
+ existing.localCommandId = input.localCommandId;
571
+ existing.updatedAt = nowIso();
572
+ writeCollection('aliases.json', 'aliases', file.items, file.syncedAt);
573
+ return existing;
574
+ }
575
+ assertUnderCap('aliases', file.items.length);
576
+ const row = {
577
+ id: (0, node_crypto_1.randomUUID)(),
578
+ alias,
579
+ localCommandId: input.localCommandId,
580
+ createdAt: nowIso(),
581
+ updatedAt: nowIso(),
582
+ };
583
+ file.items.push(row);
584
+ writeCollection('aliases.json', 'aliases', file.items, file.syncedAt);
585
+ return row;
586
+ }
587
+ function deleteLocalAlias(id) {
588
+ const file = readCollection('aliases.json');
589
+ const idx = file.items.findIndex((a) => a.id === id);
590
+ if (idx < 0)
591
+ return undefined;
592
+ const [removed] = file.items.splice(idx, 1);
593
+ writeCollection('aliases.json', 'aliases', file.items, file.syncedAt);
594
+ return removed;
595
+ }
596
+ function aliasesNeedingPush() {
597
+ return listLocalAliases().filter((a) => !a.cloudId);
598
+ }
599
+ function attachAliasCloudId(localId, cloudId) {
600
+ const file = readCollection('aliases.json');
601
+ const row = file.items.find((a) => a.id === localId);
602
+ if (!row)
603
+ return;
604
+ row.cloudId = cloudId;
605
+ row.updatedAt = nowIso();
606
+ writeCollection('aliases.json', 'aliases', file.items, file.syncedAt);
607
+ }
608
+ function localCollectionCounts() {
609
+ return {
610
+ commands: 0, // filled by caller from commands vault
611
+ groups: listLocalGroups().length,
612
+ templates: listLocalTemplates().length,
613
+ snippets: listLocalSnippets().length,
614
+ envSets: listLocalEnvSets().length,
615
+ aliases: listLocalAliases().length,
616
+ };
617
+ }
@@ -20,6 +20,7 @@ const node_crypto_1 = require("node:crypto");
20
20
  const node_fs_1 = __importDefault(require("node:fs"));
21
21
  const node_os_1 = __importDefault(require("node:os"));
22
22
  const node_path_1 = __importDefault(require("node:path"));
23
+ const local_collections_1 = require("./local-collections");
23
24
  const VAULT_DIR = node_path_1.default.join(process.env.RECALL_HOME?.trim() || node_os_1.default.homedir(), '.recall');
24
25
  exports.VAULT_FILE = node_path_1.default.join(VAULT_DIR, 'commands.json');
25
26
  function nowIso() {
@@ -127,13 +128,19 @@ function findLocalByName(name) {
127
128
  return readVault().commands.find((c) => c.name?.toLowerCase() === needle);
128
129
  }
129
130
  function saveLocalCommand(input) {
131
+ const command = input.command.trim();
132
+ if (!command)
133
+ throw new local_collections_1.LocalVaultSizeError('Command is required');
134
+ if (command.length > local_collections_1.LOCAL_SIZE_LIMITS.commandChars) {
135
+ throw new local_collections_1.LocalVaultSizeError(`Command too long (max ${local_collections_1.LOCAL_SIZE_LIMITS.commandChars})`);
136
+ }
130
137
  const vault = readVault();
131
138
  const stamp = nowIso();
132
139
  const name = input.name?.trim() || undefined;
133
140
  if (name) {
134
141
  const existing = vault.commands.find((c) => c.name?.toLowerCase() === name.toLowerCase());
135
142
  if (existing) {
136
- existing.command = input.command;
143
+ existing.command = command;
137
144
  existing.tags = input.tags?.length ? input.tags : undefined;
138
145
  existing.updatedAt = stamp;
139
146
  // Local edit invalidates cloud link until next push maps again
@@ -141,9 +148,12 @@ function saveLocalCommand(input) {
141
148
  return existing;
142
149
  }
143
150
  }
151
+ if (vault.commands.length >= local_collections_1.LOCAL_SOFT_CAPS.commands) {
152
+ throw new local_collections_1.LocalVaultLimitError('commands', local_collections_1.LOCAL_SOFT_CAPS.commands, vault.commands.length);
153
+ }
144
154
  const saved = {
145
155
  id: (0, node_crypto_1.randomUUID)(),
146
- command: input.command,
156
+ command,
147
157
  name,
148
158
  tags: input.tags?.length ? input.tags : undefined,
149
159
  createdAt: stamp,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devkitvault/recall",
3
- "version": "1.2.10",
3
+ "version": "1.2.11",
4
4
  "description": "recall CLI — personal command vault for your terminal (local-first)",
5
5
  "keywords": [
6
6
  "cli",
@@ -19,11 +19,11 @@
19
19
  "license": "MIT",
20
20
  "homepage": "https://recall.devkitvault.com",
21
21
  "bugs": {
22
- "url": "https://github.com/devkitvault/recall-cli/issues"
22
+ "url": "https://github.com/devkitvaultorg/recall-cli/issues"
23
23
  },
24
24
  "repository": {
25
25
  "type": "git",
26
- "url": "git+https://github.com/devkitvault/recall-cli.git"
26
+ "url": "git+https://github.com/devkitvaultorg/recall-cli.git"
27
27
  },
28
28
  "bin": {
29
29
  "recall": "dist/index.js",