Commit Graph

6 Commits

Author SHA1 Message Date
nullkey a2e368b1e8 refactor(cli): command-surface rename — session ask / doc fetch / doc create / doc delete --all
Three command renames consolidate the v0.7 verb table:

- `weknora agent invoke` → `weknora session ask --agent <id>`.
  Server route POST /sessions/{session_id}/agent-qa is session-
  anchored, so the verb moves with it. `weknora agent` subtree keeps
  CRUD only (list / view / create / edit / delete / status / check).
  SDK call (AgentQAStreamWithRequest) preserved verbatim; only the
  command surface + flag layout move.

- `weknora doc upload` split into three commands:
  - `weknora doc upload <file>`  — local file (only).
  - `weknora doc fetch <url>`    — server-side remote fetch (was
    `upload --from-url`). URL-only flags (--title / --file-type /
    --tag-id) move with the verb.
  - `weknora doc create --text`  — direct text knowledge entry via
    server CreateManualKnowledge.

- `weknora kb empty <id>` → `weknora doc delete --all --kb=<id>`.
  Atomic server ClearKnowledgeBaseContents (no list-then-delete
  race). Same exit-10 -y/--yes guard as other destructive verbs;
  unified through the extended ConfirmDestructive helper.

Parent commands (agent, kb, doc, chunk, session, auth, profile,
search) lose their explicit Args:NoArgs + Run:cmd.Help so the
unknown-subcommand guard fires correctly — `weknora agent invoke
ag_x q` now emits the typed input.unknown_subcommand envelope with
detail.available[] instead of cobra's free-form exit-2 prose.

Spec: docs/superpowers/specs/2026-05-20-weknora-cli-v0.7-design.md §3.4 / §10.7
2026-05-27 10:56:34 +08:00
nullkey 7eeb3bec5d feat(cli): doc wait command (multi-target wait-all)
weknora doc wait <doc-id> [<doc-id>...] blocks until every given document
reaches a terminal parse_status (completed / failed), --timeout expires,
or the user interrupts (SIGINT).

* --timeout DURATION (default 10m; exit 124 on timeout, matches GNU
  timeout(1) convention)
* --interval DURATION (default 2s; exponential backoff to 15s + jitter)
* Multi-id polled concurrently (max 5 parallel)
* Exit code priority 1 > 124 > 0 (failed > timeout > completed)

New typed errors:
* operation.timeout → exit 124
* operation.failed → exit 1
* operation.cancelled → exit 1 (main raises to 130 on signal)

server.session_create_failed gets a special case in ExitCode to map to
exit 1 (workflow failure, not transient retry).

doc view and doc download positional id namespaced to <doc-id>.
2026-05-18 11:10:19 +08:00
nullkey f2e8e3f56c refactor(cli): drop aiclient package; align AGENTS.md with mainstream
Survey of 10 mainstream CLIs (gh, lark, stripe, vercel, supabase, aws,
azure, gcloud, openai/codex, github-copilot-cli) showed env-gated
per-command --help blurbs are a Stripe-only pattern; gh uses env detect
for telemetry only, and lark relies on installed agent Skills + MCP.
Our cmd/mcp/serve already covers the dominant 2025/26 path, so
internal/aiclient/ (136 LOC + 38 callsites) is net maintenance burden
without precedent.

- Drop internal/aiclient/ entirely (annotations + detect + tests)
- Remove 38 SetAgentHelp callsites + agentAwareHelpFunc / SetHelpFunc
  wiring in cmd/root.go
- Migrate 4 command-level rules to standard Long help (visible to all,
  not env-gated): doc upload mode mutex, kb edit at-least-one,
  kb pin idempotent, search chunks channel mutex
- Rewrite AGENTS.md as a developer guide (gh-style 6 H2 / 167 lines):
  audience preamble + Build / Architecture / Command Structure /
  Testing / Code Style / Error Handling. Drops sections absent in
  surveyed projects (Commit & PR Conventions, Who Uses This CLI)
- Clean 14 internal doc refs (ADR-N, spec §X, v0.X) in source comments
  and docs that pointed at docs/superpowers/ — that directory is
  local-only / uncommitted, so refs are dead for outside readers
- Drop forward-looking "once v0.2 ships" from README
2026-05-15 12:03:56 +08:00
nullkey 35c79281c8 feat(cli): doc view + unlink (fill v0.3 design-gap audit)
Final design-pass audit on the v0.3 surface flagged two real gaps.

(A) doc view <id> was missing. Every other resource subtree exposes
a view verb (kb view, session view) for inspecting a single record,
but doc — which has the richest metadata of the three (title, file
name, type, size, parse_status, embedding_model, processed_at,
error_message) — had no single-doc surface. Users wanting one
doc's metadata had to `doc list | grep`.

Implementation mirrors kb view: narrow ViewService(GetKnowledge)
interface, --json envelope path, human KEY: VALUE renderer. Optional
fields are omitted rather than rendered as "-" so the panel is
dense. Tested: human renderer, title fallback when FileName empty,
omit-empty contract, JSON envelope shape, 404 classification.

(B) link had no counterpart. Once .weknora/project.yaml is written,
the only way to clear it was `rm` by hand. Both vercel and netlify
ship `unlink` as a top-level verb; not having one was a
discoverability gap. Top-level rather than `link --clear` follows
the verb-noun convention of the rest of the surface — the verb
stands alone and the operation isn't parameterised.

