diff --git a/docs/README.md b/docs/README.md index cb828f0..6304012 100644 --- a/docs/README.md +++ b/docs/README.md @@ -53,7 +53,7 @@ root and links here for anything beyond install + quickstart. ## Advanced -- [Escape hatches](advanced/escape-hatches.md) — `huly api` and +- [Direct SDK and HTTP access](advanced/direct-access.md) — `huly api` and `huly ws` for raw RPCs - [CLI architecture](advanced/architecture.md) — source layout, connection flow, markup handling diff --git a/docs/advanced/architecture.md b/docs/advanced/architecture.md index 9bb16e0..79794b5 100644 --- a/docs/advanced/architecture.md +++ b/docs/advanced/architecture.md @@ -115,7 +115,7 @@ warning to stderr, and — if `HULY_MARKDOWN_FALLBACK_FAIL=1` is set prosemirror-JSON directly. For rich-text round-trip features (mention nodes, embeds) that -don't survive the JSON round-trip, use the raw escape hatch with a +don't survive the JSON round-trip, use the raw direct SDK access with a direct transaction object. The `params` argument is a JSON array containing a single `TxCreateDoc` transaction object. @@ -150,7 +150,7 @@ huly ws tx '[{ }]' ``` -See [Escape hatches — WebSocket (`huly ws`)](escape-hatches.md#websocket-huly-ws) +See [Direct SDK and HTTP access — WebSocket (`huly ws`)](direct-access.md#websocket-huly-ws) for the full RPC contract. The `tx` RPC accepts every transaction type — `TxCreateDoc`, `TxUpdateDoc`, `TxRemoveDoc`, `TxMixin`, `TxApplyIf` — with the same JSON shape as the `core:class:*` diff --git a/docs/advanced/direct-access.md b/docs/advanced/direct-access.md new file mode 100644 index 0000000..45a3972 --- /dev/null +++ b/docs/advanced/direct-access.md @@ -0,0 +1,84 @@ +--- +title: Direct SDK and HTTP access (advanced) +description: When huly-cli doesn't have a flag for what you need — `huly api` and `huly ws` for raw, unvalidated passthroughs against your self-hosted Huly workspace. Advanced use only. +--- + +# Direct SDK and HTTP access (advanced) + +> **Advanced only.** Two commands bypass every CLI safety check — ref resolution, type checking, cascade awareness, error mapping, and (for destructive calls) confirmation prompts: +> +> - **`huly api`** — raw HTTP passthrough. +> - **`huly ws`** — raw WebSocket RPC. +> +> Treat them like raw SQL. Most workflows do not need them. If you find yourself reaching for them often for a pattern the CLI should expose, file an issue — that's a missing-feature signal. + +When a CLI command doesn't exist for what you need, or the flag you need isn't exposed, talk to the server directly. Both commands are pass-through — they don't filter or transform the response. + +## Table of contents + +- [HTTP (`huly api`)](#http-huly-api) +- [WebSocket (`huly ws`)](#websocket-huly-ws) +- [When to use direct SDK access](#when-to-use-direct-sdk-access) + +--- + +## HTTP (`huly api`) + +```bash +huly api GET /api/v1/version +huly api GET /config.json +huly api POST /api/v1/something --body '{"key":"value"}' +huly api GET /api/v1/things --query foo=bar --query baz=qux +huly api GET /api/v1/things --header "Authorization: Bearer ..." +``` + +Available methods: `GET | POST | PUT | PATCH | DELETE`. The path +is appended to the workspace's API URL. The CLI does not validate the path, method, body, or any custom headers — anything you send goes straight to the server. + +> **`Authorization` is not overridable.** The CLI always sets `Authorization: Bearer ` after merging your custom headers (`packages/cli/src/raw/api.ts:43-49`), so passing `--header "Authorization: Bearer …"` has no effect. All other custom headers pass through verbatim. + +--- + +## WebSocket (`huly ws`) + +The Huly RPC protocol uses WebSocket for the SDK connection, but the +raw `huly ws` command is **text JSON only**. Use it for direct +method calls without opening the SDK's binary transport: + +```bash +# findAll +huly ws findAll '[{"_class":"tracker:class:Project"},{}]' + +# tx (raw transaction) +huly ws tx '[{"_class":"core:class:TxCreateDoc",...}]' +``` + +> `huly ws` accepts a single positional `` followed by an +> optional `[params]` argument that is a **JSON-encoded array of +> positional parameters** for that method. On Huly 0.7.x the raw +> socket dispatches a small whitelist: `findAll`, `tx`, `hello`, and +> `ping`. Do not rely on `findOne`, `createDoc`, `updateDoc`, or other +> SDK methods through this command — use the high-level commands +> for writes, or `tx` for raw transaction payloads. +> +> The `tx` RPC supports every transaction type — `TxCreateDoc`, +> `TxUpdateDoc`, `TxRemoveDoc`, `TxMixin`, `TxApplyIf`. Build the +> payload directly; the CLI doesn't validate. **Confirm with the user before invoking** — raw RPC has no CLI confirmation prompt and bypasses every safety check. + +--- + +## When to use direct SDK access + +- A command exists but doesn't expose the flag you need (rare). Use the high-level command with `--set key=value` first; reach for `huly ws` / `huly api` only when the field is not exposed at all. +- A command exists but operates on a wrong sub-resource. +- You're debugging and need to see the raw server response. +- The CLI doesn't support the surface you need (use the SDK + instead — see + [Migration — from the SDK](../guides/migration.md#from-the-huly-sdk-typescript)). + +The commands pass through directly; the CLI handles auth and +caching, not transformation. If you find yourself reaching for +`huly ws` often, that's a signal the CLI should expose that surface +natively — file an issue. + +**Do not use raw RPC to bypass `--yes`, validation, or duplicate-identifier checks.** Those refusals are intentional. diff --git a/docs/advanced/escape-hatches.md b/docs/advanced/escape-hatches.md deleted file mode 100644 index 7675729..0000000 --- a/docs/advanced/escape-hatches.md +++ /dev/null @@ -1,78 +0,0 @@ ---- -title: Escape hatches -description: When huly-cli doesn't have a flag for what you need — `huly api` and `huly ws` for raw SDK RPCs against your self-hosted Huly workspace. ---- - -# Escape hatches - -When a CLI command doesn't exist for what you need, or the flag you -need isn't exposed, talk to the server directly. Both escape hatches -are pass-through — they don't filter or transform the response. - -## Table of contents - -- [HTTP (`huly api`)](#http-huly-api) -- [WebSocket (`huly ws`)](#websocket-huly-ws) -- [When to use escape hatches](#when-to-use-escape-hatches) - ---- - -## HTTP (`huly api`) - -```bash -huly api GET /api/v1/version -huly api GET /config.json -huly api POST /api/v1/something --body '{"key":"value"}' -huly api GET /api/v1/things --query foo=bar --query baz=qux -huly api GET /api/v1/things --header "Authorization: Bearer ..." -``` - -Available methods: `GET | POST | PUT | PATCH | DELETE`. The path -is appended to the workspace's API URL. - ---- - -## WebSocket (`huly ws`) - -The Huly RPC protocol uses WebSocket for the SDK connection, but the -raw `huly ws` escape hatch is **text JSON only**. Use it for direct -method calls without opening the SDK's binary transport: - -```bash -# findAll -huly ws findAll '[{"_class":"tracker:class:Project"},{}]' - -# tx (raw transaction) -huly ws tx '[{"_class":"core:class:TxCreateDoc",...}]' -``` - -> `huly ws` accepts a single positional `` followed by an -> optional `[params]` argument that is a **JSON-encoded array of -> positional parameters** for that method. On Huly 0.7.x the raw -> socket dispatches a small whitelist: `findAll`, `tx`, `hello`, and -> `ping`. Do not rely on `findOne`, `createDoc`, `updateDoc`, or other -> SDK methods through this escape hatch — use the high-level commands -> for writes, or `tx` for raw transaction payloads. -> -> The `tx` RPC supports every transaction type — `TxCreateDoc`, -> `TxUpdateDoc`, `TxRemoveDoc`, `TxMixin`, `TxApplyIf`. Build the -> payload directly; the CLI doesn't validate. Use this for things -> the CLI doesn't expose (custom mixins, batched transactions, -> advanced markup round-trips). - ---- - -## When to use escape hatches - -- A command exists but doesn't expose the flag you need (rare). -- A command exists but operates on a wrong sub-resource. -- You're doing batch operations and need to skip validation. -- You're debugging and need to see the raw server response. -- The CLI doesn't support the surface you need (use the SDK - instead — see - [Migration — from the SDK](../guides/migration.md#from-the-huly-sdk-typescript)). - -The escape hatches pass through directly; the CLI handles auth and -caching, not transformation. If you find yourself reaching for -`huly ws` often, that's a signal the CLI should expose that surface -natively — file an issue. diff --git a/docs/advanced/server-architecture.md b/docs/advanced/server-architecture.md index c163eed..7ed9303 100644 --- a/docs/advanced/server-architecture.md +++ b/docs/advanced/server-architecture.md @@ -151,7 +151,7 @@ For self-hosted single-pod deployments, use `WS_OPERATION=all+backup`. ## The WebSocket protocol The SDK connection speaks Huly's binary RPC protocol over WebSocket. -The CLI's raw `huly ws` escape hatch is a separate **text-JSON** +The CLI's raw `huly ws` direct SDK access is a separate **text-JSON** channel — the two are different transports to the transactor. Key methods on the binary SDK side: diff --git a/docs/guides/migration.md b/docs/guides/migration.md index b5ca93c..71226fe 100644 --- a/docs/guides/migration.md +++ b/docs/guides/migration.md @@ -136,7 +136,7 @@ error formatting. Prefer the CLI for one-off scripts; prefer the SDK for long-running services. If you need to call a method the CLI doesn't expose, see -[Escape hatches](../advanced/escape-hatches.md) for `huly ws` (raw +[Direct SDK and HTTP access](../advanced/direct-access.md) for `huly ws` (raw WebSocket RPC). --- @@ -156,7 +156,7 @@ huly api GET /api/v1/version The CLI's `api` command passes through to the REST API but handles auth headers automatically. Use it for ad-hoc endpoints the CLI doesn't cover. See -[Escape hatches — HTTP (`huly api`)](../advanced/escape-hatches.md#http-huly-api). +[Direct SDK and HTTP access — HTTP (`huly api`)](../advanced/direct-access.md#http-huly-api). --- diff --git a/docs/reference/environment.md b/docs/reference/environment.md index 7ca998c..13a8757 100644 --- a/docs/reference/environment.md +++ b/docs/reference/environment.md @@ -148,7 +148,7 @@ huly ws findAll '[{"_class":"core.class.Tx"},{"objectId":"","modifiedOn" Each tx carries `modifiedBy`, `modifiedOn`, `space`, `objectId`, and the full operations payload. See -[Escape hatches — WebSocket (`huly ws`)](../advanced/escape-hatches.md#websocket-huly-ws). +[Direct SDK and HTTP access — WebSocket (`huly ws`)](../advanced/direct-access.md#websocket-huly-ws). --- diff --git a/docs/reference/model.md b/docs/reference/model.md index 703c8e5..e4d771a 100644 --- a/docs/reference/model.md +++ b/docs/reference/model.md @@ -7,7 +7,7 @@ description: Huly class IDs and plugin-to-CLI mapping — the canonical referenc Class IDs and plugin-to-CLI mapping. The CLI's canonical class IDs live in `src/transport/identifiers.ts` — that's the reference for -escape-hatch use ([`huly ws findAll ...`](../advanced/escape-hatches.md#websocket-huly-ws)). +direct SDK access use ([`huly ws findAll ...`](../advanced/direct-access.md#websocket-huly-ws)). ## Table of contents @@ -21,7 +21,7 @@ escape-hatch use ([`huly ws findAll ...`](../advanced/escape-hatches.md#websocke ## Class ID reference The platform's class hierarchy. Used as `_class` in JSON, as class -IDs in escape-hatch calls, and as class filters in queries. +IDs in direct SDK access calls, and as class filters in queries. | Plugin | Class ID pattern | Examples | | -------------- | ---------------------- | -------------------------------------------------------------------------------------------------------------------- | diff --git a/skills/huly/SKILL.md b/skills/huly/SKILL.md index c69237e..cf68aa7 100644 --- a/skills/huly/SKILL.md +++ b/skills/huly/SKILL.md @@ -1,6 +1,6 @@ --- name: huly -description: Drive a self-hosted Huly workspace through the `huly` CLI — issues, projects, cards, documents, calendars, channels, DMs, actions/todos, time tracking, notifications, and approvals. Use this skill for project tracking, time management, or anything that required interfacing with Huly. +description: Drive a self-hosted Huly workspace through the `huly` CLI when the user names a Huly-specific entity or action — tracker issue, project, channel, DM, calendar event, planner action/todo, time entry, notification, or approval. Use it for project tracking, time management, and workspace automation against a self-hosted Huly instance. Do not invoke on unconfirmed intent; if the user is ambiguous about whether to persist, ASK first. --- # huly-cli skill @@ -61,21 +61,23 @@ Full env-var cheat sheet, the auth-state machine, and precedence rules live in ` --- -## The 7 rules. Read these first. +## The 8 rules. Read these first. + +0. **ASK before persisting.** If the user's request is ambiguous about whether to write to the workspace, ASK before invoking any `create`, `update`, `delete`, `send`, `message`, or `log` subcommand. The CLI never writes without an explicit subcommand; the agent must not invoke those on unconfirmed intent. Trigger phrases like "save", "capture", "write down", or "document a …" do not by themselves authorize a write. 1. **Verify before you mutate.** Use a read action (e.g. `huly project list --json`, `huly action list --json`) to confirm context, workspace, and project, and to gain the context you need for the task. Run `huly list --json` to discover refs when they aren't given to you explicitly, then `huly issue get --json` (or the surface's `get`) to inspect the target before changing it. NEVER guess a ref, person, or status name. 2. **Use `--json` for every programmatic read.** Tables are for humans. If you're piping, branching, or capturing an `_id`, use `--json` (or equivalently `--ci`). The CLI also auto-enables JSON when `CI=1`. -3. **Prefer Cards over Documents for new knowledge content.** When the user says "create a doc", "write down...", "save this...", default to `huly card create` — UNLESS they explicitly ask for nested hierarchy, versioned snapshots, controlled-document/e-signature workflow, or training. See `references/cards.md` vs `references/documents.md`. +3. **Prefer Cards over Documents for new knowledge content — but only after the user confirms a write is wanted.** Cards are the simpler primitive; offer them first for new knowledge content the user wants to save. UNLESS the user explicitly asks for nested hierarchy, versioned snapshots, controlled-document/e-signature workflow, or training. See `references/cards.md` vs `references/documents.md`. When in doubt about _whether_ to persist, ASK. 4. **The Issue ↔ Action state machine is one machine.** Changing an issue's status or assignee auto-creates/closes `ProjectToDo` records. Completing/scheduling/deleting an `action` (todo) can auto-advance or auto-rollback the parent issue's status. This is the most common silent cascade you will hit. See the diagram below. -5. **Don't use destructive verbs without checking first.** Run `huly get --json` or the surface's `preview-` verb if it has one, then ask the user before proceeding. Bulk deletes (`` with 2+ refs) require `--yes`. +5. **Don't use destructive verbs without checking first.** Run `huly get --json` or the surface's `preview-` verb if it has one, then ask the user before proceeding. Bulk deletes (`` with 2+ refs) require `--yes`. Single-ref deletes, `dm create --person`, `dm send --person`, `dm send`, `action unschedule --slot-id`, and any raw `huly ws removeDoc`/`tx` are **irreversible and proceed without a CLI prompt** — confirm out loud with the user before invoking. 6. **Ask, don't guess, when context is missing.** Workspace name, project identifier, person email, exact ISO timestamp — none of these can be inferred. If the user gave you one but not the others, ask. -7. **Reach for `huly ws` only when the CLI falls short.** The CLI covers ~95% of use. Use `huly ws ` for raw RPC when a flag is missing, when you need fulltext search, or when you need to query the tx audit log. +7. **Reach for `huly ws` / `huly api` only as a last resort.** The CLI covers ~95% of use. These are raw, unvalidated passthroughs — treat them like raw SQL. Use them only for diagnostics (audit log, model inspection) or for fields the high-level command deliberately does not expose (with user sign-off). See `references/direct-sdk-access.md` for the full safety checklist. --- @@ -83,25 +85,25 @@ Full env-var cheat sheet, the auth-state machine, and precedence rules live in ` When the user asks you to do something, pick the right top-level command first. The order below is "from most likely to be correct": -| User intent | Surface | Reference | -| ------------------------------------------------------------- | -------------------------------------------------------- | --------------------------------------------------------------------------------- | -| create / list / update / comment on a tracker item | `huly issue …` | `references/issues-and-todos.md` | -| create / list / update a Planner task / todo | `huly action …` (NOT `huly todo` — that doesn't exist) | `references/issues-and-todos.md` | -| log time on an issue | `huly time …` | `references/issues-and-todos.md` | -| create a project / tracker bucket | `huly project …` | `references/tracker-projects.md` | -| create / update a component, milestone, or issue template | `huly {component,milestone,issue-template} …` | `references/tracker-projects.md` | -| post in a channel; send a DM | `huly {channel,dm} …` | `references/chat-and-collaboration.md` | -| reply to a message thread | `huly thread …` | `references/chat-and-collaboration.md` | -| react, pin, save, view mentions | `huly activity …` | `references/chat-and-collaboration.md` | -| create a CARD (default for "doc"/"page"/"note") | `huly card …` | `references/cards.md` | -| create a DOCUMENT (nested wiki, snapshots, controlled) | `huly document …` | `references/documents.md` | -| create a calendar event (one-off or recurring) | `huly calendar …` | `references/calendar-and-schedule.md` | -| create an owner-availability schedule | `huly schedule …` | `references/calendar-and-schedule.md` | -| create / inspect a workspace, project type, task type, status | `huly {workspace,project-type,task-type,issue-status} …` | `references/spaces-types-and-relations.md` and `references/workspace-and-user.md` | -| create / inspect / reply to an approval request | `huly approval …` | `references/notifications-and-approvals.md` | -| read / mark / subscribe to inbox notifications | `huly notification …` | `references/notifications-and-approvals.md` | -| log in / check identity / look up a user by email | `huly {login,whoami,user} …` | `references/auth-and-setup.md` and `references/workspace-and-user.md` | -| something the CLI doesn't expose | `huly ws …` or `huly api …` | `references/escape-hatches-and-internals.md` | +| User intent | Surface | Reference | +| -------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | +| create / list / update / comment on a tracker item | `huly issue …` | `references/issues-and-todos.md` | +| create / list / update a Planner task / todo | `huly action …` (NOT `huly todo` — that doesn't exist) | `references/issues-and-todos.md` | +| log time on an issue | `huly time …` | `references/issues-and-todos.md` | +| create a project / tracker bucket | `huly project …` | `references/tracker-projects.md` | +| create / update a component, milestone, or issue template | `huly {component,milestone,issue-template} …` | `references/tracker-projects.md` | +| post in a channel; send a DM | `huly {channel,dm} …` | `references/chat-and-collaboration.md` | +| reply to a message thread | `huly thread …` | `references/chat-and-collaboration.md` | +| react, pin, save, view mentions | `huly activity …` | `references/chat-and-collaboration.md` | +| create a CARD (default for new knowledge content — only after the user confirms) | `huly card …` | `references/cards.md` | +| create a DOCUMENT (nested wiki, snapshots, controlled) | `huly document …` | `references/documents.md` | +| create a calendar event (one-off or recurring) | `huly calendar …` | `references/calendar-and-schedule.md` | +| create an owner-availability schedule | `huly schedule …` | `references/calendar-and-schedule.md` | +| create / inspect a workspace, project type, task type, status | `huly {workspace,project-type,task-type,issue-status} …` | `references/spaces-types-and-relations.md` and `references/workspace-and-user.md` | +| create / inspect / reply to an approval request | `huly approval …` | `references/notifications-and-approvals.md` | +| read / mark / subscribe to inbox notifications | `huly notification …` | `references/notifications-and-approvals.md` | +| log in / check identity / look up a user by email | `huly {login,whoami,user} …` | `references/auth-and-setup.md` and `references/workspace-and-user.md` | +| something the CLI genuinely doesn't expose (advanced; confirm with user first) | `huly ws …` or `huly api …` (read-only diagnostics, or state changes the CLI does not expose) | `references/direct-sdk-access.md` | --- @@ -128,16 +130,7 @@ huly --workspace production issue list huly --workspace production issue list --json ``` -There is **no `huly logout` command**. To clear credentials: - -```bash -rm -f ~/.config/huly/credentials.json \ - ~/.config/huly/active-workspace \ - ~/.config/huly/active-account -unset HULY_TOKEN HULY_EMAIL HULY_PASSWORD HULY_WORKSPACE -``` - -All three files are mode 0600. This is intentional — see `references/auth-and-setup.md` for why. +There is **no `huly logout` command**. Clearing credentials means removing the JWT cache (mode 0600, in `$XDG_CONFIG_HOME`-aware `~/.config/huly/`), the active-workspace / active-account pointers, the per-account bootstrap marker, the dotenv file the CLI loaded (`HULY_ENV_FILE` if set, otherwise `$HOME/.config/huly/.env` — the dotenv loader is **not** XDG-aware, so the dotenv path always derives from `$HOME`, even when `XDG_CONFIG_HOME` is set), and unsetting `HULY_TOKEN` / `HULY_EMAIL` / `HULY_PASSWORD` / `HULY_WORKSPACE` in your shell. **This is irreversible** — re-auth requires `huly login` again. See `references/auth-and-setup.md#why-there-is-no-huly-logout-command` for the exact paths and the safety checklist. Full env var cheat sheet and the auth-state machine: `references/auth-and-setup.md`. @@ -171,9 +164,9 @@ When you pass a positional `` (e.g. `huly issue get TSK-1`), the CLI tries **Critical:** the cache is invalidated after every write to the same class. Cross-class writes in the same process may see stale refs — restart the process, or run any write to the changed class to force refresh. -For ref-accepting FLAGS (`--assignee`, `--owner`, `--person`), there's a separate algorithm with one critical asymmetry: **`--assignee` has a substring fallback. `--owner` does NOT.** Always pass the full email or full name to `--owner`. (`--calendar` has its own resolver — see `references/escape-hatches-and-internals.md`.) +For ref-accepting FLAGS (`--assignee`, `--owner`, `--person`), there's a separate algorithm with one critical asymmetry: **`--assignee` has a substring fallback. `--owner` does NOT.** Always pass the full email or full name to `--owner`. (`--calendar` has its own resolver — see `references/direct-sdk-access.md`.) -Full algorithm and edge cases: `references/escape-hatches-and-internals.md`. +Full algorithm and edge cases: `references/direct-sdk-access.md`. --- @@ -274,7 +267,14 @@ These are silently stripped. `defaultProjectIdentifier` is an internal helper op - `workspace delete` (and `--force` if deleting the active workspace) - Any ` delete ` with ≥2 refs -Single-ref deletes proceed without confirmation. `dm create --person`, `dm send --person`, and `action unschedule --slot-id ` are non-destructive in the sense that they don't prompt. +> **The agent should still confirm out loud with the user before invoking ANY of the following** — the CLI does not prompt for them, but they are irreversible: +> +> - Any single-ref ` delete ` (no `--yes` required by the CLI, but a misfire deletes the wrong record). +> - `dm create --person ` and `dm send --person ` — auto-create a new DM doc; no `find-or-create`. A misfire spams a duplicate DM. Run `huly dm list --json` first if duplicates matter. +> - Bare `dm send ` (positional DM ref instead of `--person`) — sends into the existing DM at ``. A misfire delivers the message to the wrong DM (or fails with NotFound). Resolve with `huly dm list --json` or `huly dm get --json` first. +> - `action unschedule --slot-id ` — removes that one WorkSlot from the calendar; the todo itself is unchanged but the calendar entry disappears. +> - Any `huly ws removeDoc` / `huly ws tx` containing `TxRemoveDoc` — there is **no CLI confirmation prompt** on raw RPC. +> - Card `MasterTag` delete via raw RPC — cascades to every Card of that MasterTag. --- @@ -369,25 +369,24 @@ huly ws findAll '["core:class:Tx",{"objectId":""}]' --json \ --- -## When the CLI falls short: escape hatches +## When the CLI doesn't cover what you need: direct SDK and HTTP access -Two escape hatches handle the ~5% the CLI doesn't cover. Use them only when you have to — the CLI handles auth, ref resolution, output formatting, and error mapping for you. +> **Advanced only.** The two commands below bypass every CLI safety check — ref resolution, type checking, cascade awareness, error mapping, and (for destructive calls) confirmation prompts. Treat them like raw SQL. Most workflows do not need them; this section exists so the rare cases are documented correctly. Always confirm with the user out loud before any state-changing `huly ws` or `huly api` call. ```bash -# REST escape hatch +# Raw HTTP passthrough (no validation, no schema check) huly api GET /api/v1/version huly api POST /api/v1/foo --body '{"key":"value"}' -# Raw WebSocket RPC (method names mirror the SDK's PlatformClient interface). -# `huly ws` takes ONE positional [params] arg that must be a JSON-encoded ARRAY — +# Raw WebSocket RPC (no validation, no confirmation, no cascade awareness) +# `[params]` is ONE positional arg that must be a JSON-encoded ARRAY — # multi-positional SDK signatures have to be wrapped, e.g. findAll(classId, query, options). huly ws findAll '["tracker:class:Issue", {"_class":"tracker:class:Issue"}, {}]' -# Space for a project is resolved via getHierarchy().getDomain(CLASS.Project); -# use `huly ws getHierarchy` first if you need the literal id. -huly ws createDoc '["tracker:class:Project", "", {"identifier":"NEW","name":"New"}]' +# Read the full safety checklist before invoking state-changing raw RPC: +# references/direct-sdk-access.md ``` -Full list of methods, timeouts, and chunks handling: `references/escape-hatches-and-internals.md`. +Use them only for read-only diagnostics (audit log, model inspection, permission matrix) or for state changes the high-level CLI deliberately does not expose (with user sign-off). **Never use them to bypass `--yes`, pre-validation, or duplicate-identifier checks.** --- @@ -400,12 +399,12 @@ If the task is about a specific surface, load the matching reference file **befo - Issues, actions/todos, comments, time, the state machine → `references/issues-and-todos.md` - Projects, components, milestones, issue templates → `references/tracker-projects.md` - Channels, DMs, threads, activity (reactions/pins/saved) → `references/chat-and-collaboration.md` -- Cards (preferred for new content) → `references/cards.md` +- Cards (offered for new content the user wants to save) → `references/cards.md` - Documents (only when nested/snapshots/controlled needed) → `references/documents.md` - Calendar events, recurring events, schedules → `references/calendar-and-schedule.md` - Spaces, relations, project types, task types, statuses → `references/spaces-types-and-relations.md` - Notifications inbox, approval requests → `references/notifications-and-approvals.md` -- `huly ws` / `huly api` escape hatches, ref resolver internals, error codes, caches → `references/escape-hatches-and-internals.md` +- `huly ws` / `huly api` direct SDK access, ref resolver internals, error codes, caches → `references/direct-sdk-access.md` --- diff --git a/skills/huly/references/auth-and-setup.md b/skills/huly/references/auth-and-setup.md index 2f4e212..39e48cc 100644 --- a/skills/huly/references/auth-and-setup.md +++ b/skills/huly/references/auth-and-setup.md @@ -110,16 +110,45 @@ Two sibling files: ## Why there is NO `huly logout` command -Intentionally. The CLI is designed for long automation runs. An accidental `logout` mid-automation would force every subsequent command to re-`selectWorkspace` for every workspace, breaking idempotency. The only way to "log out" is manual: +Intentionally. The CLI is designed for long automation runs. An accidental `logout` mid-automation would force every subsequent command to re-`selectWorkspace` for every workspace, breaking idempotency. The only way to "log out" is manual. + +> **Manual cleanup (advanced).** You are about to delete local credentials and unset authentication env vars. This is irreversible until you `huly login` again. Confirm with the user before running this in any context other than your own machine. +> +> The procedure below is the **complete** reset: it clears the on-disk JWT cache (XDG-aware), the active-workspace / active-account pointers, the per-account bootstrap marker, the dotenv file the CLI loaded (`HULY_ENV_FILE` if set, else `$HOME/.config/huly/.env`), and unsets the auth env vars in the current shell. A partial reset that leaves the dotenv file intact will allow a later process to re-authenticate from that file. +> +> **Dotenv path note:** the dotenv loader (`packages/cli/src/auth/env.ts:23`) is **not** XDG-aware — its default is `$HOME/.config/huly/.env`, even when `XDG_CONFIG_HOME` is set. The cached credential files (`credentials.json`, `active-workspace`, `active-account`, `bootstrap.json`) **are** XDG-aware (`configDir()` honors `$XDG_CONFIG_HOME`). The reset handles both paths correctly. ```bash -rm -f ~/.config/huly/credentials.json \ - ~/.config/huly/active-workspace \ - ~/.config/huly/active-account +# Advanced — manual credentials reset. +# Deletes the on-disk token cache (XDG-aware, mode 0600), the active-workspace +# pointer, the per-account bootstrap marker, the dotenv file the CLI loaded +# (note: dotenv loader is NOT XDG-aware — default is $HOME/.config/huly/.env), +# and unsets the auth env vars in the current shell. +config_dir="${XDG_CONFIG_HOME:-$HOME/.config}/huly" +env_file="${HULY_ENV_FILE:-$HOME/.config/huly/.env}" +rm -f "$config_dir/credentials.json" \ + "$config_dir/active-workspace" \ + "$config_dir/active-account" \ + "$config_dir/bootstrap.json" \ + "$env_file" unset HULY_TOKEN HULY_EMAIL HULY_PASSWORD HULY_WORKSPACE ``` -Then `huly login --headless` to re-auth. +Then re-export the auth inputs the reset just unset, and re-authenticate. `--headless` reads ONLY env vars and will fail if `HULY_URL` / `HULY_EMAIL` / `HULY_PASSWORD` are missing: + +```bash +# Re-export the auth inputs the reset just unset. +export HULY_URL=https://huly.example.com +export HULY_EMAIL=you@example.com +export HULY_PASSWORD=… # or: export HULY_TOKEN=eyJ… + +# Then re-auth. +huly login --headless +``` + +(Or skip the env setup and run interactive `huly login`.) + +If you want a partial reset (only clear one host / email pair, keep the others), edit `credentials.json` by hand — the file is keyed by host then email — and keep the rest of the cache intact. The dotenv file and env vars above are NOT cleared in that case. --- diff --git a/skills/huly/references/calendar-and-schedule.md b/skills/huly/references/calendar-and-schedule.md index 6a45dd6..c1f8acb 100644 --- a/skills/huly/references/calendar-and-schedule.md +++ b/skills/huly/references/calendar-and-schedule.md @@ -101,7 +101,12 @@ huly calendar update --location "Room 5" huly calendar update --attendee bob@example.com ``` -**Note:** the CLI does NOT expose `--rrule`, `--calendar-id`, `--time-zone`, or `--visibility` as update flags. To change those, use `huly ws updateDoc`. +**Note:** the CLI does NOT expose `--rrule`, `--calendar-id`, `--time-zone`, or `--visibility` as update flags. **These fields are only settable via raw RPC.** Confirm with the user out loud before invoking `huly ws updateDoc`; the recipe is the only path but bypasses CLI safety checks. + +```bash +# Advanced — confirm with user first +huly ws updateDoc '["calendar:class:Event", "", "", {"$set":{"visibility":"freeBusy"}}]' +``` `--start` update writes BOTH `date` AND `startDate` (a bug fix; older versions only wrote `startDate`). @@ -210,7 +215,7 @@ The platform maps `visibility ↔ Google transparency`: - Google `transparency:transparent` ↔ Huly `visibility:freeBusy` - Huly `private` ↔ Google `private` -The CLI sets `visibility: 'public'` by default. To set `freeBusy` or `private`, use `huly ws updateDoc '["calendar:class:Event", "", "", {"$set":{"visibility":"freeBusy"}}]'`. +The CLI sets `visibility: 'public'` by default. **To set `freeBusy` or `private`, only raw RPC works** (`huly ws updateDoc`); confirm with the user first — see the `calendar update` notes above. --- @@ -231,7 +236,8 @@ huly calendar create \ If the user wants this to block their calendar: ```bash -# CLI sets blockTime:false by default; flip via raw update +# CLI sets blockTime:false by default; flip via raw update. +# Advanced — confirm with the user first; raw RPC bypasses CLI safety checks. huly ws updateDoc '["calendar:class:Event", "calendar:space:Calendar", "", {"$set":{"blockTime":true}}]' ``` @@ -252,9 +258,11 @@ huly calendar recurring-instances --json | jq 'length' ### "Skip next Monday's standup" -There is no CLI surface for this. EXDATE is silently ignored. Options: +> **Advanced — confirm with the user before invoking.** There is no CLI surface for this. EXDATE is silently ignored. The raw-RPC fallback below is irreversible and bypasses CLI safety checks. + +Options: -- Delete the one instance via `huly ws findAll '["calendar:class:ReccuringInstance",{"recurringEventId":"","originalStartTime":}]'` and `huly ws tx` with a `removeDoc`. +- Delete the one instance via `huly ws findAll '["calendar:class:ReccuringInstance",{"recurringEventId":"","originalStartTime":}]'` and `huly ws tx` with a `removeDoc`. **Confirm with the user out loud before invoking; there is no CLI confirmation prompt on raw RPC.** - Or accept that all instances will exist. ### "Show me everything happening today" diff --git a/skills/huly/references/cards.md b/skills/huly/references/cards.md index a77f62e..aeb010f 100644 --- a/skills/huly/references/cards.md +++ b/skills/huly/references/cards.md @@ -1,6 +1,8 @@ -# Cards — the default for new knowledge content +# Cards — the default for new knowledge content (after user confirmation) -Cards are the knowledge primitive you should reach for FIRST when the user asks to "save", "capture", "write down", "document a …", or create structured records. They organize by MasterTag (a Type/tag with custom attributes), not by Teamspace. +Cards are one of the two knowledge primitives in Huly. Offer them first when the user has explicitly asked to persist structured, record-like knowledge. They organize by MasterTag (a Type/tag with custom attributes), not by Teamspace. + +> **Do not auto-persist on ambiguous prompts.** Phrases like "save", "capture", "write this down", or "document a …" do not by themselves authorize a write. If the user has not confirmed they want content stored in the workspace, ASK before invoking `huly card create`. The same applies to any other `create` / `update` / `delete` subcommand. --- @@ -15,7 +17,9 @@ Cards are the knowledge primitive you should reach for FIRST when the user asks | The user said "doc"/"page" without further specification | The user mentioned "ControlledDocument", e-signatures, or training | | You want kanban-style by Type/Tag | You want sidebar-by-teamspace organization | -When in doubt, USE CARDS. The CLI surfaces card creation more conveniently, custom attributes are a major capability, and you can always migrate to Documents later if the structure demands it. +If the user said "create a document" or "make a wiki page" → use Documents. + +When in doubt about _whether_ to persist, ASK. When the user has confirmed a write is wanted and you have to pick a primitive, prefer Cards: the CLI surfaces card creation more conveniently, custom attributes are a major capability, and you can always migrate to Documents later if the structure demands it. --- @@ -102,18 +106,26 @@ each edit. Storage grows by one snapshot per edit, but no longer two. ### Reparent and move -The CLI's `huly card update` does NOT accept `--parent`. There is no `card move` command. To reparent a card, do it via `huly ws updateDoc` or via the web UI. +> **Advanced — confirm with the user before invoking.** The CLI's `huly card update` does NOT accept `--parent`. There is no `card move` command. The raw-RPC recipe below moves the card to the target CardSpace's root (it sets `parent: null` — it does NOT attach a target parent). For true reparenting (preserving a parent) use the web UI drag-and-drop, which is the safe path. Run `huly card get --json` first to confirm the current parent and target space, then ask the user to confirm before continuing. + +```bash +huly ws updateDoc '["card:class:Card", "", "", {"$set":{"space":"","parent":null}}]' +``` Cycle detection is server-side: parent walks up; the tx is rolled back on cycle. ### Delete +> **Single-ref card delete is irreversible.** The CLI does not prompt; the agent must confirm out loud with the user before invoking. Cascade-deletes any sub-cards via server mixin. + ```bash -huly card delete # single, no --yes +huly card delete # single, no --yes; CONFIRM WITH USER FIRST huly card delete --yes # bulk, REQUIRED --yes ``` -A 100ms sleep between deletes throttles the server. +A 100 ms sleep between deletes throttles the server. + +**Cascade-on-delete:** deleting a `MasterTag` (only possible via raw `huly ws removeDoc`; there is no `master-tag delete` on the CLI) cascade-deletes every Card of that MasterTag. **There is no CLI confirmation prompt on raw RPC.** Confirm with the user out loud before invoking any raw-RPC MasterTag deletion. --- @@ -156,7 +168,7 @@ This is the master-tag `OnCardTag` mixin. The CLI never directly modifies the Ma ### Deleting a Card Type/MasterTag cascade-deletes all cards of that type -Cannot be undone. There is no `master-tag delete` in the CLI — but if you were to reach for `huly ws removeDoc '["card:class:MasterTag", "", ""]'`, the platform cascade-deletes every card of that type. There is no confirmation prompt via raw RPC. +> **Irreversible; raw-RPC only.** There is no `master-tag delete` in the CLI. The only path is `huly ws removeDoc '["card:class:MasterTag", "", ""]'`, which bypasses every CLI safety check and has no CLI confirmation prompt. The platform cascade-deletes every Card of that MasterTag. **Always confirm out loud with the user before invoking — and prefer the web UI for this operation.** ### File Type is undeletable; uploads are permanent @@ -187,7 +199,7 @@ Public = workspace-wide. Private = only you. No CLI exposure; this is a web UI f ## Common task recipes -### "Save this to the workspace" +### "Save this to the workspace" (after the user has confirmed) ```bash # 1. Verify a card-space + master-tag exist @@ -215,11 +227,17 @@ huly card update --body "…full new body…" ### "Move this card to another space" -The CLI cannot. Tell the user: +> **Advanced — confirm with the user before invoking.** Reparenting a card between CardSpaces is not exposed in the CLI. The web UI drag-and-drop is the safe path. The raw-RPC fallback below moves the card to the new space's root (sets `parent: null`); it does not attach a target parent. To attach a specific parent, do it via the web UI. + +Tell the user: > "Reparenting a card between CardSpaces isn't exposed in the CLI. Open the card in the web UI and drag it to the new space." -Or use `huly ws updateDoc '["card:class:Card", "", "", {"$set":{"space":"","parent":null}}]'`. +Or, with explicit user confirmation, use raw RPC for a root move: + +```bash +huly ws updateDoc '["card:class:Card", "", "", {"$set":{"space":"","parent":null}}]' +``` ### Build a card from structured user input @@ -240,7 +258,7 @@ $FREEFORM" ## Gotchas -- **MasterTag creation requires the web UI.** Don't try `huly ws createDoc '["card:class:MasterTag", "", {...}]'` unless you're prepared to set the attribute schema manually. +- **MasterTag creation requires the web UI.** Don't try `huly ws createDoc '["card:class:MasterTag", "", {...}]'` unless you're prepared to set the attribute schema manually — and even then, confirm with the user first. - **Default CardSpace `card:space:Default` likely doesn't exist.** Pass `--card-space ` explicitly. - **Attribute changes on one card propagate to ALL cards of the MasterTag.** This is intended platform behavior; warn users before they think they're "just adding a field to this one card". - **`--description` on update is a guard.** It changes the card's short summary field, not the body. If you mean "set the body", use `--body` or `--replace-content`. diff --git a/skills/huly/references/escape-hatches-and-internals.md b/skills/huly/references/direct-sdk-access.md similarity index 72% rename from skills/huly/references/escape-hatches-and-internals.md rename to skills/huly/references/direct-sdk-access.md index d4dd709..14788f4 100644 --- a/skills/huly/references/escape-hatches-and-internals.md +++ b/skills/huly/references/direct-sdk-access.md @@ -1,23 +1,50 @@ -# Escape hatches & internals — when the CLI falls short +# Direct SDK and HTTP access (advanced) -This reference covers: `huly api` (HTTP), `huly ws` (raw WebSocket RPC), and the internal mechanics of ref resolution, output formatting, error codes, and caches. You usually don't need this for everyday work — load it when a command is missing a flag, when you need fulltext search, when you're debugging, or when the CLI's behavior surprises you. +> **This file is for advanced use only.** The high-level CLI surface (`huly issue …`, `huly document …`, `huly calendar …`, …) handles authentication, ref resolution, cascade awareness, type checking, and error mapping. Two commands bypass all of that and talk to the server directly: +> +> - **`huly api `** — raw HTTP passthrough. Any path on the configured workspace API URL, any supported method (`GET | POST | PUT | PATCH | DELETE`), any header (except `Authorization`, which the CLI always overwrites with the resolved token — see below). No validation, no schema check, no ref resolution. +> - **`huly ws [params]`** — raw WebSocket RPC. Calls SDK methods directly with whatever payload you hand it. No validation, no schema check, no confirmation, no cascade awareness. +> +> Treat these like raw SQL: powerful, untyped, unguarded, and irreversible. Most workflows do not need them — prefer the high-level commands. If you find yourself reaching for these often for a pattern the CLI should expose, that's a missing-feature signal: file an issue. + +This reference also covers the internals of ref resolution, output formatting, error codes, and caches — load it when a high-level command surprises you, when a flag is missing, or when you need fulltext search / the tx audit log. + +--- + +## Before you reach for `huly api` or `huly ws` + +Ask yourself in order: + +1. **Is there a high-level command for this?** Run `huly --help` and skim. The CLI covers ~95% of common workflows. +2. **Is there a `--set key=value` escape on the existing command?** Many `update` verbs accept arbitrary attribute writes via `--set`, which still gets the CLI's ref resolution and error mapping. +3. **Is the operation a one-off diagnostic** (read the model, query the tx audit log, inspect a permission matrix)? Raw RPC is acceptable for diagnostics — there's no persistent side effect. +4. **Is the operation a state change the CLI does not expose at all** (e.g. setting `blockTime` on a calendar event, unarchiving a document, reparenting a card)? Raw RPC is the only path; the recipe is documented here so you do it correctly. **Confirm with the user out loud before invoking.** +5. **Is the operation something the CLI deliberately refuses for safety** (bypassing the duplicate-identifier pre-check, bulk-deleting via raw `removeDoc`, skipping `--yes`)? **Stop. File a feature request, or use the high-level command with `--yes`.** The CLI's refusal is intentional. + +The remaining sections of this file assume you've answered "yes, raw RPC is the only path" and have user confirmation. --- -## `huly api ` — REST escape hatch +## `huly api ` — raw HTTP passthrough -Plain HTTP passthrough to the workspace API URL. Auth header is auto-attached from the resolved token. +Plain HTTP passthrough to the workspace API URL. The auth header is auto-attached from the resolved token. **The CLI does not validate the path, the method, the body, or any custom headers** — anything you send goes straight to the server. ```bash +# Read-only diagnostics (low risk) huly api GET /api/v1/version huly api GET /config.json + +# State-changing calls (high risk — confirm with the user first) huly api POST /api/v1/things --body '{"key":"value"}' -huly api GET /api/v1/things --query foo=bar --query baz=qux + +# Custom headers (the CLI will pass them through verbatim) huly api GET /api/v1/private --header "Authorization: Bearer …" ``` Methods: `GET | POST | PUT | PATCH | DELETE`. Query params and headers accept repeated `k=v`. +> **`Authorization` is not overridable.** The CLI sets `Authorization: Bearer ` after merging your custom headers (`packages/cli/src/raw/api.ts:43-49`), so passing `--header "Authorization: Bearer …"` has no effect — your custom value is silently replaced. All other custom headers pass through verbatim. + Status codes map to exit codes: - `2xx` → `Ok` @@ -26,17 +53,23 @@ Status codes map to exit codes: - `4xx` → `Validation` (4) or `Conflict` (6) - `5xx` → `Server` (7) -Use this when: +**Use this only when:** -- You want to hit an undocumented endpoint -- The CLI command exists but doesn't expose a flag you need (rare) -- You're hitting a custom plugin route +- You want to hit a specific endpoint for a diagnostic (read-only). +- The high-level command exists but doesn't expose the field you need, AND there is no `--set key=value` workaround. (See per-surface reference files.) +- You're debugging a custom plugin route and need to see the raw response. + +**Do NOT use this for:** + +- Anything that the high-level command handles with proper validation. The high-level command exists for a reason — re-implementing it via `huly api` loses ref resolution, cascade awareness, and error mapping. --- -## `huly ws [params]` — WebSocket RPC escape hatch +## `huly ws [params]` — raw WebSocket RPC + +Speaks Huly's RPC directly. Method names mirror the SDK's `PlatformClient` interface. **The CLI does not validate the method name, the params shape, or any side effects.** Whatever you send is dispatched verbatim. -Speaks Huly's binary RPC directly. Method names mirror the SDK's `PlatformClient` interface. +> **Method availability depends on the server version.** Newer Huly servers expose a richer method whitelist (`findAll`, `findOne`, `queryAll`, `createDoc`, `updateDoc`, `removeDoc`, `tx`, `getModel`, `getHierarchy`, …). Older servers may only allow `findAll`, `tx`, `hello`, and `ping`. If a method is rejected by the server, that is a server-side restriction, not a CLI bug — fall back to `tx` with an explicit transaction payload, or use `huly api` for HTTP-only operations. **`[params]` is ONE positional argument that must be a JSON-encoded array.** SDK methods with multiple positional parameters (e.g. `findAll(classId, query, options)`) must be wrapped into a single array — passing them as separate CLI positional args will fail with "too many arguments". @@ -79,13 +112,17 @@ huly ws findAll '["core:class:Tx", {"objectId":""}]' --json \ - 5s ping interval (disable with `--no-ping`) - Default chunked responses are buffered and flushed as JSON arrays -**When to reach for this:** +**Use this only when:** + +- You need a diagnostic read (fulltext search with ES query operators, audit-trail queries against `core:class:Tx`, inspecting the `Space` permission matrix). +- You need to set a field that the CLI does not expose and there is no `--set key=value` workaround (calendar `blockTime` / `visibility`, document unarchive, card reparenting, etc.). Confirm with the user first. +- You need to manually construct a transaction the CLI doesn't build for you (custom mixins, batched transactions). + +**Do NOT use this for:** -- Fulltext search with ES query string operators (`AND`, `OR`, `+`, `-`, `"…"`, `field:value`) -- Audit-trail queries (`core:class:Tx`) -- Bulk operations where you want to skip CLI validation -- Plugin methods the CLI doesn't expose -- Reading the `Space` permission matrix directly +- Bulk-deleting records to "skip CLI validation." The CLI's `--yes` guard exists for a reason. The CLI's pre-checks (duplicate identifier, validation, dry-run) exist for a reason. +- Bypassing the duplicate-identifier pre-check on `project create`. The CLI pre-check is a defense-in-depth against an identifier collision on self-hosted servers; bypassing it produces two projects with the same identifier and breaks ref resolution. +- Anything the user has not explicitly asked you to do. --- @@ -218,6 +255,8 @@ Used by `huly project update`, `huly issue update`, etc.: `defaultProjectIdentifier` is the internal helper used by `--project TSK-5` ref resolution; `set` / `unset` only exist on `update`. +> **Prefer `--set` over raw `huly ws updateDoc`** whenever the high-level command accepts `--set`. You get the CLI's ref resolution, error mapping, and confirmation flow for free. Reach for `huly ws updateDoc` only when the field is not exposed on the high-level command's flags. + --- ## Filtering & matching semantics (cheat sheet) @@ -249,7 +288,7 @@ The CLI deliberately bypasses the SDK's `MarkupContent` upload. Every body field - `huly document get --markdown` round-trips cleanly on CLI-created docs (returns your literal markdown). - For web-UI-created docs, `--markdown` calls `fetchMarkup` with a 5s timeout. On timeout, you get the raw markup-ref string (e.g. `markup:abc123…`). -If you need collaborative editing features (mentions as actual nodes, embeds), the CLI will NOT preserve them. Use `huly ws tx` with a manually-constructed `MarkupContent` instead. +If you need collaborative editing features (mentions as actual nodes, embeds), the CLI will NOT preserve them. Construct a `MarkupContent` transaction via `huly ws tx` — this is the legitimate advanced path, not a workaround. --- @@ -261,12 +300,12 @@ If you need collaborative editing features (mentions as actual nodes, embeds), t - `workspace delete` (plus `--force` for the active workspace) - ANY `delete ` with ≥2 refs -NOT required for: +NOT required for (the CLI does not prompt, but **the agent should still confirm with the user before invoking**): -- `dm create --person` (auto-creates) -- `dm send --person` (auto-creates) -- `action unschedule` with a single `--slot-id` -- All single-ref deletes +- `dm create --person` — auto-creates a new DM doc; no `find-or-create`. A misfire spams a duplicate DM. +- `dm send --person` — auto-creates a new DM doc; no `find-or-create`. +- `action unschedule` with a single `--slot-id` — removes that one WorkSlot from the calendar; the todo itself is unchanged. +- All single-ref deletes — irreversible. A misfire deletes the wrong record. A 100ms sleep between consecutive deletes throttles the server tx stream during bulk operations. @@ -308,9 +347,11 @@ If you see "Model version mismatch", the workspace was upgraded under you. Refre --- -## Common recipes using escape hatches +## Common recipes (advanced; confirm with user first) + +The recipes below use raw RPC because the high-level CLI does not expose the operation. **Run them only after the user has confirmed the target, the field, and the value.** -### Audit who changed a doc +### Audit who changed a doc (read-only — generally safe) ```bash huly ws findAll '["core:class:Tx",{"objectId":"","modifiedOn":{"$gte":,"$lte":}}]' \ @@ -318,7 +359,7 @@ huly ws findAll '["core:class:Tx",{"objectId":"","modifiedOn":{"$gte": --json ``` -### Get the raw model +### Get the raw model (read-only — generally safe) ```bash huly ws getModel --json | jq '.classes | length' ``` -### Trigger a reindex (rare; usually the server self-heals) +### Trigger a reindex (rare; usually the server self-heals; confirm before running) ```bash huly ws tx '[{"method":"triggerReindex","params":[]}]' ``` + +### Set a field the high-level command does not expose (state-changing — ALWAYS confirm) + +Examples include `calendar:blockTime`, `calendar:visibility` set to `freeBusy`/`private`, `document:archived` cleared, `card:parent` reparented, `calendar:ReccuringInstance` removed. Each is a deliberate CLI gap; raw RPC is the only path. Always read the doc first (`huly ws findOne` or the high-level `get`), confirm the change, then write. diff --git a/skills/huly/references/documents.md b/skills/huly/references/documents.md index 6cc9c9e..2544cbe 100644 --- a/skills/huly/references/documents.md +++ b/skills/huly/references/documents.md @@ -97,11 +97,11 @@ huly document update --archived # archives (cannot unar - `--old-text` appears ≥ 2 times without `--replace-all` → `Ambiguous: N occurrences of --old-text — pass --replace-all`. - `--old-text` appears ≥ 2 times with `--replace-all` → replaces all. -**`--archived` flag:** presence = true. There is no value (the CLI currently exposes only archive, not unarchive, via this flag). To unarchive, use `huly ws updateDoc`. +**`--archived` flag:** presence = true. There is no value (the CLI currently exposes only archive, not unarchive, via this flag). **Unarchive is only possible via raw RPC** (`huly ws updateDoc` to clear `archived`); the recipe below is the only path, but it bypasses CLI safety checks. Confirm with the user out loud before invoking. ### Move / reparent -The CLI's `huly document update` does NOT accept `--parent`. To reparent: +> **Advanced — confirm with the user before invoking.** The CLI's `huly document update` does NOT accept `--parent`. Reparenting requires raw RPC; the recipe below is the only path. ```bash huly ws updateDoc '["document:class:Document", "", "", {"$set":{"parent":""}}]' @@ -243,7 +243,7 @@ huly document update --old-text "acme.com" --new-text "acme.io" --replace- huly document list --json | jq -r '.[] | select(.space == null) | ._id' ``` -Then reparent via `huly ws updateDoc`. +Then reparent via `huly ws updateDoc` (advanced; confirm with the user first — see "Move / reparent" above). ### Snapshot history of a doc @@ -268,8 +268,8 @@ huly ws findAll '["core:class:Tx",{"objectId":""}]' --json \ ## Gotchas -- **`--archived` flag value:** there is no `--archived false`. Pass `--archived` (presence = true) to archive, or use `huly ws updateDoc` to clear. The CLI help text doesn't mention this asymmetry. -- **`document update` cannot `--parent`.** Reparent via `huly ws`. +- **`--archived` flag value:** there is no `--archived false`. Pass `--archived` (presence = true) to archive. **Unarchive is only available via raw `huly ws updateDoc` to clear `archived`**; confirm with the user first. The CLI help text doesn't mention this asymmetry. +- **`document update` cannot `--parent`.** Reparent only via `huly ws`; confirm with the user first (see "Move / reparent" above). - **`document update` `--body` vs `--old-text`/`--new-text`** are mutually exclusive. Pick one strategy. - **Inline comments cannot be created, replied to, or resolved via the CLI.** Only listed. - **Resolving an inline thread DELETES all replies.** Don't write "important" content there. diff --git a/skills/huly/references/spaces-types-and-relations.md b/skills/huly/references/spaces-types-and-relations.md index d21f880..46c9d42 100644 --- a/skills/huly/references/spaces-types-and-relations.md +++ b/skills/huly/references/spaces-types-and-relations.md @@ -287,7 +287,7 @@ huly project get TSK --json | jq -r '._id' \ - **`Issue.relations`** is a special-case field on Issue itself, NOT the same as `core:class:Relation`. Don't confuse them. - **`huly space permissions ` is read-only.** No CLI command to add/remove permissions. The server manages this automatically on member changes. - **Per-space permission checks happen on EVERY TxCUD** — there is no global role override. Granting rights is per-space. -- **Disabling RBAC**: Settings → General → disable RBAC for the whole workspace. The CLI doesn't expose this; it's a server-side setting. Useful for test scripting; do NOT leave enabled in production. +- **Disabling RBAC** is a global, workspace-wide privilege escalation. The CLI does not expose the toggle (server-side only: Settings → General → disable RBAC for the whole workspace). Anyone with any account on the workspace gains full read/write access to every space, document, issue, and channel. **Never disable RBAC on a workspace that contains real customer data.** Re-enabling RBAC does not retroactively restore previous ACLs — access changes made while RBAC was off persist. There is no undo. Prefer per-space permission grants via the web UI instead. This is a one-way door. --- diff --git a/skills/huly/references/tracker-projects.md b/skills/huly/references/tracker-projects.md index a3b77c7..700a5ca 100644 --- a/skills/huly/references/tracker-projects.md +++ b/skills/huly/references/tracker-projects.md @@ -209,7 +209,7 @@ huly ws findAll '["core:class:Tx",{"space":"","modifiedOn":{"$ ## Gotchas -- **Identifier uniqueness:** the CLI pre-checks but the server doesn't (on selfhost). If you bypass the CLI and POST a duplicate identifier via `huly ws createDoc`, you'll get two projects with the same identifier — the resolver will pick the first one alphabetically. +- **Identifier uniqueness:** the CLI pre-checks but the server doesn't (on selfhost). If you bypass the CLI and POST a duplicate identifier via `huly ws createDoc`, you'll get two projects with the same identifier. For exact `_id` references the resolver returns the literal ref directly; for any other ref, `buildIndex` overwrites duplicate keys during `findAll` processing, so non-`_id` references resolve to the **last** project returned (not the first alphabetically). Either way, the web UI and CLI silently disagree, and ref resolution becomes nondeterministic. **Do not bypass the pre-check.** - **`project delete` has NO preview**, unlike `issue preview-delete`. Inspect with `huly project get --json` first and confirm counts. - **`component delete`** is reversible-ish: orphans get `component: null` (detached, not deleted). You can manually reassign by listing and updating. - **`milestone --status`** stores raw strings. The CLI doesn't enforce a state machine; the platform may reject invalid statuses at update time. Verify with `huly milestone get --json` if unsure. @@ -224,11 +224,62 @@ huly ws findAll '["core:class:Tx",{"space":"","modifiedOn":{"$ ## Migration: copying issues between projects (the SDK has no cross-project move) +> **This is a destructive, multi-step migration. Confirm with the user out loud before each phase.** Resolve the source project's space `_id`, snapshot the source tx audit log, then read-validate every source issue, then capture the pre-copy destination count, then run `--dry-run` on the first copy and STOP for explicit user confirmation, then (only after the user re-confirms) copy for real, then verify the destination delta equals the source count, then (only after a final re-confirm) delete the originals. Stop on any count mismatch. + ```bash -set -e -SOURCE=Q3-2025 +set -euo pipefail +SOURCE=Q3-2025 # project identifier (e.g. Q3-2025), NOT the space _id DEST=Q3-2026 + +# Phase 1 — resolve the source project's actual space _id. The CLI's high-level +# commands resolve identifier -> _id; raw `huly ws` does not, so we must +# resolve FIRST and pass the literal space _id to the snapshot. +SOURCE_SPACE=$(huly project get "$SOURCE" --json | jq -r '._id') +if [ -z "$SOURCE_SPACE" ] || [ "$SOURCE_SPACE" = "null" ]; then + echo "Could not resolve space _id for $SOURCE" >&2 + exit 1 +fi +SINCE_MS=$(date -u -d '-1 hour' +%s)000 + +# Phase 2 — snapshot the source tx audit log so the migration is reversible in +# forensics if not in data. Filter by the resolved space _id, not the identifier. +huly ws findAll '["core:class:Tx",{"space":"'"$SOURCE_SPACE"'","modifiedOn":{"$gte":'"$SINCE_MS"'}}]' \ + --json > "/tmp/${SOURCE}-tx-snapshot-$(date -u +%Y%m%dT%H%M%SZ).json" + +# Phase 3 — capture source IDs and read-validate every issue (no writes yet). IDS=$(huly issue list --project "$SOURCE" --json | jq -r '.[]._id') +if [ -z "$IDS" ]; then + echo "No issues in $SOURCE — nothing to migrate." >&2 + exit 0 +fi +SOURCE_COUNT=$(printf '%s\n' "$IDS" | wc -l | tr -d ' ') +echo "About to copy $SOURCE_COUNT issues from $SOURCE (space $SOURCE_SPACE) to $DEST" >&2 +for id in $IDS; do huly issue get "$id" --json >/dev/null; done + +# Phase 4 — capture the destination count BEFORE copying, so Phase 8 can +# verify the delta (not the total). +DEST_BEFORE=$(huly issue list --project "$DEST" --json | jq 'length') + +# Phase 5 — dry-run the first issue (--dry-run prints the would-be tx JSON, +# makes no server writes). Inspect the output, then STOP. +FIRST_ID=$(printf '%s\n' "$IDS" | head -n1) +issue=$(huly issue get "$FIRST_ID" --json) +title=$(jq -r .title <<<"$issue") +prio=$(jq -r .priority <<<"$issue") +asg=$(jq -r '.assignee // empty' <<<"$issue") +huly issue create --project "$DEST" --title "$title" \ + --priority "$prio" \ + ${asg:+--assignee "$asg"} --yes --dry-run + +# Phase 6 — CONFIRMATION GATE. Do NOT proceed past this point without an +# explicit "yes, run the real copy" from the user. +read -r -p "Dry-run looks right? Type 'yes' to run the real copy (anything else aborts): " CONFIRM +if [ "$CONFIRM" != "yes" ]; then + echo "Aborted before any write. No issues copied." >&2 + exit 1 +fi + +# Phase 7 — run the real copy. for id in $IDS; do issue=$(huly issue get "$id" --json) title=$(jq -r .title <<<"$issue") @@ -238,8 +289,24 @@ for id in $IDS; do --priority "$prio" \ ${asg:+--assignee "$asg"} --yes done -# Delete originals after verifying copies: + +# Phase 8 — verify the DESTINATION DELTA equals SOURCE_COUNT, not the total. +DEST_AFTER=$(huly issue list --project "$DEST" --json | jq 'length') +DEST_DELTA=$((DEST_AFTER - DEST_BEFORE)) +if [ "$DEST_DELTA" -ne "$SOURCE_COUNT" ]; then + echo "Count mismatch: source=$SOURCE_COUNT dest-delta=$DEST_DELTA (before=$DEST_BEFORE after=$DEST_AFTER) — DO NOT delete originals." >&2 + exit 1 +fi + +# Phase 9 — only after the user has re-confirmed, delete originals. +read -r -p "Copies verified ($DEST_DELTA == $SOURCE_COUNT). Type 'yes' to delete originals: " CONFIRM2 +if [ "$CONFIRM2" != "yes" ]; then + echo "Copies exist; originals left untouched." >&2 + exit 0 +fi # for id in $IDS; do huly issue delete "$id" --yes; done ``` Comments, time entries, sub-issues, and labels do NOT carry over with this recipe. For richer migration, write a script that fetches each issue with `comments` / `subIssues` / `labels` and reconstructs them. + +**Do NOT bypass the CLI's duplicate-identifier pre-check via `huly ws createDoc` to create a project.** The pre-check is a defense-in-depth guard against identifier collisions on self-hosted servers (the platform does not enforce uniqueness); bypassing it produces two projects with the same identifier and breaks ref resolution for every subsequent command. Use `huly project create` and let the pre-check return the existing `_id` on duplicates.