@specset/cli 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,96 @@
1
+ ---
2
+ name: specset-rfis
3
+ description: Manage Specset RFIs — list, search, create, update, and record responses including the official answer of record. Use for any RFI logging or tracking task.
4
+ allowed-tools: Bash Read AskUserQuestion
5
+ ---
6
+
7
+ # Specset RFIs
8
+
9
+ How the data is shaped:
10
+
11
+ - An RFI is a `subject` + `question`, numbered within the project, with a lifecycle `status` (RfiStatus: `Draft | Open | Closed`).
12
+ - `rfiNumber` is unique within a project — `createRfi` rejects a duplicate outright.
13
+ - Responses accumulate on the RFI; exactly one can be marked **official** — the answer of record that justifies closing the RFI.
14
+
15
+ The sketches below are minimal — inline enums, ints, and lists in the operation text (`-F` is strings-only; Variable Limitations and Schema Discovery live in the `specset` skill).
16
+
17
+ ## Reading
18
+
19
+ ```bash
20
+ # One RFI with its responses
21
+ specset api --query 'query($id: ID!) {
22
+ rfi(id: $id) {
23
+ id rfiNumber subject question status dueDate costImpact scheduleImpact
24
+ responses { id body isOfficialResponse createdAt }
25
+ attachments { id }
26
+ }
27
+ }' -F id=<rfi-id>
28
+
29
+ # Filtered list — enums inlined, never via -F
30
+ specset api --query 'query($projectId: ID!) {
31
+ rfis(projectId: $projectId, statuses: [Open]) {
32
+ id rfiNumber subject status dueDate
33
+ }
34
+ }' -F projectId=<project-id>
35
+ ```
36
+
37
+ Also available:
38
+
39
+ - `paginatedRfis(projectId, search, limit: 25, offset: 0) { totalFilteredCount items { ... } }` — offset pagination; `search` matches subject and number. Use it for dedupe checks.
40
+ - `nextAvailableRfiNumber(projectId)` — the next number in the project's sequence, as a string.
41
+ - `rfiResponses(projectId) { id body isOfficialResponse }` — every response across the project.
42
+
43
+ ## Creating
44
+
45
+ Dedupe first: search `paginatedRfis` with the RFI number from the document. A match by `rfiNumber` within the project is authoritative — never create a duplicate.
46
+
47
+ ```bash
48
+ specset api --query 'query($projectId: ID!, $search: String) {
49
+ paginatedRfis(projectId: $projectId, search: $search, limit: 5, offset: 0) {
50
+ totalFilteredCount
51
+ items { id rfiNumber subject status }
52
+ }
53
+ }' -F projectId=<project-id> -F search=RFI-000017
54
+ ```
55
+
56
+ If the source document carries no number, omit `rfiNumber` and the project's numbering scheme assigns the next one (fetch `nextAvailableRfiNumber` first if the user wants to see it). Numbers you do supply are canonicalized — generic `RFI-` prefixes are normalized — so the stored value may differ slightly from your input; read `rfiNumber` back from the mutation result.
57
+
58
+ `createRfi(input: { projectId, subject, question, status, specSectionIds: [...], drawingSheetIds: [...], dueDate }) { id rfiNumber subject status }`
59
+
60
+ Field conventions:
61
+
62
+ - `status` defaults to `Draft`. Promote to `Open` when the RFI is formally issued; move to `Closed` only once the official response is recorded.
63
+ - `costImpact` and `scheduleImpact` are RfiImpact enums (`Yes | No`) — set them only when the user or source document states an impact explicitly; leave them unset otherwise.
64
+ - `rfiManagerId` assigns the managing org member.
65
+
66
+ Update with `updateRfi(input: { id, ... })` — same fields minus `projectId`; pass only what changes:
67
+
68
+ ```bash
69
+ specset api --query 'mutation($id: ID!) {
70
+ updateRfi(input: { id: $id, status: Open }) { id status }
71
+ }' -F id=<rfi-id>
72
+ ```
73
+
74
+ ## Responses
75
+
76
+ Responses form the answer trail. Attach the reply document via `attachmentCloudFileIds` (file ids come from Uploading Files in the `specset` skill):
77
+
78
+ - `createRfiResponse(input: { rfiId, body, attachmentCloudFileIds: [...] }) { id isOfficialResponse }`
79
+ - `updateRfiResponse(input: { id, body })` — edit a response body.
80
+ - `markRfiResponseAsOfficial(id)` / `unmarkRfiResponseAsOfficial(id)` — designate or revoke the answer of record. There is no official flag on create: create the response first, then mark it. The official response is normally the signed reply of record (typically the architect's or engineer's), not a discussion comment.
81
+ - `deleteRfiResponse(id)` — confirm with the user first.
82
+
83
+ The typical close-out sequence is: `createRfiResponse` → `markRfiResponseAsOfficial` → `updateRfi(input: { id, status: Closed })`.
84
+
85
+ ## Attachments
86
+
87
+ Attachments on the RFI itself carry the question-side documents (sketches, photos, the original request PDF); response documents ride on the response via `attachmentCloudFileIds` above. Upload files per Uploading Files in the `specset` skill.
88
+
89
+ - `createRfiAttachment(input: { rfiId, cloudFileId })`
90
+ - `deleteRfiAttachment(id)`
91
+ - `reorderRfiAttachments(input: { rfiId, attachmentIds: [...] })`
92
+
93
+ ## Safety
94
+
95
+ - `deleteRfi(id)` removes the RFI along with its responses and attachments. Prefer closing an RFI over deleting it; delete only on explicit instruction, after showing what will be lost.
96
+ - Marking or unmarking an official response changes the project's answer of record — name the response you're promoting before running it.
@@ -0,0 +1,104 @@
1
+ ---
2
+ name: specset-search
3
+ description: Use when the user asks to find, look up, or locate anything in their Specset projects — semantic and keyword search across specs, drawing sheets, submittals, RFIs, documents, and closeout records.
4
+ allowed-tools: Bash Read AskUserQuestion
5
+ ---
6
+
7
+ # Specset Search
8
+
9
+ This skill is read-only — everything here is a query, so nothing needs user confirmation before running.
10
+
11
+ `-F` sends strings only: inline enums, booleans, ints, and lists as literals in the operation text (Variable Limitations and Schema Discovery live in the `specset` skill).
12
+
13
+ ## Choosing an Operation
14
+
15
+ - **`search`** — the main tool: semantic (natural-language) and/or keyword search over indexed content, results grouped by record type.
16
+ - **`quickSearch`** — fast flat matching against titles and identifiers; use when the user roughly knows the record's name or number.
17
+ - **Precise lookups** — when the user already has a spec section number or drawing sheet number, skip search and fetch directly.
18
+
19
+ > Need the actual content, not just previews and IDs? The `specset-tools` skill exposes Specset's own retrieval tools (`specset tools run searchSpecSections`, `searchDrawingSheets`, `searchDocuments`, …), which return expanded, citation-anchored markdown. Use `search`/`quickSearch` here to locate records cheaply; reach for `specset-tools` when you need to read and reason over the content.
20
+
21
+ ## Unified Search: `search`
22
+
23
+ ```graphql
24
+ search(
25
+ orgId: ID!
26
+ projectId: ID # required when projectScope is CurrentProject
27
+ query: String # natural-language semantic query
28
+ keywords: [String!] # exact term matching — inline the list
29
+ referenceTypes: [SourceReferenceType!] # inline; omit to search all types
30
+ projectScope: ProjectScope = CurrentProject
31
+ titleOnly: Boolean = false
32
+ limit: Int = 10
33
+ offset: Int = 0
34
+ ): SearchResults!
35
+ ```
36
+
37
+ At least one of `query` or `keywords` is required; they can be combined. Results:
38
+
39
+ ```graphql
40
+ { groups { referenceType totalCount items { url title previewText score projectId projectName } } nextOffset }
41
+ ```
42
+
43
+ ### Project Scope (`ProjectScope`)
44
+
45
+ - `CurrentProject` — default; `projectId` is required.
46
+ - `MyProjects` — every project the user is a member of in the org.
47
+ - `AllProjects` — every project in the org. Requires the org **Manager** or **Admin** role — plain Members get an authorization error, so fall back to `MyProjects` when that happens.
48
+
49
+ ### Record Types (`SourceReferenceType`)
50
+
51
+ Filter with `referenceTypes`. Common values: `SpecSection`, `DrawingSheet`, `Document`, `Submittal`, `SubmittalAttachment`, `Rfi`, and the closeout types `Company`, `Location`, `Product`, `Asset`, `Warranty`, `MaintenanceTask`. Introspect the `SourceReferenceType` enum for the full list.
52
+
53
+ ## Quick Search: `quickSearch`
54
+
55
+ Substring matching against titles and identifiers — cheap and fast, no semantic ranking.
56
+
57
+ ```graphql
58
+ quickSearch(
59
+ orgId: ID!
60
+ projectId: ID
61
+ query: String!
62
+ referenceTypes: [SourceReferenceType!]
63
+ projectScope: ProjectScope = CurrentProject
64
+ limit: Int = 20
65
+ ): QuickSearchResults!
66
+ ```
67
+
68
+ Results: `{ items { id url number title referenceType score projectId projectName summary } totalCount }`
69
+
70
+ ## Precise Lookups
71
+
72
+ When the user hands you a section or sheet number, look it up directly:
73
+
74
+ ```graphql
75
+ specSectionByNumber(projectId: ID!, sectionNumber: String!): SpecSection # nullable
76
+ drawingSheetByNumber(projectId: ID!, sheetNumber: String!): DrawingSheet # nullable
77
+ ```
78
+
79
+ To browse or filter within a project, the paginated lists take `projectId`, an optional `search` string, and `limit`/`offset` ints; both return `{ items totalFilteredCount }`:
80
+
81
+ ```graphql
82
+ paginatedSpecSections(projectId: ID!, search: String, limit: Int, offset: Int): SpecSectionPage!
83
+ paginatedDrawingSheets(projectId: ID!, search: String, limit: Int, offset: Int): DrawingSheetPage!
84
+ ```
85
+
86
+ Useful item fields: `id sectionNumber title numberAndTitle` on SpecSection, `id sheetNumber title` on DrawingSheet.
87
+
88
+ ## Example
89
+
90
+ ```bash
91
+ specset api --query 'query($orgId: ID!) {
92
+ search(orgId: $orgId, keywords: ["AHU-1"],
93
+ referenceTypes: [Submittal, SubmittalAttachment],
94
+ projectScope: MyProjects, limit: 20) {
95
+ groups { referenceType totalCount
96
+ items { url title previewText projectName } }
97
+ }
98
+ }' -F orgId=<org-id>
99
+ ```
100
+
101
+ ## Working With Results
102
+
103
+ - Each result's `url` is a Specset reference URL (`sb://spec/<id>`, `sb://sub/<id>`, …); the UUID inside is the record's GraphQL `id` — extract it for follow-up queries. The scheme is documented in the `specset-tools` skill.
104
+ - For follow-up work on what you found — updating submittals, answering RFIs, closeout records, or project/document changes — hand off to the `specset-submittals`, `specset-rfis`, `specset-closeout`, or `specset-projects` skill.
@@ -0,0 +1,114 @@
1
+ ---
2
+ name: specset-submittals
3
+ description: Manage Specset submittals — list, search, create, revise, attach files, track approvers, and run AI spec-compliance reviews. Use for any submittal log or submittal review task.
4
+ allowed-tools: Bash Read AskUserQuestion
5
+ ---
6
+
7
+ # Specset Submittals
8
+
9
+ How the data is shaped:
10
+
11
+ - `status` (SubmittalStatus: `Draft | Open | Closed`) is the log lifecycle; `workingStatus` (SubmittalWorkingStatus: `Draft | Submitted | Pending | Approved | ApprovedAsNoted | Rejected | ReviseAndResubmit | ForRecordOnly | Void`) is the ball-in-court state. Set either — the server reconciles the other.
12
+ - Revisions are separate submittal records sharing a `submittalNumber`; list and search queries return only the current revision.
13
+ - A submittal number is only unique per (spec section, number) within a project — the bare number alone is not.
14
+
15
+ The sketches below are minimal — inline enums, ints, and lists in the operation text (`-F` is strings-only; Variable Limitations and Schema Discovery live in the `specset` skill).
16
+
17
+ ## Reading
18
+
19
+ ```bash
20
+ # One submittal with its relationships
21
+ specset api --query 'query($id: ID!) {
22
+ submittal(id: $id) {
23
+ id submittalNumber sectionNumber title revision status workingStatus
24
+ attachments { id description }
25
+ approvers { id status comment returnedDate }
26
+ submittalReviews { id status oneLiner lastReviewedAt }
27
+ }
28
+ }' -F id=<submittal-id>
29
+
30
+ # Filtered list — enums inlined, never via -F
31
+ specset api --query 'query($projectId: ID!) {
32
+ submittals(projectId: $projectId, statuses: [Open], workingStatuses: [Pending, Submitted]) {
33
+ id submittalNumber sectionNumber title workingStatus submitByDate
34
+ }
35
+ }' -F projectId=<project-id>
36
+ ```
37
+
38
+ Also available:
39
+
40
+ - `paginatedSubmittals(projectId, search, limit: 25, offset: 0) { totalFilteredCount items { ... } }` — offset pagination with text search.
41
+ - `searchSubmittals(projectId, search)` — lightweight lookup by number or title; use it for dedupe checks.
42
+ - `submittalRevisions(projectId, submittalNumber, specSectionId)` — every revision of one number.
43
+ - `submittalTypes(projectId) { id name label }` — the project's configured types, for `submittalTypeId`.
44
+
45
+ ## Creating
46
+
47
+ Dedupe first: run `searchSubmittals` with the submittal number (or title plus section). A match on (spec section, number) is authoritative — never create a duplicate; the API does not enforce this for you.
48
+
49
+ `createSubmittal(input: { projectId, title, submittalNumber, specSectionIds: [...], submittalTypeId, status }) { id submittalNumber title }` — omit `submittalNumber` to auto-number in the project's sequence.
50
+
51
+ **From a PDF**: upload it first (see Uploading Files in the `specset` skill) and pass the resulting file id as `cloudFileId` — Specset extracts the number, title, and section from the document, so you can omit them. Add `autoReviewMode: GeneratePlanAndApprove` to start an AI compliance review in the same call (`GeneratePlan` pauses at the plan for approval; default `None`).
52
+
53
+ Update with `updateSubmittal(input: { id, ... })` — same fields as create, minus `projectId`/`cloudFileId`. Pass only what changes.
54
+
55
+ ## Revisions
56
+
57
+ `createSubmittalRevision` supersedes the current revision with a fresh one reset to Draft:
58
+
59
+ ```bash
60
+ specset api --query 'mutation($id: ID!) {
61
+ createSubmittalRevision(input: { id: $id, cloudFileIds: ["<file-id>"] }) {
62
+ id revision status
63
+ }
64
+ }' -F id=<submittal-id>
65
+ ```
66
+
67
+ The revision string auto-increments (`0` → `1`, `A` → `B`); pass `revision` only when the document shows one that doesn't match. `autoReviewMode` works here too.
68
+
69
+ ## Attachments
70
+
71
+ File ids come from Uploading Files in the `specset` skill.
72
+
73
+ - `createSubmittalAttachment(input: { submittalId, cloudFileId, type, description, order })`
74
+ - `createSubmittalAttachments(input: { submittalId, cloudFileIds: [...], autoReviewMode })` — batch form
75
+ - `updateSubmittalAttachment(input: { id, type, description, order })`
76
+ - `deleteSubmittalAttachment(id)`
77
+
78
+ `type` is a SubmittalAttachmentType enum (`ProductData`, `ShopDrawing`, `Certifications`, …) — introspect for the full list and inline the value.
79
+
80
+ ## Approvers
81
+
82
+ Track who reviews the submittal and what they returned:
83
+
84
+ - `createSubmittalApprover(input: { submittalId, userId, dueDate, responseRequired }) { id order status }`
85
+
86
+ Record a returned response with `updateSubmittalApprover(input: { id, status: ApprovedAsNoted, comment: "...", returnedDate: "...", cloudFileIds: [...] })`. `status` is SubmittalApproverResponseStatus: `Approved | ApprovedAsNoted | ForRecordOnly | Pending | Rejected | ReviseAndResubmit | Void | Submitted`. `approverType` distinguishes `Approver` (default) from `Submitter`; `sentDate` and `distributed` track routing. Remove with `deleteSubmittalApprover(id)`.
87
+
88
+ ## AI Compliance Reviews
89
+
90
+ Three steps — arguments are top-level (no input object):
91
+
92
+ 1. `createSubmittalReview(submittalId, createMode: GeneratePlanAndApprove) { id }` — optional: `prompt` (focus instructions, a plain string), and `specSectionIds` / `drawingSheetIds` (inline lists) to pin the spec basis.
93
+ 2. Poll `submittalReview(id) { id status oneLiner lastReviewedAt }` until `lastReviewedAt` is set — reviews take minutes; see Waiting on Background Processing in the `specset` skill for cadence. `status` is the verdict, ReviewStatus: `Compliant | NonCompliant | MissingInfo | ClarificationsNeeded | NotApplicable`.
94
+ 3. `compileSubmittalReview(id, includeIssueToc: true) { id compiledReview { id } }` compiles the annotated, marked-up PDF. `excludedIssueIds: [...]` (inlined) drops chosen issues from the compiled PDF and TOC.
95
+
96
+ `updateSubmittalReview(id, comments)` records reviewer comments; `deleteSubmittalReview(id)` removes a review.
97
+
98
+ ## Drafting a Whole Submittal Log
99
+
100
+ Generating a full log from the specs is an agent job, not raw GraphQL. Use the `specset-agent` skill and ask the in-app agent to draft a submittal log for the project — the run produces a SubmittalLog artifact on its thread. Read the drafted rows (each entry has a numeric id) with `agentArtifact(id) { id title content }`, let the user pick, then apply:
101
+
102
+ ```bash
103
+ specset api --query 'mutation($artifactId: ID!) {
104
+ applySubmittalLog(input: { artifactId: $artifactId, selectedSubmittalLogIds: [1, 2, 3] }) {
105
+ id content
106
+ }
107
+ }' -F artifactId=<artifact-id>
108
+ ```
109
+
110
+ Applying skips rows whose (section, number) already exists and writes per-row results back onto the artifact — re-read `content` to report errors and warnings.
111
+
112
+ ## Safety
113
+
114
+ `deleteSubmittal(id)` removes one revision; `deleteSubmittal(id, deleteAllRevisions: true)` removes the entire revision chain — state that difference when confirming the delete.
@@ -0,0 +1,104 @@
1
+ ---
2
+ name: specset-tools
3
+ description: Use when an agent should call Specset's own retrieval tools directly — semantic/keyword search and structured reads over specs, drawings, submittals, RFIs, documents, closeout, and schedule — via `specset tools` or a `specset mcp` server. Deeper than raw GraphQL search; lighter than delegating to the in-app agent.
4
+ allowed-tools: Bash Read AskUserQuestion
5
+ ---
6
+
7
+ # Specset Agent Tools (`specset tools`, `specset mcp`)
8
+
9
+ These are the same read-only retrieval tools Specset's own in-app agent uses — exposed so external agents can call them directly. Everything here is read-only; nothing needs user confirmation.
10
+
11
+ ## When to use which surface
12
+
13
+ There are three tiers of Specset retrieval. Pick the lightest one that answers the question:
14
+
15
+ 1. **Typed GraphQL search** (`specset-search` skill: `search`, `quickSearch`, `specSectionByNumber`, …) — cheapest, returns previews and IDs. Best for "does X exist / find the record."
16
+ 2. **Agent tools** (this skill) — the middle tier. `searchSpecSections` / `searchDrawingSheets` / `searchDocuments` return expanded, citation-anchored markdown (the real retrieval), and the `get*Content` tools read full sections. Best when you need the actual content to reason over.
17
+ 3. **In-app agent** (`specset-agent` skill: `specset agent chat`) — heaviest. Delegates the whole question to Specset's agent, which reasons and synthesizes. Best when the answer needs cross-document judgment, not just retrieval.
18
+
19
+ ## Listing tools
20
+
21
+ ```bash
22
+ specset tools list
23
+ specset tools list --project <projectId|sb://project/...>
24
+ specset tools list --json # full input schemas
25
+ ```
26
+
27
+ The listing includes usage guidance and marks project-scoped tools. The exact set depends on the org (closeout and schedule tools require their SKUs) and on whether a project is in scope.
28
+
29
+ ## Running a tool
30
+
31
+ Provide input as a JSON object (`--input`) or as repeated `-F key=value` fields. **Unlike `specset api -F` (strings only), `specset tools run -F` JSON-parses each value**, so numbers, booleans, and lists work:
32
+
33
+ ```bash
34
+ # JSON input
35
+ specset tools run searchSpecSections \
36
+ --project <projectId> \
37
+ --input '{"query": "concrete curing requirements"}'
38
+
39
+ # Field form (values are JSON-parsed, string fallback)
40
+ specset tools run searchSpecSections --project <projectId> \
41
+ -F query="fire rating" -F keywords='["ASTM E119"]' -F limit=5
42
+
43
+ # Read a specific section's full content using an ID from a search result
44
+ specset tools run getSpecSectionContent -F id='sb://spec/....'
45
+ ```
46
+
47
+ Output is the tool's JSON payload (`{ success, message?, result? }`); a `{ success: false }` payload exits non-zero.
48
+
49
+ ### Identifiers are citation IDs, not links
50
+
51
+ Tool results embed identifiers shaped like `sb://spec/<uuid>` or `sb://drawing/<uuid>`. Pass them back into other tools verbatim (e.g. into `getSpecSectionContent`, `getDrawingSheet`). They are citation identifiers, not web URLs.
52
+
53
+ ### Project scoping
54
+
55
+ Most tools are project-scoped. Either pass `--project` (a UUID or `sb://project/...`), or include `projectId` in the tool input. Use `listProjects` to discover project IDs. With `--project` set, per-call `projectId` becomes optional.
56
+
57
+ ## Downloading source files (drawings, documents, specs, submittals, RFIs)
58
+
59
+ The retrieval tools return **extracted text**, which is authoritative for schedules, notes, and spec paragraphs — but cannot answer spatial/visual questions (routing, clearances, what a plan view actually shows) and never carries the original file. Every entity has a download command, and **each accepts its `sb://` citation URL verbatim** (as returned by issues, search, and tool results — any `#page=`/`#nodeId=` fragment is ignored) or a bare UUID:
60
+
61
+ | Entity | Command | Also accepts | Formats / options |
62
+ | -------------- | ------------------------------------------------- | ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
63
+ | Drawing sheets | `specset drawings download 'sb://drawing/<uuid>'` | sheet number + `--project` | `--format image` (rendered PNG, default), `pdf` (single sheet), `file` (whole set) |
64
+ | Documents | `specset docs download 'sb://doc/<uuid>'` | | also covers RFI attachments and closeout documents |
65
+ | Spec sections | `specset specs download 'sb://spec/<uuid>'` | section number (e.g. `'23 31 19'`) + `--project` | per-section PDF slice (default), `--asset book` (whole source spec file) |
66
+ | Submittals | `specset submittals download 'sb://sub/<uuid>'` | | compiled package (default when present); `--list` shows the asset inventory; `--asset attachment --index 2`, `--asset attachment --all` |
67
+ | RFIs | `specset rfis download 'sb://rfi/<uuid>'` | | question + response attachments; `--list`, `--all` |
68
+ | File resources | `specset files download 'sb://file/<uuid>'` | | escape hatch when you already hold an `sb://file` id |
69
+
70
+ All commands support `--json` (structured `{files: [{path, bytes, ...}]}` output) and `--out` (file path, or directory for multi-file downloads). A citation passed to the wrong command errors with the right one to use.
71
+
72
+ Typical loop for drawings: search or `getDrawingSheetContent` to find the sheet → hit a visual question the text can't settle → `drawings download` → read the PNG to inspect the geometry.
73
+
74
+ ## MCP servers (local and hosted)
75
+
76
+ The same server-side tool registry is available through two transports:
77
+
78
+ - `specset mcp` is the local stdio transport for Claude Code, Codex, and other clients with shell access. It reuses the CLI login and active organization.
79
+ - `https://<specset-host>/mcp` is the stateless Streamable HTTP transport for hosted MCP clients. Its OAuth 2.1 consent flow selects an organization and binds it into the grant, so hosted clients must not put `orgId` in the connection URL. Add `projectId=<project-id>` to pin project scope.
80
+ - First-party clients may instead supply a Specset session bearer token and select the organization with `x-org-id` (or the legacy `orgId` query parameter).
81
+
82
+ Register the local transport once and the client can call the tools directly:
83
+
84
+ ```bash
85
+ # Claude Code
86
+ claude mcp add specset -- specset mcp
87
+ # Optionally pin a project and/or host:
88
+ claude mcp add specset -- specset mcp --project <projectId> --host https://app.specbook.ai
89
+
90
+ # Codex
91
+ codex mcp add specset -- specset mcp
92
+ ```
93
+
94
+ Notes:
95
+
96
+ - The server writes only the MCP protocol to stdout; diagnostics go to stderr.
97
+ - Scope (org, optional project, host) is fixed for the life of the server — restart it to change scope.
98
+ - If the session expires mid-run, tool calls return an actionable error; run `specset login` and restart the server.
99
+ - The hosted transport additionally exposes `uploadFile` (OpenAI host file parameters, streamed into Specset storage) and `getFileDownload` (an MCP resource link backed by a short-lived signed URL). The underlying retrieval tools are still the same allowlisted registry used by stdio.
100
+ - The hosted transport advertises standards-compliant MCP OAuth 2.1 protected-resource and authorization-server metadata. It supports Client ID Metadata Documents, PKCE, resource/audience binding, scoped tools, short-lived access tokens, and rotating refresh tokens.
101
+
102
+ ## Stability
103
+
104
+ This surface is experimental — tool names and input schemas may change. For automation you need to keep stable, prefer the typed `search` / `quickSearch` GraphQL queries in the `specset-search` skill.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: specset
3
- description: Core CLI for Specset — install or update the CLI, authenticate with a browser or device code, switch organizations, and run GraphQL with `specset api`. Start here for any Specset task, and whenever a specset command fails with auth or org errors.
3
+ description: Core CLI for Specset — install or update the CLI, authenticate with a browser or device code, switch organizations, and run GraphQL with `specset api`. Start here for CLI setup or raw GraphQL access, and whenever a specset command fails with auth or org errors.
4
4
  allowed-tools: Bash Read AskUserQuestion
