syntaur 0.12.0 → 0.14.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.
Files changed (82) hide show
  1. package/.claude-plugin/plugin.json +5 -1
  2. package/README.md +23 -0
  3. package/dashboard/dist/assets/{_basePickBy-XOV2F7dX.js → _basePickBy-qEDLAY_J.js} +1 -1
  4. package/dashboard/dist/assets/{_baseUniq-Dccfk3fK.js → _baseUniq-7tDs18Gn.js} +1 -1
  5. package/dashboard/dist/assets/{arc-TwJ5RF-H.js → arc-Brdw0dYW.js} +1 -1
  6. package/dashboard/dist/assets/{architectureDiagram-2XIMDMQ5-CpkVJudw.js → architectureDiagram-2XIMDMQ5-BiEdLT9m.js} +1 -1
  7. package/dashboard/dist/assets/{blockDiagram-WCTKOSBZ-Ck6VQ4Fh.js → blockDiagram-WCTKOSBZ-HC-j_hPZ.js} +1 -1
  8. package/dashboard/dist/assets/{c4Diagram-IC4MRINW-B5V7YG03.js → c4Diagram-IC4MRINW-DA_rWbl7.js} +1 -1
  9. package/dashboard/dist/assets/channel-D2-caOn6.js +1 -0
  10. package/dashboard/dist/assets/{chunk-4BX2VUAB-rBdyOxAe.js → chunk-4BX2VUAB-CF3hguyj.js} +1 -1
  11. package/dashboard/dist/assets/{chunk-55IACEB6-DKlNXjYt.js → chunk-55IACEB6-BTeCeIc_.js} +1 -1
  12. package/dashboard/dist/assets/{chunk-FMBD7UC4-Cqm0PtUI.js → chunk-FMBD7UC4-8qVvxmks.js} +1 -1
  13. package/dashboard/dist/assets/{chunk-JSJVCQXG-CQyXQcG3.js → chunk-JSJVCQXG-DCf8n7EO.js} +1 -1
  14. package/dashboard/dist/assets/{chunk-KX2RTZJC-DmqjHPpJ.js → chunk-KX2RTZJC-CcpjTmME.js} +1 -1
  15. package/dashboard/dist/assets/{chunk-NQ4KR5QH-CHXoBcYa.js → chunk-NQ4KR5QH-BnFPKo4-.js} +1 -1
  16. package/dashboard/dist/assets/{chunk-QZHKN3VN-Dd4sChBS.js → chunk-QZHKN3VN-IPMVLLv2.js} +1 -1
  17. package/dashboard/dist/assets/{chunk-WL4C6EOR-D-QLnkY8.js → chunk-WL4C6EOR-D1pQmHqX.js} +1 -1
  18. package/dashboard/dist/assets/classDiagram-VBA2DB6C-HFOb_XEt.js +1 -0
  19. package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-HFOb_XEt.js +1 -0
  20. package/dashboard/dist/assets/clone-BrYOwgIq.js +1 -0
  21. package/dashboard/dist/assets/{cose-bilkent-S5V4N54A-VZi6uV56.js → cose-bilkent-S5V4N54A-tvJjvBQr.js} +1 -1
  22. package/dashboard/dist/assets/{dagre-KLK3FWXG-CtT97Qhx.js → dagre-KLK3FWXG-qp1vt6LY.js} +1 -1
  23. package/dashboard/dist/assets/{diagram-E7M64L7V-BjYTNNHb.js → diagram-E7M64L7V-VF5eizNr.js} +1 -1
  24. package/dashboard/dist/assets/{diagram-IFDJBPK2-qj8_r6fb.js → diagram-IFDJBPK2-C3pCbJ1e.js} +1 -1
  25. package/dashboard/dist/assets/{diagram-P4PSJMXO-DlfWo7Vt.js → diagram-P4PSJMXO-D-taXZwH.js} +1 -1
  26. package/dashboard/dist/assets/{erDiagram-INFDFZHY-Ch_nApTv.js → erDiagram-INFDFZHY-QjY-NHVs.js} +1 -1
  27. package/dashboard/dist/assets/{flowDiagram-PKNHOUZH-EOVsqNyi.js → flowDiagram-PKNHOUZH-FjO8P5Tq.js} +1 -1
  28. package/dashboard/dist/assets/{ganttDiagram-A5KZAMGK-CS-lx0B-.js → ganttDiagram-A5KZAMGK-Bsn7CyK3.js} +1 -1
  29. package/dashboard/dist/assets/{gitGraphDiagram-K3NZZRJ6-Ba6RDzPy.js → gitGraphDiagram-K3NZZRJ6-BZ8DjhnF.js} +1 -1
  30. package/dashboard/dist/assets/{graph-BKaAyLk-.js → graph-AdliIYqX.js} +1 -1
  31. package/dashboard/dist/assets/{index-BTWPZaIA.css → index-BoR_oyUZ.css} +1 -1
  32. package/dashboard/dist/assets/index-RCBI4aX2.js +525 -0
  33. package/dashboard/dist/assets/{infoDiagram-LFFYTUFH-Cr20xEsd.js → infoDiagram-LFFYTUFH-BfZjLA86.js} +1 -1
  34. package/dashboard/dist/assets/{ishikawaDiagram-PHBUUO56-CuILV9ph.js → ishikawaDiagram-PHBUUO56-BSgSGMp5.js} +1 -1
  35. package/dashboard/dist/assets/{journeyDiagram-4ABVD52K-CO-l7Mpm.js → journeyDiagram-4ABVD52K-BlQ7qeMC.js} +1 -1
  36. package/dashboard/dist/assets/{kanban-definition-K7BYSVSG-CcirPw_S.js → kanban-definition-K7BYSVSG-CifO0AUu.js} +1 -1
  37. package/dashboard/dist/assets/{layout-BXvr8dMw.js → layout-B9r-gpD9.js} +1 -1
  38. package/dashboard/dist/assets/{linear-B85hgYnX.js → linear-CaaIRxVZ.js} +1 -1
  39. package/dashboard/dist/assets/{mermaid.core-DcZN9Xp2.js → mermaid.core-C8wCWYw-.js} +4 -4
  40. package/dashboard/dist/assets/{mindmap-definition-YRQLILUH-Bxi6p5_3.js → mindmap-definition-YRQLILUH-BpX5T7iT.js} +1 -1
  41. package/dashboard/dist/assets/{pieDiagram-SKSYHLDU-DOSvU0UD.js → pieDiagram-SKSYHLDU-BNGYtWrc.js} +1 -1
  42. package/dashboard/dist/assets/{quadrantDiagram-337W2JSQ-Cmo-5tW9.js → quadrantDiagram-337W2JSQ-CR4GeM9D.js} +1 -1
  43. package/dashboard/dist/assets/{requirementDiagram-Z7DCOOCP-Dw8alwuI.js → requirementDiagram-Z7DCOOCP-CXrPkMLl.js} +1 -1
  44. package/dashboard/dist/assets/{sankeyDiagram-WA2Y5GQK-ZPB-rzOg.js → sankeyDiagram-WA2Y5GQK-CIzof7Dm.js} +1 -1
  45. package/dashboard/dist/assets/{sequenceDiagram-2WXFIKYE-Ct8bkK2h.js → sequenceDiagram-2WXFIKYE-BJZLt0O2.js} +1 -1
  46. package/dashboard/dist/assets/{stateDiagram-RAJIS63D-DnGOXlmx.js → stateDiagram-RAJIS63D-x9gC6zy6.js} +1 -1
  47. package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-CN5Dcv7u.js +1 -0
  48. package/dashboard/dist/assets/{timeline-definition-YZTLITO2-AUFmbJjr.js → timeline-definition-YZTLITO2-D_ZSQn1K.js} +1 -1
  49. package/dashboard/dist/assets/{treemap-KZPCXAKY-Dw5FDFAV.js → treemap-KZPCXAKY-LPpMEco4.js} +1 -1
  50. package/dashboard/dist/assets/{vennDiagram-LZ73GAT5-DfxicU4N.js → vennDiagram-LZ73GAT5-CRFZQpr7.js} +1 -1
  51. package/dashboard/dist/assets/{xychartDiagram-JWTSCODW--9N0V1r_.js → xychartDiagram-JWTSCODW-Bivutynw.js} +1 -1
  52. package/dashboard/dist/index.html +2 -2
  53. package/dist/dashboard/server.js +419 -84
  54. package/dist/dashboard/server.js.map +1 -1
  55. package/dist/db/leases-db.d.ts +227 -0
  56. package/dist/db/leases-db.js +679 -0
  57. package/dist/db/leases-db.js.map +1 -0
  58. package/dist/index.js +1825 -445
  59. package/dist/index.js.map +1 -1
  60. package/package.json +1 -1
  61. package/platforms/claude-code/.claude-plugin/plugin.json +5 -1
  62. package/platforms/claude-code/skills/claim-resource/SKILL.md +89 -0
  63. package/platforms/claude-code/skills/extend-resource/SKILL.md +83 -0
  64. package/platforms/claude-code/skills/list-resources/SKILL.md +74 -0
  65. package/platforms/claude-code/skills/release-resource/SKILL.md +65 -0
  66. package/platforms/claude-code/skills/syntaur-protocol/SKILL.md +1 -0
  67. package/platforms/codex/skills/claim-resource/SKILL.md +89 -0
  68. package/platforms/codex/skills/extend-resource/SKILL.md +83 -0
  69. package/platforms/codex/skills/list-resources/SKILL.md +74 -0
  70. package/platforms/codex/skills/release-resource/SKILL.md +65 -0
  71. package/platforms/codex/skills/syntaur-protocol/SKILL.md +1 -0
  72. package/skills/claim-resource/SKILL.md +89 -0
  73. package/skills/extend-resource/SKILL.md +83 -0
  74. package/skills/list-resources/SKILL.md +74 -0
  75. package/skills/release-resource/SKILL.md +65 -0
  76. package/skills/syntaur-protocol/SKILL.md +1 -0
  77. package/dashboard/dist/assets/channel-AEMTX70R.js +0 -1
  78. package/dashboard/dist/assets/classDiagram-VBA2DB6C-LzuOAGzu.js +0 -1
  79. package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-LzuOAGzu.js +0 -1
  80. package/dashboard/dist/assets/clone-Cw63QGUJ.js +0 -1
  81. package/dashboard/dist/assets/index-Dk9PBm5B.js +0 -510
  82. package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-COGzlZxn.js +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "syntaur",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "Project workflow CLI with dashboard, Claude Code plugin, and Codex plugin",
