Skip to content
Merged
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
30 changes: 30 additions & 0 deletions cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@ When `subtext live signal` returns `operator=human`, the session has been handed
| `comment` | passthrough | Add, list, reply to, and resolve session replay comments |
| `doc` | passthrough | Create and manage proof documents |
| `artifact` | passthrough | Upload and retrieve file artifacts |
| `privacy` | passthrough | Detect PII and manage element-block privacy rules |
| `review` | passthrough | Deep-review a recorded session (screenshots, diffs, component trees) |
| `tunnel` | native | Manage reverse tunnels for localhost access |
| `sightmap` | native | Upload `.sightmap/` component definitions |
| `auth` | native | Verify authentication (`whoami`) |
Expand Down Expand Up @@ -288,6 +290,34 @@ subtext artifact upload --filename <name> --base64_data <b64> # binary conten
subtext artifact url --artifact_id <id> --ext <ext> # refresh an expired URL
```

### `privacy`

```bash
subtext privacy propose --session_url <url> # scan a session for PII (dry-run)
subtext privacy create --selectors '[{"selector":".email"}]' # create rules in preview scope
subtext privacy list # list all editable rules
subtext privacy list --scope_filter preview # preview-scoped rules only
subtext privacy promote --rule_ids '["<id>"]' # promote to all sessions
subtext privacy delete --rule_ids '["<id>"]' # delete a preview-scoped rule
```

Rules are always created in `PREVIEW_SESSIONS_ONLY` scope and must be explicitly promoted. `propose` is a dry-run and persists nothing. Only `mask` and `exclude` block types are supported — unmask rules cannot be created or deleted here.

### `review`

```bash
subtext review open --trace_id <id> # open by trace ID
subtext review open --session_url <url> # open by session URL
subtext review view --client_id <id> --page_id <p> --timestamp <ts> # screenshot + component tree
subtext review inspect --client_id <id> --page_id <p> --timestamp <ts> # detailed component tree with selectors
subtext review diff --client_id <id> --page_id <p> --from_ts <ts> --to_ts <ts> # diff two moments
subtext review close --client_id <id> --use_case bug_diagnosis --was_helpful true
```

`open` accepts `trace_id`, `session_url`, `device_id`+`session_id`, `email_address`, or `user_uid`. Always call `close` when done — it releases server resources and records feedback.

Primary use cases: verify another agent's proof work (chapter markers as the spine), diagnose a bug from a captured session, produce a structured summary of what happened. Sessions are read-only — use `subtext live` to drive a running app instead.

### `auth`

```bash
Expand Down
2 changes: 1 addition & 1 deletion cli/hack/buildskills/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import (

// toolNamespaces is the allowlist for {{tool "name"}} references.
var toolNamespaces = []string{
"live", "comment", "doc", "tunnel", "review", "sightmap", "artifact", "auth",
"live", "comment", "doc", "tunnel", "review", "privacy", "sightmap", "artifact", "auth",
}

func main() {
Expand Down
54 changes: 47 additions & 7 deletions cli/internal/cli/namespaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,19 @@ var liveCmd = &cobra.Command{
take screenshots, run JavaScript in the page, and hand off control.

For workflow guidance and tool reference:
https://github.com/fullstorydev/subtext/tree/main/skills/live`,
https://github.com/fullstorydev/subtext/blob/main/cli/skills/live/SKILL.md`,
DisableFlagParsing: true,
RunE: namespaceRunE,
}

var commentCmd = &cobra.Command{
Use: "comment",
Short: "Create and retrieve session comments",
Long: `Create and retrieve comments attached to FullStory sessions.
Long: `Create and retrieve comments attached to Fullstory sessions.
Comments anchor observations to specific moments in a recording.

For workflow guidance and tool reference:
https://github.com/fullstorydev/subtext/tree/main/skills/comments`,
https://github.com/fullstorydev/subtext/blob/main/cli/skills/comments/SKILL.md`,
DisableFlagParsing: true,
RunE: namespaceRunE,
}
Expand All @@ -50,7 +50,7 @@ var docCmd = &cobra.Command{
Documents are the primary deliverable for sharing agent findings with reviewers.

For workflow guidance and tool reference:
https://github.com/fullstorydev/subtext/tree/main/skills/docs`,
https://github.com/fullstorydev/subtext/blob/main/cli/skills/docs/SKILL.md`,
DisableFlagParsing: true,
RunE: namespaceRunE,
}
Expand All @@ -66,12 +66,52 @@ Artifacts persist across sessions and can be linked from proof documents.`,
RunE: namespaceRunE,
}

var privacyCmd = &cobra.Command{
Use: "privacy",
Short: "Detect PII and manage element-block privacy rules",
Long: `Propose CSS selectors for PII elements in a session, persist them as
element-block rules, and manage their lifecycle (list, delete, promote).

Rules are created in preview scope (PREVIEW_SESSIONS_ONLY) and must be
explicitly promoted to apply to all sessions. Only mask and exclude rules
are supported — unmask rules cannot be created or deleted via this tool.

For workflow guidance and tool reference:
https://github.com/fullstorydev/subtext/blob/main/cli/skills/privacy/SKILL.md`,
DisableFlagParsing: true,
RunE: namespaceRunE,
}

var reviewCmd = &cobra.Command{
Use: "review",
Short: "Deep-review a recorded Fullstory session",
Long: `Open a completed Fullstory session for deep agent review: capture screenshots,
walk the component tree, and diff two moments in time to understand what happened.

Primary use cases:
- Verify another agent's proof work (BEFORE/AFTER chapter markers as the spine)
- Diagnose a bug or regression from a captured session
- Produce a structured summary of what an agent or user did

Sessions are identified by trace_id, session URL, device+session IDs,
email address, or user UID. Always close when done to release resources.

For workflow guidance and tool reference:
https://github.com/fullstorydev/subtext/blob/main/cli/skills/review/SKILL.md`,
DisableFlagParsing: true,
RunE: namespaceRunE,
}

func init() {
liveCmd.SetHelpFunc(namespaceHelpFunc("live-",
"https://github.com/fullstorydev/subtext/tree/main/skills/live"))
"https://github.com/fullstorydev/subtext/blob/main/cli/skills/live/SKILL.md"))
commentCmd.SetHelpFunc(namespaceHelpFunc("comment-",
"https://github.com/fullstorydev/subtext/tree/main/skills/comments"))
"https://github.com/fullstorydev/subtext/blob/main/cli/skills/comments/SKILL.md"))
docCmd.SetHelpFunc(namespaceHelpFunc("doc-",
"https://github.com/fullstorydev/subtext/tree/main/skills/docs"))
"https://github.com/fullstorydev/subtext/blob/main/cli/skills/docs/SKILL.md"))
artifactCmd.SetHelpFunc(namespaceHelpFunc("artifact-", ""))
privacyCmd.SetHelpFunc(namespaceHelpFunc("privacy-",
"https://github.com/fullstorydev/subtext/blob/main/cli/skills/privacy/SKILL.md"))
reviewCmd.SetHelpFunc(namespaceHelpFunc("review-",
"https://github.com/fullstorydev/subtext/blob/main/cli/skills/review/SKILL.md"))
}
2 changes: 2 additions & 0 deletions cli/internal/cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ func init() {
rootCmd.AddCommand(tunnelCmd)
rootCmd.AddCommand(artifactCmd)
rootCmd.AddCommand(sightmapCmd)
rootCmd.AddCommand(privacyCmd)
rootCmd.AddCommand(reviewCmd)
}