5
5
  ---
6
6
 
@@ -14,71 +14,27 @@ The `specset` CLI handles OAuth login, org switching, and request signing. The e
14
14
 
15
15
  This is the core skill — setup and mechanics only. Domain workflows live in sibling skills:
16
16
 
17
- | Skill | Use for |
18
- |---|---|
19
- | `specset-search` | Finding anything across specs, drawings, submittals, RFIs, documents, and closeout records |
20
- | `specset-tools` | Calling Specset's own retrieval tools directly (`specset tools`, `specset mcp`) — deeper than search, lighter than the in-app agent |
21
- | `specset-projects` | Creating projects, uploading spec/drawing PDFs, publishing spec and drawing sets |
22
- | `specset-submittals` | Submittal lifecycle, attachments, approvers, and AI compliance reviews |
23
- | `specset-rfis` | RFI logging, tracking, and responses |
24
- | `specset-closeout` | Assets, locations, products, companies, warranties, and maintenance |
25
- | `specset-agent` | Delegating deep project questions to Specset's in-app AI agent |
26
- | `specset-admin` | Org members, invites, and whitelabel branding |
17
+ | Skill | Use for |
18
+ | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
19
+ | `specset-search` | Finding anything across specs, drawings, submittals, RFIs, documents, and closeout records |
20
+ | `specset-tools` | Calling Specset's own retrieval tools directly (`specset tools`, `specset mcp`) — deeper than search, lighter than the in-app agent |
21
+ | `specset-projects` | Creating projects, uploading spec/drawing PDFs, publishing spec and drawing sets |
22
+ | `specset-submittals` | Submittal lifecycle, attachments, approvers, and AI compliance reviews |
23
+ | `specset-rfis` | RFI logging, tracking, and responses |
24
+ | `specset-closeout` | Assets, locations, products, companies, warranties, and maintenance |
25
+ | `specset-agent` | Delegating deep project questions to Specset's in-app AI agent |
26
+ | `specset-admin` | Org members, invites, and whitelabel branding |
27
27
 
