@fluxvita/jovida-cli 0.0.1

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 FluxVita
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,76 @@
1
+ # jovida-cli
2
+
3
+ **English** · [简体中文](./README.zh-CN.md)
4
+
5
+ The **Jovida Daily CLI** — capture and manage your **Jovida Daily** todos from the terminal and from AI coding agents (Codex, Claude Code, …). It ships with an agent **skill** so your AI knows when and how to use it.
6
+
7
+ > **If you are the AI agent setting this up:** do **[Setup](#setup)** in order. Steps 1–2 you do yourself. **Step 3 (`jovida login`) you cannot do — but you must not skip it:** after installing, explicitly tell the user to run `jovida login` (a browser opens; they sign in and approve) and wait until they confirm. Verify with `jovida whoami`. Don't consider setup finished — or try to manage todos — until sign-in succeeds. After that, drive the CLI per **[`SKILL.md`](./SKILL.md)**; if any command later exits `2` (`NOT_SIGNED_IN`), stop and ask the user to run `jovida login` again.
8
+
9
+ > **Beta.** Jovida Daily is in private beta; the CLI requires a Jovida account (no anonymous mode).
10
+
11
+ ## Two parts
12
+
13
+ - **`jovida` command** — talks to your Jovida account over HTTPS (login required), keeps **no local todo database**, and syncs with your other Jovida devices.
14
+ - **`SKILL.md`** (name: `jovida-cli`) — a portable behavior guide that teaches your AI to drive the CLI on a **single track: writes apply immediately** (no proposal/confirmation step, no undo yet).
15
+
16
+ ## Setup
17
+
18
+ **1. Install the `jovida` command** so it is on your `PATH`. Pre-release — build from source:
19
+
20
+ ```bash
21
+ git clone https://github.com/FluxVita/jovida-cli && cd jovida-cli
22
+ npm install && npm run build && npm link
23
+ ```
24
+
25
+ (When published: `npm i -g @fluxvita/jovida-cli`.) Verify with `jovida --version`.
26
+
27
+ **2. Install the skill** so your AI knows when/how to use the CLI:
28
+
29
+ ```bash
30
+ npx skills add FluxVita/jovida-cli
31
+ ```
32
+
33
+ Installs `SKILL.md` into detected agents (`~/.codex/skills/jovida-cli/`, `~/.claude/skills/jovida-cli/`, …). Or paste this repo's URL to your agent and ask it to add the skill.
34
+
35
+ **3. Sign in — this is the user's step (interactive; the agent cannot do it):**
36
+
37
+ ```bash
38
+ jovida login # opens a browser; sign in and approve the CLI
39
+ ```
40
+
41
+ Verify with `jovida whoami`. From here the CLI stays signed in (auto-renews) until the session is revoked.
42
+
43
+ ## Updating
44
+
45
+ - **Installed from npm** (`@fluxvita/jovida-cli`): `npm i -g @fluxvita/jovida-cli@latest`.
46
+ - **Installed from source** (current pre-release): in the cloned repo run `git pull && npm install && npm run build`. The `npm link` symlink persists, so the rebuilt CLI takes effect immediately — no need to re-link.
47
+ - **The skill updates separately:** re-run `npx skills add FluxVita/jovida-cli` to refresh `SKILL.md` in your agents. Do this whenever the CLI's commands change, so the agent's knowledge stays in sync.
48
+
49
+ ## Quickstart
50
+
51
+ ```bash
52
+ jovida create "submit the report by Friday 6pm" --when 2026-06-12T18:00:00+08:00
53
+ jovida list
54
+ jovida show <entry_id>
55
+ jovida complete <entry_id>
56
+ ```
57
+
58
+ - **JSON output is automatic when piped** (for scripts/agents); use `--json` / `--no-json` to force.
59
+ - **Exit codes**: `0` ok · `1` usage · `2` not signed in · `3` backend/network · `4` not found.
60
+
61
+ ## Commands
62
+
63
+ `create` · `list` · `show` · `update` · `complete` · `delete` · `login` · `logout` · `whoami`.
64
+ Run `jovida help` for usage, or see [`SKILL.md`](./SKILL.md) for flags & field conventions.
65
+
66
+ ## Auth
67
+
68
+ `jovida login` uses the **OAuth device authorization flow**: it shows a URL and a short code (and tries to open your browser); you sign in and approve the CLI there, and the CLI receives a session token that **auto-renews** (set-and-forget) until the session is revoked. No keys to copy, no anonymous mode.
69
+
70
+ ## The skill
71
+
72
+ [`SKILL.md`](./SKILL.md) · [`SKILL.zh.md`](./SKILL.zh.md) — the agent skill (English / 中文对照) that teaches your AI when and how to use the CLI.
73
+
74
+ ## License
75
+
76
+ [MIT](./LICENSE) © FluxVita
@@ -0,0 +1,76 @@
1
+ # jovida-cli
2
+
3
+ [English](./README.md) · **简体中文**
4
+
5
+ **Jovida Daily CLI**——在终端、以及在 AI 编码 agent(Codex、Claude Code……)里捕获和管理你的 **Jovida Daily** 待办。随仓附带一份 agent **skill**,让 AI 知道何时、如何使用它。
6
+
7
+ > **如果你是正在安装它的 AI agent:** 按 **[安装](#安装)** 顺序执行。第 1–2 步你自己做。**第 3 步 `jovida login` 你做不了——但绝不能跳过:** 装好后要**明确让用户运行 `jovida login`**(浏览器会打开,他登录并点「允许」),并等他确认。用 `jovida whoami` 验证。**登录成功前,别认为安装完成、也别去操作待办。** 此后按 **[`SKILL.md`](./SKILL.md)** 驱动 CLI;之后任何命令以 `2`(`NOT_SIGNED_IN`)退出,停下并让用户重新 `jovida login`。
8
+
9
+ > **内测中。** Jovida Daily 处于私有内测;CLI 需要 Jovida 账号(无匿名模式)。
10
+
11
+ ## 两部分
12
+
13
+ - **`jovida` 命令** —— 经 HTTPS 操作你的 Jovida 账号(需登录),**不留本地待办库**,与你的其他 Jovida 设备同步。
14
+ - **`SKILL.md`**(name:`jovida-cli`)—— 可移植的行为指引,教你的 AI 走**单轨:写即时生效**(无提议/确认步骤,暂无撤回)。
15
+
16
+ ## 安装
17
+
18
+ **1. 安装 `jovida` 命令**,让它在 `PATH` 上。预发布——暂时从源码构建:
19
+
20
+ ```bash
21
+ git clone https://github.com/FluxVita/jovida-cli && cd jovida-cli
22
+ npm install && npm run build && npm link
23
+ ```
24
+
25
+ (发布后:`npm i -g @fluxvita/jovida-cli`。)用 `jovida --version` 验证。
26
+
27
+ **2. 安装 skill**,让 AI 知道何时/如何用 CLI:
28
+
29
+ ```bash
30
+ npx skills add FluxVita/jovida-cli
31
+ ```
32
+
33
+ 把 `SKILL.md` 装进探测到的 agent(`~/.codex/skills/jovida-cli/`、`~/.claude/skills/jovida-cli/`……)。或把本仓库 URL 贴给 agent、让它自行安装。
34
+
35
+ **3. 登录 —— 这是用户的步骤(交互式;agent 做不了):**
36
+
37
+ ```bash
38
+ jovida login # 打开浏览器;登录并点「允许」授权该 CLI
39
+ ```
40
+
41
+ 用 `jovida whoami` 验证。此后 CLI 保持登录态(自动续期),直到会话被吊销。
42
+
43
+ ## 更新
44
+
45
+ - **npm 安装**(`@fluxvita/jovida-cli`):`npm i -g @fluxvita/jovida-cli@latest`。
46
+ - **源码安装**(当前预发布):在克隆的仓库里运行 `git pull && npm install && npm run build`。`npm link` 的软链常驻,重新 build 即刻生效——无需重新 link。
47
+ - **skill 单独更新:** 重跑 `npx skills add FluxVita/jovida-cli` 刷新 agent 里的 `SKILL.md`。CLI 命令有变化时记得做,让 agent 的认知保持同步。
48
+
49
+ ## 快速开始
50
+
51
+ ```bash
52
+ jovida create "周五下午6点前交报告" --when 2026-06-12T18:00:00+08:00
53
+ jovida list
54
+ jovida show <entry_id>
55
+ jovida complete <entry_id>
56
+ ```
57
+
58
+ - **管道输出自动为 JSON**(供脚本/agent);`--json` / `--no-json` 强制。
59
+ - **退出码**:`0` 成功 · `1` 用法 · `2` 未登录 · `3` 后端/网络 · `4` 不存在。
60
+
61
+ ## 命令
62
+
63
+ `create` · `list` · `show` · `update` · `complete` · `delete` · `login` · `logout` · `whoami`。
64
+ `jovida help` 查看用法,或见 [`SKILL.md`](./SKILL.md) 了解参数与字段约定。
65
+
66
+ ## 鉴权
67
+
68
+ `jovida login` 走 **OAuth 设备授权流**:显示一个 URL 和一个短码(并尽力打开你的浏览器);你在浏览器登录并点「允许」授权该 CLI,CLI 随即拿到会话 token。token **自动续期**(set-and-forget),除非会话被吊销。无需复制任何 key,也无匿名模式。
69
+
70
+ ## skill
71
+
72
+ [`SKILL.md`](./SKILL.md) · [`SKILL.zh.md`](./SKILL.zh.md) —— agent skill(英文 / 中文对照),教你的 AI 何时、如何使用 CLI。
73
+
74
+ ## 许可
75
+
76
+ [MIT](./LICENSE) © FluxVita
package/SKILL.md ADDED
@@ -0,0 +1,94 @@
1
+ ---
2
+ name: jovida-cli
3
+ description: Capture and manage the user's Jovida Daily todos from the command line via the `jovida` CLI — create, list, update, complete, or delete tasks (changes apply immediately, no confirmation step). Use when the conversation surfaces action items, follow-ups, or commitments, or when the user asks to track / remember / organize a task.
4
+ allowed-tools: Bash(jovida:*)
5
+ ---
6
+
7
+ # Jovida Daily Todo (CLI)
8
+
9
+ Help the user capture and manage their **Jovida Daily** todos by shelling out to the `jovida` command-line tool. Run `jovida` commands through your shell — the CLI is the interface to the user's account, and changes sync to their other Jovida devices.
10
+
11
+ > **If the `jovida` command isn't found**, the Jovida Daily CLI isn't installed here — tell the user to install it (see the jovida-cli README); don't pretend you tracked anything.
12
+ > **Sign-in is required — and it is the user's step.** No anonymous mode; nothing works until the user runs `jovida login` (interactive browser sign-in — you **cannot** do it for them, and **must not** skip it). Be proactive: if you're not sure they're signed in, run `jovida whoami` first. If `whoami` or any command exits `2` (`NOT_SIGNED_IN`), **stop, tell the user to run `jovida login`, and wait until they confirm before retrying** — don't silently drop the task or claim you can't help.
13
+
14
+ ## Core mental model — read this first
15
+
16
+ Writes apply **immediately**. There is **no proposal or confirmation step**: when you run `jovida create / update / complete / delete`, it takes effect on the user's account at once and syncs to their devices. So:
17
+
18
+ - Say "I've **created / completed / deleted** …" — never "I've proposed …".
19
+ - Because it's immediate, only run a write when the user **clearly wants that change**. When unsure, ask first (see *Clarify*).
20
+ - **There is no undo** from the CLI — be especially careful with `delete` and `complete`.
21
+
22
+ ## When to use
23
+
24
+ Act when the context holds a real, actionable item:
25
+ - an explicit action item, follow-up, or commitment ("I need to…", "remember to…", "before launch, check…");
26
+ - the user asks to track / remember / add / organize a task, or to mark one done / remove it.
27
+
28
+ Do **not** over-capture:
29
+ - ignore hypotheticals, brainstorming, and things already done;
30
+ - when unsure whether something is a real task, ask rather than writing noise.
31
+
32
+ ## Clarify before writing
33
+
34
+ A write changes the user's data immediately — and `delete` / `complete` can't be undone here — so a vague or wrong call is costly. Before writing, make sure the essentials are concrete: *what* the task is, and — when time is implied — *which day / deadline / reminder time*. If the user's message leaves these genuinely ambiguous, **ask one brief question first**; don't fill the gaps with guesses.
35
+
36
+ - "remind me about the thing tomorrow" → ask *what* the thing is, and roughly *when*, before writing.
37
+ - "set a reminder" with no time → ask for the time.
38
+ - Don't over-correct: when details are clear ("submit the report by Friday 6pm"), just act.
39
+
40
+ ## Commands
41
+
42
+ You invoke these through your shell. When run non-interactively (as you do), output is **JSON on stdout** — parse it. Errors go to **stderr** as `{"error":{"code","message"}}` with a **non-zero exit code** (`2` = not signed in, `3` = backend/network, `4` = entry not found, `1` = usage). You can pass `--json` to force JSON.
43
+
44
+ Run `jovida help` to confirm it's available and see the current usage before relying on a flag.
45
+
46
+ **Read** (understand state, and get the real `entry_id` before any update/complete/delete):
47
+ - `jovida list [--scope today|upcoming|recent|range|all] [--status pending|completed|all] [--from YYYY-MM-DD] [--to YYYY-MM-DD] [--limit N]`
48
+ → `{ "todos": [ { "entry_id", "title", "when", "priority", "status", "category" } ] }`. A scoped view (defaults: `scope=today`, `status=pending`, `limit=20`), **not** a search.
49
+ - `jovida show <entry_id>` → the full todo (description, subtasks, remind_at, hint, …).
50
+
51
+ **Write** (immediate):
52
+ - `jovida create "<title>" [--when <ISO>] [--priority none|low|medium|high] [--remind <ISO> …] [--category <s>] [--desc <s>] [--subtask "<title>" …] [--hint <s>]`
53
+ → `{ "entry_id", "status": "created" }`. **One todo per call** — run it again for more.
54
+ - **Recurring**: add `--repeat day|week|month|year` (with `--when` as the **first occurrence**) → creates a recurring series, returns `{ "recurring_id", "status": "created" }`. Tune with `--every N` (e.g. every 2 weeks), `--weekdays mon,wed,fri` (weekly), `--day-of-month N` (monthly/yearly), `--month-of-year N` (yearly), `--until YYYY-MM-DD` (end). Occurrences then show up in `list` like normal todos.
55
+ - `jovida update <entry_id> [--title <s>] [--when <ISO>] [--priority …] [--remind <ISO> …] [--category <s>] [--desc <s>] [--subtask "<title>" …] [--hint <s>]`
56
+ → `{ "entry_id", "status": "updated" }`.
57
+ - `jovida complete <entry_id> [<entry_id> ...]` → `{ "entry_ids", "status": "completed" }`.
58
+ - `jovida delete <entry_id> [<entry_id> …]` → `{ "entry_ids": […], "status": "deleted" }`. Pass several ids in one call.
59
+
60
+ Quote the title and any value containing spaces. **Keep `--title` / `--desc` to single-line plain text** — passing newlines or shell metacharacters as arguments is fragile (they get mangled). For a long note, keep it short and single-line rather than embedding markdown/newlines.
61
+
62
+ **Never put a token in a command** (it lands in shell history / process listings). Signing in is the user's step (interactive browser) — see the not-signed-in note above.
63
+
64
+ ## Field conventions
65
+
66
+ - **`--when`** — the todo's time, **one flag, two granularities** (ISO 8601):
67
+ - a **date** (`2026-06-05`) → belongs to *that day*, no hard deadline;
68
+ - a **datetime** (`2026-06-05T18:00:00+08:00`) → a precise **deadline**.
69
+ - Give whichever you actually know. Don't split "do it Wed but due Fri" — if it's due Friday, it's a Friday todo.
70
+ - **`--priority`**: `none` | `low` | `medium` | `high`.
71
+ - **`--category`**: a grouping label. **`--desc`**: a free-text note.
72
+ - **`--subtask "<title>"`** (repeatable): break a task into steps.
73
+ - **`--remind <ISO>`** (repeatable) — when to alert the user; **separate from `--when`**:
74
+ - each must be **at or before** the todo's time (before the deadline, or any time on a date-only todo's day);
75
+ - **reminding ≠ deadline**: "remind me about X tomorrow" → `--when "<tomorrow>" --remind "<tomorrow>T09:00:00+08:00"` (it does *not* make X due);
76
+ - if you give only `--remind` and no `--when`, the todo lands on the **latest** reminder's day.
77
+ - **`--hint <s>`** — an *optional* one-line nudge shown under the todo. Off by default; add only when it genuinely helps (≤ ~20 chars, never restate the title).
78
+ - Recurring tasks are **not supported in the CLI yet** — don't try to express them; create individual todos or tell the user.
79
+
80
+ ## Read before you write
81
+
82
+ `update` / `complete` / `delete` need a **real `entry_id`** — get it from `jovida list` or `jovida show` (parse the JSON) first. Never guess an id.
83
+
84
+ ## Grouping
85
+
86
+ - Several independent todos → run `jovida create` once per todo.
87
+ - Deleting several related todos at once → **one** `jovida delete` with all the ids.
88
+
89
+ ## Do / Don't
90
+
91
+ - DO `list` / `show` before `update` / `complete` / `delete` (you need the real `entry_id`).
92
+ - DO write clear, action-oriented titles (verb-first, concrete).
93
+ - DO say "created / updated / completed / deleted" — writes are immediate.
94
+ - DON'T over-capture; DON'T run an immediate write the user didn't clearly ask for (there's no undo); DON'T claim success if a command exited non-zero — read the error and tell the user (e.g. exit `2` → ask them to `jovida login`).
package/dist/api.js ADDED
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ApiClient = exports.ApiError = void 0;
4
+ // Jovida 后端 HTTP 客户端(node)。
5
+ // body = proto3-JSON(camelCase);鉴权 = Sign 态 vita token 走 `Vita-Token` header。
6
+ // 设备授权流的 device_authorize/device_token 是匿名端点(登录时尚无 token,不带 Vita-Token)。
7
+ const node_os_1 = require("node:os");
8
+ const config_1 = require("./config");
9
+ class ApiError extends Error {
10
+ status;
11
+ reason;
12
+ constructor(status, reason, message) {
13
+ super(message);
14
+ this.status = status;
15
+ this.reason = reason;
16
+ this.name = 'ApiError';
17
+ }
18
+ }
19
+ exports.ApiError = ApiError;
20
+ class ApiClient {
21
+ cfg;
22
+ token = ''; // Sign 态 vita token(Vita-Token)
23
+ constructor(cfg) {
24
+ this.cfg = cfg;
25
+ }
26
+ setToken(token) {
27
+ this.token = token;
28
+ }
29
+ async post(path, body = {}) {
30
+ return this.fetchJson(path, 'POST', body);
31
+ }
32
+ async get(path) {
33
+ return this.fetchJson(path, 'GET');
34
+ }
35
+ headers() {
36
+ const utcOffsetSec = -new Date().getTimezoneOffset() * 60;
37
+ const h = {
38
+ 'Content-Type': 'application/json',
39
+ Accept: 'application/json',
40
+ 'Vita-Aid': this.cfg.appId,
41
+ 'Vita-Did': this.cfg.deviceId,
42
+ 'Vita-Platform': this.cfg.platform,
43
+ 'Vita-App-Version': config_1.APP_VERSION,
44
+ 'Vita-Os-Name': process.platform,
45
+ 'Vita-Os-Version': (0, node_os_1.release)(),
46
+ 'Vita-Time-Zone': Intl.DateTimeFormat().resolvedOptions().timeZone ?? '',
47
+ 'Vita-UTC-Offset': String(utcOffsetSec),
48
+ 'Vita-Language': 'en'
49
+ };
50
+ if (this.token)
51
+ h['Vita-Token'] = this.token;
52
+ return h;
53
+ }
54
+ async fetchJson(path, method, body) {
55
+ const res = await fetch(`${this.cfg.baseUrl}${path}`, {
56
+ method,
57
+ headers: this.headers(),
58
+ body: method === 'GET' ? undefined : JSON.stringify(body ?? {})
59
+ });
60
+ if (!res.ok) {
61
+ let reason = '';
62
+ try {
63
+ reason = JSON.parse(await res.text()).reason ?? '';
64
+ }
65
+ catch {
66
+ /* 非 JSON 错误体 */
67
+ }
68
+ throw new ApiError(res.status, reason, `HTTP ${res.status} on ${path}${reason ? ` (${reason})` : ''}`);
69
+ }
70
+ return (await res.json());
71
+ }
72
+ }
73
+ exports.ApiClient = ApiClient;
package/dist/cli.js ADDED
@@ -0,0 +1,188 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const ctx_1 = require("./ctx");
5
+ const create_1 = require("./commands/create");
6
+ const list_1 = require("./commands/list");
7
+ const show_1 = require("./commands/show");
8
+ const update_1 = require("./commands/update");
9
+ const complete_1 = require("./commands/complete");
10
+ const delete_1 = require("./commands/delete");
11
+ const login_1 = require("./commands/login");
12
+ const whoami_1 = require("./commands/whoami");
13
+ const shared_1 = require("./commands/shared");
14
+ const session_1 = require("./session");
15
+ const api_1 = require("./api");
16
+ const state_1 = require("./state");
17
+ // 可重复的值 flag(收集成数组)。其余值 flag 取最后一次,无值则布尔 true。
18
+ const REPEATABLE = new Set(['remind', 'subtask']);
19
+ function parse(argv) {
20
+ const positionals = [];
21
+ const flags = {};
22
+ for (let i = 0; i < argv.length; i++) {
23
+ const a = argv[i];
24
+ if (a.startsWith('--')) {
25
+ const key = a.slice(2);
26
+ const next = argv[i + 1];
27
+ if (next === undefined || next.startsWith('--')) {
28
+ flags[key] = true;
29
+ }
30
+ else {
31
+ i++;
32
+ if (REPEATABLE.has(key)) {
33
+ const cur = flags[key] ?? [];
34
+ cur.push(next);
35
+ flags[key] = cur;
36
+ }
37
+ else {
38
+ flags[key] = next;
39
+ }
40
+ }
41
+ }
42
+ else {
43
+ positionals.push(a);
44
+ }
45
+ }
46
+ return { positionals, flags };
47
+ }
48
+ const str = (v) => typeof v === 'string' ? v : undefined;
49
+ const arr = (v) => Array.isArray(v) ? v : typeof v === 'string' ? [v] : undefined;
50
+ const num = (v) => typeof v === 'string' && v.trim() !== '' && !Number.isNaN(Number(v)) ? Number(v) : undefined;
51
+ const HELP = `Jovida Daily CLI
52
+
53
+ Usage:
54
+ jovida login [--json] # device authorization: open the URL, enter the code, approve
55
+ jovida login --token <vita-token> # dev-only interim: paste a signed-in vita token
56
+ jovida logout
57
+ jovida whoami [--json]
58
+
59
+ jovida create "<title>" [--when <ISO>] [--priority none|low|medium|high]
60
+ [--remind <ISO> ...] [--category <s>] [--desc <s>]
61
+ [--subtask <title> ...] [--hint <s>] [--json]
62
+ recurring: [--repeat day|week|month|year] [--every N] [--weekdays mon,wed,fri]
63
+ [--day-of-month N] [--month-of-year N] [--until YYYY-MM-DD]
64
+ (--repeat requires --when as the first occurrence)
65
+ jovida list [--scope today|upcoming|recent|range|all] [--status pending|completed|all]
66
+ [--from YYYY-MM-DD] [--to YYYY-MM-DD] [--limit N] [--json]
67
+ jovida show <entry_id> [--json]
68
+ jovida update <entry_id> [--title ...] [--when ...] [--priority ...] [--remind ...] [...]
69
+ jovida complete <entry_id> [<entry_id> ...] [--json]
70
+ jovida delete <entry_id> [<entry_id> ...] [--json]
71
+
72
+ Output: JSON is emitted automatically when stdout is not a TTY (use --json/--no-json to force).
73
+ Env: JOVIDA_API_URL=<url> (default https://api.jovida.ai) · JOVIDA_HOME=<dir> (default ~/.jovida)
74
+ `;
75
+ /** 错误 → 退出码(0 ok / 1 用法 / 2 未登录 / 3 后端 / 4 not found)。 */
76
+ function exitCodeFor(e) {
77
+ if (e instanceof session_1.NotSignedInError)
78
+ return 2;
79
+ if (e instanceof shared_1.NotFoundError)
80
+ return 4;
81
+ if (e instanceof api_1.ApiError)
82
+ return 3;
83
+ return 1;
84
+ }
85
+ function errCode(e) {
86
+ if (e instanceof session_1.NotSignedInError)
87
+ return 'NOT_SIGNED_IN';
88
+ if (e instanceof shared_1.NotFoundError)
89
+ return 'NOT_FOUND';
90
+ if (e instanceof api_1.ApiError)
91
+ return 'BACKEND';
92
+ return 'USAGE';
93
+ }
94
+ async function main() {
95
+ const [cmd, ...rest] = process.argv.slice(2);
96
+ const { positionals, flags } = parse(rest);
97
+ if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') {
98
+ console.log(HELP);
99
+ return;
100
+ }
101
+ if (cmd === 'version' || cmd === '--version' || cmd === '-v') {
102
+ console.log(require('../package.json').version);
103
+ return;
104
+ }
105
+ // JSON 输出:非 TTY 自动开;--json / --no-json 强制。
106
+ const json = flags.json === true ? true : flags['no-json'] === true ? false : !process.stdout.isTTY;
107
+ const ctx = (0, ctx_1.makeCtx)();
108
+ switch (cmd) {
109
+ case 'login':
110
+ await (0, login_1.cmdLogin)(ctx, { token: str(flags.token), json });
111
+ break;
112
+ case 'logout':
113
+ (0, state_1.clearCredentials)();
114
+ console.log(json ? JSON.stringify({ status: 'signed_out' }) : '✓ signed out');
115
+ break;
116
+ case 'whoami':
117
+ await (0, whoami_1.cmdWhoami)(ctx, { json });
118
+ break;
119
+ case 'create':
120
+ await (0, create_1.cmdCreate)(ctx, {
121
+ title: positionals.join(' ').trim(),
122
+ when: str(flags.when),
123
+ priority: str(flags.priority),
124
+ category: str(flags.category),
125
+ desc: str(flags.desc),
126
+ remind: arr(flags.remind),
127
+ subtask: arr(flags.subtask),
128
+ hint: str(flags.hint),
129
+ repeat: str(flags.repeat),
130
+ every: num(flags.every),
131
+ weekdays: str(flags.weekdays),
132
+ dayOfMonth: num(flags['day-of-month']),
133
+ monthOfYear: num(flags['month-of-year']),
134
+ until: str(flags.until),
135
+ json
136
+ });
137
+ break;
138
+ case 'list':
139
+ await (0, list_1.cmdList)(ctx, {
140
+ scope: str(flags.scope),
141
+ status: str(flags.status),
142
+ from: str(flags.from),
143
+ to: str(flags.to),
144
+ limit: num(flags.limit),
145
+ json
146
+ });
147
+ break;
148
+ case 'show':
149
+ await (0, show_1.cmdShow)(ctx, { id: positionals[0], json });
150
+ break;
151
+ case 'update':
152
+ await (0, update_1.cmdUpdate)(ctx, {
153
+ id: positionals[0],
154
+ title: str(flags.title),
155
+ when: str(flags.when),
156
+ priority: str(flags.priority),
157
+ category: str(flags.category),
158
+ desc: str(flags.desc),
159
+ remind: arr(flags.remind),
160
+ subtask: arr(flags.subtask),
161
+ hint: str(flags.hint),
162
+ json
163
+ });
164
+ break;
165
+ case 'complete':
166
+ await (0, complete_1.cmdComplete)(ctx, { ids: positionals, json });
167
+ break;
168
+ case 'delete':
169
+ await (0, delete_1.cmdDelete)(ctx, { ids: positionals, json });
170
+ break;
171
+ default:
172
+ console.error(`unknown command: ${cmd}\n`);
173
+ console.log(HELP);
174
+ process.exitCode = 1;
175
+ }
176
+ }
177
+ main().catch((e) => {
178
+ const msg = e instanceof Error ? e.message : String(e);
179
+ const reason = e instanceof api_1.ApiError ? e.reason : undefined;
180
+ // JSON 错误走 stderr(成功 JSON 走 stdout)。非 TTY 时也用 JSON。
181
+ if (!process.stdout.isTTY) {
182
+ process.stderr.write(JSON.stringify({ error: { code: errCode(e), message: msg, ...(reason ? { reason } : {}) } }) + '\n');
183
+ }
184
+ else {
185
+ process.stderr.write(`jovida: ${msg}\n`);
186
+ }
187
+ process.exitCode = exitCodeFor(e);
188
+ });
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.cmdComplete = cmdComplete;
4
+ const shared_1 = require("./shared");
5
+ /** 完成一个或多个 entry。一次 pull 解析全部 id → 批量 put;任一 id 不存在则整体失败(不改任何条目)。 */
6
+ async function cmdComplete(ctx, a) {
7
+ if (!a.ids || a.ids.length === 0) {
8
+ throw new Error('entry_id(s) required: jovida complete <entry_id> [<entry_id> ...]');
9
+ }
10
+ await ctx.session.ensureSession();
11
+ const snap = await ctx.sync.pull();
12
+ const t = (0, shared_1.nowSec)();
13
+ const done = [];
14
+ const missing = [];
15
+ for (const id of a.ids) {
16
+ const e = snap.entries.find((x) => x.entryId === id);
17
+ if (e)
18
+ done.push({ ...e, completedAt: t, updatedAt: t });
19
+ else
20
+ missing.push(id);
21
+ }
22
+ if (missing.length)
23
+ throw new shared_1.NotFoundError(missing.join(', '));
24
+ await ctx.sync.putEntries(done);
25
+ if (a.json) {
26
+ console.log(JSON.stringify({ entry_ids: done.map((e) => e.entryId), status: 'completed' }));
27
+ return;
28
+ }
29
+ for (const e of done)
30
+ console.log(`✓ completed ${e.title} (${e.entryId})`);
31
+ }
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.cmdCreate = cmdCreate;
4
+ const convert_1 = require("../core/convert");
5
+ const shared_1 = require("./shared");
6
+ const PRIORITIES = ['none', 'low', 'medium', 'high'];
7
+ // --repeat 取值(含别名)→ 存储 unit。
8
+ const UNIT_ALIAS = {
9
+ day: 'day',
10
+ daily: 'day',
11
+ week: 'week',
12
+ weekly: 'week',
13
+ month: 'month',
14
+ monthly: 'month',
15
+ year: 'year',
16
+ yearly: 'year'
17
+ };
18
+ const WEEKDAY_ALIAS = { mon: 1, tue: 2, wed: 3, thu: 4, fri: 5, sat: 6, sun: 7 };
19
+ /** "mon,wed,fri" 或 "1,3,5" → ISO 1-7 数组。 */
20
+ function parseWeekdays(s) {
21
+ if (!s)
22
+ return undefined;
23
+ const out = s
24
+ .split(',')
25
+ .map((x) => x.trim().toLowerCase())
26
+ .filter(Boolean)
27
+ .map((x) => WEEKDAY_ALIAS[x] ?? Number(x))
28
+ .filter((n) => Number.isInteger(n) && n >= 1 && n <= 7);
29
+ return out.length ? out : undefined;
30
+ }
31
+ async function cmdCreate(ctx, a) {
32
+ if (!a.title)
33
+ throw new Error('title is required: jovida create "<title>" [--when <ISO>] ...');
34
+ if (a.priority && !PRIORITIES.includes(a.priority)) {
35
+ throw new Error(`--priority must be one of: ${PRIORITIES.join(', ')}`);
36
+ }
37
+ let repeat;
38
+ if (a.repeat) {
39
+ const unit = UNIT_ALIAS[a.repeat.toLowerCase()];
40
+ if (!unit)
41
+ throw new Error('--repeat must be one of: day, week, month, year');
42
+ if (!a.when)
43
+ throw new Error('--repeat needs --when (the first occurrence date, e.g. --when 2026-06-15)');
44
+ repeat = {
45
+ unit,
46
+ interval: a.every,
47
+ weekdays: parseWeekdays(a.weekdays),
48
+ day_of_month: a.dayOfMonth,
49
+ month_of_year: a.monthOfYear,
50
+ until: a.until
51
+ };
52
+ }
53
+ const input = {
54
+ title: a.title,
55
+ when: a.when,
56
+ priority: a.priority,
57
+ category: a.category,
58
+ description: a.desc,
59
+ remind_at: a.remind && a.remind.length ? a.remind : undefined,
60
+ subtasks: a.subtask && a.subtask.length ? a.subtask.map((s) => ({ title: s })) : undefined,
61
+ hint: a.hint,
62
+ repeat
63
+ };
64
+ const draft = (0, convert_1.toDraft)(input); // toDraft 内含 when→belong/due、提醒锚、repeat 解析
65
+ await ctx.session.ensureSession();
66
+ // 循环「类」走 putRecurrings;普通待办走 putEntries。
67
+ if (repeat) {
68
+ const series = (0, shared_1.draftToRecurring)(draft);
69
+ await ctx.sync.putRecurrings([series]);
70
+ if (a.json)
71
+ console.log(JSON.stringify({ recurring_id: series.recurringId, status: 'created' }));
72
+ else
73
+ console.log(`✓ created (recurring) ${series.title} (${series.recurringId})`);
74
+ return;
75
+ }
76
+ const entry = (0, shared_1.draftToEntry)(draft);
77
+ await ctx.sync.putEntries([entry]);
78
+ if (a.json)
79
+ console.log(JSON.stringify({ entry_id: entry.entryId, status: 'created' }));
80
+ else
81
+ console.log(`✓ created ${entry.title} (${entry.entryId})`);
82
+ }
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.cmdDelete = cmdDelete;
4
+ async function cmdDelete(ctx, a) {
5
+ if (!a.ids || a.ids.length === 0) {
6
+ throw new Error('entry_id(s) required: jovida delete <entry_id> [<entry_id> ...]');
7
+ }
8
+ await ctx.session.ensureSession();
9
+ await ctx.sync.deleteObjects(a.ids); // 硬删、幂等;不需先 fetch
10
+ if (a.json)
11
+ console.log(JSON.stringify({ entry_ids: a.ids, status: 'deleted' }));
12
+ else
13
+ console.log(`✓ deleted ${a.ids.join(', ')}`);
14
+ }