@hintoai/cli 0.2.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/LICENSE +21 -0
- package/README.md +429 -0
- package/dist/api/articles.d.ts +101 -0
- package/dist/api/articles.js +40 -0
- package/dist/api/client.d.ts +2 -0
- package/dist/api/client.js +48 -0
- package/dist/api/export.d.ts +6 -0
- package/dist/api/export.js +24 -0
- package/dist/api/folders.d.ts +18 -0
- package/dist/api/folders.js +14 -0
- package/dist/api/generate.d.ts +15 -0
- package/dist/api/generate.js +23 -0
- package/dist/api/project.d.ts +43 -0
- package/dist/api/project.js +19 -0
- package/dist/api/publish.d.ts +31 -0
- package/dist/api/publish.js +23 -0
- package/dist/api/templates.d.ts +17 -0
- package/dist/api/templates.js +8 -0
- package/dist/api/videos.d.ts +48 -0
- package/dist/api/videos.js +30 -0
- package/dist/commands/articles.d.ts +3 -0
- package/dist/commands/articles.js +297 -0
- package/dist/commands/completion.d.ts +2 -0
- package/dist/commands/completion.js +73 -0
- package/dist/commands/export.d.ts +3 -0
- package/dist/commands/export.js +76 -0
- package/dist/commands/folders.d.ts +3 -0
- package/dist/commands/folders.js +108 -0
- package/dist/commands/generate-batch.d.ts +3 -0
- package/dist/commands/generate-batch.js +158 -0
- package/dist/commands/generate.d.ts +3 -0
- package/dist/commands/generate.js +83 -0
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.js +25 -0
- package/dist/commands/project.d.ts +3 -0
- package/dist/commands/project.js +112 -0
- package/dist/commands/publish.d.ts +3 -0
- package/dist/commands/publish.js +74 -0
- package/dist/commands/templates.d.ts +3 -0
- package/dist/commands/templates.js +50 -0
- package/dist/commands/videos.d.ts +3 -0
- package/dist/commands/videos.js +176 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.js +31 -0
- package/dist/errors.d.ts +5 -0
- package/dist/errors.js +16 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +56 -0
- package/dist/output.d.ts +3 -0
- package/dist/output.js +43 -0
- package/dist/poll.d.ts +2 -0
- package/dist/poll.js +23 -0
- package/package.json +68 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Hinto AI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
# @hintoai/cli
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@hintoai/cli)
|
|
4
|
+
[](https://github.com/hintoai/hinto-cli/actions/workflows/ci.yml)
|
|
5
|
+
[](./LICENSE)
|
|
6
|
+
|
|
7
|
+
Command-line interface for the [Hinto AI](https://hinto.ai) API. Manage videos, articles, folders, templates, and publishing from your terminal or from AI agents and scripts.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install -g @hintoai/cli
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Requires Node.js 18+.
|
|
16
|
+
|
|
17
|
+
### Use it from an AI agent (Claude Code, Cursor, …)
|
|
18
|
+
|
|
19
|
+
Install the bundled skill into any supported agent with the universal [skills CLI](https://github.com/vercel-labs/skills):
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npx skills add hintoai/hinto-cli
|
|
23
|
+
# target specific agents:
|
|
24
|
+
npx skills add hintoai/hinto-cli -a claude-code -a cursor
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The skill bootstraps the CLI itself (`npm install -g @hintoai/cli`) on first use, so this one command is enough to go from zero to a working agent integration.
|
|
28
|
+
|
|
29
|
+
## Authentication
|
|
30
|
+
|
|
31
|
+
Run `init` once with your API key to store credentials locally:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
hinto init --key <your-api-key>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Credentials are stored in `~/.hinto/config.json` with `0600` permissions (owner-read only). You can override the stored key at any time with the `HINTO_API_KEY` environment variable — the env var always takes precedence.
|
|
38
|
+
|
|
39
|
+
## Quickstart
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm install -g @hintoai/cli
|
|
43
|
+
hinto init --key <your-api-key>
|
|
44
|
+
hinto videos upload --file ./demo.mp4 --json
|
|
45
|
+
hinto templates article --json
|
|
46
|
+
hinto generate start --video <videoId> --template <templateId> --wait --json
|
|
47
|
+
hinto publish now --json
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Shell completions
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# bash (add to ~/.bashrc)
|
|
54
|
+
eval "$(hinto completion bash)"
|
|
55
|
+
|
|
56
|
+
# zsh (add to ~/.zshrc)
|
|
57
|
+
eval "$(hinto completion zsh)"
|
|
58
|
+
|
|
59
|
+
# fish
|
|
60
|
+
hinto completion fish | source
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Global flags
|
|
64
|
+
|
|
65
|
+
| Flag | Description |
|
|
66
|
+
|------|-------------|
|
|
67
|
+
| `--api-url <url>` | Override the Hinto base URL (useful for local dev or self-hosted) |
|
|
68
|
+
| `--json` | Output raw JSON instead of human-readable tables / key-value pairs |
|
|
69
|
+
| `--version` | Print CLI version |
|
|
70
|
+
| `--help` | Print help for any command |
|
|
71
|
+
|
|
72
|
+
## Commands
|
|
73
|
+
|
|
74
|
+
### `hinto project`
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
hinto project get
|
|
78
|
+
hinto project get --json
|
|
79
|
+
hinto project structure --json
|
|
80
|
+
hinto project update --name "New Name"
|
|
81
|
+
hinto project languages
|
|
82
|
+
hinto project retranslate --lang fr # fire-and-forget
|
|
83
|
+
hinto project retranslate --lang fr --wait # block until done
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`project get --json` — the response is wrapped in a `project` key:
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"project": {
|
|
91
|
+
"id": "cc63bccf-4b76-4672-b46f-92a9c0a01567",
|
|
92
|
+
"name": "My Docs",
|
|
93
|
+
"url_slug": "my-docs",
|
|
94
|
+
"description": null,
|
|
95
|
+
"language": "en",
|
|
96
|
+
"is_published": true,
|
|
97
|
+
"logo_url": null,
|
|
98
|
+
"project_type": "docs",
|
|
99
|
+
"is_archived": false,
|
|
100
|
+
"created_at": "2026-01-10T09:00:00Z",
|
|
101
|
+
"custom_domain": null,
|
|
102
|
+
"custom_domain_verified": false
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### `hinto templates`
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
hinto templates article # article templates for this project type
|
|
111
|
+
hinto templates article --json
|
|
112
|
+
hinto templates structure # structure templates for this project type
|
|
113
|
+
hinto templates structure --json
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Templates are automatically scoped to your project type.
|
|
117
|
+
|
|
118
|
+
`templates article --json` — `requires_video` tells you whether a template needs a video to generate from:
|
|
119
|
+
|
|
120
|
+
```json
|
|
121
|
+
{
|
|
122
|
+
"templates": [
|
|
123
|
+
{
|
|
124
|
+
"id": 1,
|
|
125
|
+
"name": "Tutorial",
|
|
126
|
+
"description": "Step-by-step guide format",
|
|
127
|
+
"requires_video": true,
|
|
128
|
+
"image_url": "https://cdn.hinto.ai/templates/tutorial.png",
|
|
129
|
+
"sort_order": 1
|
|
130
|
+
}
|
|
131
|
+
]
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### `hinto folders`
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
hinto folders list
|
|
139
|
+
hinto folders list --json
|
|
140
|
+
hinto folders get <id>
|
|
141
|
+
hinto folders get <id> --json
|
|
142
|
+
hinto folders create --name "Release Notes"
|
|
143
|
+
hinto folders create --name "Q2" --parent <parentId>
|
|
144
|
+
hinto folders update <id> --name "Q3"
|
|
145
|
+
hinto folders move <id> --parent <newParentId> # move into another folder
|
|
146
|
+
hinto folders move <id> # move to root
|
|
147
|
+
hinto folders delete <id>
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
`folders create --json`:
|
|
151
|
+
|
|
152
|
+
```json
|
|
153
|
+
{ "id": 7, "name": "Release Notes", "parent_id": null }
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
> **Note:** `folders move` does **not** return the updated folder — it returns a confirmation object:
|
|
157
|
+
>
|
|
158
|
+
> ```json
|
|
159
|
+
> { "message": "Folder moved", "parentId": null }
|
|
160
|
+
> ```
|
|
161
|
+
|
|
162
|
+
### `hinto articles`
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
hinto articles list
|
|
166
|
+
hinto articles list --folder <folderId>
|
|
167
|
+
hinto articles list --json
|
|
168
|
+
|
|
169
|
+
hinto articles get <id>
|
|
170
|
+
hinto articles get <id> --json
|
|
171
|
+
|
|
172
|
+
# --content is required; pass a Markdown string or a @filepath
|
|
173
|
+
hinto articles create --title "Getting Started" --content "# Hello\n\nWorld."
|
|
174
|
+
hinto articles create --title "From file" --content @path/to/article.md
|
|
175
|
+
hinto articles create --title "In a folder" --content "..." --folder <folderId>
|
|
176
|
+
|
|
177
|
+
hinto articles update <id> --title "New Title"
|
|
178
|
+
hinto articles update <id> --slug "new-slug"
|
|
179
|
+
|
|
180
|
+
hinto articles duplicate <id>
|
|
181
|
+
hinto articles move <id> --folder <folderId>
|
|
182
|
+
hinto articles regenerate <id>
|
|
183
|
+
|
|
184
|
+
hinto articles versions <id>
|
|
185
|
+
hinto articles restore <id> --vid <vId>
|
|
186
|
+
|
|
187
|
+
hinto articles translations <id>
|
|
188
|
+
hinto articles translate <id> --lang fr
|
|
189
|
+
|
|
190
|
+
hinto articles delete <id>
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
`articles list --json` — includes a `pagination` object:
|
|
194
|
+
|
|
195
|
+
```json
|
|
196
|
+
{
|
|
197
|
+
"articles": [
|
|
198
|
+
{
|
|
199
|
+
"id": 42,
|
|
200
|
+
"title": "Getting Started",
|
|
201
|
+
"slug": "getting-started",
|
|
202
|
+
"folder_id": null,
|
|
203
|
+
"inserted_at": "2026-01-15T10:00:00Z",
|
|
204
|
+
"updated_at": "2026-05-01T14:32:00Z"
|
|
205
|
+
}
|
|
206
|
+
],
|
|
207
|
+
"pagination": { "limit": 50, "offset": 0, "count": 1 }
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
`articles get <id> --json` — note the `metadata` wrapper for SEO fields, and that `content` is the full rendered output in the requested format:
|
|
212
|
+
|
|
213
|
+
```json
|
|
214
|
+
{
|
|
215
|
+
"id": 42,
|
|
216
|
+
"title": "Getting Started",
|
|
217
|
+
"slug": "getting-started",
|
|
218
|
+
"format": "markdown",
|
|
219
|
+
"content": "# Getting Started\n\nWelcome to Hinto.",
|
|
220
|
+
"metadata": {
|
|
221
|
+
"metaDescription": null,
|
|
222
|
+
"metaKeywords": null,
|
|
223
|
+
"jsonLd": null,
|
|
224
|
+
"createdAt": "2026-01-15T10:00:00Z",
|
|
225
|
+
"updatedAt": "2026-05-01T14:32:00Z"
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
`articles create --json` — returns only the essentials (`slug` may be `null` immediately after creation); fetch with `articles get` if you need the full article:
|
|
231
|
+
|
|
232
|
+
```json
|
|
233
|
+
{ "id": 42, "title": "Getting Started", "slug": null }
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
> **Note:** `articles move` does **not** return the updated article — it returns a confirmation object:
|
|
237
|
+
>
|
|
238
|
+
> ```json
|
|
239
|
+
> { "message": "Article moved", "folderId": 7 }
|
|
240
|
+
> ```
|
|
241
|
+
|
|
242
|
+
### `hinto videos`
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
hinto videos list
|
|
246
|
+
hinto videos list --json
|
|
247
|
+
hinto videos get <videoId>
|
|
248
|
+
hinto videos status <videoId>
|
|
249
|
+
hinto videos import --url https://example.com/video.mp4
|
|
250
|
+
hinto videos delete <videoId>
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### `hinto generate`
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
# fire-and-forget with explicit template
|
|
257
|
+
hinto generate start --video <videoId> --template <templateId>
|
|
258
|
+
|
|
259
|
+
# fire-and-forget with auto-selected template (optional --template)
|
|
260
|
+
hinto generate start --video <videoId>
|
|
261
|
+
|
|
262
|
+
# block until the job completes
|
|
263
|
+
hinto generate start --video <videoId> --template <templateId> --wait --json
|
|
264
|
+
|
|
265
|
+
# without --template, server picks the default
|
|
266
|
+
hinto generate start --video <videoId> --wait --json
|
|
267
|
+
|
|
268
|
+
# check an existing job
|
|
269
|
+
hinto generate status <jobId> --json
|
|
270
|
+
|
|
271
|
+
# generate / refresh project structure
|
|
272
|
+
hinto generate structure --video <videoId>
|
|
273
|
+
hinto generate structure --video <videoId> --wait
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
`generate start --json` (fire-and-forget) — use `jobId` to poll status:
|
|
277
|
+
|
|
278
|
+
```json
|
|
279
|
+
{
|
|
280
|
+
"jobId": "job_a1b2c3d4",
|
|
281
|
+
"articleId": 42,
|
|
282
|
+
"status": "pending",
|
|
283
|
+
"message": "Article generation started. Poll the job status endpoint for updates."
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
`generate status <jobId> --json` once complete:
|
|
288
|
+
|
|
289
|
+
```json
|
|
290
|
+
{
|
|
291
|
+
"jobId": "job_a1b2c3d4",
|
|
292
|
+
"type": "generate_article",
|
|
293
|
+
"status": "completed",
|
|
294
|
+
"output": { "articleId": 42 },
|
|
295
|
+
"error": null,
|
|
296
|
+
"createdAt": "2026-05-16T10:00:00Z",
|
|
297
|
+
"completedAt": "2026-05-16T10:02:34Z"
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Possible `status` values: `pending` · `processing` · `completed` · `failed`.
|
|
302
|
+
|
|
303
|
+
### `hinto export`
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
hinto export article <id> --format md # print Markdown to stdout
|
|
307
|
+
hinto export article <id> --format html # print HTML to stdout
|
|
308
|
+
hinto export article <id> --format md --out article.md # write to file
|
|
309
|
+
|
|
310
|
+
hinto export folder <id> --out folder.zip
|
|
311
|
+
hinto export project --out project.zip
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### `hinto publish`
|
|
315
|
+
|
|
316
|
+
```bash
|
|
317
|
+
hinto publish status
|
|
318
|
+
hinto publish status --json
|
|
319
|
+
hinto publish now # fire-and-forget
|
|
320
|
+
hinto publish now --wait # block until live
|
|
321
|
+
hinto publish now --wait --json
|
|
322
|
+
hinto publish republish
|
|
323
|
+
hinto publish republish --wait
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
`publish status --json` — the `status` field is a convenience alias derived from `isPublished`:
|
|
327
|
+
|
|
328
|
+
```json
|
|
329
|
+
{
|
|
330
|
+
"isPublished": true,
|
|
331
|
+
"slug": "my-docs",
|
|
332
|
+
"url": "https://my-docs.hintoai.com",
|
|
333
|
+
"publicationId": "pub_xyz789",
|
|
334
|
+
"publishedAt": "2026-04-20T09:00:00Z",
|
|
335
|
+
"articlesCount": 12,
|
|
336
|
+
"foldersCount": 3,
|
|
337
|
+
"status": "published"
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
When not yet published, `status` is `"unpublished"` and `url`, `publicationId`, `publishedAt`, `articlesCount`, `foldersCount` are all `null`.
|
|
342
|
+
|
|
343
|
+
## Machine-readable output (`--json`)
|
|
344
|
+
|
|
345
|
+
Every command that returns data supports `--json`. Output is newline-terminated JSON suitable for piping into `jq` or consuming from scripts and AI agents:
|
|
346
|
+
|
|
347
|
+
```bash
|
|
348
|
+
hinto articles list --json | jq '.articles[] | .title'
|
|
349
|
+
hinto project get --json | jq '.project.id'
|
|
350
|
+
hinto publish status --json | jq '.status'
|
|
351
|
+
hinto templates list --json | jq '[.templates[] | select(.requires_video) | .id]'
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
When an error occurs with `--json`, stdout is always empty and the error message goes to stderr — so it is safe to parse stdout without guarding for error objects.
|
|
355
|
+
|
|
356
|
+
## Exit codes
|
|
357
|
+
|
|
358
|
+
| Code | Meaning |
|
|
359
|
+
|------|---------|
|
|
360
|
+
| `0` | Success |
|
|
361
|
+
| `1` | Error (invalid API key, not found, network error, etc.) |
|
|
362
|
+
|
|
363
|
+
## Async jobs (`--wait`)
|
|
364
|
+
|
|
365
|
+
Commands that start background jobs — `generate start`, `publish now`, `publish republish`, `project retranslate` — default to fire-and-forget: they print the job metadata and return exit 0 immediately. Pass `--wait` to poll until the job completes (or fails) and print its output.
|
|
366
|
+
|
|
367
|
+
## Environment variables
|
|
368
|
+
|
|
369
|
+
| Variable | Description |
|
|
370
|
+
|----------|-------------|
|
|
371
|
+
| `HINTO_API_KEY` | Override the API key stored in `~/.hinto/config.json` |
|
|
372
|
+
|
|
373
|
+
## Development
|
|
374
|
+
|
|
375
|
+
### Setup
|
|
376
|
+
|
|
377
|
+
```bash
|
|
378
|
+
git clone <repo-url>
|
|
379
|
+
cd hinto-cli
|
|
380
|
+
npm install
|
|
381
|
+
npm run build # compile TypeScript → dist/
|
|
382
|
+
npm test # run unit tests (nock-based, no network required)
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
### Running locally without installing globally
|
|
386
|
+
|
|
387
|
+
Use `node dist/index.js` directly — no `npm link` or global install needed:
|
|
388
|
+
|
|
389
|
+
```bash
|
|
390
|
+
npm run build
|
|
391
|
+
node dist/index.js --api-url http://localhost:3000 project get
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
Or set a shell alias for the session:
|
|
395
|
+
|
|
396
|
+
```bash
|
|
397
|
+
alias hinto="node $(pwd)/dist/index.js"
|
|
398
|
+
hinto --api-url http://localhost:3000 videos list
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
Set `HINTO_API_KEY` in your environment to avoid passing the key on every command:
|
|
402
|
+
|
|
403
|
+
```bash
|
|
404
|
+
export HINTO_API_KEY=hinto_...
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### Testing against a local server
|
|
408
|
+
|
|
409
|
+
Point the CLI at the Next.js dev server running on port 3000 (or the e2e port 3099):
|
|
410
|
+
|
|
411
|
+
```bash
|
|
412
|
+
node dist/index.js --api-url http://localhost:3000 videos list --json
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
Grab an API key from the local Supabase DB or from the e2e test setup.
|
|
416
|
+
|
|
417
|
+
### Skill development
|
|
418
|
+
|
|
419
|
+
The Claude Code skill lives in `skills/hinto-cli/`. After editing any file there, copy it to Claude's global skills directory so it takes effect immediately:
|
|
420
|
+
|
|
421
|
+
```bash
|
|
422
|
+
cp -r skills/hinto-cli/. ~/.claude/skills/hinto-cli/
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
**Rule:** whenever you change a CLI flag or command behaviour, update **both**:
|
|
426
|
+
1. The relevant `skills/hinto-cli/references/*.md` file
|
|
427
|
+
2. The usage examples in this `README.md`
|
|
428
|
+
|
|
429
|
+
Then copy and commit together so the repo and the local skill stay in sync.
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { AxiosInstance } from 'axios';
|
|
2
|
+
export interface Article {
|
|
3
|
+
id: number;
|
|
4
|
+
title: string;
|
|
5
|
+
slug: string | null;
|
|
6
|
+
folderId: number | null;
|
|
7
|
+
createdAt: string;
|
|
8
|
+
updatedAt: string;
|
|
9
|
+
}
|
|
10
|
+
export interface ArticleDetail {
|
|
11
|
+
id: number;
|
|
12
|
+
title: string;
|
|
13
|
+
slug: string | null;
|
|
14
|
+
folderId: number | null;
|
|
15
|
+
format: string;
|
|
16
|
+
content: string;
|
|
17
|
+
createdAt: string | null;
|
|
18
|
+
updatedAt: string | null;
|
|
19
|
+
metadata: {
|
|
20
|
+
metaDescription: string | null;
|
|
21
|
+
metaKeywords: string[] | null;
|
|
22
|
+
jsonLd: string | null;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export interface ArticleVersion {
|
|
26
|
+
id: string;
|
|
27
|
+
versionNumber: number;
|
|
28
|
+
createdAt: string;
|
|
29
|
+
createdBy: string;
|
|
30
|
+
changeDescription: string | null;
|
|
31
|
+
isAutoSave: boolean;
|
|
32
|
+
}
|
|
33
|
+
export interface ArticleTranslation {
|
|
34
|
+
languageCode: string;
|
|
35
|
+
status: string;
|
|
36
|
+
title: string | null;
|
|
37
|
+
slug: string | null;
|
|
38
|
+
metaDescription: string | null;
|
|
39
|
+
metaKeywords: string[] | null;
|
|
40
|
+
hasContent: boolean;
|
|
41
|
+
updatedAt: string;
|
|
42
|
+
}
|
|
43
|
+
export declare const articlesApi: (client: AxiosInstance) => {
|
|
44
|
+
list: (params?: {
|
|
45
|
+
folderId?: string;
|
|
46
|
+
offset?: number;
|
|
47
|
+
limit?: number;
|
|
48
|
+
}) => Promise<{
|
|
49
|
+
articles: Article[];
|
|
50
|
+
pagination: {
|
|
51
|
+
limit: number;
|
|
52
|
+
offset: number;
|
|
53
|
+
count: number;
|
|
54
|
+
};
|
|
55
|
+
}>;
|
|
56
|
+
get: (id: string, format?: "markdown" | "html") => Promise<ArticleDetail>;
|
|
57
|
+
create: (body: {
|
|
58
|
+
title: string;
|
|
59
|
+
content?: string;
|
|
60
|
+
folderId?: string;
|
|
61
|
+
}) => Promise<ArticleDetail>;
|
|
62
|
+
update: (id: string, body: {
|
|
63
|
+
title?: string;
|
|
64
|
+
slug?: string;
|
|
65
|
+
metaDescription?: string;
|
|
66
|
+
metaKeywords?: string[];
|
|
67
|
+
}) => Promise<ArticleDetail>;
|
|
68
|
+
delete: (id: string) => Promise<any>;
|
|
69
|
+
duplicate: (id: string) => Promise<ArticleDetail>;
|
|
70
|
+
move: (id: string, folderId: string | null) => Promise<ArticleDetail>;
|
|
71
|
+
regenerate: (id: string, callbackUrl?: string, callbackSecret?: string) => Promise<import("./generate").Job>;
|
|
72
|
+
createEmpty: (body: {
|
|
73
|
+
title?: string;
|
|
74
|
+
folderId?: number;
|
|
75
|
+
}) => Promise<ArticleDetail>;
|
|
76
|
+
listVersions: (id: string) => Promise<{
|
|
77
|
+
versions: ArticleVersion[];
|
|
78
|
+
}>;
|
|
79
|
+
restoreVersion: (id: string, versionId: string) => Promise<{
|
|
80
|
+
message: string;
|
|
81
|
+
articleId: number;
|
|
82
|
+
versionId: string;
|
|
83
|
+
}>;
|
|
84
|
+
listTranslations: (id: string) => Promise<{
|
|
85
|
+
translations: ArticleTranslation[];
|
|
86
|
+
}>;
|
|
87
|
+
getTranslation: (id: string, lang: string, format?: "markdown" | "html") => Promise<{
|
|
88
|
+
languageCode: string;
|
|
89
|
+
status: string;
|
|
90
|
+
title: string | null;
|
|
91
|
+
slug: string | null;
|
|
92
|
+
format: string;
|
|
93
|
+
content: string | null;
|
|
94
|
+
metadata: {
|
|
95
|
+
metaDescription: string | null;
|
|
96
|
+
metaKeywords: string[] | null;
|
|
97
|
+
updatedAt: string | null;
|
|
98
|
+
};
|
|
99
|
+
}>;
|
|
100
|
+
triggerTranslate: (id: string, lang: string, callbackUrl?: string, callbackSecret?: string) => Promise<import("./generate").Job>;
|
|
101
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.articlesApi = void 0;
|
|
4
|
+
const articlesApi = (client) => ({
|
|
5
|
+
list: (params) => client
|
|
6
|
+
.get('/articles', { params })
|
|
7
|
+
.then((r) => r.data),
|
|
8
|
+
get: (id, format) => client
|
|
9
|
+
.get(`/articles/${id}`, { params: format ? { format } : undefined })
|
|
10
|
+
.then((r) => r.data),
|
|
11
|
+
create: (body) => client.post('/articles', body).then((r) => r.data),
|
|
12
|
+
update: (id, body) => client.put(`/articles/${id}`, body).then((r) => r.data),
|
|
13
|
+
delete: (id) => client.delete(`/articles/${id}`).then((r) => r.data),
|
|
14
|
+
duplicate: (id) => client.post(`/articles/${id}/duplicate`).then((r) => r.data),
|
|
15
|
+
move: (id, folderId) => client.patch(`/articles/${id}/move`, { folderId }).then((r) => r.data),
|
|
16
|
+
regenerate: (id, callbackUrl, callbackSecret) => client
|
|
17
|
+
.post(`/articles/${id}/regenerate`, {
|
|
18
|
+
...(callbackUrl ? { callbackUrl } : {}),
|
|
19
|
+
...(callbackSecret ? { callbackSecret } : {}),
|
|
20
|
+
})
|
|
21
|
+
.then((r) => r.data),
|
|
22
|
+
createEmpty: (body) => client.post('/articles/empty', body).then((r) => r.data),
|
|
23
|
+
listVersions: (id) => client.get(`/articles/${id}/versions`).then((r) => r.data),
|
|
24
|
+
restoreVersion: (id, versionId) => client
|
|
25
|
+
.post(`/articles/${id}/versions/${versionId}/restore`)
|
|
26
|
+
.then((r) => r.data),
|
|
27
|
+
listTranslations: (id) => client
|
|
28
|
+
.get(`/articles/${id}/translations`)
|
|
29
|
+
.then((r) => r.data),
|
|
30
|
+
getTranslation: (id, lang, format) => client
|
|
31
|
+
.get(`/articles/${id}/translations/${lang}`, { params: format ? { format } : undefined })
|
|
32
|
+
.then((r) => r.data),
|
|
33
|
+
triggerTranslate: (id, lang, callbackUrl, callbackSecret) => client
|
|
34
|
+
.post(`/articles/${id}/translations/${lang}`, {
|
|
35
|
+
...(callbackUrl ? { callbackUrl } : {}),
|
|
36
|
+
...(callbackSecret ? { callbackSecret } : {}),
|
|
37
|
+
})
|
|
38
|
+
.then((r) => r.data),
|
|
39
|
+
});
|
|
40
|
+
exports.articlesApi = articlesApi;
|
|
@@ -0,0 +1,48 @@
|
|
|
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.createClient = createClient;
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
const errors_1 = require("../errors");
|
|
9
|
+
function createClient(apiKey, baseUrl) {
|
|
10
|
+
const instance = axios_1.default.create({
|
|
11
|
+
baseURL: `${baseUrl}/api/external/v2`,
|
|
12
|
+
headers: { 'X-API-Key': apiKey, 'Content-Type': 'application/json' },
|
|
13
|
+
timeout: 30000,
|
|
14
|
+
});
|
|
15
|
+
instance.interceptors.response.use((res) => res, (err) => {
|
|
16
|
+
const apiError = err.response?.data?.error;
|
|
17
|
+
if (apiError) {
|
|
18
|
+
if (apiError.code === 'UNAUTHORIZED') {
|
|
19
|
+
throw new errors_1.CliError('UNAUTHORIZED', 'Invalid API key. Run `hinto init --key <your-api-key>` to authenticate.');
|
|
20
|
+
}
|
|
21
|
+
throw new errors_1.CliError(apiError.code, apiError.message);
|
|
22
|
+
}
|
|
23
|
+
if (!apiError && err.response?.data) {
|
|
24
|
+
try {
|
|
25
|
+
const raw = typeof err.response.data === 'string'
|
|
26
|
+
? err.response.data
|
|
27
|
+
: Buffer.from(err.response.data).toString('utf-8');
|
|
28
|
+
const parsed = JSON.parse(raw);
|
|
29
|
+
if (parsed?.error) {
|
|
30
|
+
if (parsed.error.code === 'UNAUTHORIZED') {
|
|
31
|
+
throw new errors_1.CliError('UNAUTHORIZED', 'Invalid API key. Run `hinto init --key <your-api-key>` to authenticate.');
|
|
32
|
+
}
|
|
33
|
+
throw new errors_1.CliError(parsed.error.code, parsed.error.message ?? 'Unknown error');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch (parseErr) {
|
|
37
|
+
if (parseErr instanceof errors_1.CliError)
|
|
38
|
+
throw parseErr;
|
|
39
|
+
// ignore parse failures — fall through to generic message
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (err.code === 'ECONNREFUSED' || err.code === 'ENOTFOUND') {
|
|
43
|
+
throw new errors_1.CliError('NETWORK_ERROR', 'Could not reach Hinto API — check your connection');
|
|
44
|
+
}
|
|
45
|
+
throw new errors_1.CliError('UNKNOWN_ERROR', err.message ?? 'An unexpected error occurred');
|
|
46
|
+
});
|
|
47
|
+
return instance;
|
|
48
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { AxiosInstance } from 'axios';
|
|
2
|
+
export declare const exportApi: (client: AxiosInstance) => {
|
|
3
|
+
article: (id: string, format?: "markdown" | "html" | "pdf", lang?: string) => Promise<string | Buffer>;
|
|
4
|
+
folder: (id: string) => Promise<Buffer<ArrayBufferLike>>;
|
|
5
|
+
project: (format?: "markdown" | "html" | "pdf" | "llm-text") => Promise<Buffer<ArrayBufferLike>>;
|
|
6
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.exportApi = void 0;
|
|
4
|
+
const exportApi = (client) => ({
|
|
5
|
+
article: (id, format, lang) => {
|
|
6
|
+
const isPdf = format === 'pdf';
|
|
7
|
+
return client
|
|
8
|
+
.get(`/export/articles/${id}`, {
|
|
9
|
+
params: { ...(format ? { format } : {}), ...(lang ? { lang } : {}) },
|
|
10
|
+
responseType: isPdf ? 'arraybuffer' : 'text',
|
|
11
|
+
})
|
|
12
|
+
.then((r) => (isPdf ? Buffer.from(r.data) : r.data));
|
|
13
|
+
},
|
|
14
|
+
folder: (id) => client
|
|
15
|
+
.get(`/export/folders/${id}`, { responseType: 'arraybuffer' })
|
|
16
|
+
.then((r) => r.data),
|
|
17
|
+
project: (format) => client
|
|
18
|
+
.get('/export/project', {
|
|
19
|
+
params: format ? { format } : undefined,
|
|
20
|
+
responseType: 'arraybuffer',
|
|
21
|
+
})
|
|
22
|
+
.then((r) => r.data),
|
|
23
|
+
});
|
|
24
|
+
exports.exportApi = exportApi;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { AxiosInstance } from 'axios';
|
|
2
|
+
export interface Folder {
|
|
3
|
+
id: number;
|
|
4
|
+
name: string;
|
|
5
|
+
parentId: number | null;
|
|
6
|
+
createdAt: string;
|
|
7
|
+
updatedAt: string;
|
|
8
|
+
}
|
|
9
|
+
export declare const foldersApi: (client: AxiosInstance) => {
|
|
10
|
+
list: () => Promise<{
|
|
11
|
+
folders: Folder[];
|
|
12
|
+
}>;
|
|
13
|
+
get: (id: string) => Promise<Folder>;
|
|
14
|
+
create: (name: string, parentId?: string) => Promise<Folder>;
|
|
15
|
+
update: (id: string, name: string) => Promise<Folder>;
|
|
16
|
+
delete: (id: string) => Promise<any>;
|
|
17
|
+
move: (id: string, parentId: string | null) => Promise<Folder>;
|
|
18
|
+
};
|