28
28
  `specset skill install --target <claude|codex|chatgpt>` installs and updates the whole family (`specset skill list` shows what's bundled) — if a skill named above is missing from your skills directory, run it for the current agent.
29
29
 
30
30
  ## First-Run Setup
31
31
 
32
- Perform this setup yourself when the user installs or updates the skill/plugin, asks to get started with Specset, or when a command fails because the CLI is missing or unauthenticated. Every step is idempotent — skip any that's already satisfied.
32
+ Run this when the user installs or updates the skill/plugin, asks to get started with Specset, or when a command fails because the CLI is missing or unauthenticated. Every step is idempotent — skip any that's already satisfied.
33
33
 
34
- 1. Ensure the CLI is installed and current. Installing or updating an agent skill/plugin does not upgrade an existing global npm package. Check the installed and published versions:
35
-
36
- ```bash
37
- specset --version 2>/dev/null || true
38
- npm view @specset/cli version
39
- ```
40
-
41
- If `specset` is missing or its version is older than the published version, update it and verify the result:
42
-
43
- ```bash
44
- npm install -g @specset/cli@latest
45
- specset --version
46
- ```
47
-
48
- Requires Node.js 20+ with npm. If `npm` itself is missing, stop and ask the user to install Node.js (https://nodejs.org) first. Do not replace a source-linked development CLI when its version is newer than npm.
49
-
50
- 2. Check authentication with `specset auth status`. If not logged in, choose the login mode based on whether the user can see a browser opened by this environment.
51
-
52
- **If a visible browser is unavailable** — including remote or in-app agent sessions, SSH, containers, and CI — use device authentication from the outset:
53
-
54
- ```bash
55
- specset login --device
56
- ```
57
-
58
- It prints a short code and a link (`https://<host>/device?code=XXXX-XXXX`). Show both to the user and ask them to open the link on any device and approve; the command completes on its own once they do. The code expires after 15 minutes — if it does, run the command again for a fresh one.
59
-
60
- When a visible browser is available on the same machine, use the browser flow and tell the user to complete sign-in there while the command waits:
61
-
62
- ```bash
63
- specset login
64
- ```
65
-
66
- Login is interactive by design — never try to bypass it or handle credentials directly. Don't launch a login mid-task without telling the user what's happening.
67
-
68
- 3. Pick the active organization (required for org-scoped queries):
69
-
70
- ```bash
71
- specset org list
72
- specset org use <slug>
73
- ```
74
-
75
- If the user belongs to exactly one org, select it; otherwise ask which to use.
76
-
77
- 4. Confirm everything is ready:
78
-
79
- ```bash
80
- specset auth status
81
- ```
34
+ 1. **Install or update the CLI** (Node.js 20+). Installing a skill/plugin does not upgrade the npm package. If `specset --version` is missing or older than `npm view @specset/cli version`, run `npm install -g @specset/cli@latest` (don't replace a source-linked development CLI whose version is newer than npm). After upgrading, refresh the skills with `specset skill install --target <claude|codex|chatgpt>`.
35
+ 2. **Authenticate** — check with `specset auth status`. If not logged in: `specset login` when a visible browser is available on this machine (tell the user to complete sign-in there while the command waits); `specset login --device` otherwise (SSH, containers, CI, remote/in-app sessions). Device login prints a code and a link (`https://<host>/device?code=XXXX-XXXX`) — show both to the user; the command completes once they approve. Codes expire after 15 minutes; rerun for a fresh one.
36
+ 3. **Pick the active organization** (required for org-scoped queries): `specset org list`, then `specset org use <slug>`. If the user belongs to exactly one org, select it; otherwise ask which to use.
37
+ 4. **Confirm** with `specset auth status`.
82
38
 
83
39
  Credentials persist across sessions in `~/.config/specset/config.yml` (mode 0600) — setup only needs to happen once per machine.
84
40
 
@@ -109,71 +65,30 @@ GQL
109
65
 
110
66
  ### Schema Discovery
111
67
 
112
- Discover the API shape through introspection queries:
68
+ The domain skills document the operations that matter for each workflow, but they are not exhaustive — when an operation or argument doesn't match, introspect rather than guess:
113
69
 
114
70
  ```bash
115
- # Top-level queries
116
- specset api --query '{ __schema { queryType { fields { name description } } } }'
117
-
118
- # Fields on a specific type
119
71
  specset api --query '{ __type(name: "Project") { fields { name type { name kind ofType { name } } } } }'
120
-
121
- # Input object shape
122
- specset api --query '{ __type(name: "CreateSubmittalInput") { inputFields { name type { name kind ofType { name } } } } }'
123
72
  ```
124
73
 
125
- The domain skills document the operations that matter for each workflow, but they are not exhaustive — when an operation or argument doesn't match, introspect rather than guess.
74
+ The same pattern works for input objects (`inputFields`) and the top-level surface (`{ __schema { queryType { fields { name description } } } }`).
126
75
 
127
76
  ## Agent Tools and MCP
128
77
 
129
- Beyond raw GraphQL, the CLI exposes Specset's own retrieval tools — semantic and keyword search over specs, drawings, submittals, RFIs, documents (plus closeout and schedule where enabled) — for an AI agent to call directly. Reach for these when you want the actual content to reason over, not just record previews.
130
-
131
- - **As commands:** `specset tools list` to see what's available, then `specset tools run <name> --input '{...}'` (or `-F key=value`).
132
- - **As a native MCP server:** `specset mcp` runs a stdio MCP server that surfaces these tools to an MCP client, reusing your CLI login, org, and host. Register it once:
133
-
134
- ```bash
135
- claude mcp add specset -- specset mcp # Claude Code
136
- codex mcp add specset -- specset mcp # Codex
137
- ```
138
-
139
- See the `specset-tools` skill for the full workflow — project scoping, the `-F` JSON-parsing difference, and the `sb://` citation convention.
78
+ Beyond raw GraphQL, the CLI exposes Specset's own retrieval tools — semantic and keyword search with actual content to reason over, not just record previews — as direct commands (`specset tools`) and as a native MCP server (`specset mcp`). The `specset-tools` skill owns that workflow: tool listing, project scoping, the `-F` JSON-parsing difference, MCP registration, and the `sb://` citation convention.
140
79
 
141
80
  ## Uploading Files
142
81
 
143
- Several workflows attach an uploaded file — submittal PDFs, RFI attachments, project documents, org logos. Files are uploaded directly to storage via a presigned URL, so attaching one is a three-step flow that needs `bash`, `curl`, and `jq`. The result is a **file id** you pass to whatever mutation consumes it.
144
-
145
- 1. Request a presigned upload URL:
146
-
147
- ```bash
148
- specset api --query 'mutation($orgId: ID!) {
149
- generateFileUploadUrl(orgId: $orgId) { id url fields { key value } }
150
- }' -F orgId=<your-org-id> > upload.json
151
- ```
152
-
153
- 2. POST the file to that URL with the returned form fields — the `file` field must come **last**:
82
+ Several workflows attach an uploaded file — submittal PDFs, RFI attachments, project documents, and org logos. Use the streaming upload command; it requests a presigned storage URL, sends the file without buffering it whole, finalizes the upload, and returns the **cloud file id** required by attachment mutations:
154
83
 
155
84
  ```bash
156
- URL=$(jq -r '.data.generateFileUploadUrl.url' upload.json)
157
- ARGS=()
158
- while IFS= read -r kv; do ARGS+=(-F "$kv"); done \
159
- < <(jq -r '.data.generateFileUploadUrl.fields[] | "\(.key)=\(.value)"' upload.json)
160
- curl -sf -X POST "${ARGS[@]}" -F "file=@document.pdf" "$URL"
85
+ specset files upload ./document.pdf
86
+ specset files upload ./photo.jpg --type image/jpeg --json
161
87
  ```
162
88
 
163
- 3. Finalize the upload to create the file record and capture its id:
89
+ The active organization is used automatically. With `--json`, read the id from `.id`; without it, the command prints the id. The optional `--type` defaults to `application/octet-stream`.
164
90
 
165
- ```bash
166
- UPLOAD_ID=$(jq -r '.data.generateFileUploadUrl.id' upload.json)
167
- FILE_ID=$(specset api --query 'mutation($orgId: ID!, $id: ID!, $filename: String!) {
168
- completeUpload(orgId: $orgId, id: $id, filename: $filename) { id }
169
- }' -F orgId=<your-org-id> -F id=$UPLOAD_ID -F filename=document.pdf | jq -r '.data.completeUpload.id')
170
- ```
171
-
172
- Get your org id first if you don't have it:
173
-
174
- ```bash
175
- specset api --query '{ me { orgMembers { org { id slug name } } } }'
176
- ```
91
+ Hosted MCP clients that support OpenAI file parameters receive the equivalent `uploadFile` tool. Pass its returned `.cloudFileId` to domain mutations that accept attachment ids; its `.id` is an `sb://file/...` citation. `getFileDownload` returns an MCP resource link backed by a short-lived URL when the hosted client needs bytes back.
177
92
 
178
93
  ## Waiting on Background Processing
179
94
 
@@ -187,19 +102,11 @@ specset api --query 'query($id: ID!) {
187
102
 
188
103
  Processing large PDF sets takes minutes; poll every 15–30 seconds and tell the user what's in flight rather than blocking silently.
189
104
 
190
- ## Safety Rules
105
+ ## Safety
191
106
 
192
- - Default to read-only queries.
193
- - Confirm with the user before executing mutations that create, update, or delete data.
194
- - Never hardcode credentials in commands — the CLI manages tokens via OAuth.
195
- - Tokens live in `~/.config/specset/config.yml`; do not echo or copy that file's contents.
107
+ Confirm with the user before executing mutations that create, update, or delete data. The domain skills note the mutations with non-obvious consequences.
196
108
 
197
109
  ## Troubleshooting
198
110
 
199
- - `command not found: specset` — run the First-Run Setup above.
200
- - `Not logged in` — run `specset login` (opens the user's browser; tell them to complete sign-in there).
201
- - `No active organization selected` — run `specset org list`, then `specset org use <slug>`.
111
+ - Auth, org, or missing-CLI errors (`command not found`, `Not logged in`, `No active organization selected`, login hangs) — run the matching First-Run Setup step above.
202
112
  - A lookup by id returns `null` without an error — the record usually belongs to a different org than the active one; check `specset auth status` and switch with `specset org use <slug>`.
203
- - Login hangs or the browser can't reach this machine (SSH, container, CI) — use `specset login --device`: it prints a code + link the user approves from any device.
204
- - `specset --version` is older than `npm view @specset/cli version`, or a documented flag is missing — run `npm install -g @specset/cli@latest`, then verify with `specset --version`.
205
- - After upgrading the CLI (`npm i -g @specset/cli@latest`), refresh the skills with `specset skill install --target <claude|codex|chatgpt>`.