kivault-cli 1.4.1__py3-none-any.whl

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.
@@ -0,0 +1 @@
1
+ """KiVault CLI package."""
kivault_cli/app.py ADDED
@@ -0,0 +1,363 @@
1
+ from __future__ import annotations
2
+
3
+ from enum import Enum
4
+ from importlib.metadata import PackageNotFoundError, version
5
+ from pathlib import Path
6
+ import re
7
+ import subprocess
8
+ import sys
9
+ import tomllib
10
+ from typing import Any, TypedDict
11
+
12
+ from agent_skill_dist import (
13
+ SkillAlreadyExists,
14
+ SkillDistError,
15
+ install_bundled_skill,
16
+ skill_status,
17
+ )
18
+ from agent_skill_dist.cli import already_exists_to_text, install_to_text, status_to_text
19
+ import httpx
20
+ import typer
21
+
22
+ from kivault_cli.commands import items, public, tags, vaults
23
+ from kivault_cli.commands.identity import whoami
24
+ from kivault_cli.errors import CliError
25
+ from kivault_cli.output import emit
26
+ from kivault_cli.settings import load_settings
27
+ from kivault_cli.utils import help_markup_mode
28
+
29
+
30
+ PACKAGE_NAME = "kivault-cli"
31
+ RELEASE_APP = "kivault_cli"
32
+ RELEASE_TAG = "default"
33
+ LATEST_RELEASE_URL = "https://releases.kispace.cc/api/public/latest"
34
+ SKILL_PACKAGE = "kivault_cli"
35
+ SKILL_RESOURCE_ROOT = "bundled_skills"
36
+ SKILL_NAME = "kivault-cli"
37
+
38
+
39
+ class SkillTarget(str, Enum):
40
+ repo = "repo"
41
+ global_target = "global"
42
+
43
+
44
+ class ReleaseInfo(TypedDict):
45
+ version: str
46
+ tags: list[str]
47
+ download_url: str
48
+ release_notes: str | None
49
+ created_at: str
50
+
51
+
52
+ def _package_version() -> str:
53
+ pyproject_version = _pyproject_version()
54
+ if pyproject_version:
55
+ return pyproject_version
56
+ try:
57
+ return version(PACKAGE_NAME)
58
+ except PackageNotFoundError:
59
+ return "unknown"
60
+
61
+
62
+ def _pyproject_version() -> str | None:
63
+ pyproject_path = Path(__file__).resolve().parents[1] / "pyproject.toml"
64
+ if not pyproject_path.exists():
65
+ return None
66
+ try:
67
+ data = tomllib.loads(pyproject_path.read_text(encoding="utf-8"))
68
+ except (OSError, tomllib.TOMLDecodeError):
69
+ return None
70
+ project = data.get("project")
71
+ if not isinstance(project, dict):
72
+ return None
73
+ project_version = project.get("version")
74
+ if not isinstance(project_version, str):
75
+ return None
76
+ return project_version
77
+
78
+
79
+ def _version_callback(value: bool) -> None:
80
+ if not value:
81
+ return
82
+ typer.echo(f"kivault {_package_version()}")
83
+ raise typer.Exit()
84
+
85
+
86
+ def _latest_release_url() -> str:
87
+ return f"{LATEST_RELEASE_URL}?app={RELEASE_APP}&tags={RELEASE_TAG}"
88
+
89
+
90
+ def _fetch_latest_release() -> ReleaseInfo:
91
+ try:
92
+ response = httpx.get(
93
+ _latest_release_url(), timeout=30, follow_redirects=True, trust_env=False
94
+ )
95
+ except httpx.HTTPError as exc:
96
+ raise CliError(f"无法获取最新版本信息:{exc}") from exc
97
+ if response.status_code >= 400:
98
+ raise CliError(
99
+ f"无法获取最新版本信息:HTTP {response.status_code} {response.reason_phrase}"
100
+ )
101
+ try:
102
+ payload = response.json()
103
+ except ValueError as exc:
104
+ raise CliError("无法解析最新版本信息:服务端返回的不是 JSON。") from exc
105
+ return _parse_release_info(payload)
106
+
107
+
108
+ def _parse_release_info(payload: Any) -> ReleaseInfo:
109
+ if not isinstance(payload, dict):
110
+ raise CliError("无法解析最新版本信息:响应不是 JSON object。")
111
+ version_value = payload.get("version")
112
+ download_url = payload.get("download_url")
113
+ if not isinstance(version_value, str) or not version_value:
114
+ raise CliError("无法解析最新版本信息:缺少 version。")
115
+ if not isinstance(download_url, str) or not download_url:
116
+ raise CliError("无法解析最新版本信息:缺少 download_url。")
117
+ tags_value = payload.get("tags")
118
+ tags = [str(tag) for tag in tags_value] if isinstance(tags_value, list) else []
119
+ release_notes_value = payload.get("release_notes")
120
+ created_at_value = payload.get("created_at")
121
+ return {
122
+ "version": version_value,
123
+ "tags": tags,
124
+ "download_url": download_url,
125
+ "release_notes": release_notes_value
126
+ if isinstance(release_notes_value, str)
127
+ else None,
128
+ "created_at": created_at_value if isinstance(created_at_value, str) else "",
129
+ }
130
+
131
+
132
+ def _is_newer_version(latest: str, current: str) -> bool:
133
+ return _version_parts(latest) > _version_parts(current)
134
+
135
+
136
+ def _version_parts(value: str) -> tuple[int, ...]:
137
+ parts = [int(part) for part in re.findall(r"\d+", value)]
138
+ return tuple(parts) if parts else (0,)
139
+
140
+
141
+ def _install_release(download_url: str) -> None:
142
+ command = [
143
+ sys.executable,
144
+ "-m",
145
+ "pip",
146
+ "install",
147
+ "--upgrade",
148
+ download_url,
149
+ ]
150
+ try:
151
+ subprocess.run(command, check=True, capture_output=True, text=True)
152
+ except FileNotFoundError as exc:
153
+ raise CliError("无法执行 pip:当前 Python 环境不可用。") from exc
154
+ except subprocess.CalledProcessError as exc:
155
+ detail = (exc.stderr or exc.stdout or "").strip()
156
+ message = f"升级失败:pip 退出码 {exc.returncode}"
157
+ if detail:
158
+ message = f"{message}\n{detail}"
159
+ raise CliError(message) from exc
160
+
161
+
162
+ app = typer.Typer(
163
+ no_args_is_help=True,
164
+ rich_markup_mode=help_markup_mode(),
165
+ help=(
166
+ "KiVault 命令行客户端。\n\n"
167
+ "\b\n示例:\n"
168
+ " kivault health\n"
169
+ " kivault --server http://127.0.0.1:8000 whoami\n"
170
+ ' kivault item add-text --vault-id vault_123 --title Note --content "hello"'
171
+ ),
172
+ )
173
+ skill_app = typer.Typer(
174
+ no_args_is_help=True,
175
+ rich_markup_mode=help_markup_mode(),
176
+ help="安装或查看 KiVault CLI 内置 agent skill。",
177
+ )
178
+
179
+
180
+ @app.callback()
181
+ def cli(
182
+ ctx: typer.Context,
183
+ server: str | None = typer.Option(
184
+ None,
185
+ "--server",
186
+ help="临时覆盖 KiVault 服务地址;未传时使用 KIVAULT_SERVER_URL 或默认 http://127.0.0.1:8000。",
187
+ ),
188
+ json_output: bool = typer.Option(
189
+ False, "--json", help="输出服务端返回的原始 JSON,便于脚本处理。"
190
+ ),
191
+ version_output: bool = typer.Option(
192
+ False,
193
+ "--version",
194
+ callback=_version_callback,
195
+ is_eager=True,
196
+ help="显示版本号并退出。",
197
+ ),
198
+ ) -> None:
199
+ ctx.obj = {
200
+ "settings": load_settings(server_override=server),
201
+ "json": json_output,
202
+ }
203
+
204
+
205
+ @app.command()
206
+ def health(ctx: typer.Context) -> None:
207
+ """检查服务健康状态。
208
+
209
+ \b
210
+ 示例:
211
+ kivault health
212
+ kivault --server http://127.0.0.1:8000 health
213
+ """
214
+ from kivault_cli.client import api
215
+
216
+ emit(ctx, api(ctx, auth=False).request("GET", "/api/health"))
217
+
218
+
219
+ @app.command()
220
+ def upgrade(
221
+ ctx: typer.Context,
222
+ yes: bool = typer.Option(
223
+ False, "--yes", "-y", help="确认升级。未传时只检查最新版本,不安装。"
224
+ ),
225
+ ) -> None:
226
+ """检查并升级 KiVault CLI。
227
+
228
+ \b
229
+ 示例:
230
+ kivault upgrade
231
+ kivault upgrade --yes
232
+ """
233
+ current_version = _package_version()
234
+ release = _fetch_latest_release()
235
+ is_newer = _is_newer_version(release["version"], current_version)
236
+ result = {
237
+ "current_version": current_version,
238
+ "latest_version": release["version"],
239
+ "latest_tags": release["tags"],
240
+ "download_url": release["download_url"],
241
+ "release_notes": release["release_notes"],
242
+ "created_at": release["created_at"],
243
+ "upgrade_available": is_newer,
244
+ "installed": False,
245
+ }
246
+
247
+ if not ctx.obj.get("json"):
248
+ typer.echo(f"current: {current_version}")
249
+ typer.echo(f"latest: {release['version']}")
250
+ typer.echo(f"url: {release['download_url']}")
251
+
252
+ if not is_newer:
253
+ if ctx.obj.get("json"):
254
+ emit(ctx, result)
255
+ else:
256
+ typer.echo("当前已经是最新版本,无需升级。")
257
+ return
258
+
259
+ if not yes:
260
+ if ctx.obj.get("json"):
261
+ emit(ctx, result)
262
+ else:
263
+ typer.echo("发现新版本。执行 `kivault upgrade --yes` 确认升级。")
264
+ return
265
+
266
+ if not ctx.obj.get("json"):
267
+ typer.echo("开始升级 KiVault CLI...")
268
+ _install_release(release["download_url"])
269
+ result["installed"] = True
270
+ if ctx.obj.get("json"):
271
+ emit(ctx, result)
272
+ else:
273
+ typer.echo("升级完成。")
274
+
275
+
276
+ @skill_app.command("status")
277
+ def skill_install_status(
278
+ ctx: typer.Context,
279
+ target: SkillTarget = typer.Option(
280
+ SkillTarget.repo,
281
+ "--target",
282
+ help="安装目标。repo 为当前目录 .agents/skills;global 为 CODEX_HOME skills。",
283
+ ),
284
+ output: Path | None = typer.Option(
285
+ None, "--output", help="自定义 skills 根目录;最终安装到 <output>/kivault-cli。"
286
+ ),
287
+ ) -> None:
288
+ """查看内置 agent skill 的安装状态。"""
289
+ try:
290
+ status = skill_status(
291
+ package=SKILL_PACKAGE,
292
+ distribution=PACKAGE_NAME,
293
+ resource_root=SKILL_RESOURCE_ROOT,
294
+ skill_name=SKILL_NAME,
295
+ package_version=_package_version(),
296
+ target=_skill_dist_target(target),
297
+ output=output,
298
+ )
299
+ except SkillDistError as exc:
300
+ raise CliError(str(exc)) from exc
301
+
302
+ if ctx.obj.get("json"):
303
+ emit(ctx, status.to_dict())
304
+ else:
305
+ typer.echo(status_to_text(status))
306
+
307
+
308
+ @skill_app.command("install")
309
+ def install_skill(
310
+ ctx: typer.Context,
311
+ target: SkillTarget = typer.Option(
312
+ SkillTarget.repo,
313
+ "--target",
314
+ help="安装目标。repo 为当前目录 .agents/skills;global 为 CODEX_HOME skills。",
315
+ ),
316
+ output: Path | None = typer.Option(
317
+ None, "--output", help="自定义 skills 根目录;最终安装到 <output>/kivault-cli。"
318
+ ),
319
+ yes: bool = typer.Option(
320
+ False, "--yes", "-y", help="目标已存在时允许覆盖。"
321
+ ),
322
+ ) -> None:
323
+ """安装 KiVault CLI 内置 agent skill。"""
324
+ try:
325
+ status = install_bundled_skill(
326
+ package=SKILL_PACKAGE,
327
+ distribution=PACKAGE_NAME,
328
+ resource_root=SKILL_RESOURCE_ROOT,
329
+ skill_name=SKILL_NAME,
330
+ package_version=_package_version(),
331
+ target=_skill_dist_target(target),
332
+ output=output,
333
+ yes=yes,
334
+ )
335
+ except SkillAlreadyExists as exc:
336
+ raise CliError(already_exists_to_text(exc)) from exc
337
+ except SkillDistError as exc:
338
+ raise CliError(str(exc)) from exc
339
+
340
+ if ctx.obj.get("json"):
341
+ emit(ctx, status.to_dict())
342
+ else:
343
+ typer.echo(install_to_text(status))
344
+
345
+
346
+ def _skill_dist_target(target: SkillTarget) -> str:
347
+ return target.value
348
+
349
+
350
+ app.command("whoami")(whoami)
351
+ app.add_typer(vaults.app, name="vault")
352
+ app.add_typer(items.app, name="item")
353
+ app.add_typer(tags.app, name="tag")
354
+ app.add_typer(public.app, name="public")
355
+ app.add_typer(skill_app, name="skill")
356
+
357
+
358
+ def run() -> None:
359
+ try:
360
+ app()
361
+ except CliError as exc:
362
+ typer.secho(exc.message, fg=typer.colors.RED, err=True)
363
+ sys.exit(exc.exit_code)
@@ -0,0 +1,97 @@
1
+ ---
2
+ name: kivault-cli
3
+ description: Use the KiVault CLI from this repository to manage KiVault data through the HTTP API. Use when an agent needs to check KiVault health or identity, configure or run the CLI, create/list/update/delete vaults, create/search/export items, upload/download item objects, manage tags, or read public KiVault items and objects. Also use for Chinese requests mentioning KiVault CLI, Vault, Item, Object, Tag, 公开读取, 上传附件, 下载附件, or token/server configuration.
4
+ ---
5
+
6
+ # KiVault CLI
7
+
8
+ Use this skill to operate the local KiVault command-line client safely and consistently.
9
+
10
+ ## Quick Start
11
+
12
+ Assume the CLI is installed and available as `kivault`. Check the current CLI version when behavior matters:
13
+
14
+ ```bash
15
+ kivault --version
16
+ kivault --help
17
+ ```
18
+
19
+ Run commands from the user's current working directory unless a file path or repository checkout path requires otherwise.
20
+
21
+ Set these environment variables for authenticated commands:
22
+
23
+ ```bash
24
+ export KIVAULT_SERVER_URL=http://127.0.0.1:8000
25
+ export KIVAULT_TOKEN=<token>
26
+ ```
27
+
28
+ `KIVAULT_SERVER_URL` defaults to `http://127.0.0.1:8000`. The CLI has no login command and does not persist tokens.
29
+
30
+ ## Operating Rules
31
+
32
+ - Use `--json` for commands whose output will be parsed by an agent or script.
33
+ - `item list` uses the v2 search endpoint. In `--json` mode it returns a page object with `items`, `limit`, `offset`, and `total`, not a bare array.
34
+ - Use `kivault upgrade` to check the release service. Only run `kivault upgrade --yes` when the user explicitly wants to install the latest wheel.
35
+ - Set `KIVAULT_PLAIN_HELP=1` before help commands when plain text is easier to capture.
36
+ - Never print or echo `KIVAULT_TOKEN`; `whoami` is safe because it reports token presence and server identity.
37
+ - Prefer `--no-wait` for file or object uploads in agent shell tools, then poll with `item object upload-task` or `item object upload-task-wait`.
38
+ - Use `--yes` only for deletes the user explicitly requested.
39
+ - Distinguish text content from file uploads: `--content-file` reads UTF-8 text into `Item.content_text`; binary/PDF/image uploads must use `item add-file` or `item object upload`.
40
+
41
+ ## Workflow
42
+
43
+ 1. Check the server and auth context before mutating data:
44
+
45
+ ```bash
46
+ kivault health
47
+ kivault --version
48
+ kivault whoami
49
+ ```
50
+
51
+ 2. Use `vault`, `item`, `item object`, `tag`, or `public` commands depending on the task.
52
+ 3. Read [command-guide.md](references/command-guide.md) before composing non-trivial command sequences, uploads, downloads, public access flows, or destructive operations.
53
+ 4. Capture IDs from JSON output, then pass those IDs to follow-up commands.
54
+
55
+ ## Common Patterns
56
+
57
+ Create a vault, add Markdown text, and export it:
58
+
59
+ ```bash
60
+ kivault --json vault create Inbox
61
+ kivault --json item add-text --vault-id <vault-id> --title "Note.md" --content-file ./note.md
62
+ kivault item export <item-id> --format markdown --output ./note.md
63
+ ```
64
+
65
+ Search items with v2 field filters:
66
+
67
+ ```bash
68
+ kivault --json item list -q report --search-field title --search-field object_filename --limit 20
69
+ ```
70
+
71
+ Check for a CLI upgrade without installing:
72
+
73
+ ```bash
74
+ kivault upgrade
75
+ ```
76
+
77
+ Upload a file without long blocking waits:
78
+
79
+ ```bash
80
+ kivault --json item add-file ./document.pdf --vault-id <vault-id> --no-wait
81
+ kivault --json item object upload-task <item-id> <task-id>
82
+ kivault --json item object upload-task-wait <item-id> <task-id>
83
+ ```
84
+
85
+ Download an object:
86
+
87
+ ```bash
88
+ kivault item object download <item-id> <object-id> --output ./document.pdf
89
+ ```
90
+
91
+ ## Validation
92
+
93
+ For CLI code changes, run the project tests:
94
+
95
+ ```bash
96
+ .venv/bin/python -m pytest . -q
97
+ ```
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "KiVault CLI"
3
+ short_description: "Use the KiVault command-line client"
4
+ default_prompt: "Use $kivault-cli to manage KiVault vaults, items, objects, tags, and public reads."
@@ -0,0 +1,171 @@
1
+ # KiVault CLI Command Guide
2
+
3
+ Use this reference for command composition. Assume the CLI is installed as `kivault`; do not run it through `python main.py` unless the user explicitly asks to work from a source checkout.
4
+
5
+ ## Environment
6
+
7
+ Authenticated commands need `KIVAULT_TOKEN`.
8
+
9
+ ```bash
10
+ export KIVAULT_SERVER_URL=http://127.0.0.1:8000
11
+ export KIVAULT_TOKEN=<token>
12
+ ```
13
+
14
+ Use `--server <url>` to override the server for one command:
15
+
16
+ ```bash
17
+ kivault --server http://127.0.0.1:8000 whoami
18
+ ```
19
+
20
+ Check the CLI version:
21
+
22
+ ```bash
23
+ kivault --version
24
+ ```
25
+
26
+ Check or install the latest wheel from the KiSpace release service:
27
+
28
+ ```bash
29
+ kivault upgrade
30
+ kivault upgrade --yes
31
+ ```
32
+
33
+ `upgrade` reads `https://releases.kispace.cc/api/public/latest?app=kivault_cli&tags=default`. Without `--yes`, it only reports `current`, `latest`, and the wheel URL. Use `--yes` only when the user explicitly approves installing the latest wheel.
34
+
35
+ Use `--json` when the next step needs IDs or structured data:
36
+
37
+ ```bash
38
+ kivault --json item list --vault-id <vault-id>
39
+ ```
40
+
41
+ For `item list`, JSON output is a v2 search page object:
42
+
43
+ ```json
44
+ {"items": [], "limit": 50, "offset": 0, "total": 0}
45
+ ```
46
+
47
+ ## Health and Identity
48
+
49
+ ```bash
50
+ kivault health
51
+ kivault whoami
52
+ ```
53
+
54
+ `health` does not require auth. `whoami` calls `/api/whoami` and reports current server URL plus whether `KIVAULT_TOKEN` is set.
55
+
56
+ ## Vaults
57
+
58
+ ```bash
59
+ kivault --json vault list
60
+ kivault --json vault create Inbox
61
+ kivault --json vault create Public --visibility public --default-level public --description "公开资料"
62
+ kivault --json vault get <vault-id>
63
+ kivault --json vault update <vault-id> --name Archive
64
+ kivault --json vault update <vault-id> --visibility public --default-level public
65
+ kivault --json vault delete <vault-id> --yes
66
+ ```
67
+
68
+ Vault visibility values are `private` and `public`. Default item level values are `public`, `private`, `sensitive`, and `danger`.
69
+
70
+ ## Items
71
+
72
+ List and search:
73
+
74
+ ```bash
75
+ kivault --json item list --vault-id <vault-id> --limit 50
76
+ kivault --json item list --keyword "deploy note" --type markdown
77
+ kivault --json item list -q report --search-field title --search-field object_filename
78
+ kivault --json item list --tag-id <tag-id> --level private
79
+ ```
80
+
81
+ `item list` calls `/api/v2/items/search`. Use `--search-field` to constrain keyword matching; repeat it for multiple fields. Valid search fields are `title`, `content`, `metadata`, `object_filename`, and `object_key`.
82
+
83
+ Create text or Markdown:
84
+
85
+ ```bash
86
+ kivault --json item add-text --vault-id <vault-id> --title "Note" --content "hello"
87
+ kivault --json item add-text --vault-id <vault-id> --title "Note.md" --content-file ./note.md
88
+ ```
89
+
90
+ Use `item create` when more fields are needed:
91
+
92
+ ```bash
93
+ kivault --json item create --vault-id <vault-id> --title "Web clip" --type markdown --source-url https://example.com --source-type web --metadata '{"kind":"clip"}'
94
+ ```
95
+
96
+ Update, read, export, delete:
97
+
98
+ ```bash
99
+ kivault --json item get <item-id>
100
+ kivault --json item update <item-id> --title "New title"
101
+ kivault --json item update <item-id> --content-file ./note.md --tag-id <tag-a> --tag-id <tag-b>
102
+ kivault item export <item-id> --format markdown --output ./item.md
103
+ kivault --json item export <item-id> --format json
104
+ kivault --json item delete <item-id> --yes
105
+ ```
106
+
107
+ Item type values are `text`, `markdown`, `html`, `web_page`, `image`, `audio`, `video`, `pdf`, `document`, `spreadsheet`, `structured_data`, `file`, and `other`. Status values are `active`, `archived`, and `deleted`.
108
+
109
+ ## File Items and Objects
110
+
111
+ Create an item and upload an initial attachment:
112
+
113
+ ```bash
114
+ kivault --json item add-file ./document.pdf --vault-id <vault-id> --no-wait
115
+ ```
116
+
117
+ Add one or more objects to an existing item:
118
+
119
+ ```bash
120
+ kivault --json item object upload <item-id> ./a.pdf ./b.png --no-wait
121
+ kivault --json item object upload-multipart <item-id> ./a.pdf ./b.md --no-wait
122
+ ```
123
+
124
+ Both upload commands create asynchronous upload tasks. The current implementation sends base64 JSON payloads; `upload-multipart` is a compatibility command name.
125
+
126
+ Check or wait for a task:
127
+
128
+ ```bash
129
+ kivault --json item object upload-task <item-id> <task-id>
130
+ kivault --json item object upload-task-wait <item-id> <task-id> --poll-interval 1 --timeout 600
131
+ ```
132
+
133
+ List, inspect, download, or delete objects:
134
+
135
+ ```bash
136
+ kivault --json item object list <item-id>
137
+ kivault --json item object get <item-id> <object-id>
138
+ kivault item object download <item-id> <object-id> --output ./document.pdf
139
+ kivault --json item object delete <item-id> <object-id> --yes
140
+ ```
141
+
142
+ Upload limits from the README: each file must be under 100 MB, and one task can include at most 20 files.
143
+
144
+ ## Tags
145
+
146
+ ```bash
147
+ kivault --json tag list
148
+ kivault --json tag create research --color '#3b82f6'
149
+ kivault --json tag update <tag-id> --name reading
150
+ kivault --json tag add <tag-id> <item-id>
151
+ kivault --json tag remove <tag-id> <item-id>
152
+ kivault --json tag delete <tag-id> --yes
153
+ ```
154
+
155
+ ## Public Reads
156
+
157
+ Public commands do not require auth. They work only when the vault is public, the item level is public, and the item status is active.
158
+
159
+ ```bash
160
+ kivault --json public item <item-id>
161
+ kivault public download-object <item-id> <object-id> --output ./file.pdf
162
+ ```
163
+
164
+ ## Help
165
+
166
+ Use plain help for command discovery:
167
+
168
+ ```bash
169
+ KIVAULT_PLAIN_HELP=1 kivault item create --help
170
+ KIVAULT_PLAIN_HELP=1 kivault item object upload --help
171
+ ```