Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,21 @@ What to understand first:
- `Memories`: list, search, detail view, and chat
- `Insert`: add files, text, or manual embeddings
- `Create`: create a new memory
- `Market`: reserved and not implemented yet
- `Wiki`: browse databases from the configured wiki canister; `KINIC_WIKI_CANISTER_ID` can override the default
- `Settings`: principal, balance, default memory, saved tags, and retrieval settings

For local deploys, `scripts/setup.sh` deploys the bundled Wiki wasm at the default Wiki canister id.
The TUI groups Wiki databases under `Private / Shared` and `Public`, marks rows based on anonymous access and the current identity's role, reads public databases with an anonymous agent, and opens editing only for `Owner` or `Writer` databases.

Wiki write/delete and database management are available from the CLI:

```bash
kinic-cli --ic --identity alice wiki database list
kinic-cli --ic --identity alice wiki read --database-id DATABASE_ID --path /Wiki/index.md
kinic-cli --ic --identity alice wiki write --database-id DATABASE_ID --path /Wiki/new.md --input ./new.md
kinic-cli --ic --identity alice wiki delete --database-id DATABASE_ID --path /Wiki/new.md --yes
```

### Step 2: Confirm Identity and Balance

Open `Settings` and confirm that `Principal ID` matches the identity you launched with. Then check whether `KINIC balance` is high enough to create a memory. If needed, you can transfer tokens from the transfer modal, and `Ctrl+R` refreshes both `Principal ID` and `KINIC balance`.
Expand Down
5 changes: 5 additions & 0 deletions dfx.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
"type": "custom",
"candid": "https://github.com/dfinity/internet-identity/releases/latest/download/internet_identity.did",
"wasm": "https://github.com/dfinity/internet-identity/releases/latest/download/internet_identity_dev.wasm.gz"
},
"wiki": {
"type": "custom",
"candid": "wasm/wiki/vfs.did",
"wasm": "wasm/wiki/vfs_canister_nowasi.wasm"
}
},
"defaults": {
Expand Down
27 changes: 27 additions & 0 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ Agent-friendly discovery tips:
- Start with `kinic-cli --help` for auth mode guidance and top-level entrypoints
- Start with `kinic-cli capabilities` to get a JSON execution contract for global flags, auth sources, output behavior, major arguments, and arg-group constraints
- Use `kinic-cli prefs --help` to inspect the JSON contract for shared local preferences
- Use `kinic-cli wiki --help` to inspect Wiki database, read/search, and write/delete operations against the configured Wiki canister
- `capabilities` and `prefs` commands return JSON; `list`, `show`, and `search` also support `--json` for agent/script consumption while keeping human-friendly text output by default
- Keychain failures use stable text prefixes such as `KEYCHAIN_LOOKUP_FAILED`, `KEYCHAIN_ACCESS_DENIED`, `KEYCHAIN_INTERACTION_NOT_ALLOWED`, and `KEYCHAIN_ERROR`; agents should branch on the leading `[KEYCHAIN_*]` code instead of parsing the rest of the sentence

Expand Down Expand Up @@ -132,6 +133,32 @@ Notes:
- Delegations are stored at `~/.config/kinic/identity.json`.
- The login flow uses a local callback on port `8620`.

### Wiki CLI

`wiki` operates the configured Wiki canister. It uses `xis3j-paaaa-aaaai-axumq-cai` by default and `KINIC_WIKI_CANISTER_ID` can override that target.
For local deploys, `scripts/setup.sh` deploys the bundled Wiki wasm at that same canister id.

```bash
cargo run -- --ic --identity alice wiki database list
cargo run -- --ic --identity alice wiki children --database-id DATABASE_ID --path /Wiki
cargo run -- --ic --identity alice wiki read --database-id DATABASE_ID --path /Wiki/index.md
cargo run -- --ic --identity alice wiki search --database-id DATABASE_ID "release notes"
```

Write operations are CLI-only:

```bash
cargo run -- --ic --identity alice wiki write \
--database-id DATABASE_ID \
--path /Wiki/new.md \
--input ./new.md

cargo run -- --ic --identity alice wiki delete \
--database-id DATABASE_ID \
--path /Wiki/new.md \
--yes
```

### Convert PDF to markdown (inspect only)

```bash
Expand Down
16 changes: 13 additions & 3 deletions docs/tui.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,18 +80,20 @@ The TUI has five tabs.
- `Memories`: list, search, and details
- `Insert`: add data
- `Create`: create a new memory
- `Market`: reserved for future use and currently not implemented
- `Wiki`: browse databases from the configured wiki canister; `KINIC_WIKI_CANISTER_ID` can override the default
- `Settings`: view and change current connection info and saved settings

The `Memories` tab opens first when the TUI starts.
By default, the Wiki tab uses `xis3j-paaaa-aaaai-axumq-cai`.
For local deploys, `scripts/setup.sh` deploys the bundled Wiki wasm at that same canister id.
The local preferences shown in `Settings`, including the default memory, saved tags, and manually tracked memories, can also be managed from the CLI with `kinic-cli prefs ...`.

## Basic Controls

- `1` to `5`: switch tabs
- `Tab`: move focus forward within the screen
- `Shift+Tab`: move focus backward
- `/`: focus the search field
- `/`: focus the `Memories` search field
- `↑` `↓`: move through lists and form fields
- `Enter`: open, confirm, or submit the current item
- `Esc`: go back one step, return to the list, or close the picker
Expand All @@ -104,6 +106,15 @@ The local preferences shown in `Settings`, including the default memory, saved t

The status bar at the bottom also shows the keys available in the current context.

In the `Wiki` tab, `Tab` and `Shift+Tab` switch between the Browser pane and the Document pane.
Wiki databases are grouped under `Private / Shared` and `Public`.
Database rows show `Public` when `anonymous` has access, `Private` for owned databases without anonymous access, and `Shared` for non-public databases shared with the current identity.
Public database reads use an anonymous agent so identities without direct membership can still browse anonymous-readable databases.
Wiki edit opens only for databases where the current identity is `Owner` or `Writer`.
When Browser is focused, `↑` `↓` moves the selected database or wiki node, `Enter` opens it, and `e` edits a loaded `/Wiki/*.md` file.
When Document is focused, `↑` `↓`, `PageUp`, `PageDown`, `Home`, and `End` scroll the document.
In Wiki edit mode, `Ctrl+S` saves, `Tab` moves to the footer `Save` / `Cancel` controls, and `Esc` exits or asks to discard dirty changes.

`?` and `q` are intended for normal list and tab navigation, not while typing in a search field or form, and not while the chat input is focused.
`Shift+C` (toggle chat on `Memories`) follows the same focus rules.
`Shift+S` toggles the settings overlay from the search field, lists, and other panes, but not while editing a form field or while the chat input is focused.
Expand Down Expand Up @@ -307,7 +318,6 @@ The main saved values are:

- `--identity` is required
- `--ii` is not supported yet
- The `Market` tab is not implemented yet
- `pdftotext` is required for PDF insertion
- There is no dedicated copy shortcut for Principal ID
- Local use requires a prepared local replica and supporting canisters
Expand Down
180 changes: 179 additions & 1 deletion rust/cli_defs.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::path::PathBuf;

use clap::{ArgGroup, Args, Parser, Subcommand};
use clap::{ArgGroup, Args, Parser, Subcommand, ValueEnum};

pub fn parse_identity_arg(value: &str) -> Result<String, String> {
if value.trim().is_empty() {
Expand Down Expand Up @@ -153,6 +153,10 @@ pub enum Command {
after_help = "Configuration:\n Set KINIC_TOOL_IDENTITY=<IDENTITY>\n Set KINIC_TOOL_NETWORK=local|mainnet\n\nNotes:\n tools serve does not accept global --identity, --ii, --ic, or --identity-path."
)]
Tools(ToolsArgs),
#[command(
about = "Operate the configured Wiki canister. Requires --identity <NAME> or --ii. Returns text output by default."
)]
Wiki(WikiArgs),
#[command(
about = "Launch the Kinic terminal UI. Requires global --identity <NAME>. --ii is not supported. Returns an interactive TUI, not JSON.",
after_help = "Requires:\n kinic-cli --identity <NAME> tui\n\nReturns:\n Interactive terminal UI.\n\nExample:\n kinic-cli --identity alice tui"
Expand All @@ -175,6 +179,180 @@ pub struct CreateArgs {
#[derive(Args, Debug, Default)]
pub struct TuiArgs {}

#[derive(Args, Debug)]
pub struct WikiArgs {
#[command(subcommand)]
pub command: WikiCommand,
}

#[derive(Subcommand, Debug)]
pub enum WikiCommand {
#[command(about = "Manage Wiki databases")]
Database(WikiDatabaseArgs),
#[command(about = "Read a Wiki node")]
Read(WikiReadArgs),
#[command(about = "List Wiki children under a path")]
Children(WikiChildrenArgs),
#[command(about = "Search Wiki nodes")]
Search(WikiSearchArgs),
#[command(about = "Write a Wiki node from a file")]
Write(WikiWriteArgs),
#[command(about = "Append file contents to a Wiki node")]
Append(WikiAppendArgs),
#[command(about = "Replace text in a Wiki node")]
Edit(WikiEditArgs),
#[command(about = "Delete a Wiki node. Requires --yes.")]
Delete(WikiDeleteArgs),
}

#[derive(Args, Debug)]
pub struct WikiDatabaseArgs {
#[command(subcommand)]
pub command: WikiDatabaseCommand,
}

#[derive(Subcommand, Debug)]
pub enum WikiDatabaseCommand {
#[command(about = "List databases visible to the caller")]
List(WikiJsonArgs),
#[command(about = "Create a new database")]
Create(WikiJsonArgs),
#[command(about = "Grant database access to a principal")]
Grant(WikiDatabaseGrantArgs),
}

#[derive(Args, Debug, Default)]
pub struct WikiJsonArgs {
#[arg(long, help = "Return machine-readable JSON output")]
pub json: bool,
}

#[derive(Args, Debug)]
pub struct WikiDatabaseGrantArgs {
pub database_id: String,
pub principal: String,
#[arg(value_enum)]
pub role: WikiDatabaseRoleArg,
#[arg(long, help = "Return machine-readable JSON output")]
pub json: bool,
}

#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq)]
pub enum WikiDatabaseRoleArg {
Owner,
Writer,
Reader,
}

#[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq)]
pub enum WikiNodeKindArg {
File,
Source,
}

#[derive(Args, Debug)]
pub struct WikiReadArgs {
#[arg(long, required = true)]
pub database_id: String,
#[arg(long, required = true)]
pub path: String,
#[arg(long, help = "Return machine-readable JSON output")]
pub json: bool,
}

#[derive(Args, Debug)]
pub struct WikiChildrenArgs {
#[arg(long, required = true)]
pub database_id: String,
#[arg(long, default_value = "/Wiki")]
pub path: String,
#[arg(long, help = "Return machine-readable JSON output")]
pub json: bool,
}

#[derive(Args, Debug)]
pub struct WikiSearchArgs {
#[arg(long, required = true)]
pub database_id: String,
pub query: String,
#[arg(long, default_value = "/Wiki")]
pub prefix: String,
#[arg(long, default_value_t = 10)]
pub top_k: u32,
#[arg(long, help = "Return machine-readable JSON output")]
pub json: bool,
}

#[derive(Args, Debug)]
pub struct WikiWriteArgs {
#[arg(long, required = true)]
pub database_id: String,
#[arg(long, required = true)]
pub path: String,
#[arg(long, value_name = "PATH", required = true)]
pub input: PathBuf,
#[arg(long, value_enum, default_value_t = WikiNodeKindArg::File)]
pub kind: WikiNodeKindArg,
#[arg(long, default_value = "{}")]
pub metadata_json: String,
#[arg(long)]
pub expected_etag: Option<String>,
#[arg(long, help = "Return machine-readable JSON output")]
pub json: bool,
}

#[derive(Args, Debug)]
pub struct WikiAppendArgs {
#[arg(long, required = true)]
pub database_id: String,
#[arg(long, required = true)]
pub path: String,
#[arg(long, value_name = "PATH", required = true)]
pub input: PathBuf,
#[arg(long)]
pub expected_etag: Option<String>,
#[arg(long)]
pub separator: Option<String>,
#[arg(long, value_enum)]
pub kind: Option<WikiNodeKindArg>,
#[arg(long)]
pub metadata_json: Option<String>,
#[arg(long, help = "Return machine-readable JSON output")]
pub json: bool,
}

#[derive(Args, Debug)]
pub struct WikiEditArgs {
#[arg(long, required = true)]
pub database_id: String,
#[arg(long, required = true)]
pub path: String,
#[arg(long, required = true)]
pub old_text: String,
#[arg(long, required = true)]
pub new_text: String,
#[arg(long)]
pub replace_all: bool,
#[arg(long)]
pub expected_etag: Option<String>,
#[arg(long, help = "Return machine-readable JSON output")]
pub json: bool,
}

#[derive(Args, Debug)]
pub struct WikiDeleteArgs {
#[arg(long, required = true)]
pub database_id: String,
#[arg(long, required = true)]
pub path: String,
#[arg(long)]
pub expected_etag: Option<String>,
#[arg(long, help = "Confirm deletion")]
pub yes: bool,
#[arg(long, help = "Return machine-readable JSON output")]
pub json: bool,
}

#[derive(Args, Debug)]
pub struct ToolsArgs {
#[command(subcommand)]
Expand Down
11 changes: 11 additions & 0 deletions rust/cli_policy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,17 @@ pub fn command_policy_for_path(path: &str) -> CommandPolicy {
};
}

if path == "wiki" || path.starts_with("wiki.") {
return CommandPolicy {
auth_sources: &["global_identity", "global_ii"],
conditional_auth: &[],
output_default: "text",
output_supported: &["text", "json"],
interactive: false,
global_flags_supported: GLOBAL_FLAGS_ALL,
};
}

CommandPolicy {
auth_sources: &["global_identity", "global_ii"],
conditional_auth: &[],
Expand Down
2 changes: 2 additions & 0 deletions rust/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pub mod show;
pub mod tagged_embeddings;
pub mod transfer;
pub mod update;
pub mod wiki;

#[derive(Clone)]
pub struct CommandContext {
Expand Down Expand Up @@ -58,6 +59,7 @@ pub async fn run_command(command: Command, ctx: CommandContext) -> Result<()> {
Command::AskAi(args) => ask_ai::handle(args, &ctx).await,
Command::Login(args) => ii_login::handle(args, &ctx).await,
Command::Tools(_) => unreachable!("tools command is handled before agent setup"),
Command::Wiki(args) => wiki::handle(args, &ctx).await,
Command::Tui(_) => unreachable!("TUI command is handled before command dispatch"),
}
}
Loading