@keeply-link/cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +279 -0
- package/dist/api.d.ts +20 -0
- package/dist/api.js +68 -0
- package/dist/commands/add.d.ts +2 -0
- package/dist/commands/add.js +61 -0
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +42 -0
- package/dist/commands/folders.d.ts +2 -0
- package/dist/commands/folders.js +32 -0
- package/dist/commands/get.d.ts +2 -0
- package/dist/commands/get.js +27 -0
- package/dist/commands/list.d.ts +2 -0
- package/dist/commands/list.js +73 -0
- package/dist/commands/search.d.ts +2 -0
- package/dist/commands/search.js +42 -0
- package/dist/commands/tags.d.ts +2 -0
- package/dist/commands/tags.js +33 -0
- package/dist/commands/update.d.ts +2 -0
- package/dist/commands/update.js +75 -0
- package/dist/config.d.ts +9 -0
- package/dist/config.js +25 -0
- package/dist/format.d.ts +13 -0
- package/dist/format.js +62 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +27 -0
- package/dist/types.d.ts +59 -0
- package/dist/types.js +1 -0
- package/package.json +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
# keeply-cli
|
|
2
|
+
|
|
3
|
+
Command-line interface for [Keeply](https://keeply.tools) — read, save, search, and manage your bookmarks from the terminal.
|
|
4
|
+
|
|
5
|
+
Designed to work great standalone and as a data source for AI agents and shell scripts.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
### Homebrew (macOS and Linux) — recommended
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
brew tap keeply-link/keeply
|
|
15
|
+
brew install keeply
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### npm (requires Node.js 18+)
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install -g @keeply-link/cli
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### From source
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
git clone https://github.com/keeply-link/keeply-cli
|
|
28
|
+
cd keeply-cli
|
|
29
|
+
npm install && npm run build
|
|
30
|
+
npm link
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Setup
|
|
36
|
+
|
|
37
|
+
Generate an API key in the Keeply web app under **Settings → API Keys**. Your key needs at minimum these scopes:
|
|
38
|
+
|
|
39
|
+
| Scope | Used by |
|
|
40
|
+
| ----------------- | ------------------------------ |
|
|
41
|
+
| `read_bookmarks` | `list`, `get`, `search` |
|
|
42
|
+
| `create_bookmark` | `add` |
|
|
43
|
+
| `update_bookmark` | `update` |
|
|
44
|
+
| `read_folders` | `folders`, `--folder` filters |
|
|
45
|
+
| `search_bookmarks`| `search` |
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# Connect to Keeply (verifies your key before saving)
|
|
49
|
+
keeply config set-key <your-api-key>
|
|
50
|
+
|
|
51
|
+
# Check current config
|
|
52
|
+
keeply config show
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Config is stored at `~/.config/keeply-cli/config.json`.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Commands
|
|
60
|
+
|
|
61
|
+
### `keeply list` · `keeply ls`
|
|
62
|
+
|
|
63
|
+
List your bookmarks with optional filters and pagination.
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
keeply list # All bookmarks, 20 per page
|
|
67
|
+
keeply list --folder "Reading List" # Filter by folder name
|
|
68
|
+
keeply list --folder <folder-id> # Filter by folder ID
|
|
69
|
+
keeply list --tag typescript # Filter by tag
|
|
70
|
+
keeply list --archived # Archived bookmarks only
|
|
71
|
+
keeply list --page 2 # Go to page 2
|
|
72
|
+
keeply list --limit 50 # 50 results per page
|
|
73
|
+
keeply list --limit 0 # No pagination, return all
|
|
74
|
+
keeply list --short # Compact one-line output
|
|
75
|
+
keeply list --json # Raw JSON output
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Options**
|
|
79
|
+
|
|
80
|
+
| Flag | Description |
|
|
81
|
+
|------|-------------|
|
|
82
|
+
| `-f, --folder <name\|id>` | Filter by folder name or ID |
|
|
83
|
+
| `-t, --tag <name>` | Filter by tag name |
|
|
84
|
+
| `-a, --archived` | Show archived bookmarks only |
|
|
85
|
+
| `-l, --limit <n>` | Results per page (default: 20, 0 = all) |
|
|
86
|
+
| `-p, --page <n>` | Page number (default: 1) |
|
|
87
|
+
| `-s, --short` | Compact one-line output |
|
|
88
|
+
| `--json` | Raw JSON (great for piping) |
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
### `keeply search <query>`
|
|
93
|
+
|
|
94
|
+
Full-text search powered by MeiliSearch — searches titles, URLs, descriptions, and notes.
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
keeply search "rust async"
|
|
98
|
+
keeply search "machine learning" --short
|
|
99
|
+
keeply search "react hooks" --json
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Options**
|
|
103
|
+
|
|
104
|
+
| Flag | Description |
|
|
105
|
+
|------|-------------|
|
|
106
|
+
| `-s, --short` | Compact one-line output |
|
|
107
|
+
| `--json` | Raw JSON output |
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
### `keeply get <id>`
|
|
112
|
+
|
|
113
|
+
Fetch a single bookmark by its ID.
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
keeply get cm3x7abc123
|
|
117
|
+
keeply get cm3x7abc123 --json
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
### `keeply add <url>`
|
|
123
|
+
|
|
124
|
+
Save a new bookmark.
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
keeply add https://example.com
|
|
128
|
+
keeply add https://example.com --title "Example Site"
|
|
129
|
+
keeply add https://example.com --folder "Dev"
|
|
130
|
+
keeply add https://example.com --tags "web,tools,reference"
|
|
131
|
+
keeply add https://example.com --note "Check this out later"
|
|
132
|
+
keeply add https://example.com --json # Returns created bookmark as JSON
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**Options**
|
|
136
|
+
|
|
137
|
+
| Flag | Description |
|
|
138
|
+
|------|-------------|
|
|
139
|
+
| `-t, --title <title>` | Bookmark title |
|
|
140
|
+
| `-n, --note <note>` | Personal note |
|
|
141
|
+
| `-f, --folder <name\|id>` | Folder to save into |
|
|
142
|
+
| `--tags <tags>` | Comma-separated tag names (must already exist in Keeply) |
|
|
143
|
+
| `--json` | Output created bookmark as JSON |
|
|
144
|
+
|
|
145
|
+
> **Note:** Tags must already exist in your Keeply account. Create new tags in the web app or browser extension.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
### `keeply update <id>`
|
|
150
|
+
|
|
151
|
+
Update an existing bookmark.
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
keeply update <id> --title "Better title"
|
|
155
|
+
keeply update <id> --url https://new-url.com
|
|
156
|
+
keeply update <id> --folder "Archive"
|
|
157
|
+
keeply update <id> --unfolder # Move to Unsorted
|
|
158
|
+
keeply update <id> --tags "rust,cli" # Replace all tags
|
|
159
|
+
keeply update <id> --archive
|
|
160
|
+
keeply update <id> --unarchive
|
|
161
|
+
keeply update <id> --json # Output updated bookmark as JSON
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**Options**
|
|
165
|
+
|
|
166
|
+
| Flag | Description |
|
|
167
|
+
|------|-------------|
|
|
168
|
+
| `--url <url>` | New URL |
|
|
169
|
+
| `-t, --title <title>` | New title |
|
|
170
|
+
| `-n, --note <note>` | New note |
|
|
171
|
+
| `-f, --folder <name\|id>` | Move to folder |
|
|
172
|
+
| `--unfolder` | Remove from folder (move to Unsorted) |
|
|
173
|
+
| `--tags <tags>` | Replace all tags (comma-separated names) |
|
|
174
|
+
| `--archive` | Mark as archived |
|
|
175
|
+
| `--unarchive` | Remove archived status |
|
|
176
|
+
| `--json` | Output updated bookmark as JSON |
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
### `keeply folders`
|
|
181
|
+
|
|
182
|
+
List all your folders.
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
keeply folders
|
|
186
|
+
keeply folders --json
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
### `keeply tags`
|
|
192
|
+
|
|
193
|
+
List all your tags, sorted by usage.
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
keeply tags
|
|
197
|
+
keeply tags --json
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
### `keeply config`
|
|
203
|
+
|
|
204
|
+
Manage CLI configuration.
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
keeply config set-key <api-key> # Set API key (verifies first)
|
|
208
|
+
keeply config show # Show current config
|
|
209
|
+
keeply config clear # Remove stored API key
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## Using with AI agents
|
|
215
|
+
|
|
216
|
+
All commands support `--json` output for clean, machine-readable data. This makes it easy to pipe bookmarks into any AI agent or script.
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
# Give an AI agent your full bookmark list
|
|
220
|
+
keeply list --limit 0 --json | llm "what topics do I read most about?"
|
|
221
|
+
|
|
222
|
+
# Search and summarize
|
|
223
|
+
keeply search "python" --json | llm "summarize these links"
|
|
224
|
+
|
|
225
|
+
# Extract just URLs
|
|
226
|
+
keeply list --folder "To Read" --json | jq '.[].url'
|
|
227
|
+
|
|
228
|
+
# Save a bookmark from a script
|
|
229
|
+
URL="https://example.com"
|
|
230
|
+
TITLE=$(curl -s "$URL" | grep -oP '(?<=<title>)[^<]+')
|
|
231
|
+
keeply add "$URL" --title "$TITLE" --folder "Inbox"
|
|
232
|
+
|
|
233
|
+
# Find bookmarks modified recently and process them
|
|
234
|
+
keeply list --json | jq '[.[] | select(.updatedAt > "2026-01-01")]'
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## Output formats
|
|
240
|
+
|
|
241
|
+
### Default (human-readable)
|
|
242
|
+
|
|
243
|
+
```
|
|
244
|
+
1. My Bookmark Title
|
|
245
|
+
https://example.com/some/page
|
|
246
|
+
#typescript #tools
|
|
247
|
+
Note: Really useful reference
|
|
248
|
+
id: cm3x7abc123 · 3/15/2026
|
|
249
|
+
|
|
250
|
+
2. Another Bookmark
|
|
251
|
+
https://another.com
|
|
252
|
+
...
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Short (`--short`)
|
|
256
|
+
|
|
257
|
+
```
|
|
258
|
+
1. My Bookmark Title https://example.com/some/page
|
|
259
|
+
2. Another Bookmark https://another.com
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### JSON (`--json`)
|
|
263
|
+
|
|
264
|
+
Standard JSON array of bookmark objects, suitable for piping to `jq`, AI agents, or any other tool.
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## API scopes reference
|
|
269
|
+
|
|
270
|
+
When creating an API key in Keeply, select the scopes you need:
|
|
271
|
+
|
|
272
|
+
| Command | Required scope |
|
|
273
|
+
|---------|---------------|
|
|
274
|
+
| `list`, `get` | `read_bookmarks` |
|
|
275
|
+
| `search` | `search_bookmarks` |
|
|
276
|
+
| `add` | `create_bookmark` |
|
|
277
|
+
| `update` | `update_bookmark` |
|
|
278
|
+
| `folders` | `read_folders` |
|
|
279
|
+
| `tags` | `read_bookmarks` |
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Bookmark, Folder, Tag, CreateBookmarkPayload, UpdateBookmarkPayload } from './types.js';
|
|
2
|
+
export declare class KeeplyApi {
|
|
3
|
+
private readonly apiKey;
|
|
4
|
+
private readonly baseUrl;
|
|
5
|
+
constructor(apiKey: string, baseUrl: string);
|
|
6
|
+
private get headers();
|
|
7
|
+
private request;
|
|
8
|
+
whoami(): Promise<{
|
|
9
|
+
email: string;
|
|
10
|
+
name?: string;
|
|
11
|
+
isPro: boolean;
|
|
12
|
+
}>;
|
|
13
|
+
listBookmarks(includeDeleted?: boolean): Promise<Bookmark[]>;
|
|
14
|
+
getBookmark(id: string): Promise<Bookmark>;
|
|
15
|
+
createBookmark(payload: CreateBookmarkPayload): Promise<Bookmark>;
|
|
16
|
+
updateBookmark(id: string, payload: UpdateBookmarkPayload): Promise<Bookmark>;
|
|
17
|
+
searchBookmarks(query: string): Promise<Bookmark[]>;
|
|
18
|
+
listFolders(): Promise<Folder[]>;
|
|
19
|
+
listTags(): Promise<Tag[]>;
|
|
20
|
+
}
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export class KeeplyApi {
|
|
2
|
+
apiKey;
|
|
3
|
+
baseUrl;
|
|
4
|
+
constructor(apiKey, baseUrl) {
|
|
5
|
+
this.apiKey = apiKey;
|
|
6
|
+
this.baseUrl = baseUrl;
|
|
7
|
+
}
|
|
8
|
+
get headers() {
|
|
9
|
+
return {
|
|
10
|
+
'Content-Type': 'application/json',
|
|
11
|
+
Authorization: `ApiKey ${this.apiKey}`,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
async request(path, init) {
|
|
15
|
+
const url = `${this.baseUrl.replace(/\/$/, '')}${path}`;
|
|
16
|
+
const res = await fetch(url, { ...init, headers: { ...this.headers, ...init?.headers } });
|
|
17
|
+
if (!res.ok) {
|
|
18
|
+
const body = await res.json().catch(() => ({}));
|
|
19
|
+
const msg = Array.isArray(body.message) ? body.message.join(', ') : (body.message ?? `HTTP ${res.status}`);
|
|
20
|
+
throw new Error(msg);
|
|
21
|
+
}
|
|
22
|
+
// 204 No Content
|
|
23
|
+
if (res.status === 204)
|
|
24
|
+
return undefined;
|
|
25
|
+
return res.json();
|
|
26
|
+
}
|
|
27
|
+
async whoami() {
|
|
28
|
+
return this.request('/users/me');
|
|
29
|
+
}
|
|
30
|
+
// Bookmarks
|
|
31
|
+
async listBookmarks(includeDeleted = false) {
|
|
32
|
+
const qs = includeDeleted ? '?includeDeleted=true' : '';
|
|
33
|
+
return this.request(`/bookmarks${qs}`);
|
|
34
|
+
}
|
|
35
|
+
async getBookmark(id) {
|
|
36
|
+
// No single-get endpoint exists; fetch all and filter
|
|
37
|
+
const all = await this.listBookmarks();
|
|
38
|
+
const found = all.find((b) => b.id === id);
|
|
39
|
+
if (!found)
|
|
40
|
+
throw new Error(`Bookmark ${id} not found`);
|
|
41
|
+
return found;
|
|
42
|
+
}
|
|
43
|
+
async createBookmark(payload) {
|
|
44
|
+
return this.request('/bookmarks', {
|
|
45
|
+
method: 'POST',
|
|
46
|
+
body: JSON.stringify(payload),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
async updateBookmark(id, payload) {
|
|
50
|
+
return this.request(`/bookmarks/${id}`, {
|
|
51
|
+
method: 'PATCH',
|
|
52
|
+
body: JSON.stringify(payload),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
async searchBookmarks(query) {
|
|
56
|
+
const res = await this.request(`/search?q=${encodeURIComponent(query)}`);
|
|
57
|
+
return res.hits;
|
|
58
|
+
}
|
|
59
|
+
// Folders
|
|
60
|
+
async listFolders() {
|
|
61
|
+
return this.request('/folders');
|
|
62
|
+
}
|
|
63
|
+
// Tags
|
|
64
|
+
async listTags() {
|
|
65
|
+
const data = await this.request('/bookmarks/sidebar-data');
|
|
66
|
+
return data.tags;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { requireConfig } from '../config.js';
|
|
3
|
+
import { KeeplyApi } from '../api.js';
|
|
4
|
+
import { printError, printSuccess } from '../format.js';
|
|
5
|
+
export function registerAddCommand(program) {
|
|
6
|
+
program
|
|
7
|
+
.command('add <url>')
|
|
8
|
+
.description('Save a new bookmark')
|
|
9
|
+
.option('-t, --title <title>', 'Bookmark title')
|
|
10
|
+
.option('-n, --note <note>', 'Personal note')
|
|
11
|
+
.option('-f, --folder <name|id>', 'Folder name or ID')
|
|
12
|
+
.option('--tags <tags>', 'Comma-separated tag names (must already exist)')
|
|
13
|
+
.option('--json', 'Output created bookmark as JSON')
|
|
14
|
+
.action(async (url, opts) => {
|
|
15
|
+
const { apiKey, apiUrl } = requireConfig();
|
|
16
|
+
const api = new KeeplyApi(apiKey, apiUrl);
|
|
17
|
+
try {
|
|
18
|
+
let folderId;
|
|
19
|
+
let tagIds;
|
|
20
|
+
if (opts.folder) {
|
|
21
|
+
const folders = await api.listFolders();
|
|
22
|
+
const match = folders.find((f) => f.name.toLowerCase() === opts.folder.toLowerCase() || f.id === opts.folder);
|
|
23
|
+
if (!match) {
|
|
24
|
+
printError(`Folder "${opts.folder}" not found. Create it in the web app first.`);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
folderId = match.id;
|
|
28
|
+
}
|
|
29
|
+
if (opts.tags) {
|
|
30
|
+
const tagNames = opts.tags.split(',').map((t) => t.trim().toLowerCase());
|
|
31
|
+
const allTags = await api.listTags();
|
|
32
|
+
tagIds = [];
|
|
33
|
+
for (const name of tagNames) {
|
|
34
|
+
const found = allTags.find((t) => t.name === name);
|
|
35
|
+
if (!found) {
|
|
36
|
+
printError(`Tag "${name}" not found. Create it in the web app first.`);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
tagIds.push(found.id);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const bookmark = await api.createBookmark({
|
|
43
|
+
url,
|
|
44
|
+
title: opts.title,
|
|
45
|
+
note: opts.note,
|
|
46
|
+
folderId,
|
|
47
|
+
tagIds,
|
|
48
|
+
});
|
|
49
|
+
if (opts.json) {
|
|
50
|
+
process.stdout.write(JSON.stringify(bookmark, null, 2));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
printSuccess(`Saved ${chalk.bold(bookmark.title ?? url)}`);
|
|
54
|
+
console.log(chalk.dim(`id: ${bookmark.id}`));
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
printError(err instanceof Error ? err.message : 'Failed to save bookmark');
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { conf, getConfig } from '../config.js';
|
|
3
|
+
import { KeeplyApi } from '../api.js';
|
|
4
|
+
import { printError, printSuccess } from '../format.js';
|
|
5
|
+
export function registerConfigCommands(program) {
|
|
6
|
+
const config = program.command('config').description('Manage CLI configuration');
|
|
7
|
+
config
|
|
8
|
+
.command('set-key <api-key>')
|
|
9
|
+
.description('Set your Keeply API key')
|
|
10
|
+
.action(async (apiKey) => {
|
|
11
|
+
const url = conf.get('apiUrl');
|
|
12
|
+
process.stdout.write('Verifying API key… ');
|
|
13
|
+
try {
|
|
14
|
+
const api = new KeeplyApi(apiKey, url);
|
|
15
|
+
const user = await api.whoami();
|
|
16
|
+
conf.set('apiKey', apiKey);
|
|
17
|
+
process.stdout.write('\n');
|
|
18
|
+
printSuccess(`Connected as ${chalk.bold(user.email)}${user.isPro ? chalk.cyan(' (Pro)') : ''}`);
|
|
19
|
+
console.log(chalk.dim(`Config saved to ${conf.path}`));
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
process.stdout.write('\n');
|
|
23
|
+
printError(err instanceof Error ? err.message : 'Failed to verify key');
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
config
|
|
28
|
+
.command('show')
|
|
29
|
+
.description('Show current configuration')
|
|
30
|
+
.action(() => {
|
|
31
|
+
const { apiKey } = getConfig();
|
|
32
|
+
console.log(`API Key ${apiKey ? chalk.dim(`${apiKey.slice(0, 8)}…`) : chalk.red('not set')}`);
|
|
33
|
+
console.log(chalk.dim(`\nConfig file: ${conf.path}`));
|
|
34
|
+
});
|
|
35
|
+
config
|
|
36
|
+
.command('clear')
|
|
37
|
+
.description('Remove stored API key')
|
|
38
|
+
.action(() => {
|
|
39
|
+
conf.set('apiKey', '');
|
|
40
|
+
printSuccess('API key cleared');
|
|
41
|
+
});
|
|
42
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { requireConfig } from '../config.js';
|
|
3
|
+
import { KeeplyApi } from '../api.js';
|
|
4
|
+
import { formatFolder, printError } from '../format.js';
|
|
5
|
+
export function registerFoldersCommand(program) {
|
|
6
|
+
program
|
|
7
|
+
.command('folders')
|
|
8
|
+
.description('List all folders')
|
|
9
|
+
.option('--json', 'Output raw JSON')
|
|
10
|
+
.action(async (opts) => {
|
|
11
|
+
const { apiKey, apiUrl } = requireConfig();
|
|
12
|
+
const api = new KeeplyApi(apiKey, apiUrl);
|
|
13
|
+
try {
|
|
14
|
+
const folders = await api.listFolders();
|
|
15
|
+
if (opts.json) {
|
|
16
|
+
process.stdout.write(JSON.stringify(folders, null, 2));
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (folders.length === 0) {
|
|
20
|
+
console.log(chalk.dim('No folders yet.'));
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
console.log('');
|
|
24
|
+
folders.forEach((f, i) => console.log(formatFolder(f, i)));
|
|
25
|
+
console.log(chalk.dim(`\n${folders.length} folder${folders.length !== 1 ? 's' : ''}`));
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
printError(err instanceof Error ? err.message : 'Failed to list folders');
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { requireConfig } from '../config.js';
|
|
2
|
+
import { KeeplyApi } from '../api.js';
|
|
3
|
+
import { formatBookmark, printError } from '../format.js';
|
|
4
|
+
export function registerGetCommand(program) {
|
|
5
|
+
program
|
|
6
|
+
.command('get <id>')
|
|
7
|
+
.description('Get a single bookmark by ID')
|
|
8
|
+
.option('--json', 'Output raw JSON')
|
|
9
|
+
.action(async (id, opts) => {
|
|
10
|
+
const { apiKey, apiUrl } = requireConfig();
|
|
11
|
+
const api = new KeeplyApi(apiKey, apiUrl);
|
|
12
|
+
try {
|
|
13
|
+
const bookmark = await api.getBookmark(id);
|
|
14
|
+
if (opts.json) {
|
|
15
|
+
process.stdout.write(JSON.stringify(bookmark, null, 2));
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
console.log('');
|
|
19
|
+
console.log(formatBookmark(bookmark));
|
|
20
|
+
console.log('');
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
printError(err instanceof Error ? err.message : 'Failed to get bookmark');
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { requireConfig } from '../config.js';
|
|
3
|
+
import { KeeplyApi } from '../api.js';
|
|
4
|
+
import { formatBookmark, formatBookmarkShort, paginate, printPaginationInfo, printError } from '../format.js';
|
|
5
|
+
export function registerListCommand(program) {
|
|
6
|
+
program
|
|
7
|
+
.command('list')
|
|
8
|
+
.alias('ls')
|
|
9
|
+
.description('List bookmarks')
|
|
10
|
+
.option('-f, --folder <name|id>', 'Filter by folder name or ID')
|
|
11
|
+
.option('-t, --tag <name>', 'Filter by tag name')
|
|
12
|
+
.option('-a, --archived', 'Show archived bookmarks only')
|
|
13
|
+
.option('-l, --limit <n>', 'Results per page (0 = no pagination)', '20')
|
|
14
|
+
.option('-p, --page <n>', 'Page number', '1')
|
|
15
|
+
.option('-s, --short', 'Compact one-line output')
|
|
16
|
+
.option('--json', 'Output raw JSON (for piping to AI agents)')
|
|
17
|
+
.action(async (opts) => {
|
|
18
|
+
const { apiKey, apiUrl } = requireConfig();
|
|
19
|
+
const api = new KeeplyApi(apiKey, apiUrl);
|
|
20
|
+
try {
|
|
21
|
+
let bookmarks = await api.listBookmarks();
|
|
22
|
+
// Filters
|
|
23
|
+
if (opts.folder) {
|
|
24
|
+
const folders = await api.listFolders();
|
|
25
|
+
const match = folders.find((f) => f.name.toLowerCase() === opts.folder.toLowerCase() || f.id === opts.folder);
|
|
26
|
+
if (!match) {
|
|
27
|
+
printError(`Folder "${opts.folder}" not found`);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
bookmarks = bookmarks.filter((b) => b.folderId === match.id);
|
|
31
|
+
}
|
|
32
|
+
if (opts.tag) {
|
|
33
|
+
const tagLower = opts.tag.toLowerCase();
|
|
34
|
+
bookmarks = bookmarks.filter((b) => b.tags.some((t) => t.tag.name.toLowerCase() === tagLower));
|
|
35
|
+
}
|
|
36
|
+
if (opts.archived) {
|
|
37
|
+
bookmarks = bookmarks.filter((b) => b.archived);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
bookmarks = bookmarks.filter((b) => !b.archived && !b.deletedAt);
|
|
41
|
+
}
|
|
42
|
+
// JSON output (no pagination)
|
|
43
|
+
if (opts.json) {
|
|
44
|
+
process.stdout.write(JSON.stringify(bookmarks, null, 2));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
// Paginate
|
|
48
|
+
const limit = parseInt(opts.limit, 10);
|
|
49
|
+
const page = parseInt(opts.page, 10);
|
|
50
|
+
const { items, total, pages } = paginate(bookmarks, page, limit);
|
|
51
|
+
if (items.length === 0) {
|
|
52
|
+
console.log(chalk.dim('No bookmarks found.'));
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
console.log('');
|
|
56
|
+
if (opts.short) {
|
|
57
|
+
items.forEach((b, i) => console.log(formatBookmarkShort(b, (page - 1) * limit + i)));
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
items.forEach((b, i) => {
|
|
61
|
+
console.log(formatBookmark(b, (page - 1) * limit + i));
|
|
62
|
+
if (i < items.length - 1)
|
|
63
|
+
console.log('');
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
printPaginationInfo(page, pages, total, items.length);
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
printError(err instanceof Error ? err.message : 'Failed to list bookmarks');
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { requireConfig } from '../config.js';
|
|
3
|
+
import { KeeplyApi } from '../api.js';
|
|
4
|
+
import { formatBookmark, formatBookmarkShort, printError } from '../format.js';
|
|
5
|
+
export function registerSearchCommand(program) {
|
|
6
|
+
program
|
|
7
|
+
.command('search <query>')
|
|
8
|
+
.description('Full-text search across your bookmarks')
|
|
9
|
+
.option('-s, --short', 'Compact one-line output')
|
|
10
|
+
.option('--json', 'Output raw JSON')
|
|
11
|
+
.action(async (query, opts) => {
|
|
12
|
+
const { apiKey, apiUrl } = requireConfig();
|
|
13
|
+
const api = new KeeplyApi(apiKey, apiUrl);
|
|
14
|
+
try {
|
|
15
|
+
const bookmarks = await api.searchBookmarks(query);
|
|
16
|
+
if (opts.json) {
|
|
17
|
+
process.stdout.write(JSON.stringify(bookmarks, null, 2));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (bookmarks.length === 0) {
|
|
21
|
+
console.log(chalk.dim(`No results for "${query}"`));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
console.log('');
|
|
25
|
+
if (opts.short) {
|
|
26
|
+
bookmarks.forEach((b, i) => console.log(formatBookmarkShort(b, i)));
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
bookmarks.forEach((b, i) => {
|
|
30
|
+
console.log(formatBookmark(b, i));
|
|
31
|
+
if (i < bookmarks.length - 1)
|
|
32
|
+
console.log('');
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
console.log(chalk.dim(`\n${bookmarks.length} result${bookmarks.length !== 1 ? 's' : ''} for "${query}"`));
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
printError(err instanceof Error ? err.message : 'Search failed');
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { requireConfig } from '../config.js';
|
|
3
|
+
import { KeeplyApi } from '../api.js';
|
|
4
|
+
import { formatTag, printError } from '../format.js';
|
|
5
|
+
export function registerTagsCommand(program) {
|
|
6
|
+
program
|
|
7
|
+
.command('tags')
|
|
8
|
+
.description('List all tags')
|
|
9
|
+
.option('--json', 'Output raw JSON')
|
|
10
|
+
.action(async (opts) => {
|
|
11
|
+
const { apiKey, apiUrl } = requireConfig();
|
|
12
|
+
const api = new KeeplyApi(apiKey, apiUrl);
|
|
13
|
+
try {
|
|
14
|
+
const tags = await api.listTags();
|
|
15
|
+
const sorted = tags.sort((a, b) => b.count - a.count);
|
|
16
|
+
if (opts.json) {
|
|
17
|
+
process.stdout.write(JSON.stringify(sorted, null, 2));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (sorted.length === 0) {
|
|
21
|
+
console.log(chalk.dim('No tags yet.'));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
console.log('');
|
|
25
|
+
sorted.forEach((t, i) => console.log(formatTag(t, i)));
|
|
26
|
+
console.log(chalk.dim(`\n${sorted.length} tag${sorted.length !== 1 ? 's' : ''}`));
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
printError(err instanceof Error ? err.message : 'Failed to list tags');
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { requireConfig } from '../config.js';
|
|
3
|
+
import { KeeplyApi } from '../api.js';
|
|
4
|
+
import { printError, printSuccess } from '../format.js';
|
|
5
|
+
export function registerUpdateCommand(program) {
|
|
6
|
+
program
|
|
7
|
+
.command('update <id>')
|
|
8
|
+
.description('Update a bookmark')
|
|
9
|
+
.option('--url <url>', 'New URL')
|
|
10
|
+
.option('-t, --title <title>', 'New title')
|
|
11
|
+
.option('-n, --note <note>', 'New note')
|
|
12
|
+
.option('-f, --folder <name|id>', 'Move to folder')
|
|
13
|
+
.option('--unfolder', 'Remove from folder (move to unsorted)')
|
|
14
|
+
.option('--tags <tags>', 'Replace tags (comma-separated names)')
|
|
15
|
+
.option('--archive', 'Mark as archived')
|
|
16
|
+
.option('--unarchive', 'Remove archived status')
|
|
17
|
+
.option('--json', 'Output updated bookmark as JSON')
|
|
18
|
+
.action(async (id, opts) => {
|
|
19
|
+
const { apiKey, apiUrl } = requireConfig();
|
|
20
|
+
const api = new KeeplyApi(apiKey, apiUrl);
|
|
21
|
+
try {
|
|
22
|
+
const payload = {};
|
|
23
|
+
if (opts.url)
|
|
24
|
+
payload.url = opts.url;
|
|
25
|
+
if (opts.title)
|
|
26
|
+
payload.title = opts.title;
|
|
27
|
+
if (opts.note)
|
|
28
|
+
payload.note = opts.note;
|
|
29
|
+
if (opts.archive)
|
|
30
|
+
payload.archived = true;
|
|
31
|
+
if (opts.unarchive)
|
|
32
|
+
payload.archived = false;
|
|
33
|
+
if (opts.unfolder) {
|
|
34
|
+
payload.folderId = null;
|
|
35
|
+
}
|
|
36
|
+
else if (opts.folder) {
|
|
37
|
+
const folders = await api.listFolders();
|
|
38
|
+
const match = folders.find((f) => f.name.toLowerCase() === opts.folder.toLowerCase() || f.id === opts.folder);
|
|
39
|
+
if (!match) {
|
|
40
|
+
printError(`Folder "${opts.folder}" not found`);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
payload.folderId = match.id;
|
|
44
|
+
}
|
|
45
|
+
if (opts.tags) {
|
|
46
|
+
const tagNames = opts.tags.split(',').map((t) => t.trim().toLowerCase());
|
|
47
|
+
const allTags = await api.listTags();
|
|
48
|
+
const tagIds = [];
|
|
49
|
+
for (const name of tagNames) {
|
|
50
|
+
const found = allTags.find((t) => t.name === name);
|
|
51
|
+
if (!found) {
|
|
52
|
+
printError(`Tag "${name}" not found`);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
tagIds.push(found.id);
|
|
56
|
+
}
|
|
57
|
+
payload.tagIds = tagIds;
|
|
58
|
+
}
|
|
59
|
+
if (Object.keys(payload).length === 0) {
|
|
60
|
+
printError('No changes specified. Use --help to see options.');
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
const bookmark = await api.updateBookmark(id, payload);
|
|
64
|
+
if (opts.json) {
|
|
65
|
+
process.stdout.write(JSON.stringify(bookmark, null, 2));
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
printSuccess(`Updated ${chalk.bold(bookmark.title ?? bookmark.url)}`);
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
printError(err instanceof Error ? err.message : 'Failed to update bookmark');
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
package/dist/config.d.ts
ADDED
package/dist/config.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import Conf from 'conf';
|
|
2
|
+
import { homedir } from 'os';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
export const conf = new Conf({
|
|
5
|
+
projectName: 'keeply-cli',
|
|
6
|
+
cwd: join(homedir(), '.config', 'keeply-cli'),
|
|
7
|
+
defaults: {
|
|
8
|
+
apiKey: '',
|
|
9
|
+
apiUrl: 'https://api.keeply.tools',
|
|
10
|
+
},
|
|
11
|
+
});
|
|
12
|
+
export function getConfig() {
|
|
13
|
+
return {
|
|
14
|
+
apiKey: conf.get('apiKey'),
|
|
15
|
+
apiUrl: conf.get('apiUrl'),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export function requireConfig() {
|
|
19
|
+
const config = getConfig();
|
|
20
|
+
if (!config.apiKey) {
|
|
21
|
+
console.error('Not configured. Run: keeply config set-key <api-key>');
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
return config;
|
|
25
|
+
}
|
package/dist/format.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Bookmark, Folder, Tag } from './types.js';
|
|
2
|
+
export declare function formatBookmark(b: Bookmark, index?: number): string;
|
|
3
|
+
export declare function formatBookmarkShort(b: Bookmark, index?: number): string;
|
|
4
|
+
export declare function formatFolder(f: Folder, index?: number): string;
|
|
5
|
+
export declare function formatTag(t: Tag, index?: number): string;
|
|
6
|
+
export declare function paginate<T>(items: T[], page: number, limit: number): {
|
|
7
|
+
items: T[];
|
|
8
|
+
total: number;
|
|
9
|
+
pages: number;
|
|
10
|
+
};
|
|
11
|
+
export declare function printPaginationInfo(page: number, pages: number, total: number, shown: number): void;
|
|
12
|
+
export declare function printError(msg: string): void;
|
|
13
|
+
export declare function printSuccess(msg: string): void;
|
package/dist/format.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
// ── Bookmark formatting ──────────────────────────────────────────────────────
|
|
3
|
+
export function formatBookmark(b, index) {
|
|
4
|
+
const lines = [];
|
|
5
|
+
const num = index !== undefined ? chalk.dim(`${index + 1}. `) : '';
|
|
6
|
+
const title = b.title ? chalk.white.bold(b.title) : chalk.dim('(no title)');
|
|
7
|
+
lines.push(`${num}${title}`);
|
|
8
|
+
lines.push(` ${chalk.cyan(b.url)}`);
|
|
9
|
+
const meta = [];
|
|
10
|
+
if (b.tags.length > 0) {
|
|
11
|
+
const tagStr = b.tags.map((t) => chalk.magenta(`#${t.tag.name}`)).join(' ');
|
|
12
|
+
meta.push(tagStr);
|
|
13
|
+
}
|
|
14
|
+
if (b.archived)
|
|
15
|
+
meta.push(chalk.yellow('[archived]'));
|
|
16
|
+
if (meta.length > 0)
|
|
17
|
+
lines.push(` ${meta.join(' ')}`);
|
|
18
|
+
if (b.note) {
|
|
19
|
+
lines.push(` ${chalk.dim('Note:')} ${chalk.italic(b.note)}`);
|
|
20
|
+
}
|
|
21
|
+
lines.push(` ${chalk.dim(`id: ${b.id} · ${new Date(b.createdAt).toLocaleDateString()}`)}`);
|
|
22
|
+
return lines.join('\n');
|
|
23
|
+
}
|
|
24
|
+
export function formatBookmarkShort(b, index) {
|
|
25
|
+
const num = index !== undefined ? chalk.dim(`${String(index + 1).padStart(3)}. `) : '';
|
|
26
|
+
const title = (b.title ?? '(no title)').slice(0, 50).padEnd(50);
|
|
27
|
+
const url = b.url.slice(0, 55);
|
|
28
|
+
return `${num}${chalk.white(title)} ${chalk.dim(url)}`;
|
|
29
|
+
}
|
|
30
|
+
// ── Folder formatting ────────────────────────────────────────────────────────
|
|
31
|
+
export function formatFolder(f, index) {
|
|
32
|
+
const num = index !== undefined ? chalk.dim(`${index + 1}. `) : '';
|
|
33
|
+
const count = f._count ? chalk.dim(` (${f._count.bookmarks})`) : '';
|
|
34
|
+
return `${num}${chalk.white.bold(f.name)}${count} ${chalk.dim(f.id)}`;
|
|
35
|
+
}
|
|
36
|
+
// ── Tag formatting ───────────────────────────────────────────────────────────
|
|
37
|
+
export function formatTag(t, index) {
|
|
38
|
+
const num = index !== undefined ? chalk.dim(`${index + 1}. `) : '';
|
|
39
|
+
return `${num}${chalk.magenta(`#${t.name}`)} ${chalk.dim(`${t.count} bookmark${t.count !== 1 ? 's' : ''}`)}`;
|
|
40
|
+
}
|
|
41
|
+
// ── Paginate ─────────────────────────────────────────────────────────────────
|
|
42
|
+
export function paginate(items, page, limit) {
|
|
43
|
+
const total = items.length;
|
|
44
|
+
const pages = limit > 0 ? Math.ceil(total / limit) : 1;
|
|
45
|
+
const sliced = limit > 0 ? items.slice((page - 1) * limit, page * limit) : items;
|
|
46
|
+
return { items: sliced, total, pages };
|
|
47
|
+
}
|
|
48
|
+
export function printPaginationInfo(page, pages, total, shown) {
|
|
49
|
+
if (pages <= 1) {
|
|
50
|
+
console.log(chalk.dim(`\n${total} bookmark${total !== 1 ? 's' : ''}`));
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
console.log(chalk.dim(`\nPage ${page}/${pages} · showing ${shown} of ${total} · use --page and --limit to navigate`));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// ── Errors ───────────────────────────────────────────────────────────────────
|
|
57
|
+
export function printError(msg) {
|
|
58
|
+
console.error(chalk.red(`✖ ${msg}`));
|
|
59
|
+
}
|
|
60
|
+
export function printSuccess(msg) {
|
|
61
|
+
console.log(chalk.green(`✔ ${msg}`));
|
|
62
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { createRequire } from 'module';
|
|
4
|
+
import { registerConfigCommands } from './commands/config.js';
|
|
5
|
+
import { registerListCommand } from './commands/list.js';
|
|
6
|
+
import { registerGetCommand } from './commands/get.js';
|
|
7
|
+
import { registerAddCommand } from './commands/add.js';
|
|
8
|
+
import { registerUpdateCommand } from './commands/update.js';
|
|
9
|
+
import { registerSearchCommand } from './commands/search.js';
|
|
10
|
+
import { registerFoldersCommand } from './commands/folders.js';
|
|
11
|
+
import { registerTagsCommand } from './commands/tags.js';
|
|
12
|
+
const require = createRequire(import.meta.url);
|
|
13
|
+
const { version } = require('../package.json');
|
|
14
|
+
const program = new Command();
|
|
15
|
+
program
|
|
16
|
+
.name('keeply')
|
|
17
|
+
.description('Keeply CLI — manage your bookmarks from the terminal')
|
|
18
|
+
.version(version);
|
|
19
|
+
registerConfigCommands(program);
|
|
20
|
+
registerListCommand(program);
|
|
21
|
+
registerGetCommand(program);
|
|
22
|
+
registerAddCommand(program);
|
|
23
|
+
registerUpdateCommand(program);
|
|
24
|
+
registerSearchCommand(program);
|
|
25
|
+
registerFoldersCommand(program);
|
|
26
|
+
registerTagsCommand(program);
|
|
27
|
+
program.parse();
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export interface Bookmark {
|
|
2
|
+
id: string;
|
|
3
|
+
url: string;
|
|
4
|
+
title?: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
note?: string;
|
|
7
|
+
archived: boolean;
|
|
8
|
+
deletedAt?: string | null;
|
|
9
|
+
folderId?: string | null;
|
|
10
|
+
tags: {
|
|
11
|
+
tag: {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
};
|
|
15
|
+
}[];
|
|
16
|
+
createdAt: string;
|
|
17
|
+
updatedAt: string;
|
|
18
|
+
}
|
|
19
|
+
export interface Folder {
|
|
20
|
+
id: string;
|
|
21
|
+
name: string;
|
|
22
|
+
_count?: {
|
|
23
|
+
bookmarks: number;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export interface Tag {
|
|
27
|
+
id: string;
|
|
28
|
+
name: string;
|
|
29
|
+
count: number;
|
|
30
|
+
}
|
|
31
|
+
export interface SidebarData {
|
|
32
|
+
folders: {
|
|
33
|
+
id: string;
|
|
34
|
+
name: string;
|
|
35
|
+
count: number;
|
|
36
|
+
}[];
|
|
37
|
+
tags: {
|
|
38
|
+
id: string;
|
|
39
|
+
name: string;
|
|
40
|
+
count: number;
|
|
41
|
+
}[];
|
|
42
|
+
}
|
|
43
|
+
export interface CreateBookmarkPayload {
|
|
44
|
+
url: string;
|
|
45
|
+
title?: string;
|
|
46
|
+
description?: string;
|
|
47
|
+
note?: string;
|
|
48
|
+
folderId?: string;
|
|
49
|
+
tagIds?: string[];
|
|
50
|
+
}
|
|
51
|
+
export interface UpdateBookmarkPayload {
|
|
52
|
+
url?: string;
|
|
53
|
+
title?: string;
|
|
54
|
+
description?: string;
|
|
55
|
+
note?: string;
|
|
56
|
+
folderId?: string | null;
|
|
57
|
+
archived?: boolean;
|
|
58
|
+
tagIds?: string[];
|
|
59
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@keeply-link/cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Keeply CLI — read, save, and manage your bookmarks from the terminal",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"keeply": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"dev": "tsc --watch",
|
|
12
|
+
"start": "node dist/index.js",
|
|
13
|
+
"build:bundle": "esbuild dist/index.js --bundle --platform=node --target=node18 --format=cjs --outfile=bundle/keeply.cjs",
|
|
14
|
+
"build:binary": "npm run build && npm run build:bundle && pkg bundle/keeply.cjs --targets node18-linux-x64,node18-macos-x64,node18-macos-arm64 --output-path binaries/ --compress GZip",
|
|
15
|
+
"build:binary:local": "npm run build && npm run build:bundle && pkg bundle/keeply.cjs --targets node18-macos-arm64 --output-path binaries/"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"chalk": "^5.3.0",
|
|
19
|
+
"commander": "^12.1.0",
|
|
20
|
+
"conf": "^13.0.0"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/node": "^22.0.0",
|
|
24
|
+
"@yao-pkg/pkg": "^5.12.0",
|
|
25
|
+
"esbuild": "^0.24.0",
|
|
26
|
+
"typescript": "^5.6.3"
|
|
27
|
+
},
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=18"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist/",
|
|
33
|
+
"README.md"
|
|
34
|
+
],
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "https://github.com/keeply-link/keeply-cli"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://keeply.tools"
|
|
41
|
+
}
|