@fluxvita/jovida-cli 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -11
- package/README.zh-CN.md +10 -11
- package/SKILL.md +41 -69
- package/dist/cli.js +139 -8
- package/dist/commands/list.js +2 -1
- package/dist/commands/reopen.js +34 -0
- package/dist/commands/skill.js +54 -0
- package/dist/commands/{show.js → view.js} +3 -3
- package/dist/config.js +2 -1
- package/dist/lib/update-check.js +59 -0
- package/dist/state.js +12 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,22 +15,21 @@ The **Jovida Daily CLI** — capture and manage your **Jovida Daily** todos from
|
|
|
15
15
|
|
|
16
16
|
## Setup
|
|
17
17
|
|
|
18
|
-
**1. Install the `jovida` command** so it is on your `PATH
|
|
18
|
+
**1. Install the `jovida` command** so it is on your `PATH`:
|
|
19
19
|
|
|
20
20
|
```bash
|
|
21
|
-
|
|
22
|
-
npm install && npm run build && npm link
|
|
21
|
+
npm i -g @fluxvita/jovida-cli
|
|
23
22
|
```
|
|
24
23
|
|
|
25
|
-
(
|
|
24
|
+
(Or build from source: `git clone https://github.com/FluxVita/jovida-cli && cd jovida-cli && npm install && npm run build && npm link`.) Verify with `jovida --version`.
|
|
26
25
|
|
|
27
26
|
**2. Install the skill** so your AI knows when/how to use the CLI:
|
|
28
27
|
|
|
29
28
|
```bash
|
|
30
|
-
|
|
29
|
+
jovida skill install
|
|
31
30
|
```
|
|
32
31
|
|
|
33
|
-
|
|
32
|
+
Copies the bundled `SKILL.md` (kept in lockstep with the CLI version) into your detected agents (`~/.codex/skills/jovida-cli/`, `~/.claude/skills/jovida-cli/`). Add `--all` to install for all known agents even if not detected. (Alternative: `npx skills add FluxVita/jovida-cli`.)
|
|
34
33
|
|
|
35
34
|
**3. Sign in — this is the user's step (interactive; the agent cannot do it):**
|
|
36
35
|
|
|
@@ -42,16 +41,16 @@ Verify with `jovida whoami`. From here the CLI stays signed in (auto-renews) unt
|
|
|
42
41
|
|
|
43
42
|
## Updating
|
|
44
43
|
|
|
45
|
-
- **
|
|
46
|
-
- **
|
|
47
|
-
- **
|
|
44
|
+
- **CLI**: `npm i -g @fluxvita/jovida-cli@latest`. (In an interactive terminal the CLI also notifies you when a newer version exists.)
|
|
45
|
+
- **Skill**: run `jovida skill update` after updating the CLI — it re-copies the bundled `SKILL.md`, so the agent's knowledge stays in lockstep with the installed CLI version (same npm package, no drift).
|
|
46
|
+
- **From source**: in the cloned repo run `git pull && npm install && npm run build` (the `npm link` symlink persists), then `jovida skill update`.
|
|
48
47
|
|
|
49
48
|
## Quickstart
|
|
50
49
|
|
|
51
50
|
```bash
|
|
52
51
|
jovida create "submit the report by Friday 6pm" --when 2026-06-12T18:00:00+08:00
|
|
53
52
|
jovida list
|
|
54
|
-
jovida
|
|
53
|
+
jovida view <entry_id>
|
|
55
54
|
jovida complete <entry_id>
|
|
56
55
|
```
|
|
57
56
|
|
|
@@ -60,7 +59,7 @@ jovida complete <entry_id>
|
|
|
60
59
|
|
|
61
60
|
## Commands
|
|
62
61
|
|
|
63
|
-
`create` · `list` · `
|
|
62
|
+
`create` · `list` · `view` · `update` · `complete` · `reopen` · `delete` · `login` · `logout` · `whoami`.
|
|
64
63
|
Run `jovida help` for usage, or see [`SKILL.md`](./SKILL.md) for flags & field conventions.
|
|
65
64
|
|
|
66
65
|
## Auth
|
package/README.zh-CN.md
CHANGED
|
@@ -15,22 +15,21 @@
|
|
|
15
15
|
|
|
16
16
|
## 安装
|
|
17
17
|
|
|
18
|
-
**1. 安装 `jovida` 命令**,让它在 `PATH`
|
|
18
|
+
**1. 安装 `jovida` 命令**,让它在 `PATH` 上:
|
|
19
19
|
|
|
20
20
|
```bash
|
|
21
|
-
|
|
22
|
-
npm install && npm run build && npm link
|
|
21
|
+
npm i -g @fluxvita/jovida-cli
|
|
23
22
|
```
|
|
24
23
|
|
|
25
|
-
(
|
|
24
|
+
(或从源码构建:`git clone https://github.com/FluxVita/jovida-cli && cd jovida-cli && npm install && npm run build && npm link`。)用 `jovida --version` 验证。
|
|
26
25
|
|
|
27
26
|
**2. 安装 skill**,让 AI 知道何时/如何用 CLI:
|
|
28
27
|
|
|
29
28
|
```bash
|
|
30
|
-
|
|
29
|
+
jovida skill install
|
|
31
30
|
```
|
|
32
31
|
|
|
33
|
-
|
|
32
|
+
把随包的 `SKILL.md`(与 CLI 同版本)拷进探测到的 agent(`~/.codex/skills/jovida-cli/`、`~/.claude/skills/jovida-cli/`)。加 `--all` 可对所有已知 agent 安装(即使未探测到)。(备选:`npx skills add FluxVita/jovida-cli`。)
|
|
34
33
|
|
|
35
34
|
**3. 登录 —— 这是用户的步骤(交互式;agent 做不了):**
|
|
36
35
|
|
|
@@ -42,16 +41,16 @@ jovida login # 打开浏览器;登录并点「允许」授权该 CLI
|
|
|
42
41
|
|
|
43
42
|
## 更新
|
|
44
43
|
|
|
45
|
-
- **npm
|
|
46
|
-
-
|
|
47
|
-
-
|
|
44
|
+
- **CLI**:`npm i -g @fluxvita/jovida-cli@latest`。(交互式终端里,CLI 还会在有新版时提示你。)
|
|
45
|
+
- **skill**:更新完 CLI 后跑 `jovida skill update`——它重拷随包的 `SKILL.md`,让 agent 认知与已装 CLI 版本同步(同一 npm 包,不漂移)。
|
|
46
|
+
- **源码**:在克隆的仓库里 `git pull && npm install && npm run build`(`npm link` 软链常驻),再 `jovida skill update`。
|
|
48
47
|
|
|
49
48
|
## 快速开始
|
|
50
49
|
|
|
51
50
|
```bash
|
|
52
51
|
jovida create "周五下午6点前交报告" --when 2026-06-12T18:00:00+08:00
|
|
53
52
|
jovida list
|
|
54
|
-
jovida
|
|
53
|
+
jovida view <entry_id>
|
|
55
54
|
jovida complete <entry_id>
|
|
56
55
|
```
|
|
57
56
|
|
|
@@ -60,7 +59,7 @@ jovida complete <entry_id>
|
|
|
60
59
|
|
|
61
60
|
## 命令
|
|
62
61
|
|
|
63
|
-
`create` · `list` · `
|
|
62
|
+
`create` · `list` · `view` · `update` · `complete` · `reopen` · `delete` · `login` · `logout` · `whoami`。
|
|
64
63
|
`jovida help` 查看用法,或见 [`SKILL.md`](./SKILL.md) 了解参数与字段约定。
|
|
65
64
|
|
|
66
65
|
## 鉴权
|
package/SKILL.md
CHANGED
|
@@ -1,94 +1,66 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: jovida-cli
|
|
3
|
-
description: Capture and manage the user's Jovida Daily todos
|
|
3
|
+
description: Capture and manage the user's Jovida Daily todos via the `jovida` CLI — create, list, update, complete, delete (changes apply immediately, no confirmation, no undo). Use when the conversation surfaces action items, follow-ups, or commitments, or when the user asks to track / remember / organize a task.
|
|
4
4
|
allowed-tools: Bash(jovida:*)
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Jovida Daily Todo (CLI)
|
|
8
8
|
|
|
9
|
-
Help the user capture and manage their **Jovida Daily** todos by shelling out to the `jovida` command
|
|
9
|
+
Help the user capture and manage their **Jovida Daily** todos by shelling out to the `jovida` command. The CLI is the interface to the user's account; changes sync to their other Jovida devices.
|
|
10
10
|
|
|
11
|
-
|
|
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.
|
|
11
|
+
This skill teaches the *semantics* — when to act, which command, how to compose them. For the **exact flags** of any command, run `jovida <command> --help` (e.g. `jovida create --help`); that is the source of truth and stays in lockstep with the installed version. Don't rely on a flag this skill doesn't name without checking `--help` first.
|
|
13
12
|
|
|
14
|
-
|
|
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.
|
|
13
|
+
> **If `jovida` isn't found**, the CLI isn't installed here — tell the user to install it (see the jovida-cli README); don't pretend you tracked anything.
|
|
14
|
+
> **Sign-in is required, and it's the user's step.** No anonymous mode. You **cannot** sign in for them (interactive browser) and **must not** skip it. If unsure they're signed in, run `jovida whoami` first. If `whoami` or any command exits `2` (`NOT_SIGNED_IN`), **stop, ask the user to run `jovida login`, and wait for their confirmation before retrying** — don't silently drop the task or claim you can't help.
|
|
27
15
|
|
|
28
|
-
|
|
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.
|
|
16
|
+
## Core mental model — read this first
|
|
39
17
|
|
|
40
|
-
|
|
18
|
+
- **Writes apply immediately.** There is no proposal/confirmation step: `create / update / complete / delete` take effect on the user's account at once and sync to their devices. Say "I've created / completed / deleted …", never "I've proposed …". `complete` is reversible (`reopen`), but **`delete` is permanent — no undo** — so be especially careful with it.
|
|
19
|
+
- **Only write when the user clearly wants the change.** When the intent or the essentials (what the task is; which day / deadline / reminder time) are genuinely vague, **ask one short question first** — don't fill gaps with guesses. ("Remind me about the thing tomorrow" → ask *what* and roughly *when* before writing.) But don't over-correct: when it's clear ("submit the report by Friday 6pm"), just act.
|
|
20
|
+
- **Read before you change.** `update / complete / delete` need a **real `entry_id`** — get it from `jovida list` or `jovida view` and parse the JSON. **Never guess an id.**
|
|
21
|
+
- **Output is machine-readable.** Run non-interactively, the CLI prints **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` not found · `1` usage). `--json` forces JSON.
|
|
41
22
|
|
|
42
|
-
|
|
23
|
+
## When to use — and when not
|
|
43
24
|
|
|
44
|
-
|
|
25
|
+
Act when the context holds a **real, actionable** item: an explicit action item, follow-up, or commitment ("I need to…", "remember to…", "before launch, check…"), or when the user asks to track / remember / organize something, or to mark one done / remove it.
|
|
45
26
|
|
|
46
|
-
|
|
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, …).
|
|
27
|
+
Don't over-capture: ignore hypotheticals, brainstorming, and things already done. When unsure whether something is a genuine task, ask rather than writing noise.
|
|
50
28
|
|
|
51
|
-
|
|
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.
|
|
29
|
+
## Concepts
|
|
59
30
|
|
|
60
|
-
|
|
31
|
+
These shape *what you put in a command* — internalize them; flag spelling lives in `--help`.
|
|
61
32
|
|
|
62
|
-
**
|
|
33
|
+
- **A todo's time has two granularities (one `--when`).** A bare **date** (`2026-06-05`) means it *belongs to that day*, no hard deadline; a **datetime** (`2026-06-05T18:00:00+08:00`) is a precise **deadline**. Give whichever you actually know. Don't split "do it Wed, due Fri" — if it's due Friday, it's a Friday todo.
|
|
34
|
+
- **Reminders are separate from the time, and a reminder ≠ a deadline.** A reminder is *when to nudge*; each must be at or before the todo's time. "Remind me about X tomorrow" = a todo tomorrow with a reminder tomorrow morning — it does **not** make X *due* at that moment. A todo can carry several reminders.
|
|
35
|
+
- **Recurring = a series, not a todo.** Creating with a repeat rule makes a recurring **series** (you get a `recurring_id`); its individual occurrences then appear in `list` like normal todos, each with its own `entry_id` (and a `recurring_id` linking back). Use a series for a genuinely repeating commitment ("standup every weekday"), not for a handful of distinct dates — make those individual todos.
|
|
36
|
+
- **The rest:** **subtasks** break one task into steps; **category** is a grouping label; **priority** is none/low/medium/high; **hint** is an optional one-line nudge — add it only when it genuinely helps.
|
|
63
37
|
|
|
64
|
-
##
|
|
38
|
+
## Commands at a glance
|
|
65
39
|
|
|
66
|
-
|
|
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.
|
|
40
|
+
Semantics below; exact flags via `jovida <cmd> --help`.
|
|
79
41
|
|
|
80
|
-
|
|
42
|
+
- **`jovida list`** — a scoped *view* of todos (defaults to today's pending), **not a search**; widen with scope/status/range. Your go-to for finding the `entry_id` you need. Add `--full` to get every field (description, subtasks, reminders) in the same call — one round-trip instead of `list` then `view`.
|
|
43
|
+
- **`jovida view <entry_id>`** — full detail of one todo (description, subtasks, reminders, …).
|
|
44
|
+
- **`jovida create "<title>"`** — add one todo (**one per call**; run again for more). Add a repeat rule to create a recurring series instead.
|
|
45
|
+
- **`jovida update <entry_id>`** — change fields of an existing todo; **only the fields you pass change**. `--remind` / `--subtask` **replace** the whole list (not append).
|
|
46
|
+
- **`jovida complete <id> [<id> …]`** — mark done (pass several ids in one call).
|
|
47
|
+
- **`jovida reopen <id> [<id> …]`** — reopen completed todos (the inverse of `complete`).
|
|
48
|
+
- **`jovida delete <id> [<id> …]`** — permanently remove (several ids in one call; **no undo**).
|
|
49
|
+
- **`jovida whoami` / `login` / `logout`** — session. `login` is the user's interactive step.
|
|
81
50
|
|
|
82
|
-
|
|
51
|
+
## Workflows — composing the commands
|
|
83
52
|
|
|
84
|
-
|
|
53
|
+
Map the user's intent to a sequence; read before any change.
|
|
85
54
|
|
|
86
|
-
-
|
|
87
|
-
-
|
|
55
|
+
- **Capture several items from one message** (meeting notes, a brain-dump): pick out the *real* commitments, then run `jovida create` once per item. Don't cram several tasks into one todo, and don't capture the surrounding discussion.
|
|
56
|
+
- **A deliverable with steps:** one `jovida create` for the outcome, with `--subtask` per step — not many separate todos, when the steps belong to a single result.
|
|
57
|
+
- **Change or reschedule:** `jovida list` (or `view`) to get the `entry_id`, then `jovida update`. To move a deadline, update `--when`. Remember `--remind` / `--subtask` replace the list.
|
|
58
|
+
- **Finish or clean up:** `jovida list` to see what's open, then `jovida complete` (done) or `jovida delete` (remove) — pass all related ids in one call. Prefer `complete` over `delete` unless the item was never real; if you marked one done by mistake, `jovida reopen` undoes it (a `delete` cannot be undone).
|
|
59
|
+
- **A repeating commitment:** create once with a repeat rule (a series). For a few irregular dates, create individual todos instead.
|
|
60
|
+
- **"What's on my plate?"** `jovida list` (today, or widen the scope) and summarize from the JSON. Read-only — don't write anything.
|
|
88
61
|
|
|
89
|
-
##
|
|
62
|
+
## Discipline
|
|
90
63
|
|
|
91
|
-
-
|
|
92
|
-
-
|
|
93
|
-
-
|
|
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`).
|
|
64
|
+
- Quote the title and any value containing spaces. Keep titles/descriptions **single-line plain text** — newlines and shell metacharacters passed as arguments get mangled.
|
|
65
|
+
- **Never put a token in a command** (it lands in shell history / process listings). Signing in is the user's interactive step.
|
|
66
|
+
- Don't claim success if a command exited non-zero — read the error and tell the user (exit `2` → ask them to `jovida login`).
|
package/dist/cli.js
CHANGED
|
@@ -4,16 +4,20 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
4
4
|
const ctx_1 = require("./ctx");
|
|
5
5
|
const create_1 = require("./commands/create");
|
|
6
6
|
const list_1 = require("./commands/list");
|
|
7
|
-
const
|
|
7
|
+
const view_1 = require("./commands/view");
|
|
8
8
|
const update_1 = require("./commands/update");
|
|
9
9
|
const complete_1 = require("./commands/complete");
|
|
10
|
+
const reopen_1 = require("./commands/reopen");
|
|
10
11
|
const delete_1 = require("./commands/delete");
|
|
11
12
|
const login_1 = require("./commands/login");
|
|
12
13
|
const whoami_1 = require("./commands/whoami");
|
|
14
|
+
const skill_1 = require("./commands/skill");
|
|
13
15
|
const shared_1 = require("./commands/shared");
|
|
14
16
|
const session_1 = require("./session");
|
|
15
17
|
const api_1 = require("./api");
|
|
16
18
|
const state_1 = require("./state");
|
|
19
|
+
const update_check_1 = require("./lib/update-check");
|
|
20
|
+
const VERSION = require('../package.json').version;
|
|
17
21
|
// 可重复的值 flag(收集成数组)。其余值 flag 取最后一次,无值则布尔 true。
|
|
18
22
|
const REPEATABLE = new Set(['remind', 'subtask']);
|
|
19
23
|
function parse(argv) {
|
|
@@ -55,6 +59,7 @@ Usage:
|
|
|
55
59
|
jovida login --token <vita-token> # dev-only interim: paste a signed-in vita token
|
|
56
60
|
jovida logout
|
|
57
61
|
jovida whoami [--json]
|
|
62
|
+
jovida skill install # copy the bundled skill into detected agents (Codex/Claude)
|
|
58
63
|
|
|
59
64
|
jovida create "<title>" [--when <ISO>] [--priority none|low|medium|high]
|
|
60
65
|
[--remind <ISO> ...] [--category <s>] [--desc <s>]
|
|
@@ -63,15 +68,127 @@ Usage:
|
|
|
63
68
|
[--day-of-month N] [--month-of-year N] [--until YYYY-MM-DD]
|
|
64
69
|
(--repeat requires --when as the first occurrence)
|
|
65
70
|
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
|
|
71
|
+
[--from YYYY-MM-DD] [--to YYYY-MM-DD] [--limit N] [--full] [--json]
|
|
72
|
+
jovida view <entry_id> [--json]
|
|
68
73
|
jovida update <entry_id> [--title ...] [--when ...] [--priority ...] [--remind ...] [...]
|
|
69
74
|
jovida complete <entry_id> [<entry_id> ...] [--json]
|
|
75
|
+
jovida reopen <entry_id> [<entry_id> ...] [--json]
|
|
70
76
|
jovida delete <entry_id> [<entry_id> ...] [--json]
|
|
71
77
|
|
|
72
78
|
Output: JSON is emitted automatically when stdout is not a TTY (use --json/--no-json to force).
|
|
73
|
-
|
|
79
|
+
Exit: 0 ok · 1 usage · 2 not signed in · 3 backend/network · 4 not found
|
|
80
|
+
Env: JOVIDA_API_URL=<url> (default https://tapi.jovida.ai) · JOVIDA_HOME=<dir> (default ~/.jovida)
|
|
81
|
+
JOVIDA_NO_UPDATE_CHECK=1 to silence the "update available" notice
|
|
82
|
+
|
|
83
|
+
Run \`jovida <command> --help\` for details on a command (e.g. jovida create --help).
|
|
74
84
|
`;
|
|
85
|
+
// 子命令详细 help(`jovida <cmd> --help` / `jovida help <cmd>`)。
|
|
86
|
+
const COMMAND_HELP = {
|
|
87
|
+
create: `jovida create — add a todo (or a recurring series)
|
|
88
|
+
|
|
89
|
+
Usage:
|
|
90
|
+
jovida create "<title>" [options]
|
|
91
|
+
|
|
92
|
+
Options:
|
|
93
|
+
--when <ISO> date "2026-06-15" = that day · datetime "2026-06-15T18:00:00+08:00" = deadline
|
|
94
|
+
--priority <p> none | low | medium | high
|
|
95
|
+
--category <s> free-text label
|
|
96
|
+
--desc <s> description (single line)
|
|
97
|
+
--remind <ISO> ... reminder time(s), repeatable; must be at/before --when
|
|
98
|
+
--subtask "<t>" ... subtask(s), repeatable
|
|
99
|
+
--hint <s> short companion hint
|
|
100
|
+
--json
|
|
101
|
+
|
|
102
|
+
Recurring (creates a series; requires --when as the first occurrence):
|
|
103
|
+
--repeat <unit> day | week | month | year
|
|
104
|
+
--every <N> interval, e.g. --repeat week --every 2 = biweekly
|
|
105
|
+
--weekdays <list> weekly: mon,wed,fri (or 1..7)
|
|
106
|
+
--day-of-month <N> monthly/yearly
|
|
107
|
+
--month-of-year <N> yearly
|
|
108
|
+
--until <YYYY-MM-DD> end date
|
|
109
|
+
|
|
110
|
+
Examples:
|
|
111
|
+
jovida create "submit report" --when 2026-06-20T18:00:00+08:00 --priority high
|
|
112
|
+
jovida create "standup" --when 2026-06-15 --repeat week --weekdays mon,wed,fri
|
|
113
|
+
`,
|
|
114
|
+
list: `jovida list — list todos (a scoped view, not a search)
|
|
115
|
+
|
|
116
|
+
Usage:
|
|
117
|
+
jovida list [options]
|
|
118
|
+
|
|
119
|
+
Options:
|
|
120
|
+
--scope <s> today (default) | upcoming | recent | range | all
|
|
121
|
+
--status <s> pending (default) | completed | all
|
|
122
|
+
--from <YYYY-MM-DD> range start (with --scope range)
|
|
123
|
+
--to <YYYY-MM-DD> range end
|
|
124
|
+
--limit <N> max items (default 20)
|
|
125
|
+
--full JSON: include all fields (description, subtasks, reminders) — one round-trip instead of list + view
|
|
126
|
+
--json
|
|
127
|
+
|
|
128
|
+
Examples:
|
|
129
|
+
jovida list
|
|
130
|
+
jovida list --scope all --status all
|
|
131
|
+
jovida list --scope range --from 2026-06-01 --to 2026-06-30
|
|
132
|
+
jovida list --full # full detail of each todo in one call
|
|
133
|
+
`,
|
|
134
|
+
view: `jovida view — full details of one todo
|
|
135
|
+
|
|
136
|
+
Usage:
|
|
137
|
+
jovida view <entry_id> [--json]
|
|
138
|
+
`,
|
|
139
|
+
update: `jovida update — change fields of an existing todo (only the given fields change)
|
|
140
|
+
|
|
141
|
+
Usage:
|
|
142
|
+
jovida update <entry_id> [options]
|
|
143
|
+
|
|
144
|
+
Options (same as create; --title renames):
|
|
145
|
+
--title <s> --when <ISO> --priority <p> --category <s> --desc <s>
|
|
146
|
+
--remind <ISO> ... (replaces the reminder list)
|
|
147
|
+
--subtask "<t>" ... (replaces the subtask list)
|
|
148
|
+
--hint <s> --json
|
|
149
|
+
|
|
150
|
+
Example:
|
|
151
|
+
jovida update cli_01H... --priority high --when 2026-06-21T09:00:00+08:00
|
|
152
|
+
`,
|
|
153
|
+
complete: `jovida complete — mark one or more todos done
|
|
154
|
+
|
|
155
|
+
Usage:
|
|
156
|
+
jovida complete <entry_id> [<entry_id> ...] [--json]
|
|
157
|
+
`,
|
|
158
|
+
reopen: `jovida reopen — reopen one or more completed todos (the inverse of complete)
|
|
159
|
+
|
|
160
|
+
Usage:
|
|
161
|
+
jovida reopen <entry_id> [<entry_id> ...] [--json]
|
|
162
|
+
`,
|
|
163
|
+
delete: `jovida delete — permanently remove one or more todos (no undo)
|
|
164
|
+
|
|
165
|
+
Usage:
|
|
166
|
+
jovida delete <entry_id> [<entry_id> ...] [--json]
|
|
167
|
+
`,
|
|
168
|
+
login: `jovida login — sign in (OAuth device authorization; opens a browser)
|
|
169
|
+
|
|
170
|
+
Usage:
|
|
171
|
+
jovida login [--json]
|
|
172
|
+
jovida login --token <vita-token> # dev-only interim: paste a signed-in vita token
|
|
173
|
+
|
|
174
|
+
The CLI prints a URL + short code and (best-effort) opens your browser; sign in
|
|
175
|
+
and approve there. It cannot sign in for you.
|
|
176
|
+
`,
|
|
177
|
+
logout: `jovida logout — clear local credentials (~/.jovida)
|
|
178
|
+
`,
|
|
179
|
+
whoami: `jovida whoami — show the signed-in account (online check)
|
|
180
|
+
|
|
181
|
+
Usage:
|
|
182
|
+
jovida whoami [--json]
|
|
183
|
+
`,
|
|
184
|
+
skill: `jovida skill — install/update the agent skill from the bundled SKILL.md
|
|
185
|
+
|
|
186
|
+
Usage:
|
|
187
|
+
jovida skill install # copy SKILL.md into detected agents (~/.codex, ~/.claude → skills/jovida-cli/)
|
|
188
|
+
jovida skill update # same (re-copy; keeps the skill in lockstep with the CLI version)
|
|
189
|
+
jovida skill install --all # install for all known agents even if not detected
|
|
190
|
+
`
|
|
191
|
+
};
|
|
75
192
|
/** 错误 → 退出码(0 ok / 1 用法 / 2 未登录 / 3 后端 / 4 not found)。 */
|
|
76
193
|
function exitCodeFor(e) {
|
|
77
194
|
if (e instanceof session_1.NotSignedInError)
|
|
@@ -95,11 +212,17 @@ async function main() {
|
|
|
95
212
|
const [cmd, ...rest] = process.argv.slice(2);
|
|
96
213
|
const { positionals, flags } = parse(rest);
|
|
97
214
|
if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') {
|
|
98
|
-
|
|
215
|
+
const topic = positionals[0]; // `jovida help <command>`
|
|
216
|
+
console.log(topic && COMMAND_HELP[topic] ? COMMAND_HELP[topic] : HELP);
|
|
99
217
|
return;
|
|
100
218
|
}
|
|
101
219
|
if (cmd === 'version' || cmd === '--version' || cmd === '-v') {
|
|
102
|
-
console.log(
|
|
220
|
+
console.log(VERSION);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
// `jovida <command> --help` / `-h` → that command's detailed help.
|
|
224
|
+
if (flags.help === true || rest.includes('--help') || rest.includes('-h')) {
|
|
225
|
+
console.log(COMMAND_HELP[cmd] ?? HELP);
|
|
103
226
|
return;
|
|
104
227
|
}
|
|
105
228
|
// JSON 输出:非 TTY 自动开;--json / --no-json 强制。
|
|
@@ -116,6 +239,9 @@ async function main() {
|
|
|
116
239
|
case 'whoami':
|
|
117
240
|
await (0, whoami_1.cmdWhoami)(ctx, { json });
|
|
118
241
|
break;
|
|
242
|
+
case 'skill':
|
|
243
|
+
(0, skill_1.cmdSkill)(positionals[0], { all: flags.all === true, json });
|
|
244
|
+
break;
|
|
119
245
|
case 'create':
|
|
120
246
|
await (0, create_1.cmdCreate)(ctx, {
|
|
121
247
|
title: positionals.join(' ').trim(),
|
|
@@ -142,11 +268,12 @@ async function main() {
|
|
|
142
268
|
from: str(flags.from),
|
|
143
269
|
to: str(flags.to),
|
|
144
270
|
limit: num(flags.limit),
|
|
271
|
+
full: flags.full === true,
|
|
145
272
|
json
|
|
146
273
|
});
|
|
147
274
|
break;
|
|
148
|
-
case '
|
|
149
|
-
await (0,
|
|
275
|
+
case 'view':
|
|
276
|
+
await (0, view_1.cmdView)(ctx, { id: positionals[0], json });
|
|
150
277
|
break;
|
|
151
278
|
case 'update':
|
|
152
279
|
await (0, update_1.cmdUpdate)(ctx, {
|
|
@@ -165,6 +292,9 @@ async function main() {
|
|
|
165
292
|
case 'complete':
|
|
166
293
|
await (0, complete_1.cmdComplete)(ctx, { ids: positionals, json });
|
|
167
294
|
break;
|
|
295
|
+
case 'reopen':
|
|
296
|
+
await (0, reopen_1.cmdReopen)(ctx, { ids: positionals, json });
|
|
297
|
+
break;
|
|
168
298
|
case 'delete':
|
|
169
299
|
await (0, delete_1.cmdDelete)(ctx, { ids: positionals, json });
|
|
170
300
|
break;
|
|
@@ -173,6 +303,7 @@ async function main() {
|
|
|
173
303
|
console.log(HELP);
|
|
174
304
|
process.exitCode = 1;
|
|
175
305
|
}
|
|
306
|
+
await (0, update_check_1.maybeNotifyUpdate)(VERSION); // 节流 + 仅 TTY + 永不抛;放最后,不影响命令输出/退出码
|
|
176
307
|
}
|
|
177
308
|
main().catch((e) => {
|
|
178
309
|
const msg = e instanceof Error ? e.message : String(e);
|
package/dist/commands/list.js
CHANGED
|
@@ -55,7 +55,8 @@ async function cmdList(ctx, a) {
|
|
|
55
55
|
items.sort((x, y) => (anchorSec(x) || Number.POSITIVE_INFINITY) - (anchorSec(y) || Number.POSITIVE_INFINITY));
|
|
56
56
|
items = items.slice(0, a.limit ?? 20);
|
|
57
57
|
if (a.json) {
|
|
58
|
-
|
|
58
|
+
const todos = a.full ? items.map((e) => (0, convert_1.toFullTodo)(e)) : items.map(convert_1.toListItem);
|
|
59
|
+
console.log(JSON.stringify({ todos }, null, 2));
|
|
59
60
|
return;
|
|
60
61
|
}
|
|
61
62
|
if (!items.length) {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.cmdReopen = cmdReopen;
|
|
4
|
+
const shared_1 = require("./shared");
|
|
5
|
+
/**
|
|
6
|
+
* 重新打开一个或多个已完成 entry(清 completedAt)——complete 的逆操作。
|
|
7
|
+
* 一次 pull 解析全部 id → 批量 put;任一 id 不存在则整体失败(不改任何条目)。
|
|
8
|
+
*/
|
|
9
|
+
async function cmdReopen(ctx, a) {
|
|
10
|
+
if (!a.ids || a.ids.length === 0) {
|
|
11
|
+
throw new Error('entry_id(s) required: jovida reopen <entry_id> [<entry_id> ...]');
|
|
12
|
+
}
|
|
13
|
+
await ctx.session.ensureSession();
|
|
14
|
+
const snap = await ctx.sync.pull();
|
|
15
|
+
const t = (0, shared_1.nowSec)();
|
|
16
|
+
const reopened = [];
|
|
17
|
+
const missing = [];
|
|
18
|
+
for (const id of a.ids) {
|
|
19
|
+
const e = snap.entries.find((x) => x.entryId === id);
|
|
20
|
+
if (e)
|
|
21
|
+
reopened.push({ ...e, completedAt: 0, updatedAt: t });
|
|
22
|
+
else
|
|
23
|
+
missing.push(id);
|
|
24
|
+
}
|
|
25
|
+
if (missing.length)
|
|
26
|
+
throw new shared_1.NotFoundError(missing.join(', '));
|
|
27
|
+
await ctx.sync.putEntries(reopened);
|
|
28
|
+
if (a.json) {
|
|
29
|
+
console.log(JSON.stringify({ entry_ids: reopened.map((e) => e.entryId), status: 'reopened' }));
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
for (const e of reopened)
|
|
33
|
+
console.log(`✓ reopened ${e.title} (${e.entryId})`);
|
|
34
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.cmdSkill = cmdSkill;
|
|
4
|
+
const node_os_1 = require("node:os");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
const node_fs_1 = require("node:fs");
|
|
7
|
+
// 已知 agent → skill 安装位置 <home>/<dir>/skills/jovida-cli/SKILL.md。
|
|
8
|
+
const AGENTS = [
|
|
9
|
+
{ name: 'Codex', dir: '.codex' },
|
|
10
|
+
{ name: 'Claude Code', dir: '.claude' }
|
|
11
|
+
];
|
|
12
|
+
const SKILL_NAME = 'jovida-cli';
|
|
13
|
+
// 随包的 SKILL.md(包根)。dist/commands/skill.js → ../../SKILL.md;dev src/commands → 同样解析到仓根。
|
|
14
|
+
function skillSource() {
|
|
15
|
+
return (0, node_path_1.resolve)(__dirname, '..', '..', 'SKILL.md');
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 把随 CLI 包发布的 SKILL.md 装 / 更新进 agent 的 skill 目录。
|
|
19
|
+
* 与 CLI 同一个 npm 版本 → 不漂移。`install` 与 `update` 行为相同(覆盖)。
|
|
20
|
+
*/
|
|
21
|
+
function cmdSkill(sub, a) {
|
|
22
|
+
if (sub && sub !== 'install' && sub !== 'update') {
|
|
23
|
+
throw new Error('usage: jovida skill install (or: jovida skill update)');
|
|
24
|
+
}
|
|
25
|
+
const src = skillSource();
|
|
26
|
+
if (!(0, node_fs_1.existsSync)(src))
|
|
27
|
+
throw new Error(`bundled SKILL.md not found at ${src}`);
|
|
28
|
+
const installed = [];
|
|
29
|
+
const skipped = [];
|
|
30
|
+
for (const ag of AGENTS) {
|
|
31
|
+
const home = (0, node_path_1.join)((0, node_os_1.homedir)(), ag.dir);
|
|
32
|
+
if (!a.all && !(0, node_fs_1.existsSync)(home)) {
|
|
33
|
+
skipped.push(ag.name);
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
const dir = (0, node_path_1.join)(home, 'skills', SKILL_NAME);
|
|
37
|
+
(0, node_fs_1.mkdirSync)(dir, { recursive: true });
|
|
38
|
+
(0, node_fs_1.copyFileSync)(src, (0, node_path_1.join)(dir, 'SKILL.md'));
|
|
39
|
+
installed.push((0, node_path_1.join)(dir, 'SKILL.md'));
|
|
40
|
+
}
|
|
41
|
+
if (a.json) {
|
|
42
|
+
console.log(JSON.stringify({ installed, skipped }));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (installed.length === 0) {
|
|
46
|
+
console.log('No agents detected (~/.codex or ~/.claude). Re-run with --all to install for all known agents.');
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
console.log('✓ skill installed/updated:');
|
|
50
|
+
for (const p of installed)
|
|
51
|
+
console.log(` ${p}`);
|
|
52
|
+
if (skipped.length)
|
|
53
|
+
console.log(`(skipped, not detected: ${skipped.join(', ')} — use --all to force)`);
|
|
54
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.cmdView = cmdView;
|
|
4
4
|
const convert_1 = require("../core/convert");
|
|
5
5
|
const shared_1 = require("./shared");
|
|
6
|
-
async function
|
|
6
|
+
async function cmdView(ctx, a) {
|
|
7
7
|
if (!a.id)
|
|
8
|
-
throw new Error('entry_id required: jovida
|
|
8
|
+
throw new Error('entry_id required: jovida view <entry_id>');
|
|
9
9
|
const e = await (0, shared_1.fetchEntry)(ctx, a.id);
|
|
10
10
|
const full = (0, convert_1.toFullTodo)(e);
|
|
11
11
|
if (a.json) {
|
package/dist/config.js
CHANGED
|
@@ -11,7 +11,8 @@ function loadConfig() {
|
|
|
11
11
|
appId: process.env['JOVIDA_APP_ID'] || DEFAULT_APP_ID
|
|
12
12
|
};
|
|
13
13
|
}
|
|
14
|
-
|
|
14
|
+
// 单一版本来源:取 package.json,避免与 npm 版本双写漂移(dist/config.js 与 src/config.ts 都解析到包根)。
|
|
15
|
+
exports.APP_VERSION = require('../package.json').version;
|
|
15
16
|
/** process.platform → Vita-Platform 值。 */
|
|
16
17
|
function platformName() {
|
|
17
18
|
if (process.platform === 'darwin')
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.maybeNotifyUpdate = maybeNotifyUpdate;
|
|
4
|
+
// 轻量更新提示:每天最多查一次 npm 最新版,仅交互终端(TTY)、走 stderr(不污染 --json stdout)、
|
|
5
|
+
// 超时 2s、**永不抛错也永不阻塞命令**。CI / NO_UPDATE_NOTIFIER / JOVIDA_NO_UPDATE_CHECK 关闭。
|
|
6
|
+
const state_1 = require("../state");
|
|
7
|
+
const PKG = '@fluxvita/jovida-cli';
|
|
8
|
+
const DAY = 86400;
|
|
9
|
+
const nowSec = () => Math.floor(Date.now() / 1000);
|
|
10
|
+
function isNewer(latest, current) {
|
|
11
|
+
const a = latest.split('.').map((n) => parseInt(n, 10));
|
|
12
|
+
const b = current.split('.').map((n) => parseInt(n, 10));
|
|
13
|
+
for (let i = 0; i < 3; i++) {
|
|
14
|
+
const x = a[i] || 0;
|
|
15
|
+
const y = b[i] || 0;
|
|
16
|
+
if (x > y)
|
|
17
|
+
return true;
|
|
18
|
+
if (x < y)
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
async function fetchLatest() {
|
|
24
|
+
try {
|
|
25
|
+
const ctrl = new AbortController();
|
|
26
|
+
const t = setTimeout(() => ctrl.abort(), 2000);
|
|
27
|
+
const res = await fetch(`https://registry.npmjs.org/${PKG}/latest`, { signal: ctrl.signal });
|
|
28
|
+
clearTimeout(t);
|
|
29
|
+
if (!res.ok)
|
|
30
|
+
return null;
|
|
31
|
+
const j = (await res.json());
|
|
32
|
+
return typeof j.version === 'string' ? j.version : null;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async function maybeNotifyUpdate(current) {
|
|
39
|
+
try {
|
|
40
|
+
if (!process.stderr.isTTY)
|
|
41
|
+
return; // 仅给人看;脚本/agent(非 TTY)不打扰
|
|
42
|
+
if (process.env['CI'] || process.env['JOVIDA_NO_UPDATE_CHECK'] || process.env['NO_UPDATE_NOTIFIER'])
|
|
43
|
+
return;
|
|
44
|
+
const { at, latest: cached } = (0, state_1.getUpdateCheck)();
|
|
45
|
+
let latest = cached;
|
|
46
|
+
if (!at || nowSec() - at > DAY) {
|
|
47
|
+
const fresh = await fetchLatest();
|
|
48
|
+
latest = fresh ?? cached;
|
|
49
|
+
(0, state_1.setUpdateCheck)(nowSec(), latest);
|
|
50
|
+
}
|
|
51
|
+
if (latest && isNewer(latest, current)) {
|
|
52
|
+
process.stderr.write(`\n Update available: ${current} → ${latest}\n` +
|
|
53
|
+
` Run: npm i -g ${PKG}@latest (then: jovida skill update)\n\n`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
/* 更新检查永不影响命令 */
|
|
58
|
+
}
|
|
59
|
+
}
|
package/dist/state.js
CHANGED
|
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.getDeviceId = getDeviceId;
|
|
4
4
|
exports.getLastServerVersion = getLastServerVersion;
|
|
5
5
|
exports.setLastServerVersion = setLastServerVersion;
|
|
6
|
+
exports.getUpdateCheck = getUpdateCheck;
|
|
7
|
+
exports.setUpdateCheck = setUpdateCheck;
|
|
6
8
|
exports.getToken = getToken;
|
|
7
9
|
exports.setToken = setToken;
|
|
8
10
|
exports.clearCredentials = clearCredentials;
|
|
@@ -53,6 +55,16 @@ function setLastServerVersion(v) {
|
|
|
53
55
|
s.lastServerVersion = v;
|
|
54
56
|
saveState(s);
|
|
55
57
|
}
|
|
58
|
+
function getUpdateCheck() {
|
|
59
|
+
const s = loadState();
|
|
60
|
+
return { at: s.updateCheckAt ?? 0, latest: s.updateLatest ?? '' };
|
|
61
|
+
}
|
|
62
|
+
function setUpdateCheck(at, latest) {
|
|
63
|
+
const s = loadState();
|
|
64
|
+
s.updateCheckAt = at;
|
|
65
|
+
s.updateLatest = latest;
|
|
66
|
+
saveState(s);
|
|
67
|
+
}
|
|
56
68
|
function readCreds() {
|
|
57
69
|
return readJson(CRED);
|
|
58
70
|
}
|