directify-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/.gitattributes +2 -0
- package/README.md +297 -0
- package/package.json +32 -0
- package/src/commands/articles.js +193 -0
- package/src/commands/auth.js +54 -0
- package/src/commands/categories.js +146 -0
- package/src/commands/config.js +27 -0
- package/src/commands/directories.js +35 -0
- package/src/commands/fields.js +41 -0
- package/src/commands/listings.js +243 -0
- package/src/commands/tags.js +138 -0
- package/src/index.js +29 -0
- package/src/utils/api.js +94 -0
- package/src/utils/output.js +65 -0
package/.gitattributes
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
# Directify CLI
|
|
2
|
+
|
|
3
|
+
Official command-line tool for [Directify](https://directify.app) - manage your directory websites, listings, categories, tags, and articles from the terminal.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g directify-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or run directly with npx:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx directify-cli --help
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
### 1. Get your API token
|
|
20
|
+
|
|
21
|
+
Go to your Directify dashboard: **Settings > API** and generate a new token.
|
|
22
|
+
|
|
23
|
+
### 2. Authenticate
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
directify auth login YOUR_API_TOKEN
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### 3. Set your default directory
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# List your directories to find the ID
|
|
33
|
+
directify directories list
|
|
34
|
+
|
|
35
|
+
# Set the default so you don't need --directory on every command
|
|
36
|
+
directify config set-directory 123
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 4. Start managing
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# List listings
|
|
43
|
+
directify listings list
|
|
44
|
+
|
|
45
|
+
# Create a listing
|
|
46
|
+
directify listings create --name "Bella Trattoria" --description "Authentic Italian cuisine" --address "123 Main St"
|
|
47
|
+
|
|
48
|
+
# Create a category
|
|
49
|
+
directify categories create --title "Italian" --icon "🍝"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Commands
|
|
53
|
+
|
|
54
|
+
### Authentication
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
directify auth login <token> # Authenticate with your API token
|
|
58
|
+
directify auth logout # Remove stored token
|
|
59
|
+
directify auth status # Check authentication status
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Configuration
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
directify config set-directory <id> # Set default directory
|
|
66
|
+
directify config get-directory # Show default directory
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Directories
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
directify directories list # List all your directories
|
|
73
|
+
directify dirs ls # Short alias
|
|
74
|
+
directify dirs ls --json # Output as JSON
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Categories
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# List
|
|
81
|
+
directify categories list
|
|
82
|
+
directify cats ls --json
|
|
83
|
+
|
|
84
|
+
# Create
|
|
85
|
+
directify categories create --title "Italian" --icon "🍝" --description "Italian restaurants"
|
|
86
|
+
directify categories create --title "Japanese" --parent-id 5 --order 2
|
|
87
|
+
|
|
88
|
+
# Update
|
|
89
|
+
directify categories update 42 --title "Italian Cuisine" --order 1
|
|
90
|
+
|
|
91
|
+
# Delete
|
|
92
|
+
directify categories delete 42
|
|
93
|
+
|
|
94
|
+
# Get details
|
|
95
|
+
directify categories get 42
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Tags
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
# List
|
|
102
|
+
directify tags list
|
|
103
|
+
directify tags ls --json
|
|
104
|
+
|
|
105
|
+
# Create
|
|
106
|
+
directify tags create --title "Featured" --color "#f59e0b" --text-color "#ffffff"
|
|
107
|
+
directify tags create --title "New" --heroicon "heroicon-o-sparkles"
|
|
108
|
+
|
|
109
|
+
# Update
|
|
110
|
+
directify tags update 10 --title "Hot" --color "#ef4444"
|
|
111
|
+
|
|
112
|
+
# Delete
|
|
113
|
+
directify tags delete 10
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Custom Fields
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
directify fields list # List all custom fields
|
|
120
|
+
directify fields ls --json # Output as JSON
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Listings
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
# List (paginated)
|
|
127
|
+
directify listings list
|
|
128
|
+
directify listings list --page 2
|
|
129
|
+
directify listings ls --json
|
|
130
|
+
|
|
131
|
+
# Get a specific listing
|
|
132
|
+
directify listings get 456
|
|
133
|
+
|
|
134
|
+
# Create
|
|
135
|
+
directify listings create \
|
|
136
|
+
--name "Bella Trattoria" \
|
|
137
|
+
--url "https://bellatrattoria.com" \
|
|
138
|
+
--description "Authentic Italian cuisine" \
|
|
139
|
+
--address "123 Main Street, New York" \
|
|
140
|
+
--phone "+1-212-555-1234" \
|
|
141
|
+
--email "info@bellatrattoria.com" \
|
|
142
|
+
--categories 1,5,12 \
|
|
143
|
+
--tags 3,7 \
|
|
144
|
+
--featured \
|
|
145
|
+
--field "price_range=2" \
|
|
146
|
+
--field "cuisine_type=Italian, Pasta"
|
|
147
|
+
|
|
148
|
+
# Update
|
|
149
|
+
directify listings update 456 \
|
|
150
|
+
--name "Bella Trattoria NYC" \
|
|
151
|
+
--featured true \
|
|
152
|
+
--field "hours_of_operation=Mon | 11:00 - 22:00"
|
|
153
|
+
|
|
154
|
+
# Delete
|
|
155
|
+
directify listings delete 456
|
|
156
|
+
|
|
157
|
+
# Check if URL exists
|
|
158
|
+
directify listings exists --url "https://example.com"
|
|
159
|
+
|
|
160
|
+
# Bulk create from JSON file
|
|
161
|
+
directify listings bulk-create --file ./listings.json
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
#### Bulk Create JSON Format
|
|
165
|
+
|
|
166
|
+
Create a JSON file with an array of listings:
|
|
167
|
+
|
|
168
|
+
```json
|
|
169
|
+
{
|
|
170
|
+
"listings": [
|
|
171
|
+
{
|
|
172
|
+
"name": "Restaurant One",
|
|
173
|
+
"url": "https://restaurant-one.com",
|
|
174
|
+
"description": "Great food",
|
|
175
|
+
"categories": [1, 2],
|
|
176
|
+
"tags": [3],
|
|
177
|
+
"price_range": "2",
|
|
178
|
+
"cuisine_type": "Italian"
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
"name": "Restaurant Two",
|
|
182
|
+
"url": "https://restaurant-two.com",
|
|
183
|
+
"description": "Amazing sushi",
|
|
184
|
+
"categories": [3],
|
|
185
|
+
"cuisine_type": "Japanese"
|
|
186
|
+
}
|
|
187
|
+
]
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Then run:
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
directify listings bulk-create --file ./listings.json
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Articles
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
# List
|
|
201
|
+
directify articles list
|
|
202
|
+
directify articles list --page 2 --json
|
|
203
|
+
|
|
204
|
+
# Get
|
|
205
|
+
directify articles get 789
|
|
206
|
+
|
|
207
|
+
# Create
|
|
208
|
+
directify articles create \
|
|
209
|
+
--title "Best Italian Restaurants in NYC" \
|
|
210
|
+
--markdown "# Top Picks\n\nHere are our favorites..." \
|
|
211
|
+
--categories "Reviews,Italian" \
|
|
212
|
+
--thumbnail-url "https://example.com/image.jpg" \
|
|
213
|
+
--seo-title "Best Italian Restaurants" \
|
|
214
|
+
--seo-description "Discover the top Italian restaurants in New York City"
|
|
215
|
+
|
|
216
|
+
# Update
|
|
217
|
+
directify articles update 789 --title "Updated Title" --active true
|
|
218
|
+
|
|
219
|
+
# Toggle active/inactive
|
|
220
|
+
directify articles toggle 789
|
|
221
|
+
|
|
222
|
+
# Delete
|
|
223
|
+
directify articles delete 789
|
|
224
|
+
|
|
225
|
+
# Check if slug exists
|
|
226
|
+
directify articles exists --slug "best-italian-restaurants"
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Global Options
|
|
230
|
+
|
|
231
|
+
All resource commands support these options:
|
|
232
|
+
|
|
233
|
+
| Option | Description |
|
|
234
|
+
|--------|-------------|
|
|
235
|
+
| `-d, --directory <id>` | Directory ID (overrides default) |
|
|
236
|
+
| `--json` | Output as JSON (on list commands) |
|
|
237
|
+
| `--help` | Show help for a command |
|
|
238
|
+
|
|
239
|
+
## Custom Fields
|
|
240
|
+
|
|
241
|
+
When creating or updating listings, you can set custom field values using the `--field` flag:
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
directify listings create \
|
|
245
|
+
--name "My Restaurant" \
|
|
246
|
+
--field "price_range=3" \
|
|
247
|
+
--field "cuisine_type=Italian, Pizza" \
|
|
248
|
+
--field "hours_of_operation=Mon | 11:00 - 22:00
|
|
249
|
+
Tue | 11:00 - 22:00
|
|
250
|
+
Wed | Closed" \
|
|
251
|
+
--field "menu_highlights=Carbonara | Classic Roman pasta | \$22
|
|
252
|
+
Margherita | Fresh mozzarella | \$18"
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Use `directify fields list` to see available custom fields and their names.
|
|
256
|
+
|
|
257
|
+
## Using with LLMs / AI Agents
|
|
258
|
+
|
|
259
|
+
The CLI is designed to work well with AI-powered workflows. Use `--json` output for machine-readable responses:
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
# Get all listings as JSON for processing
|
|
263
|
+
directify listings list --json | jq '.data[].name'
|
|
264
|
+
|
|
265
|
+
# Pipe data between commands
|
|
266
|
+
directify listings get 456 --json | jq '.categories'
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## Rate Limits
|
|
270
|
+
|
|
271
|
+
The API allows **120 requests per minute** per directory. If you hit the rate limit, the CLI will show an error message. Implement delays between requests for bulk operations.
|
|
272
|
+
|
|
273
|
+
## Configuration Storage
|
|
274
|
+
|
|
275
|
+
The CLI stores your auth token and default directory in your system's config directory:
|
|
276
|
+
|
|
277
|
+
- **macOS**: `~/Library/Preferences/directify-cli-nodejs/`
|
|
278
|
+
- **Linux**: `~/.config/directify-cli-nodejs/`
|
|
279
|
+
- **Windows**: `%APPDATA%/directify-cli-nodejs/`
|
|
280
|
+
|
|
281
|
+
## Troubleshooting
|
|
282
|
+
|
|
283
|
+
### "Not authenticated" error
|
|
284
|
+
Run `directify auth login <token>` with your API token from Settings > API.
|
|
285
|
+
|
|
286
|
+
### "No directory specified" error
|
|
287
|
+
Either pass `--directory <id>` or set a default: `directify config set-directory <id>`
|
|
288
|
+
|
|
289
|
+
### "Rate limit exceeded" error
|
|
290
|
+
Wait a moment and retry. The limit is 120 requests per minute per directory.
|
|
291
|
+
|
|
292
|
+
### "Validation error"
|
|
293
|
+
Check the error details for which fields failed validation. Use `directify fields list` to see available custom field names.
|
|
294
|
+
|
|
295
|
+
## License
|
|
296
|
+
|
|
297
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "directify-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Official CLI tool for Directify - manage your directories, listings, categories, tags, and articles from the command line.",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"directify": "./src/index.js"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node src/index.js",
|
|
12
|
+
"lint": "eslint src/"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"directify",
|
|
16
|
+
"directory",
|
|
17
|
+
"cli",
|
|
18
|
+
"listings",
|
|
19
|
+
"api"
|
|
20
|
+
],
|
|
21
|
+
"author": "Directify",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"chalk": "^5.3.0",
|
|
25
|
+
"commander": "^12.1.0",
|
|
26
|
+
"conf": "^13.0.1",
|
|
27
|
+
"ora": "^8.1.0"
|
|
28
|
+
},
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=18.0.0"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { api, resolveDirectory } from '../utils/api.js';
|
|
3
|
+
import { printTable, printJson, printSuccess, printError } from '../utils/output.js';
|
|
4
|
+
import ora from 'ora';
|
|
5
|
+
|
|
6
|
+
const articles = new Command('articles').description('Manage blog articles');
|
|
7
|
+
|
|
8
|
+
articles
|
|
9
|
+
.command('list')
|
|
10
|
+
.alias('ls')
|
|
11
|
+
.description('List all articles')
|
|
12
|
+
.option('-d, --directory <id>', 'Directory ID')
|
|
13
|
+
.option('--page <n>', 'Page number', '1')
|
|
14
|
+
.option('--json', 'Output as JSON')
|
|
15
|
+
.action(async (opts) => {
|
|
16
|
+
const spinner = ora('Fetching articles...').start();
|
|
17
|
+
try {
|
|
18
|
+
const dir = resolveDirectory(opts);
|
|
19
|
+
const data = await api.get(`/directories/${dir}/articles?page=${opts.page}`);
|
|
20
|
+
spinner.stop();
|
|
21
|
+
if (opts.json) {
|
|
22
|
+
printJson(data);
|
|
23
|
+
} else {
|
|
24
|
+
const items = data.data || data;
|
|
25
|
+
printTable(items, [
|
|
26
|
+
{ key: 'id', label: 'ID' },
|
|
27
|
+
{ key: 'title', label: 'Title', maxWidth: 40 },
|
|
28
|
+
{ key: 'slug', label: 'Slug', maxWidth: 25 },
|
|
29
|
+
{ key: 'active', label: 'Active' },
|
|
30
|
+
{ key: 'published_at', label: 'Published' },
|
|
31
|
+
]);
|
|
32
|
+
if (data.meta) {
|
|
33
|
+
console.log(`\nPage ${data.meta.current_page} of ${data.meta.last_page} (${data.meta.total} total)`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
} catch (err) {
|
|
37
|
+
spinner.stop();
|
|
38
|
+
printError(err.message);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
articles
|
|
44
|
+
.command('get <id>')
|
|
45
|
+
.description('Get a specific article')
|
|
46
|
+
.option('-d, --directory <id>', 'Directory ID')
|
|
47
|
+
.action(async (id, opts) => {
|
|
48
|
+
try {
|
|
49
|
+
const dir = resolveDirectory(opts);
|
|
50
|
+
const data = await api.get(`/directories/${dir}/articles/${id}`);
|
|
51
|
+
printJson(data.data || data);
|
|
52
|
+
} catch (err) {
|
|
53
|
+
printError(err.message);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
articles
|
|
59
|
+
.command('create')
|
|
60
|
+
.description('Create a new article')
|
|
61
|
+
.requiredOption('--title <title>', 'Article title')
|
|
62
|
+
.option('--slug <slug>', 'URL slug (auto-generated from title if not set)')
|
|
63
|
+
.option('--content <text>', 'HTML content')
|
|
64
|
+
.option('--markdown <text>', 'Markdown content')
|
|
65
|
+
.option('--thumbnail-url <url>', 'Thumbnail image URL')
|
|
66
|
+
.option('--categories <names>', 'Category names (comma-separated)')
|
|
67
|
+
.option('--seo-title <text>', 'SEO title')
|
|
68
|
+
.option('--seo-description <text>', 'SEO description')
|
|
69
|
+
.option('--inactive', 'Create as inactive')
|
|
70
|
+
.option('-d, --directory <id>', 'Directory ID')
|
|
71
|
+
.action(async (opts) => {
|
|
72
|
+
const spinner = ora('Creating article...').start();
|
|
73
|
+
try {
|
|
74
|
+
const dir = resolveDirectory(opts);
|
|
75
|
+
const body = { title: opts.title };
|
|
76
|
+
if (opts.slug) body.slug = opts.slug;
|
|
77
|
+
if (opts.content) body.content = opts.content;
|
|
78
|
+
if (opts.markdown) body.markdown = opts.markdown;
|
|
79
|
+
if (opts.thumbnailUrl) body.thumbnail_url = opts.thumbnailUrl;
|
|
80
|
+
if (opts.categories) body.categories = opts.categories.split(',').map((s) => s.trim());
|
|
81
|
+
if (opts.seoTitle || opts.seoDescription) {
|
|
82
|
+
body.seo = {};
|
|
83
|
+
if (opts.seoTitle) body.seo.title = opts.seoTitle;
|
|
84
|
+
if (opts.seoDescription) body.seo.description = opts.seoDescription;
|
|
85
|
+
}
|
|
86
|
+
body.active = !opts.inactive;
|
|
87
|
+
|
|
88
|
+
const data = await api.post(`/directories/${dir}/articles`, body);
|
|
89
|
+
spinner.stop();
|
|
90
|
+
const result = data.data || data;
|
|
91
|
+
printSuccess(`Article created: ${result.title} (ID: ${result.id})`);
|
|
92
|
+
} catch (err) {
|
|
93
|
+
spinner.stop();
|
|
94
|
+
printError(err.message);
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
articles
|
|
100
|
+
.command('update <id>')
|
|
101
|
+
.description('Update an article')
|
|
102
|
+
.option('--title <title>', 'Article title')
|
|
103
|
+
.option('--slug <slug>', 'URL slug')
|
|
104
|
+
.option('--content <text>', 'HTML content')
|
|
105
|
+
.option('--markdown <text>', 'Markdown content')
|
|
106
|
+
.option('--thumbnail-url <url>', 'Thumbnail image URL')
|
|
107
|
+
.option('--categories <names>', 'Category names (comma-separated)')
|
|
108
|
+
.option('--seo-title <text>', 'SEO title')
|
|
109
|
+
.option('--seo-description <text>', 'SEO description')
|
|
110
|
+
.option('--active <bool>', 'Active status (true/false)')
|
|
111
|
+
.option('-d, --directory <id>', 'Directory ID')
|
|
112
|
+
.action(async (id, opts) => {
|
|
113
|
+
const spinner = ora('Updating article...').start();
|
|
114
|
+
try {
|
|
115
|
+
const dir = resolveDirectory(opts);
|
|
116
|
+
const body = {};
|
|
117
|
+
if (opts.title) body.title = opts.title;
|
|
118
|
+
if (opts.slug) body.slug = opts.slug;
|
|
119
|
+
if (opts.content) body.content = opts.content;
|
|
120
|
+
if (opts.markdown) body.markdown = opts.markdown;
|
|
121
|
+
if (opts.thumbnailUrl) body.thumbnail_url = opts.thumbnailUrl;
|
|
122
|
+
if (opts.categories) body.categories = opts.categories.split(',').map((s) => s.trim());
|
|
123
|
+
if (opts.active !== undefined) body.active = opts.active === 'true';
|
|
124
|
+
if (opts.seoTitle || opts.seoDescription) {
|
|
125
|
+
body.seo = {};
|
|
126
|
+
if (opts.seoTitle) body.seo.title = opts.seoTitle;
|
|
127
|
+
if (opts.seoDescription) body.seo.description = opts.seoDescription;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const data = await api.put(`/directories/${dir}/articles/${id}`, body);
|
|
131
|
+
spinner.stop();
|
|
132
|
+
printSuccess(`Article updated: ${data.data?.title || data.title}`);
|
|
133
|
+
} catch (err) {
|
|
134
|
+
spinner.stop();
|
|
135
|
+
printError(err.message);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
articles
|
|
141
|
+
.command('delete <id>')
|
|
142
|
+
.description('Delete an article')
|
|
143
|
+
.option('-d, --directory <id>', 'Directory ID')
|
|
144
|
+
.action(async (id, opts) => {
|
|
145
|
+
const spinner = ora('Deleting article...').start();
|
|
146
|
+
try {
|
|
147
|
+
const dir = resolveDirectory(opts);
|
|
148
|
+
await api.delete(`/directories/${dir}/articles/${id}`);
|
|
149
|
+
spinner.stop();
|
|
150
|
+
printSuccess(`Article ${id} deleted.`);
|
|
151
|
+
} catch (err) {
|
|
152
|
+
spinner.stop();
|
|
153
|
+
printError(err.message);
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
articles
|
|
159
|
+
.command('toggle <id>')
|
|
160
|
+
.description('Toggle article active/inactive status')
|
|
161
|
+
.option('-d, --directory <id>', 'Directory ID')
|
|
162
|
+
.action(async (id, opts) => {
|
|
163
|
+
const spinner = ora('Toggling article...').start();
|
|
164
|
+
try {
|
|
165
|
+
const dir = resolveDirectory(opts);
|
|
166
|
+
const data = await api.patch(`/directories/${dir}/articles/${id}/toggle`);
|
|
167
|
+
spinner.stop();
|
|
168
|
+
const result = data.data || data;
|
|
169
|
+
printSuccess(`Article "${result.title}" is now ${result.active ? 'active' : 'inactive'}.`);
|
|
170
|
+
} catch (err) {
|
|
171
|
+
spinner.stop();
|
|
172
|
+
printError(err.message);
|
|
173
|
+
process.exit(1);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
articles
|
|
178
|
+
.command('exists')
|
|
179
|
+
.description('Check if an article with a given slug exists')
|
|
180
|
+
.requiredOption('--slug <slug>', 'Slug to check')
|
|
181
|
+
.option('-d, --directory <id>', 'Directory ID')
|
|
182
|
+
.action(async (opts) => {
|
|
183
|
+
try {
|
|
184
|
+
const dir = resolveDirectory(opts);
|
|
185
|
+
const data = await api.post(`/directories/${dir}/articles/exists`, { slug: opts.slug });
|
|
186
|
+
printSuccess(data.message);
|
|
187
|
+
} catch (err) {
|
|
188
|
+
printError(err.message);
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
export default articles;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { setToken, clearToken, getToken, api } from '../utils/api.js';
|
|
3
|
+
import { printSuccess, printError, printInfo } from '../utils/output.js';
|
|
4
|
+
import ora from 'ora';
|
|
5
|
+
|
|
6
|
+
const auth = new Command('auth').description('Manage authentication');
|
|
7
|
+
|
|
8
|
+
auth
|
|
9
|
+
.command('login <token>')
|
|
10
|
+
.description('Authenticate with your Directify API token')
|
|
11
|
+
.action(async (token) => {
|
|
12
|
+
const spinner = ora('Verifying token...').start();
|
|
13
|
+
try {
|
|
14
|
+
setToken(token);
|
|
15
|
+
const data = await api.get('/user');
|
|
16
|
+
spinner.stop();
|
|
17
|
+
printSuccess(`Authenticated as ${data.name} (${data.email})`);
|
|
18
|
+
} catch (err) {
|
|
19
|
+
clearToken();
|
|
20
|
+
spinner.stop();
|
|
21
|
+
printError(`Authentication failed: ${err.message}`);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
auth
|
|
27
|
+
.command('logout')
|
|
28
|
+
.description('Remove stored authentication token')
|
|
29
|
+
.action(() => {
|
|
30
|
+
clearToken();
|
|
31
|
+
printSuccess('Logged out successfully.');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
auth
|
|
35
|
+
.command('status')
|
|
36
|
+
.description('Check current authentication status')
|
|
37
|
+
.action(async () => {
|
|
38
|
+
const token = getToken();
|
|
39
|
+
if (!token) {
|
|
40
|
+
printInfo('Not authenticated. Run: directify auth login <token>');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const spinner = ora('Checking...').start();
|
|
44
|
+
try {
|
|
45
|
+
const data = await api.get('/user');
|
|
46
|
+
spinner.stop();
|
|
47
|
+
printSuccess(`Authenticated as ${data.name} (${data.email})`);
|
|
48
|
+
} catch (err) {
|
|
49
|
+
spinner.stop();
|
|
50
|
+
printError(`Token invalid: ${err.message}`);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
export default auth;
|