unlink walks up from cwd via projectlink.Discover (the same
parent-chain logic Factory.ResolveKB uses on the read side), so a
user in a subdirectory of a linked project can unlink without
cd-ing up. Errors with input.invalid_argument when no link is
found anywhere in the chain. Idempotent under racy concurrent
removal: os.ErrNotExist on os.Remove falls through to a Success
envelope since the post-condition holds either way.

projectlink package gained Remove() alongside Save / Load /
Discover so unlink doesn't reimplement the idempotent-remove
pattern inline.

Top-level registration in cmd/root.go, alongside link.
cli/AGENTS.md verb canon line adds unlink to the locally-introduced
list. cli/CHANGELOG.md gains an Added entry for each.

5 unit tests for view + 4 for unlink (cwd / walk-up / no-link
error / JSON envelope). Full suite green.

Intentionally deferred:
- session edit (rename a session): sessions auto-name from the
  first prompt; polish rather than a gap.
- link --clear as an alternative to unlink: top-level unlink is
  the documented form; aliases would just multiply the surface.
2026-05-14 10:57:17 +08:00
nullkey 78f3994112 feat(cli): doc download + upload --recursive
Roadmap items 3-9 (download) and 3-10 (recursive upload).

SDK addition (additive, non-breaking):
- OpenKnowledgeFile(ctx, id) (filename, body io.ReadCloser, err) —
  the new primitive that returns the body as a stream plus the
  server-suggested Content-Disposition filename. The existing path-
  form DownloadKnowledgeFile is now a thin wrapper (also gained
  partial-file-on-error cleanup, a pre-existing bug exposed by the
  reshape).

doc download <id>:
Borrows shape from `gh release download` (positional id, output flag,
`-` sentinel for stdout). Flag names match gh canon verified against
the gh manual: `-O, --output <file>` for destination; `--clobber` for
overwrite control.

- Default: writes to cwd under the server-suggested filename. If the
  server didn't send one, errors with input.missing_flag.
- --output FILE / -O FILE: writes to FILE. Refuses overwrite without
  --clobber.
- --output -: stream to stdout (binary-safe).
- Partial writes on error are cleaned up.

doc upload --recursive <dir> --glob '*.pdf':
NOTE on upstream parity: `gh release upload` does NOT support
--recursive (verified — it takes individual file args only). `aws s3
cp --recursive` does, but uses `--include`/`--exclude` glob pattern
pairs rather than a single `--glob`. weknora's single positive `--glob`
is a deliberate simplification, not a direct mirror of either tool.

- Walks the tree, filters by base-name glob, uploads each match
  sequentially. Per-file line output: OK / FAIL with the underlying
  error. Exit 0 only on full success; on partial failure returns the
  first failure's typed code so callers can branch. Rejects --name
  with --recursive.
- --dry-run lists matches without uploading.
- --json emits {kb_id, uploaded[], failed[]} envelope at completion.

Bugs caught in the post-commit reviewer round:
- SECURITY: server-supplied filename was used in os.Rename without
  sanitization. A malicious / buggy server returning
  "../../etc/shadow" could escape cwd. Now filepath.Base'd; "." / "/"
  / "" rejected. Regression test added.
- Wasted-bytes path eliminated via the SDK reshape: the CLI now
  inspects filename and applies refuseIfExists BEFORE streaming.
  Two-phase temp+rename gone.
- refuseIfExists(path, clobber) helper extracted.
- --json honored in --recursive (uploadOutcome was JSON-tagged but
  the envelope was never emitted).

7 + 7 unit tests for download (+ path-traversal regression) and
recursive upload (+ JSON envelope regression).

Roadmap: 3-9, 3-10.
2026-05-14 10:57:17 +08:00
nullkey 8a0674186e feat(cli): add kb create/delete and doc list/upload/delete commands
Add the resource-management surface to the v0.2 CLI:

  - weknora kb create --name X [--description Y] [--embedding-model Z]
  - weknora kb delete <id> [--force]
  - weknora doc list [--kb-id X | --kb NAME] [--page N] [--page-size M]
  - weknora doc upload <file> [--kb-id X | --kb NAME] [--name custom]
  - weknora doc delete <id> [--force]

doc/* uses Factory.ResolveKB so the cwd's project link is honored when
--kb-id is omitted. doc upload validates path existence with os.Stat
(rejects directories; follows symlinks to mirror SDK os.Open behavior).
doc list sorts by updated_at desc so newer items surface first.

Both delete commands route through cmdutil.ConfirmDestructive — the
"destructive op needs explicit user opt-in" pattern was about to be
copy-pasted across the new subtree, so it was extracted with the delete
commands as their first consumer. Saves the same dedup pass when v0.3
adds session/agent delete. (PR-12 later renames the flag from --force
to the global -y/--yes for gh-style consistency.)

iostreams.SetForTestWithTTY pairs with the existing SetForTest helper:
the latter never reports stdout as a TTY (singleton replacement uses an
in-memory buffer), so the confirm-yes / confirm-no test branches need a
TTY-on variant.
2026-05-12 13:20:42 +08:00