5
5
  "homepage": "https://github.com/prong-horn/syntaur#readme",
6
6
  "repository": {
@@ -25,6 +25,10 @@
25
25
  "./skills/add-memory",
26
26
  "./skills/list-assignments",
27
27
  "./skills/log-progress",
28
- "./skills/set-workspace"
28
+ "./skills/set-workspace",
29
+ "./skills/claim-resource",
30
+ "./skills/release-resource",
31
+ "./skills/extend-resource",
32
+ "./skills/list-resources"
29
33
  ]
30
34
  }
@@ -0,0 +1,89 @@
1
+ ---
2
+ name: claim-resource
3
+ description: Claim an idle member of a Syntaur leases inventory (dev env, test DB, API key, named lock, etc.) for the current assignment session. Triggers on "/claim-resource", "claim a dev env", "lease an environment", "grab a test DB", "claim a slot", or when the user wants to coordinate access to a shared finite resource.
4
+ license: MIT
5
+ ---
6
+
7
+ # Claim Resource
8
+
9
+ Claim an idle member of a Syntaur leases inventory and persist the lease into `.syntaur/context.json`. The claimed member's connection metadata (URL, ssh string, port, etc.) is printed back so the agent can use it immediately.
10
+
11
+ The claim is **fail-fast** — if no member is idle, the command exits non-zero. If a long-running blocking claim is wanted, the agent retries externally with backoff.
12
+
13
+ ## Usage
14
+
15
+ User arguments: `$ARGUMENTS`
16
+
17
+ - `<inventory_slug>` (required) — which inventory to claim from
18
+ - `--ttl <duration>` (optional) — lease TTL, e.g. `15m`, `2h`. Defaults to the inventory's `default_ttl`.
19
+ - `--for <tag>` (optional) — free-form requester tag (defaults to the active assignment slug if omitted)
20
+
21
+ Examples:
22
+ - `/claim-resource dev-envs`
23
+ - `/claim-resource dev-envs --ttl 1h`
24
+ - `/claim-resource prod-migration-lock --ttl 5m --for "migration-2026-05-12"`
25
+
26
+ ## Workflow
27
+
28
+ ### Step 1: Parse arguments
29
+
30
+ Extract `<inventory_slug>` (required, first positional), plus optional `--ttl <duration>` and `--for <tag>`.
31
+
32
+ ### Step 2: Pre-check assignment context
33
+
34
+ Read `.syntaur/context.json` from the current working directory. If the file is missing, OR has neither `sessionId` nor any of `projectSlug`/`assignmentSlug`/`assignmentDir`, abort with:
35
+
36
+ > "No active Syntaur context in this workspace. Grab an assignment first (`/grab-assignment <project> <slug>`) or restart the session so the SessionStart hook can populate `.syntaur/context.json`."
37
+
38
+ This guard exists because writing a leases-only context file would trip `syntaur doctor`'s workspace check.
39
+
40
+ If `--for` was not provided, derive its value from the context: prefer `assignmentSlug` if present, otherwise `sessionId`.
41
+
42
+ ### Step 3: Run the claim CLI
43
+
44
+ ```bash
45
+ syntaur lease claim "$INVENTORY_SLUG" --json \
46
+ ${TTL:+--ttl "$TTL"} \
47
+ --for "$TAG"
48
+ ```
49
+
50
+ (Use `dangerouslyDisableSandbox: true` for the Bash call — the CLI writes to `~/.syntaur/syntaur.db`.)
51
+
52
+ Possible non-zero exits and how to surface them to the user:
53
+
54
+ | stderr fragment | meaning | how to respond |
55
+ |---|---|---|
56
+ | `no idle members in '<slug>'` | `NoIdleMemberError` — pool exhausted | Tell user; suggest waiting / retrying later or asking a human to add capacity. |
57
+ | `contention timeout on '<slug>'; retry` | `LeaseContentionError` — busy timeout | Retry once after ~1s. If it fails again, surface the error. |
58
+ | `inventory '<slug>' not found` | typo or inventory doesn't exist | Tell user; list with `syntaur lease list`. |
59
+
60
+ ### Step 4: Parse the claim result
61
+
62
+ Parse the stdout JSON: `{ lease_id, inventory_slug, member_id, member_gen, granted_at, expires_at, metadata }`.
63
+
64
+ ### Step 5: Persist the lease in `.syntaur/context.json`
65
+
66
+ Append the lease record to the `leases: []` array in `.syntaur/context.json` (creating the array if absent). Atomic write via `jq` + rename:
67
+
68
+ ```bash
69
+ mkdir -p .syntaur
70
+ jq --arg lid "$LEASE_ID" \
71
+ --arg slug "$INVENTORY_SLUG" \
72
+ --arg mid "$MEMBER_ID" \
73
+ --arg exp "$EXPIRES_AT" \
74
+ --arg claimed "$GRANTED_AT" \
75
+ --argjson meta "$METADATA_JSON" \
76
+ '.leases = ((.leases // []) | map(select(.lease_id != $lid)) +
77
+ [{ lease_id: $lid, inventory_slug: $slug, member_id: $mid,
78
+ expires_at: $exp, metadata: $meta, claimed_at: $claimed }])' \
79
+ .syntaur/context.json > .syntaur/context.json.tmp \
80
+ && mv .syntaur/context.json.tmp .syntaur/context.json
81
+ ```
82
+
83
+ ### Step 6: Report back
84
+
85
+ Tell the user:
86
+ - Which member was claimed (`<inventory>/<member_id>`) and the lease id.
87
+ - When the lease expires (`expires_at`).
88
+ - The connection metadata, if any (URL, port, credentials reference, etc.) — print as a code block so the agent (or human) can use it.
89
+ - Reminder: leases do NOT auto-release on session end or assignment completion in v1. Call `/release-resource <lease_id>` when finished, or let the TTL expire and `gc` reclaim.
@@ -0,0 +1,83 @@
1
+ ---
2
+ name: extend-resource
3
+ description: Extend the TTL on a Syntaur resource lease previously claimed via /claim-resource. Triggers on "/extend-resource", "extend my lease", "renew the lease", "bump the TTL", "keep this resource a bit longer", or when the user wants more time on an active claim.
4
+ license: MIT
5
+ ---
6
+
7
+ # Extend Resource
8
+
9
+ Extend an active Syntaur resource lease by pushing its `expires_at` forward. On success, also updates the lease's `expires_at` in `.syntaur/context.json` so the local record stays accurate. If the lease has already gone stale (expired, revoked, or member generation advanced), drops it from `.syntaur/context.json` and reports.
10
+
11
+ ## Usage
12
+
13
+ User arguments: `$ARGUMENTS`
14
+
15
+ - `<lease_id>` (required) — lease to extend
16
+ - `--ttl <duration>` (required) — new TTL from now (e.g. `15m`, `2h`)
17
+
18
+ Examples:
19
+ - `/extend-resource cf3bd0a6-ba38-4a8c-a882-bdba9cabad80 --ttl 1h`
20
+ - `/extend-resource 6d4a... --ttl 30m`
21
+
22
+ ## Workflow
23
+
24
+ ### Step 1: Parse arguments
25
+
26
+ Extract `<lease_id>` (first positional) and `--ttl <duration>` (required). If either is missing, abort with a usage message and a hint to call `/list-resources` to see active leases.
27
+
28
+ ### Step 2: Pre-check assignment context
29
+
30
+ Read `.syntaur/context.json` from the current working directory. If the file is missing, OR has neither `sessionId` nor any of `projectSlug`/`assignmentSlug`/`assignmentDir`, abort with:
31
+
32
+ > "No active Syntaur context in this workspace. Grab an assignment first (`/grab-assignment <project> <slug>`) or restart the session so the SessionStart hook can populate `.syntaur/context.json`."
33
+
34
+ This guard mirrors `/claim-resource` and `/release-resource` — writing a leases-only context file would trip `syntaur doctor`'s workspace check.
35
+
36
+ ### Step 3: Run the extend CLI
37
+
38
+ ```bash
39
+ syntaur lease extend "$LEASE_ID" --ttl "$TTL"
40
+ ```
41
+
42
+ (Use `dangerouslyDisableSandbox: true` — the CLI writes to `~/.syntaur/syntaur.db`.)
43
+
44
+ Possible non-zero exits and how to surface them:
45
+
46
+ | stderr fragment | meaning | how to respond |
47
+ |---|---|---|
48
+ | `cannot be extended (expired, revoked, or member generation advanced)` | `StaleLeaseError` — lease is no longer the holder. | Drop the entry from `.syntaur/context.json` (Step 5) and tell user the lease is dead — they'll need to `/claim-resource` again. |
49
+ | `contention timeout; retry` | `LeaseContentionError` — busy timeout. | Retry once after ~1s. If it fails again, surface to user. |
50
+ | `--ttl must be positive` | Bad duration. | Tell user to pass a positive duration like `15m`, `2h`. |
51
+
52
+ ### Step 4: Parse the new expiry from stdout
53
+
54
+ On exit 0, stdout looks like: `Extended lease <id> (new expires_at=<iso>).`
55
+
56
+ Capture the `new_expires_at` value (the ISO timestamp inside the parens).
57
+
58
+ ### Step 5a: On success, update `.syntaur/context.json`
59
+
60
+ Find the matching entry in `leases[]` by `lease_id` and rewrite its `expires_at`:
61
+
62
+ ```bash
63
+ jq --arg lid "$LEASE_ID" --arg exp "$NEW_EXPIRES_AT" \
64
+ '.leases = ((.leases // []) | map(
65
+ if .lease_id == $lid then .expires_at = $exp else . end
66
+ ))' \
67
+ .syntaur/context.json > .syntaur/context.json.tmp \
68
+ && mv .syntaur/context.json.tmp .syntaur/context.json
69
+ ```
70
+
71
+ ### Step 5b: On `StaleLeaseError`, drop the entry instead
72
+
73
+ ```bash
74
+ jq --arg lid "$LEASE_ID" \
75
+ '.leases = ((.leases // []) | map(select(.lease_id != $lid)))' \
76
+ .syntaur/context.json > .syntaur/context.json.tmp \
77
+ && mv .syntaur/context.json.tmp .syntaur/context.json
78
+ ```
79
+
80
+ ### Step 6: Report back
81
+
82
+ - On success: `Extended <lease_id> — new expires_at=<iso>.` Also surface the original member id (look it up in the context entry before mutation) so the user knows which resource was extended.
83
+ - On stale: `Lease <lease_id> is no longer active — removed from context. Run /claim-resource to grab a new one.`
@@ -0,0 +1,74 @@
1
+ ---
2
+ name: list-resources
3
+ description: List the Syntaur resource leases held by the current session, plus the workspace-wide overview of inventories and active leases. Triggers on "/list-resources", "what leases do I have", "show my leases", "list my resources", "what resources am I holding", or when the user wants both a session-local and system-wide view of leased resources.
4
+ license: MIT
5
+ ---
6
+
7
+ # List Resources
8
+
9
+ Print two views in one call:
10
+
11
+ 1. **Your active leases** — the `leases[]` array from `.syntaur/context.json` (this session's claims).
12
+ 2. **All active leases in this workspace** — output of `syntaur lease list`, which shows every inventory plus its idle/leased counts.
13
+
14
+ This is the cheap "what am I holding, and what's out there?" lookup before deciding to extend, release, or grab another resource.
15
+
16
+ ## Usage
17
+
18
+ User arguments: `$ARGUMENTS` (no arguments accepted in v1).
19
+
20
+ Examples:
21
+ - `/list-resources`
22
+
23
+ ## Workflow
24
+
25
+ ### Step 1: Read this session's leases
26
+
27
+ ```bash
28
+ jq -r '.leases // []' .syntaur/context.json 2>/dev/null
29
+ ```
30
+
31
+ (No `dangerouslyDisableSandbox` needed — read-only.)
32
+
33
+ If `.syntaur/context.json` is missing or has no `leases` array, treat it as empty. Do NOT abort — listing is informational; the absence of session context is itself a useful signal.
34
+
35
+ For each entry, capture: `lease_id`, `inventory_slug`, `member_id`, `expires_at`, `claimed_at`, and any `metadata` (e.g. connection URL).
36
+
37
+ ### Step 2: Run the system-wide overview
38
+
39
+ ```bash
40
+ syntaur lease list
41
+ ```
42
+
43
+ (Use `dangerouslyDisableSandbox: true` — the CLI reads `~/.syntaur/syntaur.db`.)
44
+
45
+ Capture stdout. If the CLI exits non-zero, surface the error message but still print whatever Step 1 produced.
46
+
47
+ ### Step 3: Report back
48
+
49
+ Print two sections:
50
+
51
+ **Your active leases (from `.syntaur/context.json`):**
52
+
53
+ If the array is empty:
54
+ > "No leases held by this session."
55
+
56
+ Otherwise, one block per lease:
57
+ ```
58
+ <lease_id>
59
+ inventory: <inventory_slug>/<member_id>
60
+ expires: <expires_at>
61
+ claimed: <claimed_at>
62
+ metadata: <metadata JSON, if any>
63
+ ```
64
+
65
+ **All inventories in this workspace (from `syntaur lease list`):**
66
+
67
+ Paste the CLI output verbatim (one row per inventory with kind/idle/leased counts).
68
+
69
+ ### Step 4: Suggest next actions if any leases are nearing expiry
70
+
71
+ If any session-local lease's `expires_at` is within the next 5 minutes, append:
72
+ > "Note: lease `<id>` expires at `<iso>` (within 5 minutes). Run `/extend-resource <id> --ttl <duration>` to keep it."
73
+
74
+ This is informational only — don't extend automatically.
@@ -0,0 +1,65 @@
1
+ ---
2
+ name: release-resource
3
+ description: Release a Syntaur resource lease previously claimed via /claim-resource, freeing the member for the next claimant. Triggers on "/release-resource", "release this dev env", "drop the lease", "release my slot", "release all leases", or when the user is done with a claimed resource.
4
+ license: MIT
5
+ ---
6
+
7
+ # Release Resource
8
+
9
+ Release one or more active resource leases, removing them from `.syntaur/context.json` and freeing their members for the next claimant.
10
+
11
+ ## Usage
12
+
13
+ User arguments: `$ARGUMENTS`
14
+
15
+ - `<lease_id>` — release a specific lease by id
16
+ - `--all` — release every active lease recorded in `.syntaur/context.json`
17
+
18
+ Examples:
19
+ - `/release-resource cf3bd0a6-ba38-4a8c-a882-bdba9cabad80`
20
+ - `/release-resource --all`
21
+
22
+ ## Workflow
23
+
24
+ ### Step 1: Parse arguments
25
+
26
+ If `--all` is passed, build the list of `lease_id`s from `.syntaur/context.json`'s `leases: []` array. Otherwise the first positional argument is the single `lease_id` to release.
27
+
28
+ If there is no `lease_id` and `--all` was not passed, abort with: "Specify `<lease_id>` or use `--all`. Active leases: $(jq -r '.leases[]?.lease_id' .syntaur/context.json 2>/dev/null || echo 'none')."
29
+
30
+ ### Step 2: Release each lease
31
+
32
+ For each `lease_id`:
33
+
34
+ ```bash
35
+ syntaur lease release "$LEASE_ID"
36
+ ```
37
+
38
+ (Use `dangerouslyDisableSandbox: true` since the CLI writes to `~/.syntaur/syntaur.db`.)
39
+
40
+ Outcomes:
41
+
42
+ | Result | Meaning | What to do |
43
+ |---|---|---|
44
+ | Exit 0 with `Released lease <id>.` | Normal release. | Continue. |
45
+ | Exit 1 with `lease ... is no longer active` | Stale lease (already expired/revoked). The slot has already been freed. | Still remove from `.syntaur/context.json` — the row is dead either way. Note the staleness in the final summary. |
46
+ | Exit 1 with `contention timeout; retry` | `LeaseContentionError`. | Retry once after ~1s; if it fails again, surface to the user. |
47
+
48
+ ### Step 3: Remove the entry from `.syntaur/context.json`
49
+
50
+ Whether the CLI succeeded OR returned a stale-lease error, drop the matching record from `.syntaur/context.json` (stale leases are dead — keeping them in context is misleading):
51
+
52
+ ```bash
53
+ jq --arg lid "$LEASE_ID" \
54
+ '.leases = ((.leases // []) | map(select(.lease_id != $lid)))' \
55
+ .syntaur/context.json > .syntaur/context.json.tmp \
56
+ && mv .syntaur/context.json.tmp .syntaur/context.json
57
+ ```
58
+
59
+ ### Step 4: Report back
60
+
61
+ For a single release: `Released <lease_id> (<inventory>/<member_id>).` or `Lease <id> was already stale; removed from context.`
62
+
63
+ For `--all`: print one line per lease, then a summary count: `Released N lease(s), M already stale.`
64
+
65
+ If no leases were recorded in context, say so explicitly.
@@ -60,6 +60,7 @@ If `.syntaur/context.json` exists in the current working directory, read it befo
60
60
  - `workspaceRoot` — absolute path to the code workspace
61
61
  - `sessionId` — real agent-runtime session id (never a synthesized UUID)
62
62
  - `transcriptPath` — absolute path to the agent's rollout/transcript file, if known
63
+ - `leases` — array of active resource-lease records (managed by `/claim-resource` and `/release-resource`). Entry shape: `{ lease_id, inventory_slug, member_id, expires_at, metadata, claimed_at }`. Leases are NOT auto-released on session end or assignment completion in v1 — call `/release-resource` explicitly (or let the TTL expire).
63
64
 
64
65
  ## Required Reading Order
65
66
 
@@ -0,0 +1,89 @@
1
+ ---
2
+ name: claim-resource
3
+ description: Claim an idle member of a Syntaur leases inventory (dev env, test DB, API key, named lock, etc.) for the current assignment session. Triggers on "/claim-resource", "claim a dev env", "lease an environment", "grab a test DB", "claim a slot", or when the user wants to coordinate access to a shared finite resource.
4
+ license: MIT
5
+ ---
6
+
7
+ # Claim Resource
8
+
9
+ Claim an idle member of a Syntaur leases inventory and persist the lease into `.syntaur/context.json`. The claimed member's connection metadata (URL, ssh string, port, etc.) is printed back so the agent can use it immediately.
10
+
11
+ The claim is **fail-fast** — if no member is idle, the command exits non-zero. If a long-running blocking claim is wanted, the agent retries externally with backoff.
12
+
13
+ ## Usage
14
+
15
+ User arguments: `$ARGUMENTS`
16
+
17
+ - `<inventory_slug>` (required) — which inventory to claim from
18
+ - `--ttl <duration>` (optional) — lease TTL, e.g. `15m`, `2h`. Defaults to the inventory's `default_ttl`.
19
+ - `--for <tag>` (optional) — free-form requester tag (defaults to the active assignment slug if omitted)
20
+
21
+ Examples:
22
+ - `/claim-resource dev-envs`
23
+ - `/claim-resource dev-envs --ttl 1h`
24
+ - `/claim-resource prod-migration-lock --ttl 5m --for "migration-2026-05-12"`
25
+
26
+ ## Workflow
27
+
28
+ ### Step 1: Parse arguments
29
+
30
+ Extract `<inventory_slug>` (required, first positional), plus optional `--ttl <duration>` and `--for <tag>`.
31
+
32
+ ### Step 2: Pre-check assignment context
33
+
34
+ Read `.syntaur/context.json` from the current working directory. If the file is missing, OR has neither `sessionId` nor any of `projectSlug`/`assignmentSlug`/`assignmentDir`, abort with:
35
+
36
+ > "No active Syntaur context in this workspace. Grab an assignment first (`/grab-assignment <project> <slug>`) or restart the session so the SessionStart hook can populate `.syntaur/context.json`."
37
+
38
+ This guard exists because writing a leases-only context file would trip `syntaur doctor`'s workspace check.
39
+
40
+ If `--for` was not provided, derive its value from the context: prefer `assignmentSlug` if present, otherwise `sessionId`.
41
+
42
+ ### Step 3: Run the claim CLI
43
+
44
+ ```bash
45
+ syntaur lease claim "$INVENTORY_SLUG" --json \
46
+ ${TTL:+--ttl "$TTL"} \
47
+ --for "$TAG"
48
+ ```
49
+
50
+ (Use `dangerouslyDisableSandbox: true` for the Bash call — the CLI writes to `~/.syntaur/syntaur.db`.)
51
+
52
+ Possible non-zero exits and how to surface them to the user:
53
+
54
+ | stderr fragment | meaning | how to respond |
55
+ |---|---|---|
56
+ | `no idle members in '<slug>'` | `NoIdleMemberError` — pool exhausted | Tell user; suggest waiting / retrying later or asking a human to add capacity. |
57
+ | `contention timeout on '<slug>'; retry` | `LeaseContentionError` — busy timeout | Retry once after ~1s. If it fails again, surface the error. |
58
+ | `inventory '<slug>' not found` | typo or inventory doesn't exist | Tell user; list with `syntaur lease list`. |
59
+
60
+ ### Step 4: Parse the claim result
61
+
62
+ Parse the stdout JSON: `{ lease_id, inventory_slug, member_id, member_gen, granted_at, expires_at, metadata }`.
63
+
64
+ ### Step 5: Persist the lease in `.syntaur/context.json`
65
+
66
+ Append the lease record to the `leases: []` array in `.syntaur/context.json` (creating the array if absent). Atomic write via `jq` + rename:
67
+
68
+ ```bash
69
+ mkdir -p .syntaur
70
+ jq --arg lid "$LEASE_ID" \
71
+ --arg slug "$INVENTORY_SLUG" \
72
+ --arg mid "$MEMBER_ID" \
73
+ --arg exp "$EXPIRES_AT" \
74
+ --arg claimed "$GRANTED_AT" \
75
+ --argjson meta "$METADATA_JSON" \
76
+ '.leases = ((.leases // []) | map(select(.lease_id != $lid)) +
77
+ [{ lease_id: $lid, inventory_slug: $slug, member_id: $mid,
78
+ expires_at: $exp, metadata: $meta, claimed_at: $claimed }])' \
79
+ .syntaur/context.json > .syntaur/context.json.tmp \
80
+ && mv .syntaur/context.json.tmp .syntaur/context.json
81
+ ```
82
+
83
+ ### Step 6: Report back
84
+
85
+ Tell the user:
86
+ - Which member was claimed (`<inventory>/<member_id>`) and the lease id.
87
+ - When the lease expires (`expires_at`).
88
+ - The connection metadata, if any (URL, port, credentials reference, etc.) — print as a code block so the agent (or human) can use it.
89
+ - Reminder: leases do NOT auto-release on session end or assignment completion in v1. Call `/release-resource <lease_id>` when finished, or let the TTL expire and `gc` reclaim.
@@ -0,0 +1,83 @@
1
+ ---
2
+ name: extend-resource
3
+ description: Extend the TTL on a Syntaur resource lease previously claimed via /claim-resource. Triggers on "/extend-resource", "extend my lease", "renew the lease", "bump the TTL", "keep this resource a bit longer", or when the user wants more time on an active claim.
4
+ license: MIT
5
+ ---
6
+
7
+ # Extend Resource
8
+
9
+ Extend an active Syntaur resource lease by pushing its `expires_at` forward. On success, also updates the lease's `expires_at` in `.syntaur/context.json` so the local record stays accurate. If the lease has already gone stale (expired, revoked, or member generation advanced), drops it from `.syntaur/context.json` and reports.
10
+
11
+ ## Usage
12
+
13
+ User arguments: `$ARGUMENTS`
14
+
15
+ - `<lease_id>` (required) — lease to extend
16
+ - `--ttl <duration>` (required) — new TTL from now (e.g. `15m`, `2h`)
17
+
18
+ Examples:
19
+ - `/extend-resource cf3bd0a6-ba38-4a8c-a882-bdba9cabad80 --ttl 1h`
20
+ - `/extend-resource 6d4a... --ttl 30m`
21
+
22
+ ## Workflow
23
+
24
+ ### Step 1: Parse arguments
25
+
26
+ Extract `<lease_id>` (first positional) and `--ttl <duration>` (required). If either is missing, abort with a usage message and a hint to call `/list-resources` to see active leases.
27
+
28
+ ### Step 2: Pre-check assignment context
29
+
30
+ Read `.syntaur/context.json` from the current working directory. If the file is missing, OR has neither `sessionId` nor any of `projectSlug`/`assignmentSlug`/`assignmentDir`, abort with:
31
+
32
+ > "No active Syntaur context in this workspace. Grab an assignment first (`/grab-assignment <project> <slug>`) or restart the session so the SessionStart hook can populate `.syntaur/context.json`."
33
+
34
+ This guard mirrors `/claim-resource` and `/release-resource` — writing a leases-only context file would trip `syntaur doctor`'s workspace check.
35
+
36
+ ### Step 3: Run the extend CLI
37
+
38
+ ```bash
39
+ syntaur lease extend "$LEASE_ID" --ttl "$TTL"
40
+ ```
41
+
42
+ (Use `dangerouslyDisableSandbox: true` — the CLI writes to `~/.syntaur/syntaur.db`.)
43
+
44
+ Possible non-zero exits and how to surface them:
45
+
46
+ | stderr fragment | meaning | how to respond |
47
+ |---|---|---|
48
+ | `cannot be extended (expired, revoked, or member generation advanced)` | `StaleLeaseError` — lease is no longer the holder. | Drop the entry from `.syntaur/context.json` (Step 5) and tell user the lease is dead — they'll need to `/claim-resource` again. |
49
+ | `contention timeout; retry` | `LeaseContentionError` — busy timeout. | Retry once after ~1s. If it fails again, surface to user. |
50
+ | `--ttl must be positive` | Bad duration. | Tell user to pass a positive duration like `15m`, `2h`. |
51
+
52
+ ### Step 4: Parse the new expiry from stdout
53
+
54
+ On exit 0, stdout looks like: `Extended lease <id> (new expires_at=<iso>).`
55
+
56
+ Capture the `new_expires_at` value (the ISO timestamp inside the parens).
57
+
58
+ ### Step 5a: On success, update `.syntaur/context.json`
59
+
60
+ Find the matching entry in `leases[]` by `lease_id` and rewrite its `expires_at`:
61
+
62
+ ```bash
63
+ jq --arg lid "$LEASE_ID" --arg exp "$NEW_EXPIRES_AT" \
64
+ '.leases = ((.leases // []) | map(
65
+ if .lease_id == $lid then .expires_at = $exp else . end
66
+ ))' \
67
+ .syntaur/context.json > .syntaur/context.json.tmp \
68
+ && mv .syntaur/context.json.tmp .syntaur/context.json
69
+ ```
70
+
71
+ ### Step 5b: On `StaleLeaseError`, drop the entry instead
72
+
73
+ ```bash
74
+ jq --arg lid "$LEASE_ID" \
75
+ '.leases = ((.leases // []) | map(select(.lease_id != $lid)))' \
76
+ .syntaur/context.json > .syntaur/context.json.tmp \
77
+ && mv .syntaur/context.json.tmp .syntaur/context.json
78
+ ```
79
+
80
+ ### Step 6: Report back
81
+
82
+ - On success: `Extended <lease_id> — new expires_at=<iso>.` Also surface the original member id (look it up in the context entry before mutation) so the user knows which resource was extended.
83
+ - On stale: `Lease <lease_id> is no longer active — removed from context. Run /claim-resource to grab a new one.`
@@ -0,0 +1,74 @@
1
+ ---
2
+ name: list-resources
3
+ description: List the Syntaur resource leases held by the current session, plus the workspace-wide overview of inventories and active leases. Triggers on "/list-resources", "what leases do I have", "show my leases", "list my resources", "what resources am I holding", or when the user wants both a session-local and system-wide view of leased resources.
4
+ license: MIT
5
+ ---
6
+
7
+ # List Resources
8
+
9
+ Print two views in one call:
10
+
11
+ 1. **Your active leases** — the `leases[]` array from `.syntaur/context.json` (this session's claims).
12
+ 2. **All active leases in this workspace** — output of `syntaur lease list`, which shows every inventory plus its idle/leased counts.
13
+
14
+ This is the cheap "what am I holding, and what's out there?" lookup before deciding to extend, release, or grab another resource.
15
+
16
+ ## Usage
17
+
18
+ User arguments: `$ARGUMENTS` (no arguments accepted in v1).
19
+
20
+ Examples:
21
+ - `/list-resources`
22
+
23
+ ## Workflow
24
+
25
+ ### Step 1: Read this session's leases
26
+
27
+ ```bash
28
+ jq -r '.leases // []' .syntaur/context.json 2>/dev/null
29
+ ```
30
+
31
+ (No `dangerouslyDisableSandbox` needed — read-only.)
32
+
33
+ If `.syntaur/context.json` is missing or has no `leases` array, treat it as empty. Do NOT abort — listing is informational; the absence of session context is itself a useful signal.
34
+
35
+ For each entry, capture: `lease_id`, `inventory_slug`, `member_id`, `expires_at`, `claimed_at`, and any `metadata` (e.g. connection URL).
36
+
37
+ ### Step 2: Run the system-wide overview
38
+
39
+ ```bash
40
+ syntaur lease list
41
+ ```
42
+
43
+ (Use `dangerouslyDisableSandbox: true` — the CLI reads `~/.syntaur/syntaur.db`.)
44
+
45
+ Capture stdout. If the CLI exits non-zero, surface the error message but still print whatever Step 1 produced.
46
+
47
+ ### Step 3: Report back
48
+
49
+ Print two sections:
50
+
51
+ **Your active leases (from `.syntaur/context.json`):**
52
+
53
+ If the array is empty:
54
+ > "No leases held by this session."
55
+
56
+ Otherwise, one block per lease:
57
+ ```
58
+ <lease_id>
59
+ inventory: <inventory_slug>/<member_id>
60
+ expires: <expires_at>
61
+ claimed: <claimed_at>
62
+ metadata: <metadata JSON, if any>
63
+ ```
64
+
65
+ **All inventories in this workspace (from `syntaur lease list`):**
66
+
67
+ Paste the CLI output verbatim (one row per inventory with kind/idle/leased counts).
68
+
69
+ ### Step 4: Suggest next actions if any leases are nearing expiry
70
+
71
+ If any session-local lease's `expires_at` is within the next 5 minutes, append:
72
+ > "Note: lease `<id>` expires at `<iso>` (within 5 minutes). Run `/extend-resource <id> --ttl <duration>` to keep it."
73
+
74
+ This is informational only — don't extend automatically.
@@ -0,0 +1,65 @@
1
+ ---
2
+ name: release-resource
3
+ description: Release a Syntaur resource lease previously claimed via /claim-resource, freeing the member for the next claimant. Triggers on "/release-resource", "release this dev env", "drop the lease", "release my slot", "release all leases", or when the user is done with a claimed resource.
4
+ license: MIT
5
+ ---
6
+
7
+ # Release Resource
8
+
9
+ Release one or more active resource leases, removing them from `.syntaur/context.json` and freeing their members for the next claimant.
10
+
11
+ ## Usage
12
+
13
+ User arguments: `$ARGUMENTS`
14
+
15
+ - `<lease_id>` — release a specific lease by id
16
+ - `--all` — release every active lease recorded in `.syntaur/context.json`
17
+
18
+ Examples:
19
+ - `/release-resource cf3bd0a6-ba38-4a8c-a882-bdba9cabad80`
20
+ - `/release-resource --all`
21
+
22
+ ## Workflow
23
+
24
+ ### Step 1: Parse arguments
25
+
26
+ If `--all` is passed, build the list of `lease_id`s from `.syntaur/context.json`'s `leases: []` array. Otherwise the first positional argument is the single `lease_id` to release.
27
+
28
+ If there is no `lease_id` and `--all` was not passed, abort with: "Specify `<lease_id>` or use `--all`. Active leases: $(jq -r '.leases[]?.lease_id' .syntaur/context.json 2>/dev/null || echo 'none')."
29
+
30
+ ### Step 2: Release each lease
31
+
32
+ For each `lease_id`:
33
+
34
+ ```bash
35
+ syntaur lease release "$LEASE_ID"
36
+ ```
37
+
38
+ (Use `dangerouslyDisableSandbox: true` since the CLI writes to `~/.syntaur/syntaur.db`.)
39
+
40
+ Outcomes:
41
+
42
+ | Result | Meaning | What to do |
43
+ |---|---|---|
44
+ | Exit 0 with `Released lease <id>.` | Normal release. | Continue. |
45
+ | Exit 1 with `lease ... is no longer active` | Stale lease (already expired/revoked). The slot has already been freed. | Still remove from `.syntaur/context.json` — the row is dead either way. Note the staleness in the final summary. |
46
+ | Exit 1 with `contention timeout; retry` | `LeaseContentionError`. | Retry once after ~1s; if it fails again, surface to the user. |
47
+
48
+ ### Step 3: Remove the entry from `.syntaur/context.json`
49
+
50
+ Whether the CLI succeeded OR returned a stale-lease error, drop the matching record from `.syntaur/context.json` (stale leases are dead — keeping them in context is misleading):
51
+
52
+ ```bash
53
+ jq --arg lid "$LEASE_ID" \
54
+ '.leases = ((.leases // []) | map(select(.lease_id != $lid)))' \
55
+ .syntaur/context.json > .syntaur/context.json.tmp \
56
+ && mv .syntaur/context.json.tmp .syntaur/context.json
57
+ ```
58
+
59
+ ### Step 4: Report back
60
+
61
+ For a single release: `Released <lease_id> (<inventory>/<member_id>).` or `Lease <id> was already stale; removed from context.`
62
+
63
+ For `--all`: print one line per lease, then a summary count: `Released N lease(s), M already stale.`
64
+
65
+ If no leases were recorded in context, say so explicitly.
@@ -60,6 +60,7 @@ If `.syntaur/context.json` exists in the current working directory, read it befo
60
60
  - `workspaceRoot` — absolute path to the code workspace
61
61
  - `sessionId` — real agent-runtime session id (never a synthesized UUID)
62
62
  - `transcriptPath` — absolute path to the agent's rollout/transcript file, if known
63
+ - `leases` — array of active resource-lease records (managed by `/claim-resource` and `/release-resource`). Entry shape: `{ lease_id, inventory_slug, member_id, expires_at, metadata, claimed_at }`. Leases are NOT auto-released on session end or assignment completion in v1 — call `/release-resource` explicitly (or let the TTL expire).
63
64
 
64
65
  ## Required Reading Order
65
66