@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.
- package/README.md +21 -4
- package/dist/main.js +471 -160
- package/package.json +1 -1
- package/plugin/specset/.claude-plugin/plugin.json +18 -0
- package/plugin/specset/.codex-plugin/plugin.json +42 -0
- package/plugin/specset/.mcp.json +8 -0
- package/plugin/specset/README.md +10 -0
- package/plugin/specset/skills/specset/SKILL.md +112 -0
- package/plugin/specset/skills/specset-admin/SKILL.md +112 -0
- package/plugin/specset/skills/specset-agent/SKILL.md +48 -0
- package/plugin/specset/skills/specset-closeout/SKILL.md +94 -0
- package/plugin/specset/skills/specset-projects/SKILL.md +110 -0
- package/plugin/specset/skills/specset-rfis/SKILL.md +96 -0
- package/plugin/specset/skills/specset-search/SKILL.md +104 -0
- package/plugin/specset/skills/specset-submittals/SKILL.md +114 -0
- package/plugin/specset/skills/specset-tools/SKILL.md +104 -0
- package/skills/specset/SKILL.md +27 -120
- package/skills/specset-admin/SKILL.md +5 -9
- package/skills/specset-agent/SKILL.md +0 -2
- package/skills/specset-closeout/SKILL.md +2 -22
- package/skills/specset-projects/SKILL.md +4 -7
- package/skills/specset-rfis/SKILL.md +4 -32
- package/skills/specset-search/SKILL.md +6 -35
- package/skills/specset-submittals/SKILL.md +10 -60
- package/skills/specset-tools/SKILL.md +21 -34
package/package.json
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "specset",
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"description": "Search and manage Specset construction project data with shared skills and MCP tools.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Specset"
|
|
7
|
+
},
|
|
8
|
+
"homepage": "https://docs.specset.com",
|
|
9
|
+
"repository": "https://github.com/specsetai/specset-plugins",
|
|
10
|
+
"license": "Apache-2.0",
|
|
11
|
+
"keywords": [
|
|
12
|
+
"construction",
|
|
13
|
+
"drawings",
|
|
14
|
+
"specifications",
|
|
15
|
+
"submittals",
|
|
16
|
+
"rfis"
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "specset",
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"description": "Search and manage Specset construction project data with shared skills and MCP tools.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Specset",
|
|
7
|
+
"email": "support@specset.com",
|
|
8
|
+
"url": "https://specset.com"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://docs.specset.com",
|
|
11
|
+
"repository": "https://github.com/specsetai/specset-plugins",
|
|
12
|
+
"license": "Apache-2.0",
|
|
13
|
+
"keywords": [
|
|
14
|
+
"construction",
|
|
15
|
+
"drawings",
|
|
16
|
+
"specifications",
|
|
17
|
+
"submittals",
|
|
18
|
+
"rfis"
|
|
19
|
+
],
|
|
20
|
+
"mcpServers": "./.mcp.json",
|
|
21
|
+
"interface": {
|
|
22
|
+
"displayName": "Specset",
|
|
23
|
+
"shortDescription": "Search and manage construction project data",
|
|
24
|
+
"longDescription": "Use Specset skills and MCP tools to search specifications and drawings, inspect project records, manage workflows, and transfer files.",
|
|
25
|
+
"developerName": "Specset",
|
|
26
|
+
"category": "Productivity",
|
|
27
|
+
"capabilities": [
|
|
28
|
+
"Skills",
|
|
29
|
+
"MCP",
|
|
30
|
+
"File transfer"
|
|
31
|
+
],
|
|
32
|
+
"websiteURL": "https://specset.com",
|
|
33
|
+
"privacyPolicyURL": "https://specset.com/privacy",
|
|
34
|
+
"termsOfServiceURL": "https://specset.com/terms",
|
|
35
|
+
"defaultPrompt": [
|
|
36
|
+
"Find the project requirements for this scope.",
|
|
37
|
+
"Compare this submittal with the specifications.",
|
|
38
|
+
"Show me the latest RFIs affecting this work."
|
|
39
|
+
],
|
|
40
|
+
"brandColor": "#1F6FEB"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Specset plugin
|
|
2
|
+
|
|
3
|
+
This is the source template for the universal Specset plugin bundle.
|
|
4
|
+
|
|
5
|
+
- Codex and Claude Code discover the bundled skills and start `specset mcp` over stdio. The CLI owns browser/device authentication and direct local file transfer.
|
|
6
|
+
- ChatGPT and other hosted clients connect to Specset's Streamable HTTP MCP endpoint at `https://<host>/mcp`. OAuth 2.1 consent binds the grant to the selected organization; the server uses the same tool registry and adds host-file upload and signed-download tools.
|
|
7
|
+
|
|
8
|
+
`yarn nx run cli:package` copies the canonical `apps/cli/skills` tree into this plugin template under `apps/cli/publish/plugin/specset`. Keep skill source changes in `apps/cli/skills`; do not hand-edit a staged copy.
|
|
9
|
+
|
|
10
|
+
The hosted transport publishes OAuth protected-resource and authorization-server metadata, supports Client ID Metadata Documents with PKCE, and issues short-lived audience-bound access tokens with rotating refresh tokens. First-party clients may continue to use a Specset session bearer token with an explicit organization header.
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
---
|
|
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 CLI setup or raw GraphQL access, and whenever a specset command fails with auth or org errors.
|
|
4
|
+
allowed-tools: Bash Read AskUserQuestion
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Specset CLI (`specset`)
|
|
8
|
+
|
|
9
|
+
Use this skill to work with a Specset organization from the command line: run GraphQL queries against your data (projects, drawings, specs, submittals, …) and manage authentication and org context.
|
|
10
|
+
|
|
11
|
+
The `specset` CLI handles OAuth login, org switching, and request signing. The exact flag surface of the installed version is authoritative in its own help output — prefer `specset --help` and `specset <command> --help` over memorized flags when something doesn't match this document.
|
|
12
|
+
|
|
13
|
+
## Skill Family
|
|
14
|
+
|
|
15
|
+
This is the core skill — setup and mechanics only. Domain workflows live in sibling skills:
|
|
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 |
|
|
27
|
+
|
|
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
|
+
|
|
30
|
+
## First-Run Setup
|
|
31
|
+
|
|
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
|
+
|
|
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`.
|
|
38
|
+
|
|
39
|
+
Credentials persist across sessions in `~/.config/specset/config.yml` (mode 0600) — setup only needs to happen once per machine.
|
|
40
|
+
|
|
41
|
+
## Running GraphQL
|
|
42
|
+
|
|
43
|
+
Use the `api` subcommand. It accepts the operation via `--query` or stdin, and string variables via `-F key=value` (repeatable). Output is the raw JSON GraphQL response (`{ data, errors }`) — non-zero exit on HTTP errors or `errors[]`.
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# Simple query
|
|
47
|
+
specset api --query '{ me { id email firstName } }'
|
|
48
|
+
|
|
49
|
+
# Query with an ID variable
|
|
50
|
+
specset api \
|
|
51
|
+
--query 'query($id: ID!) { project(id: $id) { id name } }' \
|
|
52
|
+
-F id=abc-123
|
|
53
|
+
|
|
54
|
+
# Long operation via stdin
|
|
55
|
+
specset api <<'GQL'
|
|
56
|
+
query {
|
|
57
|
+
me { id email orgMembers { id org { id slug name } } }
|
|
58
|
+
}
|
|
59
|
+
GQL
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Variable Limitations
|
|
63
|
+
|
|
64
|
+
`-F key=value` only sends **string** values. For non-string args (numbers, booleans, enums, input objects, lists), inline literals directly in the operation rather than parameterizing them. Reserve `-F` for IDs and plain strings.
|
|
65
|
+
|
|
66
|
+
### Schema Discovery
|
|
67
|
+
|
|
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:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
specset api --query '{ __type(name: "Project") { fields { name type { name kind ofType { name } } } } }'
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The same pattern works for input objects (`inputFields`) and the top-level surface (`{ __schema { queryType { fields { name description } } } }`).
|
|
75
|
+
|
|
76
|
+
## Agent Tools and MCP
|
|
77
|
+
|
|
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.
|
|
79
|
+
|
|
80
|
+
## Uploading Files
|
|
81
|
+
|
|
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:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
specset files upload ./document.pdf
|
|
86
|
+
specset files upload ./photo.jpg --type image/jpeg --json
|
|
87
|
+
```
|
|
88
|
+
|
|
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`.
|
|
90
|
+
|
|
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.
|
|
92
|
+
|
|
93
|
+
## Waiting on Background Processing
|
|
94
|
+
|
|
95
|
+
Document imports and other bulk operations return a `BulkAction` and continue in the background. There are no subscriptions over `specset api` — poll with short sleeps and cap your retries:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
specset api --query 'query($id: ID!) {
|
|
99
|
+
bulkAction(id: $id) { totalCount completedCount failedCount }
|
|
100
|
+
}' -F id=<bulk-action-id>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Processing large PDF sets takes minutes; poll every 15–30 seconds and tell the user what's in flight rather than blocking silently.
|
|
104
|
+
|
|
105
|
+
## Safety
|
|
106
|
+
|
|
107
|
+
Confirm with the user before executing mutations that create, update, or delete data. The domain skills note the mutations with non-obvious consequences.
|
|
108
|
+
|
|
109
|
+
## Troubleshooting
|
|
110
|
+
|
|
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.
|
|
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>`.
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: specset-admin
|
|
3
|
+
description: Use when administering a Specset organization — inviting and managing members and roles, or applying whitelabel branding (colors, logos) — for org membership, permissions, or appearance tasks.
|
|
4
|
+
allowed-tools: Bash Read AskUserQuestion
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Org Administration
|
|
8
|
+
|
|
9
|
+
Every operation here takes your org id — get it with `{ me { orgMembers { org { id slug name } } } }` if you don't have it. Inline enums, lists, and input objects in the operation text (`-F` is strings-only; Variable Limitations and Schema Discovery live in the `specset` skill). These mutations change real people's access and the whole org's appearance.
|
|
10
|
+
|
|
11
|
+
## Members
|
|
12
|
+
|
|
13
|
+
Roles (`OrgMemberTeamRole`): `Member`, `Manager`, `Admin`. Statuses (`OrgMemberStatus`): `PendingApproval`, `Active`, `Deactivated`. You need to be an org Admin for most mutations in this skill.
|
|
14
|
+
|
|
15
|
+
List members — filtered flat list or paginated search:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
specset api --query 'query($orgId: ID!) {
|
|
19
|
+
orgMembers(orgId: $orgId, statuses: [Active]) {
|
|
20
|
+
id role status user { id email firstName lastName }
|
|
21
|
+
}
|
|
22
|
+
}' -F orgId=<org-id>
|
|
23
|
+
|
|
24
|
+
specset api --query 'query($orgId: ID!, $search: String) {
|
|
25
|
+
paginatedOrgMembers(orgId: $orgId, search: $search, first: 20) {
|
|
26
|
+
edges { node { id role status user { email } } }
|
|
27
|
+
pageInfo { hasNextPage endCursor totalCount }
|
|
28
|
+
}
|
|
29
|
+
}' -F orgId=<org-id> -F search="smith"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Manage them (all take the **membership** id from the queries above, not the user id):
|
|
33
|
+
|
|
34
|
+
- `updateOrgMember(id: ID!, role: ..., projectIds: [ID!])` — `role` is required even when unchanged, so resend the current role when only touching `projectIds`. `projectIds` scopes a Member to specific projects; omit it to leave project access alone.
|
|
35
|
+
- `approveOrgMember(id: ID!)` — activate a `PendingApproval` member.
|
|
36
|
+
- `deactivateOrgMember(id: ID!)` / `reactivateOrgMember(id: ID!)` — suspend and restore access. Deactivation is reversible and preserves history — prefer it over any form of member deletion.
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
specset api --query 'mutation($id: ID!) {
|
|
40
|
+
updateOrgMember(id: $id, role: Manager) { id role user { email } }
|
|
41
|
+
}' -F id=<org-member-id>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Invites
|
|
45
|
+
|
|
46
|
+
- `orgInvites(orgId: ID!)` / `paginatedOrgInvites(orgId: ID!, first: Int, after: String)` — pending invites; select `{ id email role inviteUrl }`.
|
|
47
|
+
- `createOrgInvite(orgId: ID!, email: String!, role: ..., projectIds: [ID!])` — sends the invite email; `projectIds` (optional, inline the list) pre-scopes a Member to specific projects. Set `role` deliberately — don't default people to more access than they need. Share the returned `inviteUrl` if the email might not land.
|
|
48
|
+
- `updateOrgInvite(id: ID!, role: ..., projectIds: [ID!])` — `role` is required.
|
|
49
|
+
- `deleteOrgInvite(id: ID!)` — revoke a pending invite.
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
specset api --query 'mutation($orgId: ID!, $email: String!) {
|
|
53
|
+
createOrgInvite(orgId: $orgId, email: $email, role: Member) {
|
|
54
|
+
id email role inviteUrl
|
|
55
|
+
}
|
|
56
|
+
}' -F orgId=<org-id> -F email=alex@example.com
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Branding Your Organization
|
|
60
|
+
|
|
61
|
+
Appearance changes require the **whitelabel** plan feature: without it the mutation is rejected for org admins, and custom branding won't render even if set. If you hit an authorization error on `appearance`, or branding saves but doesn't show, email support@specset.com about whitelabel access.
|
|
62
|
+
|
|
63
|
+
### Colors
|
|
64
|
+
|
|
65
|
+
Theming hangs off five anchor colors; the app derives the full light and dark palettes from them:
|
|
66
|
+
|
|
67
|
+
| Anchor | Drives |
|
|
68
|
+
|---|---|
|
|
69
|
+
| `brand` | Primary buttons, links, navigation highlights |
|
|
70
|
+
| `magic` | AI/agent accent surfaces |
|
|
71
|
+
| `success` | Positive states |
|
|
72
|
+
| `danger` | Destructive/error states |
|
|
73
|
+
| `warning` | Caution states |
|
|
74
|
+
|
|
75
|
+
Each is a `#rrggbb` hex string. `appearance` is an input object, so inline it. **`updateOrg` replaces the whole appearance object** — include every anchor you want on every call; any you omit are cleared back to the stock theme.
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
specset api --query 'mutation($id: ID!) {
|
|
79
|
+
updateOrg(id: $id, appearance: { anchors: {
|
|
80
|
+
brand: "#003087", magic: "#0085CA", success: "#4B8320",
|
|
81
|
+
danger: "#B71300", warning: "#FF8400"
|
|
82
|
+
} }) {
|
|
83
|
+
id
|
|
84
|
+
appearance { anchors { brand magic success danger warning } }
|
|
85
|
+
}
|
|
86
|
+
}' -F id=<org-id>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`brand` becomes the button and link color on white backgrounds, so it needs to hold white text. Branding applies to every member of the org — confirm the palette with the user before writing it.
|
|
90
|
+
|
|
91
|
+
### Logos
|
|
92
|
+
|
|
93
|
+
Two slots, both attached by file id on `updateOrg`:
|
|
94
|
+
|
|
95
|
+
- `logoId` — square mark; shown where space is tight (collapsed navigation).
|
|
96
|
+
- `wideLogoId` — horizontal mark + wordmark; shown in the expanded header.
|
|
97
|
+
|
|
98
|
+
Upload the image first following Uploading Files in the `specset` skill to get a file id, then attach:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
specset api --query 'mutation($id: ID!, $fileId: ID!) {
|
|
102
|
+
updateOrg(id: $id, wideLogoId: $fileId) { id wideLogo { id } }
|
|
103
|
+
}' -F id=<org-id> -F fileId=<file-id>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Use `logoId: $fileId` for the square mark. Passing `null` (inline) clears a slot.
|
|
107
|
+
|
|
108
|
+
## Safety
|
|
109
|
+
|
|
110
|
+
- For role changes, deactivations, and invite deletions, echo the affected person's email back to the user first — these change real access immediately.
|
|
111
|
+
- `updateOrg` rejects `maxCredits` and `skus` — those fields are managed by Specset. Send only the fields you mean to change (plus the full `appearance` object when changing any part of it).
|
|
112
|
+
- Creating or deleting organizations, SSO, and email-domain management are out of scope for this skill — direct the user to support@specset.com.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: specset-agent
|
|
3
|
+
description: Ask Specset's in-project AI agent questions that need deep project understanding — schedules, spec interpretation, cross-document analysis — via `specset agent chat`. Use when raw GraphQL isn't enough or when unsure how to answer from data.
|
|
4
|
+
allowed-tools: Bash Read AskUserQuestion
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Specset Agent Chat
|
|
8
|
+
|
|
9
|
+
Specset runs its own project-aware AI agent with full access to a project's specs, drawings, submittals, RFIs, and closeout data. `specset agent chat` sends it a message and waits for the full response — use it for questions that need Specset's own project understanding rather than raw data access.
|
|
10
|
+
|
|
11
|
+
## When to Delegate vs. Query Directly
|
|
12
|
+
|
|
13
|
+
Delegate to the agent when the answer requires reading and interpreting documents:
|
|
14
|
+
|
|
15
|
+
- Equipment and schedule extraction ("what mechanical equipment is scheduled on level 2?")
|
|
16
|
+
- Spec interpretation ("what are the submittal requirements in section 08 71 00?")
|
|
17
|
+
- Drawing analysis ("which sheets show the roof drainage details?")
|
|
18
|
+
- Drafting a submittal log from the specs (see the `specset-submittals` skill for applying the result)
|
|
19
|
+
- Comparing plan sets or revisions
|
|
20
|
+
|
|
21
|
+
Query directly with `specset api` when the user wants structured records — lists, lookups, creates, updates. The `specset-search`, `specset-projects`, `specset-submittals`, `specset-rfis`, and `specset-closeout` skills cover those workflows.
|
|
22
|
+
|
|
23
|
+
## Driving a Conversation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# Ask a question scoped to a project
|
|
27
|
+
specset agent chat --project <projectId> -m "What mechanical equipment is scheduled on level 2?"
|
|
28
|
+
|
|
29
|
+
# Continue the same conversation (thread id is in the first response)
|
|
30
|
+
specset agent chat --thread <threadId> -m "Which of those have submittals?"
|
|
31
|
+
|
|
32
|
+
# Review a past conversation
|
|
33
|
+
specset agent show <threadId>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Use `--json` for machine-readable output (all commands accept it). Long-running questions may need `--timeout <seconds>` — document analysis can take a few minutes.
|
|
37
|
+
|
|
38
|
+
Find a project id first if you don't have one:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
specset api --query 'query { projects { id name } }'
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Notes
|
|
45
|
+
|
|
46
|
+
- Keep one thread per topic: follow-ups via `--thread` give the agent the prior context and produce better answers than fresh threads.
|
|
47
|
+
- The agent may propose changes (creating submittals, updating records) but those are applied through the Specset UI thread where the user approves them — CLI chat is best treated as read/analysis oriented. To make changes headlessly, use the mutations documented in the domain skills instead.
|
|
48
|
+
- The agent answers from the project you scope it to; double-check the `--project` id when results look unrelated.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: specset-closeout
|
|
3
|
+
description: Use when managing Specset closeout and facilities data — assets, locations, products, companies, warranties, and maintenance tasks, plus their documents and links — for closeout package or O&M/facilities tasks.
|
|
4
|
+
allowed-tools: Bash Read AskUserQuestion
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Closeout & Facilities Data
|
|
8
|
+
|
|
9
|
+
Run every operation with `specset api`. All ids are plain UUIDs. Inline enums, numbers, lists, and input objects in the operation text (`-F` is strings-only; Variable Limitations and Schema Discovery live in the `specset` skill).
|
|
10
|
+
|
|
11
|
+
## Entity Model
|
|
12
|
+
|
|
13
|
+
**Locations** are the spatial scaffold (site → building → level → room), nesting via `parentId`. **Systems** are disciplines and equipment groups (HVAC, electrical, fire protection), also nested. **Products** are the spec'd make/models. **Assets** are installed instances: each asset sits at one location, is optionally an instance of one product with one responsible **company**, and can join multiple systems through role-tagged memberships. **Warranties** and **maintenance tasks** hang off assets, and maintenance tasks accumulate **completions** (service-log entries). Everything is scoped to a project.
|
|
14
|
+
|
|
15
|
+
## The Per-Entity Pattern
|
|
16
|
+
|
|
17
|
+
Asset, Location, Product, Company, System, Warranty, and MaintenanceTask share one surface:
|
|
18
|
+
|
|
19
|
+
| Operation | Shape |
|
|
20
|
+
|---|---|
|
|
21
|
+
| `<entity>(id: ID!)` | Single record (`null` if missing or in another org) |
|
|
22
|
+
| `paginated<Entities>(projectId: ID!, limit: Int, offset: Int, ...)` | Returns `{ items { ... } totalFilteredCount }` |
|
|
23
|
+
| `search<Entities>(projectId: ID!, search: String)` | Fuzzy text lookup |
|
|
24
|
+
| `create<Entity>(input: ...)` / `update<Entity>(input: ...)` | Update inputs take `id` plus only the fields to change |
|
|
25
|
+
| `delete<Entity>(id: ID!)` | Hard delete — see Safety |
|
|
26
|
+
|
|
27
|
+
Real deviations:
|
|
28
|
+
|
|
29
|
+
- **Asset** — create requires `projectId` + `locationId`. `paginatedAssets` also takes `search` and a `filters` input (`statuses`, `locationIds`, `productIds`, `systemIds`, `responsibleCompanyIds`, `parentIds`, `archived`) for cross-entity tracing.
|
|
30
|
+
- **Warranty / MaintenanceTask** — asset-scoped: create inputs take `assetId` (not `projectId`); no `search<Entities>` query, and `search` on their paginated lists is ignored — scope by asset with `warrantiesByAsset(assetId)` / `maintenanceTasksByAsset(assetId)` instead.
|
|
31
|
+
- Batch deletes exist for some entities (`deleteLocations(ids)`, `deleteCompanies(ids)`, `deleteSystems(ids)`, `deleteWarranties(ids)`, `deleteMaintenanceTasks(ids)`) — same confirmation rules as single deletes.
|
|
32
|
+
|
|
33
|
+
Introspect the input object before composing any create or update, e.g. `{ __type(name: "CreateAssetInput") { inputFields { name type { name kind ofType { name } } } } }`.
|
|
34
|
+
|
|
35
|
+
Prefer `update<Entity>` for changes: pass `id` plus only what changes; pass `null` to clear an optional relationship (e.g. `productId: null`). Update inputs for Asset, Location, Product, Company, and System accept `archived: true` — a reversible soft-archive that drops the record from default lists. Prefer it over delete.
|
|
36
|
+
|
|
37
|
+
## Asset Links
|
|
38
|
+
|
|
39
|
+
Three `set*` mutations each **replace** the full id set for the given `(assetId, role)` tuple — anything previously linked at that role and not in the new list is removed; pass `[]` to clear. Inline the enum and the id list:
|
|
40
|
+
|
|
41
|
+
- `setAssetSystems(assetId: ID!, role: ..., systemIds: [ID!]!)` — roles: `Primary`, `Secondary`, `Control`, `MonitoredBy`, `Other`. Read back with `assetSystemMemberships(assetId)`.
|
|
42
|
+
- `setAssetSubmittals(assetId: ID!, relationship: ..., submittalIds: [ID!]!)` — relationships: `Source`, `Revision`, `Substitution`, `Addendum`. Read back with `assetSubmittalLinks(assetId)`.
|
|
43
|
+
- `setAssetDrawingSheets(assetId: ID!, role: ..., drawingSheetIds: [ID!]!)` — roles include `Plan`, `Schedule`, `Diagram`, `Detail`, `AsBuilt`, `Other`. Read back with `assetDrawingSheetLinks(assetId)`.
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
specset api --query 'mutation($assetId: ID!, $systemId: ID!) {
|
|
47
|
+
setAssetSystems(assetId: $assetId, role: Primary, systemIds: [$systemId]) {
|
|
48
|
+
id role system { id name }
|
|
49
|
+
}
|
|
50
|
+
}' -F assetId=<asset-id> -F systemId=<system-id>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Entity Documents
|
|
54
|
+
|
|
55
|
+
Each entity links to project documents through a role-tagged pivot. Find document ids with `documents(projectId: ID!) { id title }`. The pattern (uniform across Location, Product, Company, System, Warranty, MaintenanceTask; substitute the entity name):
|
|
56
|
+
|
|
57
|
+
- `<entity>Documents(<entity>Id: ID!)` — list the pivots. Select `{ id roles document { id title } }`.
|
|
58
|
+
- `set<Entity>DocumentRoles(<entity>Id: ID!, documentId: ID!, roles: [...]!)` — upsert the linkage; an empty `roles` list removes it (and the document itself if orphaned). Roles are per-entity enums — e.g. `ProductDocumentRole` is `Datasheet`, `InstallationManual`, `OperationManual`, `MaintenanceManual`, `Warranty`, `SubmittalPackage`, `Other`; introspect each entity's enum before use.
|
|
59
|
+
- `delete<Entity>Document(id: ID!)` — takes the **pivot** id from the list query, not the document id.
|
|
60
|
+
|
|
61
|
+
Asset is the exception: it has `assetDocuments(assetId)` and `deleteAssetDocument(id)` but no set-roles mutation.
|
|
62
|
+
|
|
63
|
+
## Documentation Blocks
|
|
64
|
+
|
|
65
|
+
Asset, Location, Product, Company, and System each carry a markdown documentation block used by closeout exports:
|
|
66
|
+
|
|
67
|
+
- `update<Entity>Documentation(id: ID!, markdown: String!)` — replace the block. Pass `markdown` via `-F` (it's a plain string).
|
|
68
|
+
- `mark<Entity>DocumentationComplete(id: ID!)` — attest the block is current (clears it from stale-documentation rollups).
|
|
69
|
+
|
|
70
|
+
## Maintenance Completions
|
|
71
|
+
|
|
72
|
+
Service history lives on completions under each task:
|
|
73
|
+
|
|
74
|
+
- `maintenanceTaskCompletions(maintenanceTaskId: ID!)` — select `{ id performedDate dueDate performedByName result }`.
|
|
75
|
+
- `createMaintenanceTaskCompletion(input: { maintenanceTaskId, performedDate, ... })` — `performedDate` is required; optional `dueDate`, `performedByName`, `performedByCompanyId`, `result`. Introspect `CreateMaintenanceTaskCompletionInput` for the full set.
|
|
76
|
+
- `updateMaintenanceTaskCompletion(input: { id, ... })` / `deleteMaintenanceTaskCompletion(id: ID!)`.
|
|
77
|
+
- Completion documents: `addMaintenanceTaskCompletionDocument(completionId: ID!, documentId: ID!)` and `deleteMaintenanceTaskCompletionDocument(id: ID!)` (pivot id).
|
|
78
|
+
|
|
79
|
+
## Dedup / Re-Pointing Recipe
|
|
80
|
+
|
|
81
|
+
For duplicate products P1 (loser) and P2 (survivor):
|
|
82
|
+
|
|
83
|
+
1. `paginatedAssets(projectId: ..., filters: { productIds: ["<p1-id>"] })` — collect affected assets.
|
|
84
|
+
2. `updateAsset(input: { id: ..., productId: "<p2-id>" })` per asset.
|
|
85
|
+
3. Re-run step 1 and confirm zero results.
|
|
86
|
+
4. `updateProduct(input: { id: "<p1-id>", archived: true })` — archive the loser; delete only if it is clearly an unreferenced mistake.
|
|
87
|
+
|
|
88
|
+
The same shape works for duplicate companies, locations, and systems.
|
|
89
|
+
|
|
90
|
+
## Safety
|
|
91
|
+
|
|
92
|
+
- Deletes here are hard deletes with no undo — name the exact record (tag/name + id) when confirming.
|
|
93
|
+
- Prefer `update` over delete-and-recreate, and `archived: true` over delete: deleting a product, location, or company silently nulls the references on assets that pointed at it. Check impact first with the filtered list queries.
|
|
94
|
+
- Removing a document linkage can delete the underlying document if nothing else references it — say so when confirming.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: specset-projects
|
|
3
|
+
description: Use when standing up a new Specset project or adding document sets to one — create the project, upload spec/drawing PDFs, monitor processing, and publish spec sets and drawing sets.
|
|
4
|
+
allowed-tools: Bash Read AskUserQuestion
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Specset Projects
|
|
8
|
+
|
|
9
|
+
This skill mutates data — confirm the project name and which files belong to it before creating anything. Inline enums, ints, lists, and input objects in the operation text (`-F` is strings-only; Variable Limitations and Schema Discovery live in the `specset` skill).
|
|
10
|
+
|
|
11
|
+
## Reading Projects
|
|
12
|
+
|
|
13
|
+
```graphql
|
|
14
|
+
projects(orgId: ID!, archived: Boolean, includeAll: Boolean = false): [Project!]!
|
|
15
|
+
paginatedProjects(orgId: ID!, search: String, limit: Int, offset: Int, archived: Boolean = false): ProjectPage! # { items totalFilteredCount }
|
|
16
|
+
project(id: ID!): Project!
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**`projects` is membership-filtered by default** — it returns only projects where the caller is a project member. Inline `includeAll: true` to list every project in the org. Use `paginatedProjects` with a `search` string to find a project by name.
|
|
20
|
+
|
|
21
|
+
## Creating a Project
|
|
22
|
+
|
|
23
|
+
`createProject` does **not** add the creator as a project member, so the new project won't appear in the default `projects` list — fetch it by `project(id)`, and add membership as a separate step, mirroring the UI.
|
|
24
|
+
|
|
25
|
+
1. Create it:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
specset api --query 'mutation($orgId: ID!, $name: String!) {
|
|
29
|
+
createProject(orgId: $orgId, name: $name) { id name }
|
|
30
|
+
}' -F orgId=<org-id> -F name='Riverside Medical Center'
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
2. Get your own user id with `{ me { id } }`, then add yourself (and anyone else the user names) as a member. `role` is an optional `OrgMemberTeamRole` enum — `Member`, `Manager`, or `Admin` — inlined when specified:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
specset api --query 'mutation($projectId: ID!, $userId: ID!) {
|
|
37
|
+
addProjectMember(projectId: $projectId, userId: $userId, role: Member) { id }
|
|
38
|
+
}' -F projectId=<project-id> -F userId=<user-id>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Importing Documents
|
|
42
|
+
|
|
43
|
+
1. Upload each PDF following Uploading Files in the `specset` skill. Each `completeUpload` returns a **cloud file id** — collect them.
|
|
44
|
+
2. Register the uploads on the project in a single call. The input object is inlined; ID variables are fine as its leaves:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
specset api --query 'mutation($projectId: ID!, $f1: ID!, $f2: ID!) {
|
|
48
|
+
createProjectDocuments(input: {
|
|
49
|
+
projectId: $projectId
|
|
50
|
+
cloudFileIds: [$f1, $f2]
|
|
51
|
+
autoImport: true
|
|
52
|
+
}) { id totalCount completedCount failedCount }
|
|
53
|
+
}' -F projectId=<project-id> -F f1=<cloud-file-id> -F f2=<cloud-file-id>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
3. It returns a **BulkAction** — poll it per Waiting on Background Processing in the `specset` skill. Processing extracts each document's outline and content, indexes it for search, and splits it into candidate spec files and drawing files by page range.
|
|
57
|
+
|
|
58
|
+
### `autoImport`: publish automatically or curate
|
|
59
|
+
|
|
60
|
+
- `autoImport: true` — every generated spec/drawing file is classified and published into default sets automatically. Use when the user wants everything live immediately.
|
|
61
|
+
- Omit it (or `false`) — files land **unpromoted** for manual curation below. Use when the user wants to control set titles, membership, or ordering.
|
|
62
|
+
|
|
63
|
+
Ask the user when their intent is ambiguous — this choice changes what gets published. The input also supports folders: `parentFolderId` / `newFolderName` for flat imports, or `folderTree` + `files` to mirror a directory structure — introspect `CreateProjectDocumentsInput` for those shapes.
|
|
64
|
+
|
|
65
|
+
## Publishing Spec and Drawing Sets
|
|
66
|
+
|
|
67
|
+
Publishing promotes processed files into a SpecSet or DrawingSet container. After a non-autoImport run finishes:
|
|
68
|
+
|
|
69
|
+
1. Review what processing generated, and summarize it to the user before publishing:
|
|
70
|
+
|
|
71
|
+
```graphql
|
|
72
|
+
unpromotedSpecFiles(projectId: ID!): [SpecFile!]! # { id title }
|
|
73
|
+
unpromotedDrawingFiles(projectId: ID!): [DrawingFile!]! # { id title }
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Empty lists mean processing hasn't finished, or `autoImport` already promoted everything.
|
|
77
|
+
|
|
78
|
+
2. Create a set and assign files into it (`issuedAt` is an optional DateTime — inline an ISO literal like `issuedAt: "2026-06-01T00:00:00Z"`):
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
specset api --query 'mutation($projectId: ID!, $title: String!) {
|
|
82
|
+
createSpecSet(title: $title, projectId: $projectId) { id title }
|
|
83
|
+
}' -F projectId=<project-id> -F title='Issued for Construction'
|
|
84
|
+
|
|
85
|
+
specset api --query 'mutation($specSetId: ID!, $f1: ID!) {
|
|
86
|
+
assignSpecFilesToSpecSet(specSetId: $specSetId, specFileIds: [$f1]) { id }
|
|
87
|
+
}' -F specSetId=<spec-set-id> -F f1=<spec-file-id>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The drawing side is symmetric: `createDrawingSet(title: String!, projectId: ID!, issuedAt: DateTime)` then `assignDrawingFilesToDrawingSet(drawingSetId: ID!, drawingFileIds: [ID!]!)`.
|
|
91
|
+
|
|
92
|
+
3. When a project has multiple sets (e.g. an addendum on top of the base issue), set which one governs. Pass the **complete** list of set ids, highest precedence first:
|
|
93
|
+
|
|
94
|
+
```graphql
|
|
95
|
+
updateSpecSetPrecedence(projectId: ID!, specSetIds: [ID!]!): [SpecSet!]!
|
|
96
|
+
updateDrawingSetPrecedence(projectId: ID!, drawingSetIds: [ID!]!): [DrawingSet!]!
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
4. Verify and summarize — query `project(id)` (not the membership-filtered `projects` list) with `specSets { id title }` and `drawingSets { id title }`, and report what was created.
|
|
100
|
+
|
|
101
|
+
## Archiving and Deleting
|
|
102
|
+
|
|
103
|
+
- **Prefer `archiveProject(id: ID!)`** — reversible via `unarchiveProject(id: ID!)`. Archived projects drop out of default lists but keep all data.
|
|
104
|
+
- **`deleteProject(id: ID!)` permanently destroys the entire project** — documents, specs, drawings, submittals, RFIs, everything. Confirm with the user, naming the exact project, before running it. If they just want it out of the way, archive instead.
|
|
105
|
+
|
|
106
|
+
## Troubleshooting
|
|
107
|
+
|
|
108
|
+
- Authorization error on a mutation — the user lacks that permission; the same rules as the UI apply. Surface the message, don't retry.
|
|
109
|
+
- A freshly created project "missing" from `projects` — that's the membership gotcha above, not a failure.
|
|
110
|
+
- A document reports a failed processing status on the BulkAction — report it to the user; re-uploading is their call.
|