func loadConfig() {
Expand Down
2 changes: 1 addition & 1 deletion cli/npm/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@subtextdev/subtext-cli",
"version": "1.0.3",
"version": "1.0.4",
"description": "Subtext CLI — drive the Subtext MCP server from your terminal",
"license": "MIT",
"homepage": "https://subtext.fullstory.com",
Expand Down
2 changes: 1 addition & 1 deletion cli/skills/comments/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ All comment tools are **stateless** — they identify the parent trace by `trace

### `trace_id` vs `session_id`

Comments hang off a **trace** — the durable parent identifier that survives even when no FullStory session was captured. Every tool that needs a parent accepts either:
Comments hang off a **trace** — the durable parent identifier that survives even when no Fullstory session was captured. Every tool that needs a parent accepts either:

- `trace_id` — the 12-char base62 id you get from `subtext live connect` (`trace_id:` line, or parse the trailing path of `trace_url`). **Prefer this.** It's stable, works for traces with no underlying FS session, and is the only key the storage layer actually uses.
- `session_id` — the legacy `deviceId:sessionId` form. Still accepted for callers that only have an FS session URL on hand. The server promotes it to a trace_id under the hood. Responses include a one-line deprecation hint when you use this path.
Expand Down
2 changes: 1 addition & 1 deletion cli/skills/embed.go

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

124 changes: 124 additions & 0 deletions cli/skills/privacy/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
---

name: privacy
description: Privacy rule management — detect PII in sessions and manage element-block rules. Use when you need to propose, create, list, delete, or promote CSS-selector-based privacy rules for a Fullstory org.
metadata:
_generated_from: templates/skills/privacy/SKILL.template
requires:
skills: ["subtext:shared"]
mcp-server: subtext
---


# Privacy

> **PREREQUISITE:** Read `subtext:shared` for conventions.

Privacy commands manage element-block rules — CSS-selector-based rules that mask or exclude specific page elements from Fullstory session recordings.

## Commands

| Command | Description |
|------|-------------|
| `subtext privacy propose` | Scan a session for PII and return suggested selectors (dry-run — persists nothing) |
| `subtext privacy create` | Create element-block rules from selectors in preview scope |
| `subtext privacy list` | List existing element-block rules, with optional scope/type filters |
| `subtext privacy delete` | Delete preview-scoped rules by ID |
| `subtext privacy promote` | Promote preview-scoped rules to apply to all sessions |

## Rule lifecycle

Rules always start in **preview scope** (`PREVIEW_SESSIONS_ONLY`) and must be explicitly promoted to apply broadly:

```
propose (dry-run)
create → PREVIEW_SESSIONS_ONLY
list / verify
promote → ALL_SESSIONS
│ ─ or ─
delete (preview only)
```

## Rules and constraints

- `subtext privacy propose` is always a **dry-run**. It returns suggested selectors but persists nothing. Use it to preview before committing.
- `subtext privacy create` only supports `mask` and `exclude` block types. Unmask rules cannot be created via this command.
- `subtext privacy delete` only accepts preview-scoped rules. Promoted rules cannot be deleted here.
- `subtext privacy promote` also rejects unmask rules — only mask and exclude rules can be promoted.
- System-managed rules (not user-created) are hidden by default. Pass `include_system=true` to `subtext privacy list` to see them. These rules cannot be deleted or promoted.

## Typical flow

### 1. Propose — find PII in a session

```
`subtext privacy propose` session_url=<url>
```

Returns a list of CSS selectors the auto-configure pipeline identified as likely PII, with suggested rule names. Inspect the list — reject any false positives before proceeding.

### 2. Create — persist the rules you want

```
`subtext privacy create` selectors=[{"selector": ".email-field"}, {"selector": "#ssn"}]
```

Rules land in `PREVIEW_SESSIONS_ONLY` scope. They only apply to preview sessions until promoted.

### 3. Verify — list and review

```
`subtext privacy list`
`subtext privacy list` scope_filter=preview
```

Review what was created. Note the `rule_id` values — you'll need them for delete or promote.

### 4. Promote or delete

Promote to activate for all sessions:
```
`subtext privacy promote` rule_ids=["<id1>", "<id2>"]
```

Delete if the rule was wrong:
```
`subtext privacy delete` rule_ids=["<id1>"]
```

## Parameters

### `propose`
- `session_url` (required) — the session to scan for PII
- `block_type` (optional) — `mask` (default) or `exclude`

### `create`
- `selectors` (required) — array of `{ selector: string, pii_type?: string }` objects
- `block_type` (optional) — `mask` (default) or `exclude`; `unmask` is not accepted

### `list`
- `scope_filter` (optional) — `preview` or `all`; defaults to all editable rules
- `type_filter` (optional) — `mask`, `exclude`, or `unmask`
- `include_system` (optional) — show system-managed rules alongside user rules

### `delete` / `promote`
- `rule_ids` (required) — array of rule ID strings from `privacy-list`

## Gotchas

- Running `propose` without reviewing the output — it's a starting point, not ground truth. Inspect selectors for false positives before creating rules.
- Creating rules and immediately promoting — always verify with a preview session first. The preview scope exists for exactly this purpose.
- Trying to delete a promoted rule — `delete` only works on preview-scoped rules. Promoted rules require a different workflow.
- Using `unmask` as `block_type` in `create` — this is rejected. Unmask rules are system-managed.

## See Also

- `subtext:shared` — CLI conventions

Loading
Loading