@harness-lab/cli 0.2.7 → 0.2.9

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 CHANGED
@@ -9,9 +9,14 @@ Current shipped scope:
9
9
  - `harness auth login`
10
10
  - `harness auth logout`
11
11
  - `harness auth status`
12
+ - `harness workshop current-instance`
13
+ - `harness workshop select-instance`
12
14
  - `harness workshop status`
15
+ - `harness workshop list-instances`
16
+ - `harness workshop show-instance`
13
17
  - `harness workshop create-instance`
14
18
  - `harness workshop update-instance`
19
+ - `harness workshop reset-instance`
15
20
  - `harness workshop prepare`
16
21
  - `harness workshop remove-instance`
17
22
  - `harness workshop archive`
@@ -120,16 +125,37 @@ Workshop commands:
120
125
  ```bash
121
126
  harness auth status
122
127
  harness skill install
128
+ harness workshop list-instances
129
+ harness workshop select-instance sample-workshop-demo-orbit
130
+ harness workshop current-instance
123
131
  harness workshop status
132
+ harness workshop show-instance sample-workshop-demo-orbit
124
133
  harness workshop create-instance sample-workshop-demo-orbit --event-title "Sample Workshop Demo"
125
- harness workshop update-instance sample-workshop-demo-orbit --room-name Orbit
126
- harness workshop prepare sample-workshop-demo-orbit
127
- harness workshop remove-instance sample-workshop-demo-orbit
134
+ harness workshop update-instance --room-name Orbit
135
+ harness workshop reset-instance --template-id blueprint-default
136
+ harness workshop prepare
137
+ harness workshop remove-instance
128
138
  harness workshop phase set rotation
129
139
  harness workshop archive --notes "Manual archive"
140
+ harness workshop select-instance --clear
130
141
  harness auth logout
131
142
  ```
132
143
 
144
+ Targeting model:
145
+
146
+ - `harness workshop list-instances` is the discovery entrypoint for facilitator-visible workshops
147
+ - `harness workshop select-instance <instance-id>` stores a local current target for later workshop commands
148
+ - `harness workshop current-instance` reports the stored target and resolves its current server state
149
+ - `harness workshop status` and `harness workshop phase set <phase-id>` use the selected instance when present, otherwise they fall back to deployment default behavior
150
+ - `harness workshop show-instance`, `update-instance`, `reset-instance`, `prepare`, and `remove-instance` accept an explicit `<instance-id>` but may also use the stored selection as a fallback
151
+ - `harness workshop select-instance --clear` removes the stored selection
152
+ - `HARNESS_WORKSHOP_INSTANCE_ID` remains an environment fallback when no local selection is stored
153
+
154
+ Machine-readable output:
155
+
156
+ - `harness --json ...` prints strict JSON output without headings
157
+ - prefer this for agent or script consumption instead of parsing human-oriented terminal copy
158
+
133
159
  Facilitator lifecycle commands are intentionally CLI-first:
134
160
 
135
161
  - skill invokes `harness`
@@ -214,6 +214,34 @@ This is a facilitator-only command — do not surface to participants.
214
214
 
215
215
  Show the current instance state, agenda phase, facilitator list, and team count.
216
216
  Requires active facilitator session.
217
+ If a local current instance is selected, prefer that target over deployment-default status.
218
+ When an agent needs machine-readable output, prefer `harness --json workshop status`.
219
+
220
+ ### `workshop facilitator current-instance`
221
+
222
+ Show the locally selected facilitator target, where it came from, and the resolved instance record.
223
+ Prefer invoking `harness workshop current-instance`.
224
+ Use this to confirm the CLI target before update, reset, prepare, remove, or phase operations.
225
+
226
+ ### `workshop facilitator select-instance <instance-id>`
227
+
228
+ Persist a facilitator-local current target for later workshop commands.
229
+ Prefer invoking `harness workshop select-instance <instance-id>`.
230
+ Use `harness workshop select-instance --clear` to remove the stored selection.
231
+
232
+ ### `workshop facilitator list-instances`
233
+
234
+ List the facilitator-visible workshop instance registry.
235
+ Prefer invoking `harness workshop list-instances` over raw API scripts or local session-file inspection.
236
+ Use this when the facilitator needs to discover what currently exists on a shared dashboard before choosing an explicit instance for reset, update, or agenda work.
237
+ When an agent needs strict parsing, prefer `harness --json workshop list-instances`.
238
+
239
+ ### `workshop facilitator show-instance <instance-id>`
240
+
241
+ Inspect one explicit workshop instance.
242
+ Prefer invoking `harness workshop show-instance <instance-id>` over raw API scripts.
243
+ Use this when the facilitator needs the full record for one instance rather than the deployment-default runtime status returned by `workshop facilitator status`.
244
+ If a current instance is already selected, the CLI may omit `<instance-id>` and use the stored target.
217
245
 
218
246
  ### `workshop facilitator grant <email> <role>`
219
247
 
@@ -249,21 +277,29 @@ Prefer invoking `harness workshop update-instance` over raw API scripts.
249
277
  Use this when the facilitator wants to correct or refine date, venue, room, address, or event title without resetting the instance.
250
278
  Support `contentLang` changes explicitly so facilitators can choose workshop delivery language per instance without changing admin UI language.
251
279
 
280
+ Facilitator discovery rule:
281
+ - for routine discovery, use `harness workshop list-instances` and `harness workshop show-instance`
282
+ - for repeated work on one live workshop, use `harness workshop select-instance <instance-id>` and `harness workshop current-instance`
283
+ - do not read local CLI session files or improvise authenticated `node -e` fetch scripts unless you are diagnosing the CLI itself
284
+
252
285
  ### `workshop facilitator reset-instance <instance-id>`
253
286
 
254
287
  Reset one existing workshop instance from the selected blueprint template. Requires facilitator session.
255
288
  Prefer invoking `harness workshop reset-instance` over raw API scripts.
256
289
  Use this when the facilitator wants fresh canonical agenda, runner, and presenter content for a live instance and accepts that local runtime state will be reinitialized.
290
+ If a current instance is already selected, the CLI may omit `<instance-id>` and use the stored target.
257
291
 
258
292
  ### `workshop facilitator remove-instance`
259
293
 
260
294
  Remove a workshop instance from the active list without deleting its archive history. Requires facilitator session.
261
295
  Prefer invoking `harness workshop remove-instance`.
296
+ If a current instance is already selected, the CLI may omit `<instance-id>` and use the stored target.
262
297
 
263
298
  ### `workshop facilitator prepare`
264
299
 
265
300
  Set the current instance to `prepared` state. Verify event code is ready.
266
301
  Prefer invoking `harness workshop prepare`.
302
+ If a current instance is already selected, the CLI may omit `<instance-id>` and use the stored target.
267
303
 
268
304
  ### `workshop facilitator agenda`
269
305
 
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "manifestVersion": 1,
3
3
  "bundleName": "harness-lab-workshop",
4
- "bundleVersion": "0.2.7",
5
- "contentHash": "db5557fe004d30755c4912015667daf2095c70081780f8a3418bd772e2fb9a49",
4
+ "bundleVersion": "0.2.9",
5
+ "contentHash": "671837951cf6679bea1d517c900927aab5d457806695e1a6da25574b8573f325",
6
6
  "files": [
7
7
  {
8
8
  "path": "content/challenge-cards/.gitkeep",
@@ -34,7 +34,7 @@
34
34
  },
35
35
  {
36
36
  "path": "content/facilitation/master-guide.md",
37
- "sha256": "f0edc5eef8c7bc0efc67672e184edecce46fa5fd5e9820cfda0b28fbb8cb1a8f"
37
+ "sha256": "845adbcf9284cedd589d461b40ae1cd336d816b92bcc239df4574148a57b263a"
38
38
  },
39
39
  {
40
40
  "path": "content/project-briefs/.gitkeep",
@@ -42,7 +42,7 @@
42
42
  },
43
43
  {
44
44
  "path": "content/project-briefs/code-review-helper.md",
45
- "sha256": "0d70c455d000ec8bc01337db5feee2174be8359730b5c278cccee9c3e6b0f2f7"
45
+ "sha256": "3e174ff0d097fe132088774042f9db58c9c2494613d66950bdd423116bd7986d"
46
46
  },
47
47
  {
48
48
  "path": "content/project-briefs/devtoolbox-cli.md",
@@ -98,11 +98,11 @@
98
98
  },
99
99
  {
100
100
  "path": "content/talks/context-is-king.md",
101
- "sha256": "4d0972fefac067de0b8803698b44bdf8ab0bee64b181d50d1ac5e26a92eb7089"
101
+ "sha256": "0bb48497b458474a546c351118ad455c00ad997c9efc0e227b388f81dd0b55e3"
102
102
  },
103
103
  {
104
104
  "path": "docs/harness-cli-foundation.md",
105
- "sha256": "80b321da32d110f141e1bd6a089ccc84f2a04066d76ba0258f530cc333dbaae4"
105
+ "sha256": "cff448e5c1ee4e7129724e4f8294add35cd1efb621c83182b341a1b7e5a16b86"
106
106
  },
107
107
  {
108
108
  "path": "docs/learner-reference-gallery.md",
@@ -134,11 +134,11 @@
134
134
  },
135
135
  {
136
136
  "path": "SKILL.md",
137
- "sha256": "64ddc1bf36265ea8321b2634d34499e83a8c1692e5cb10c6f30b1b07f1d51975"
137
+ "sha256": "d1e242a857f61bc2fae29c74a9e0efbd1ba0ed5beb25087dbaeffabc5f53b4a5"
138
138
  },
139
139
  {
140
140
  "path": "workshop-blueprint/agenda.json",
141
- "sha256": "6b8e1925e20fabccfd3cd9e9d79839855f51b2b7fd449efbb336bf3edd4eadce"
141
+ "sha256": "91b4037578ce61d172e09941089b97180322552cb42bb52de87b3956e60b1ed0"
142
142
  },
143
143
  {
144
144
  "path": "workshop-blueprint/control-surfaces.md",
@@ -146,7 +146,7 @@
146
146
  },
147
147
  {
148
148
  "path": "workshop-blueprint/day-structure.md",
149
- "sha256": "4980768b9e8b71d47cec19764b317d99c830908a0298c6f104e8d7bd37320966"
149
+ "sha256": "ffecae258211cef2430112ca25e1ff59658cac729ea2a07b9cac48e9a2a6244e"
150
150
  },
151
151
  {
152
152
  "path": "workshop-blueprint/edit-boundaries.md",
@@ -158,7 +158,7 @@
158
158
  },
159
159
  {
160
160
  "path": "workshop-blueprint/README.md",
161
- "sha256": "ae809a473bbc88411891044de7609fa053e66e370a8d4063e5d7b9e68179a98c"
161
+ "sha256": "badb477ecfef7df163ec41c72c39e04bcde2a5a723c1b01bf2e91ed38924e903"
162
162
  },
163
163
  {
164
164
  "path": "workshop-blueprint/teaching-spine.md",
@@ -182,7 +182,7 @@
182
182
  },
183
183
  {
184
184
  "path": "workshop-skill/facilitator.md",
185
- "sha256": "ab4721564c1dee2a24c7d4dfda229c2cfa56f5bec89a8b3580b541d9baa13e7f"
185
+ "sha256": "525fc9fdad22cf42b47854ff9ad90579d728daf2c4ac484aba13f93193eb56d3"
186
186
  },
187
187
  {
188
188
  "path": "workshop-skill/follow-up-package.md",
@@ -64,14 +64,14 @@ Pravidla:
64
64
 
65
65
  Po launchi potřebuje místnost ještě jednu konkrétní věc:
66
66
 
67
- - co má být po prvním build bloku opravdu vidět
68
- - co nestačí jen slíbit
67
+ - co má být po prvním build bloku opravdu vidět v repu
68
+ - co nestačí jen slíbit nebo dovysvětlit u stolu
69
69
 
70
70
  Do oběda má být vidět:
71
71
 
72
- - repo a `README`, které dávají smysl cizímu člověku
73
- - `AGENTS.md` jako krátká mapa
74
- - plan nebo jasně vedená implementační stopa
72
+ - `README`, které dává smysl cizímu člověku
73
+ - `AGENTS.md` jako krátká mapa, ne sklad všeho
74
+ - plan nebo jasně vedená implementační stopa, ze které je poznat další safe move
75
75
  - první explicitní check před dalším generováním
76
76
 
77
77
  ## Context is King talk
@@ -93,6 +93,8 @@ Proměnit energii z openingu v přesnou tezi a čistý most do Build fáze 1.
93
93
 
94
94
  ### Mikro-cvičení
95
95
 
96
+ Tohle je krátká facilitátorova ukázka, ne zadání pro celý room.
97
+
96
98
  Použijte stejný malý task ve dvou podmínkách:
97
99
 
98
100
  1. prompt blob
@@ -112,6 +114,7 @@ Na konci talku má být jasné:
112
114
 
113
115
  - teorie tím končí
114
116
  - tým se vrací k repu
117
+ - pokud tým ještě nemá workshop skill, teď je chvíle na `harness skill install`, pak `Codex: $workshop setup` nebo `pi: /skill:workshop`
115
118
  - nejdřív vzniká mapa a první explicitní check
116
119
  - teprve potom dává smysl další feature motion
117
120
 
@@ -2,30 +2,30 @@
2
2
 
3
3
  ## Problém
4
4
 
5
- Code review bývá nevyrovnané. Některé změny projdou s dobrým checklistem a jasným popisem rizik, jiné bez společného rámce. Reviewer pak improvizuje, autor neví, co má ověřit předem, a tým ztrácí konzistenci.
5
+ Code review bývá nevyrovnané. Některé změny projdou s dobrým checklistem a jasným popisem rizik, jiné bez společného rámce. Reviewer pak improvizuje, autor neví, co má ověřit předem, a tým ztrácí konzistenci právě tam, kde by měl být nejpřesnější.
6
6
 
7
- Vaším úkolem je navrhnout nástroj, který z diffu nebo změny vytvoří použitelný review checklist.
7
+ Vaším úkolem je navrhnout nástroj, který z diffu nebo změny vytvoří použitelný review checklist a zároveň jasně oddělí jistotu, heuristiku a místa, kde je pořád potřeba lidský úsudek.
8
8
 
9
9
  ## User stories
10
10
 
11
- - Jako reviewer chci z diffu rychle získat checklist rizik, otázek a míst, na která se zaměřit.
12
- - Jako autor změny chci vědět, co mám zkontrolovat ještě před samotným review.
11
+ - Jako reviewer chci z diffu rychle získat checklist změněných hranic, rizik, otázek a míst, na která se zaměřit.
12
+ - Jako autor změny chci vědět, co mám ověřit ještě před samotným review.
13
13
  - Jako tým po rotaci chci navázat na heuristiky, které původní tým objevil, místo abych je znovu vymýšlel.
14
14
 
15
15
  ## Architektonické poznámky
16
16
 
17
- - Může jít o CLI, web nebo jednoduchý skript. Důležitý je jasný tok `diff → analýza → checklist`.
18
- - Musí být zřejmé, jaké vstupy nástroj očekává a co naopak neumí spolehlivě vyhodnotit.
19
- - Přidejte seed diff nebo `examples/`, aby šlo workflow lokálně ověřit.
20
- - Jasně oddělte heuristiku od jistoty. Nástroj má pomáhat reviewerovi, ne předstírat neomylnost.
17
+ - Může jít o CLI, web nebo jednoduchý skript. Důležitý je jasný tok `diff → rubric → checklist`.
18
+ - Musí být zřejmé, jaké vstupy nástroj očekává, co umí označit jistě a co naopak zůstává heuristické.
19
+ - Přidejte seed diff nebo `examples/`, aby šlo workflow lokálně ověřit a další tým rychle přidal nové pravidlo.
20
+ - Nástroj má pomáhat reviewerovi, ne předstírat neomylnost.
21
21
 
22
22
  ## Hotovo když
23
23
 
24
24
  - Nástroj vytvoří review checklist ze seed diffu.
25
- - Výstup odlišuje jistá zjištění od doporučení nebo hypotéz.
25
+ - Výstup odlišuje jistá zjištění od doporučení, hypotéz a bodů pro lidský úsudek.
26
26
  - Je jasné, jak přidat nové pravidlo nebo heuristiku bez dlouhého onboardingu.
27
27
  - Další tým může během několika minut pokračovat v rozvoji bez chaosu.
28
28
 
29
29
  ## První krok pro agenta
30
30
 
31
- Nezačínej kódem. Nejdřív napiš pravidla review, tok vstupů a definici toho, co znamená dobrý checklist. Teprve potom navrhni první implementační slice.
31
+ Nezačínej kódem. Nejdřív napiš review rubric, tok vstupů a definici toho, co znamená dobrý checklist. Ukaž, kde je jistota, kde heuristika a co musí posoudit člověk. Teprve potom navrhni první implementační slice.
@@ -30,7 +30,9 @@ Pointa analogie:
30
30
 
31
31
  ## Mikro-cvičení
32
32
 
33
- Všichni dostanou stejný malý task. Jedna varianta bude prompt blob. Druhá varianta bude krátké zadání se 4 prvky a s odkazem na kontext zapsaný v repu. Pak porovnáme výsledky. Nehledáme „nejhezčí prompt“. Hledáme způsob práce, který přenese záměr, omezení a done criteria i do dalšího kroku.
33
+ Tohle je krátká facilitátorova ukázka, ne práce pro celý room.
34
+
35
+ Vezmeme stejný malý task ve dvou podmínkách. Jedna varianta bude prompt blob. Druhá varianta bude krátké zadání se 4 prvky a s odkazem na kontext zapsaný v repu. Pak porovnáme výsledky. Nehledáme „nejhezčí prompt“. Hledáme způsob práce, který přenese záměr, omezení a done criteria i do dalšího kroku.
34
36
 
35
37
  4 prvky pro druhou variantu:
36
38
 
@@ -64,6 +66,7 @@ Všichni dostanou stejný malý task. Jedna varianta bude prompt blob. Druhá va
64
66
 
65
67
  Po tomhle talku se tým nemá vracet k repu s pocitem, že potřebuje jen chytřejší prompt. Má se vracet s jedním jasným očekáváním:
66
68
 
69
+ - pokud ještě nemá workshop skill, teď je chvíle na `harness skill install`, pak `Codex: $workshop setup` nebo `pi: /skill:workshop`
67
70
  - nejdřív krátká mapa v repu
68
71
  - potom plan
69
72
  - potom první explicitní check
@@ -8,7 +8,7 @@ Current implementation in this repo:
8
8
 
9
9
  - lives in the repository `harness-cli/` package and ships publicly as `@harness-lab/cli`
10
10
  - distributes a portable participant workshop skill bundle through `harness skill install`
11
- - covers `auth login/logout/status` plus `workshop status/create-instance/update-instance/reset-instance/prepare/remove-instance/archive/phase set`
11
+ - covers `auth login/logout/status` plus `workshop current-instance/select-instance/status/list-instances/show-instance/create-instance/update-instance/reset-instance/prepare/remove-instance/archive/phase set`
12
12
  - targets the existing shared dashboard facilitator APIs
13
13
  - is tested for browser/device auth, local-dev Basic Auth fallback, and cookie-backed Neon bootstrap fallback
14
14
  - stores sessions in a local file under `HARNESS_CLI_HOME` or `~/.harness` by default
@@ -38,7 +38,11 @@ Required commands:
38
38
  - `harness auth login`
39
39
  - `harness auth logout`
40
40
  - `harness auth status`
41
+ - `harness workshop current-instance`
42
+ - `harness workshop select-instance <instance-id>`
41
43
  - `harness workshop status`
44
+ - `harness workshop list-instances`
45
+ - `harness workshop show-instance <instance-id>`
42
46
  - `harness workshop create-instance`
43
47
  - `harness workshop update-instance`
44
48
  - `harness workshop reset-instance <instance-id>`
@@ -83,28 +87,41 @@ Current auth layering:
83
87
  Current target-selection order:
84
88
 
85
89
  1. an explicit command or route instance id
86
- 2. the selected control-room instance in the dashboard
90
+ 2. the CLI-local persisted selection set by `harness workshop select-instance`
87
91
  3. the deployment default `HARNESS_WORKSHOP_INSTANCE_ID`
88
92
  4. a repository fallback only on surfaces that intentionally present a workspace-level default
89
93
 
90
94
  Current command posture:
91
95
 
92
- - explicit target required:
93
- - `harness workshop update-instance <instance-id>`
94
- - `harness workshop reset-instance <instance-id>`
95
- - `harness workshop prepare <instance-id>`
96
- - `harness workshop remove-instance <instance-id>`
96
+ - explicit workspace-discovery commands:
97
+ - `harness workshop current-instance`
98
+ - `harness workshop select-instance <instance-id>`
99
+ - `harness workshop list-instances`
100
+ - explicit target preferred but selected-instance fallback allowed:
101
+ - `harness workshop show-instance [<instance-id>]`
102
+ - `harness workshop update-instance [<instance-id>]`
103
+ - `harness workshop reset-instance [<instance-id>]`
104
+ - `harness workshop prepare [<instance-id>]`
105
+ - `harness workshop remove-instance [<instance-id>]`
97
106
  - explicit target is the created resource:
98
107
  - `harness workshop create-instance [<instance-id>]`
99
- - deployment-default target:
108
+ - selected-instance target first, otherwise deployment-default target:
100
109
  - `harness workshop status`
101
- - `harness workshop archive`
102
110
  - `harness workshop phase set <phase-id>`
111
+ - deployment-default target:
112
+ - `harness workshop archive`
103
113
 
104
114
  Current discoverability path:
105
115
 
106
116
  - facilitators can inspect available workshop instances through the dashboard workspace view
107
117
  - the same platform-scoped list lives behind the authenticated instances API surface at `/api/workshop/instances`
118
+ - the CLI should expose that list directly so facilitator skills do not need session-file inspection or raw authenticated scripts for routine discovery
119
+ - the CLI should also expose a local current-target selector so repeated facilitator work does not depend on ambient dashboard selection or deployment-default routing
120
+
121
+ Machine-readable posture:
122
+
123
+ - facilitator commands should support `--json` for strict machine parsing without headings or prose wrappers
124
+ - skills and agents should prefer `harness --json ...` over scraping human-oriented terminal formatting
108
125
 
109
126
  For the current facilitator lifecycle slice:
110
127
 
@@ -39,7 +39,7 @@ Do not use this folder for live event state. Real dates, rooms, rosters, checkpo
39
39
  ### Structured data
40
40
 
41
41
  - [`agenda.json`](agenda.json)
42
- Public-readable workshop agenda summary and workshop metadata.
42
+ Public-readable 10-phase mirror of the workshop day and workshop metadata.
43
43
 
44
44
  ## Related Runtime Documents
45
45
 
@@ -51,5 +51,6 @@ For maintainers working in the source repository, the runtime-facing structured
51
51
 
52
52
  - `docs/workshop-content-language-architecture.md`
53
53
  - `dashboard/lib/workshop-blueprint-agenda.json`
54
+ - `dashboard/lib/workshop-blueprint-localized-content.ts`
54
55
 
55
- Treat those as maintainer/source-repo references, not as portable participant-bundle docs.
56
+ Treat those as maintainer/source-repo references, not as portable participant-bundle docs. The public `agenda.json` file should stay aligned with the same phase structure, but it is not the runtime import source.
@@ -1,21 +1,23 @@
1
1
  {
2
- "version": 1,
2
+ "version": 2,
3
3
  "blueprintId": "harness-lab-core-day",
4
4
  "title": "Harness Lab",
5
- "subtitle": "Workshop operating system pro práci s AI agenty",
5
+ "subtitle": "Public-readable 10-phase mirror of the Harness Lab workshop day",
6
+ "presentationMode": "public-readable-mirror",
7
+ "sourceNote": "Runtime imports come from dashboard/lib/workshop-blueprint-agenda.json plus dashboard/lib/workshop-blueprint-localized-content.ts.",
6
8
  "principles": [
7
- "Mapa před pohybem",
8
- "Ověření je hranice důvěry",
9
- "Pište tak, aby další tým mohl pokračovat"
9
+ "Context before generation",
10
+ "Verification is the trust boundary",
11
+ "Work so another team can continue"
10
12
  ],
11
13
  "phases": [
12
14
  {
13
15
  "id": "opening",
14
16
  "order": 1,
15
- "label": "Úvod a naladění",
17
+ "label": "Opening and framing",
16
18
  "startTime": "09:10",
17
19
  "kind": "shared",
18
- "goal": "Rámec dne: nejde o promptování, ale o repo a workflow, které přežijí handoff."
20
+ "goal": "Shift the room from prompt-hackathon expectations to continuation-quality discipline."
19
21
  },
20
22
  {
21
23
  "id": "talk",
@@ -23,31 +25,71 @@
23
25
  "label": "Context is King",
24
26
  "startTime": "09:40",
25
27
  "kind": "shared",
26
- "goal": "Krátký talk a porovnání přístupů. Krátká mapa v repu je silnější než prompt blob."
28
+ "goal": "Turn context into a precise thesis: AGENTS.md, plans, and checks are working infrastructure, not polish."
27
29
  },
28
30
  {
29
- "id": "build-1",
31
+ "id": "demo",
30
32
  "order": 3,
33
+ "label": "Demo",
34
+ "startTime": "10:05",
35
+ "kind": "shared",
36
+ "goal": "Show one compact workflow story: context, planning, implementation, review, and safe fallback moves."
37
+ },
38
+ {
39
+ "id": "build-1",
40
+ "order": 4,
31
41
  "label": "Build Phase 1",
32
42
  "startTime": "10:30",
33
43
  "kind": "team",
34
- "goal": "Nejdřív udělejte z repa operating surface: krátké AGENTS.md, plán, build/test flow, první ověření a první reviewed output."
44
+ "goal": "Before lunch, get every team to a navigable repo, AGENTS.md, a plan, one explicit check, and a first reviewed slice."
45
+ },
46
+ {
47
+ "id": "intermezzo-1",
48
+ "order": 5,
49
+ "label": "Intermezzo 1",
50
+ "startTime": "11:40",
51
+ "kind": "shared",
52
+ "goal": "Surface one change, one proof, and one next move from the room before lunch momentum slips."
53
+ },
54
+ {
55
+ "id": "lunch-reset",
56
+ "order": 6,
57
+ "label": "Lunch Reset",
58
+ "startTime": "12:15",
59
+ "kind": "shared",
60
+ "goal": "Frame lunch as a handoff preparation point, not as a pause between unrelated workshop blocks."
35
61
  },
36
62
  {
37
63
  "id": "rotation",
38
- "order": 4,
39
- "label": "Rotace týmů",
64
+ "order": 7,
65
+ "label": "Continuation Shift",
40
66
  "startTime": "13:30",
41
67
  "kind": "team",
42
- "goal": "Plný přesun lidí mezi stoly. Nejdřív čtěte a napište diagnózu, pak měňte kód."
68
+ "goal": "Force silent repo reading, diagnosis, and explicit continuation before anyone edits code."
69
+ },
70
+ {
71
+ "id": "build-2",
72
+ "order": 8,
73
+ "label": "Build Phase 2",
74
+ "startTime": "14:05",
75
+ "kind": "team",
76
+ "goal": "Turn continuation friction into better repo guidance, stronger checks, and clearer next moves."
77
+ },
78
+ {
79
+ "id": "intermezzo-2",
80
+ "order": 9,
81
+ "label": "Intermezzo 2",
82
+ "startTime": "15:05",
83
+ "kind": "shared",
84
+ "goal": "Name which repo signals actually saved time during continuation and which failures repeated."
43
85
  },
44
86
  {
45
87
  "id": "reveal",
46
- "order": 5,
47
- "label": "Reveal a reflexe",
88
+ "order": 10,
89
+ "label": "Reveal and reflection",
48
90
  "startTime": "15:45",
49
91
  "kind": "shared",
50
- "goal": "Co pomohlo pokračovat, co chybělo, a kterou bolest teď proměníme v lepší harness."
92
+ "goal": "Turn the day into reusable practice: what helped continuation, what failed, and what should change next week."
51
93
  }
52
94
  ],
53
95
  "runtimeImport": {
@@ -147,8 +147,8 @@ The day should not feel like separate agenda cards. Each phase should change wha
147
147
  - `AGENTS.md` as a table of contents that points to deeper sources of truth
148
148
  - small, continuous garbage collection before bad patterns spread
149
149
 
150
- ## Canonical Agenda Source
150
+ ## Agenda Mirror
151
151
 
152
- The reusable agenda lives in [`agenda.json`](agenda.json).
152
+ The public-readable 10-phase mirror lives in [`agenda.json`](agenda.json).
153
153
 
154
- Runtime workshop instances import from that blueprint and then track live phase position, reveal state, teams, and checkpoints locally in the private runtime layer.
154
+ Runtime workshop instances import from the maintained dashboard blueprint source pair and then track live phase position, reveal state, teams, and checkpoints locally in the private runtime layer.
@@ -41,6 +41,20 @@ Note:
41
41
  - if the facilitator wants OS-native storage, they can use `HARNESS_SESSION_STORAGE=keychain`, `credential-manager`, or `secret-service`
42
42
  - `--auth basic` and `--auth neon` remain explicit fallbacks for local dev/bootstrap
43
43
 
44
+ Preferred operator flow after login:
45
+
46
+ ```bash
47
+ harness workshop list-instances
48
+ harness workshop select-instance sample-workshop-demo-orbit
49
+ harness workshop current-instance
50
+ harness workshop status
51
+ ```
52
+
53
+ Rules:
54
+ - prefer this flow over reading local CLI session files or composing ad hoc authenticated `node -e` scripts
55
+ - once a local selection exists, the CLI may use it as the default target for subsequent facilitator operations
56
+ - when an agent needs machine-readable output, prefer `harness --json ...`
57
+
44
58
  ### `/workshop facilitator logout`
45
59
 
46
60
  Ask the facilitator to run:
@@ -65,6 +79,103 @@ Show:
65
79
  - facilitator list with roles
66
80
  - team count
67
81
 
82
+ Use this for the current default or selected workshop context.
83
+ If the facilitator needs the full workspace registry first, use `list-instances` instead of probing local session files or writing raw authenticated fetch scripts.
84
+
85
+ Targeting behavior:
86
+ - if the facilitator previously ran `harness workshop select-instance <instance-id>`, `status` reports that selected instance
87
+ - if no local selection exists, `status` falls back to the deployment-default workshop context
88
+ - for exact machine parsing, prefer `harness --json workshop status`
89
+
90
+ ### `/workshop facilitator current-instance`
91
+
92
+ Preferred path:
93
+
94
+ ```bash
95
+ harness workshop current-instance
96
+ ```
97
+
98
+ Show:
99
+ - the locally selected instance id when one exists
100
+ - whether the current target came from persisted selection or `HARNESS_WORKSHOP_INSTANCE_ID`
101
+ - the resolved instance summary and full record for operator verification
102
+
103
+ Rules:
104
+ - use this after `select-instance` when the facilitator wants to confirm the CLI target before update, reset, or remove
105
+ - if nothing is selected, the command should say so clearly instead of forcing the facilitator into session-file inspection
106
+
107
+ ### `/workshop facilitator select-instance <instance-id>`
108
+
109
+ Preferred path:
110
+
111
+ ```bash
112
+ harness workshop select-instance sample-workshop-demo-orbit
113
+ ```
114
+
115
+ Clear the stored selection:
116
+
117
+ ```bash
118
+ harness workshop select-instance --clear
119
+ ```
120
+
121
+ Rules:
122
+ - use this when a facilitator will perform several operations against the same live workshop instance
123
+ - the CLI should validate the instance through the server before persisting the selection
124
+ - after selection, `status` and `phase set` should target that instance instead of the deployment default
125
+ - `show-instance`, `update-instance`, `reset-instance`, `prepare`, and `remove-instance` may omit `<instance-id>` when a valid selection already exists
126
+
127
+ ### `/workshop facilitator list-instances`
128
+
129
+ Preferred path:
130
+
131
+ ```bash
132
+ harness workshop list-instances
133
+ ```
134
+
135
+ Show:
136
+ - facilitator-visible instance ids
137
+ - template id
138
+ - status
139
+ - content language
140
+ - event title and room metadata when present
141
+
142
+ Raw API reference:
143
+
144
+ ```http
145
+ GET {DASHBOARD_URL}/api/workshop/instances
146
+ ```
147
+
148
+ Rules:
149
+ - prefer this over inspecting local session files or composing one-off authenticated scripts
150
+ - use it when the facilitator needs to discover which live instances exist before reset, update, or scene work
151
+ - keep raw API usage as a diagnostic fallback, not the default operator workflow
152
+ - when an agent needs to parse the output, prefer `harness --json workshop list-instances`
153
+
154
+ ### `/workshop facilitator show-instance <instance-id>`
155
+
156
+ Preferred path:
157
+
158
+ ```bash
159
+ harness workshop show-instance sample-workshop-demo-orbit
160
+ ```
161
+
162
+ Show:
163
+ - one explicit instance record
164
+ - summary metadata for quick operator inspection
165
+ - the full instance payload when the facilitator needs exact values before a mutation
166
+
167
+ Raw API reference:
168
+
169
+ ```http
170
+ GET {DASHBOARD_URL}/api/workshop/instances/{instanceId}
171
+ ```
172
+
173
+ Rules:
174
+ - use this when the facilitator needs one specific instance, not the deployment-default `workshop status`
175
+ - if the route returns `404`, the instance does not exist or is not visible to the facilitator
176
+ - prefer this over ad hoc authenticated scripts for routine discovery
177
+ - if a local selection already exists, the facilitator may omit `<instance-id>` and let the CLI use the stored target
178
+
68
179
  ### `/workshop facilitator grant <email> <role>`
69
180
 
70
181
  Use the CLI-backed privileged request path. The skill should not handle auth bootstrap itself.
@@ -176,6 +287,8 @@ Rules:
176
287
  - send only the fields you want to change
177
288
  - do not use reset for an ordinary title, venue, or room correction
178
289
  - if the route returns `400`, the payload is wrong; if it returns `404`, the instance does not exist
290
+ - use `--content-lang cs|en` when the facilitator intends to change the workshop delivery language for that instance
291
+ - if the facilitator already selected a local current instance, the CLI may omit `<instance-id>` and use the stored target
179
292
 
180
293
  ### `/workshop facilitator reset-instance <instance-id>`
181
294
 
@@ -202,6 +315,7 @@ Rules:
202
315
  - warn that reset archives current runtime state first and then clears live runtime state for the instance
203
316
  - prefer `update-instance` for ordinary metadata corrections; reset is the high-impact operation
204
317
  - if the facilitator does not specify a template, keep the current template unless there is a clear reason to switch
318
+ - if the facilitator already selected a local current instance, the CLI may omit `<instance-id>` and use the stored target
205
319
 
206
320
  ### `/workshop facilitator prepare`
207
321
 
@@ -221,6 +335,7 @@ Content-Type: application/json
221
335
  ```
222
336
 
223
337
  This sets the instance to `prepared` state and verifies the event code.
338
+ If a local current instance is already selected, the CLI may omit `<instance-id>` and use the stored target.
224
339
 
225
340
  ### `/workshop facilitator remove-instance <instance-id>`
226
341
 
@@ -242,6 +357,7 @@ Content-Type: application/json
242
357
  Rules:
243
358
  - remove remains an owner-only operation
244
359
  - the skill should warn the facilitator that this is destructive removal from the active list, not routine metadata editing
360
+ - if the facilitator already selected a local current instance, the CLI may omit `<instance-id>` and use the stored target
245
361
 
246
362
  ### `/workshop facilitator agenda`
247
363
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@harness-lab/cli",
3
- "version": "0.2.7",
3
+ "version": "0.2.9",
4
4
  "description": "Participant-facing Harness Lab CLI for facilitator auth and workshop operations",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/client.js CHANGED
@@ -90,12 +90,27 @@ export function createHarnessClient({ fetchFn, session }) {
90
90
  getWorkshopStatus() {
91
91
  return request("/api/workshop");
92
92
  },
93
+ listWorkshopInstances() {
94
+ return request("/api/workshop/instances");
95
+ },
96
+ getWorkshopInstance(instanceId) {
97
+ return request(`/api/workshop/instances/${encodeURIComponent(instanceId)}`);
98
+ },
93
99
  getAgenda() {
94
100
  return request("/api/agenda");
95
101
  },
102
+ getWorkshopAgenda(instanceId) {
103
+ return request(`/api/workshop/instances/${encodeURIComponent(instanceId)}/agenda`);
104
+ },
96
105
  setCurrentPhase(currentId) {
97
106
  return request("/api/agenda", { method: "PATCH", body: { currentId } });
98
107
  },
108
+ setCurrentPhaseForInstance(instanceId, currentId) {
109
+ return request(`/api/workshop/instances/${encodeURIComponent(instanceId)}/agenda`, {
110
+ method: "PATCH",
111
+ body: { itemId: currentId },
112
+ });
113
+ },
99
114
  archiveWorkshop(notes) {
100
115
  return request("/api/workshop/archive", { method: "POST", body: notes ? { notes } : {} });
101
116
  },
package/src/io.js CHANGED
@@ -84,7 +84,9 @@ function writeWrapped(stream, text, options = {}) {
84
84
  }
85
85
  }
86
86
 
87
- export function createCliUi(io) {
87
+ export function createCliUi(io, options = {}) {
88
+ const jsonMode = options.jsonMode === true;
89
+
88
90
  function streamFor(target) {
89
91
  return target === "stderr" ? io.stderr : io.stdout;
90
92
  }
@@ -115,6 +117,10 @@ export function createCliUi(io) {
115
117
  function status(kind, text, options = {}) {
116
118
  const target = options.stream ?? (kind === "error" ? "stderr" : "stdout");
117
119
  const stream = streamFor(target);
120
+ if (jsonMode) {
121
+ writeLine(stream, JSON.stringify({ ok: kind !== "error", level: kind, message: text }));
122
+ return;
123
+ }
118
124
  const chalk = createStyler(stream, io.env);
119
125
  const prefixMap = {
120
126
  ok: chalk.green.bold("[ok]"),
@@ -178,7 +184,9 @@ export function createCliUi(io) {
178
184
 
179
185
  function json(title, value, options = {}) {
180
186
  const target = options.stream ?? "stdout";
181
- heading(title, { stream: target });
187
+ if (!jsonMode) {
188
+ heading(title, { stream: target });
189
+ }
182
190
  writeLine(streamFor(target), JSON.stringify(value, null, 2));
183
191
  }
184
192
 
package/src/run-cli.js CHANGED
@@ -16,6 +16,7 @@ function sleep(ms) {
16
16
  function parseArgs(argv) {
17
17
  const positionals = [];
18
18
  const flags = {};
19
+ const booleanFlags = new Set(["json", "help", "version", "force", "no-open", "clear"]);
19
20
 
20
21
  for (let index = 0; index < argv.length; index += 1) {
21
22
  const value = argv[index];
@@ -29,6 +30,10 @@ function parseArgs(argv) {
29
30
  }
30
31
  if (value.startsWith("--")) {
31
32
  const key = value.slice(2);
33
+ if (booleanFlags.has(key)) {
34
+ flags[key] = true;
35
+ continue;
36
+ }
32
37
  const next = argv[index + 1];
33
38
  if (!next || next.startsWith("--")) {
34
39
  flags[key] = true;
@@ -103,6 +108,7 @@ async function readRequiredCommandValue(io, flags, keys, promptLabel, fallbackVa
103
108
 
104
109
  function buildWorkshopMetadataInput(flags) {
105
110
  const input = {
111
+ contentLang: readStringFlag(flags, "content-lang", "content-language"),
106
112
  eventTitle: readStringFlag(flags, "event-title", "title"),
107
113
  city: readStringFlag(flags, "city"),
108
114
  dateRange: readStringFlag(flags, "date-range", "date"),
@@ -122,6 +128,7 @@ function hasWorkshopMetadataInput(input) {
122
128
 
123
129
  async function promptWorkshopMetadataInput(io) {
124
130
  const prompts = [
131
+ ["contentLang", "Content language (cs/en, leave blank to skip): "],
125
132
  ["eventTitle", "Event title (leave blank to skip): "],
126
133
  ["city", "City (leave blank to skip): "],
127
134
  ["dateRange", "Date range (leave blank to skip): "],
@@ -148,7 +155,9 @@ function summarizeWorkshopInstance(instance) {
148
155
 
149
156
  return {
150
157
  instanceId: instance?.id ?? null,
158
+ templateId: instance?.templateId ?? null,
151
159
  status: instance?.status ?? null,
160
+ contentLang: workshopMeta.contentLang ?? null,
152
161
  eventTitle: workshopMeta.eventTitle ?? null,
153
162
  city: workshopMeta.city ?? null,
154
163
  dateRange: workshopMeta.dateRange ?? null,
@@ -157,12 +166,34 @@ function summarizeWorkshopInstance(instance) {
157
166
  };
158
167
  }
159
168
 
169
+ function resolveCurrentInstanceTarget(session, env) {
170
+ if (typeof session?.selectedInstanceId === "string" && session.selectedInstanceId.trim().length > 0) {
171
+ return {
172
+ instanceId: session.selectedInstanceId.trim(),
173
+ source: "session",
174
+ };
175
+ }
176
+
177
+ if (typeof env?.HARNESS_WORKSHOP_INSTANCE_ID === "string" && env.HARNESS_WORKSHOP_INSTANCE_ID.trim().length > 0) {
178
+ return {
179
+ instanceId: env.HARNESS_WORKSHOP_INSTANCE_ID.trim(),
180
+ source: "env",
181
+ };
182
+ }
183
+
184
+ return {
185
+ instanceId: null,
186
+ source: "none",
187
+ };
188
+ }
189
+
160
190
  function printUsage(io, ui) {
161
191
  ui.heading("Harness CLI");
162
192
  ui.paragraph(`Version ${version}`);
163
193
  ui.blank();
164
194
  ui.section("Usage");
165
195
  ui.commandList([
196
+ "harness [--json] <command>",
166
197
  "harness --help",
167
198
  "harness --version",
168
199
  "harness version",
@@ -174,10 +205,14 @@ function printUsage(io, ui) {
174
205
  "harness auth logout",
175
206
  "harness auth status",
176
207
  "harness skill install [--target PATH] [--force]",
208
+ "harness workshop current-instance",
209
+ "harness workshop select-instance <instance-id> [--clear]",
177
210
  "harness workshop status",
211
+ "harness workshop list-instances",
212
+ "harness workshop show-instance <instance-id>",
178
213
  "harness workshop archive [--notes TEXT]",
179
- "harness workshop create-instance [<instance-id>] [--template-id ID] [--event-title TEXT] [--city CITY]",
180
- "harness workshop update-instance <instance-id> [--event-title TEXT] [--city CITY]",
214
+ "harness workshop create-instance [<instance-id>] [--template-id ID] [--content-lang cs|en] [--event-title TEXT] [--city CITY]",
215
+ "harness workshop update-instance <instance-id> [--content-lang cs|en] [--event-title TEXT] [--city CITY]",
181
216
  "harness workshop reset-instance <instance-id> [--template-id ID]",
182
217
  "harness workshop prepare <instance-id>",
183
218
  "harness workshop remove-instance <instance-id>",
@@ -565,9 +600,30 @@ async function handleWorkshopStatus(io, ui, env, deps) {
565
600
 
566
601
  try {
567
602
  const client = createHarnessClient({ fetchFn: deps.fetchFn, session });
603
+ const target = resolveCurrentInstanceTarget(session, env);
604
+
605
+ if (target.source === "session" && target.instanceId) {
606
+ const [instanceResult, agenda] = await Promise.all([
607
+ client.getWorkshopInstance(target.instanceId),
608
+ client.getWorkshopAgenda(target.instanceId),
609
+ ]);
610
+ ui.json("Workshop Status", {
611
+ ok: true,
612
+ targetInstanceId: target.instanceId,
613
+ targetSource: target.source,
614
+ ...summarizeWorkshopInstance(instanceResult.instance),
615
+ workshopMeta: instanceResult.instance?.workshopMeta ?? null,
616
+ currentPhase: agenda.phase,
617
+ agendaItems: Array.isArray(agenda.items) ? agenda.items.length : null,
618
+ });
619
+ return 0;
620
+ }
621
+
568
622
  const [workshop, agenda] = await Promise.all([client.getWorkshopStatus(), client.getAgenda()]);
569
623
  ui.json("Workshop Status", {
570
624
  ok: true,
625
+ targetInstanceId: target.instanceId,
626
+ targetSource: target.source,
571
627
  workshopId: workshop.workshopId,
572
628
  workshopMeta: workshop.workshopMeta,
573
629
  currentPhase: agenda.phase,
@@ -583,6 +639,168 @@ async function handleWorkshopStatus(io, ui, env, deps) {
583
639
  }
584
640
  }
585
641
 
642
+ async function handleWorkshopCurrentInstance(io, ui, env, deps) {
643
+ const session = await requireSession(io, ui, env);
644
+ if (!session) {
645
+ return 1;
646
+ }
647
+
648
+ const target = resolveCurrentInstanceTarget(session, env);
649
+ if (!target.instanceId) {
650
+ ui.json("Workshop Current Instance", {
651
+ ok: true,
652
+ instanceId: null,
653
+ source: target.source,
654
+ selectedInstanceId: session.selectedInstanceId ?? null,
655
+ });
656
+ return 0;
657
+ }
658
+
659
+ try {
660
+ const client = createHarnessClient({ fetchFn: deps.fetchFn, session });
661
+ const result = await client.getWorkshopInstance(target.instanceId);
662
+ ui.json("Workshop Current Instance", {
663
+ ok: true,
664
+ source: target.source,
665
+ selectedInstanceId: session.selectedInstanceId ?? null,
666
+ ...summarizeWorkshopInstance(result.instance),
667
+ instance: result.instance,
668
+ });
669
+ return 0;
670
+ } catch (error) {
671
+ if (error instanceof HarnessApiError) {
672
+ ui.status("error", `Current instance lookup failed: ${error.message}`, { stream: "stderr" });
673
+ return 1;
674
+ }
675
+ throw error;
676
+ }
677
+ }
678
+
679
+ async function handleWorkshopSelectInstance(io, ui, env, positionals, flags, deps) {
680
+ const session = await requireSession(io, ui, env);
681
+ if (!session) {
682
+ return 1;
683
+ }
684
+
685
+ if (flags.clear === true) {
686
+ const nextSession = { ...session };
687
+ delete nextSession.selectedInstanceId;
688
+ if (!(await persistSession(io, ui, env, nextSession))) {
689
+ return 1;
690
+ }
691
+
692
+ const target = resolveCurrentInstanceTarget(nextSession, env);
693
+ ui.json("Workshop Select Instance", {
694
+ ok: true,
695
+ selectedInstanceId: null,
696
+ currentInstanceId: target.instanceId,
697
+ source: target.source,
698
+ cleared: true,
699
+ });
700
+ return 0;
701
+ }
702
+
703
+ const instanceId = await readRequiredCommandValue(
704
+ io,
705
+ flags,
706
+ ["id", "instance-id"],
707
+ "Instance id: ",
708
+ readOptionalPositional(positionals, 2),
709
+ );
710
+ if (!instanceId) {
711
+ ui.status("error", "Instance id is required.", { stream: "stderr" });
712
+ return 1;
713
+ }
714
+
715
+ try {
716
+ const client = createHarnessClient({ fetchFn: deps.fetchFn, session });
717
+ const result = await client.getWorkshopInstance(instanceId);
718
+ const nextSession = {
719
+ ...session,
720
+ selectedInstanceId: result.instance?.id ?? instanceId,
721
+ };
722
+ if (!(await persistSession(io, ui, env, nextSession))) {
723
+ return 1;
724
+ }
725
+
726
+ ui.json("Workshop Select Instance", {
727
+ ok: true,
728
+ source: "session",
729
+ selectedInstanceId: nextSession.selectedInstanceId,
730
+ ...summarizeWorkshopInstance(result.instance),
731
+ instance: result.instance,
732
+ });
733
+ return 0;
734
+ } catch (error) {
735
+ if (error instanceof HarnessApiError) {
736
+ ui.status("error", `Select instance failed: ${error.message}`, { stream: "stderr" });
737
+ return 1;
738
+ }
739
+ throw error;
740
+ }
741
+ }
742
+
743
+ async function handleWorkshopListInstances(io, ui, env, deps) {
744
+ const session = await requireSession(io, ui, env);
745
+ if (!session) {
746
+ return 1;
747
+ }
748
+
749
+ try {
750
+ const client = createHarnessClient({ fetchFn: deps.fetchFn, session });
751
+ const result = await client.listWorkshopInstances();
752
+ const items = Array.isArray(result?.items) ? result.items.map((instance) => summarizeWorkshopInstance(instance)) : [];
753
+ ui.json("Workshop Instances", {
754
+ ok: true,
755
+ count: items.length,
756
+ items,
757
+ });
758
+ return 0;
759
+ } catch (error) {
760
+ if (error instanceof HarnessApiError) {
761
+ ui.status("error", `List instances failed: ${error.message}`, { stream: "stderr" });
762
+ return 1;
763
+ }
764
+ throw error;
765
+ }
766
+ }
767
+
768
+ async function handleWorkshopShowInstance(io, ui, env, positionals, flags, deps) {
769
+ const session = await requireSession(io, ui, env);
770
+ if (!session) {
771
+ return 1;
772
+ }
773
+
774
+ const instanceId = await readRequiredCommandValue(
775
+ io,
776
+ flags,
777
+ ["id", "instance-id"],
778
+ "Instance id: ",
779
+ readOptionalPositional(positionals, 2) ?? session.selectedInstanceId,
780
+ );
781
+ if (!instanceId) {
782
+ ui.status("error", "Instance id is required.", { stream: "stderr" });
783
+ return 1;
784
+ }
785
+
786
+ try {
787
+ const client = createHarnessClient({ fetchFn: deps.fetchFn, session });
788
+ const result = await client.getWorkshopInstance(instanceId);
789
+ ui.json("Workshop Instance", {
790
+ ok: true,
791
+ ...summarizeWorkshopInstance(result.instance),
792
+ instance: result.instance,
793
+ });
794
+ return 0;
795
+ } catch (error) {
796
+ if (error instanceof HarnessApiError) {
797
+ ui.status("error", `Show instance failed: ${error.message}`, { stream: "stderr" });
798
+ return 1;
799
+ }
800
+ throw error;
801
+ }
802
+ }
803
+
586
804
  async function handleWorkshopArchive(io, ui, env, flags, deps) {
587
805
  const session = await requireSession(io, ui, env);
588
806
  if (!session) {
@@ -657,7 +875,7 @@ async function handleWorkshopUpdateInstance(io, ui, env, positionals, flags, dep
657
875
  flags,
658
876
  ["id"],
659
877
  "Instance id: ",
660
- readOptionalPositional(positionals, 2),
878
+ readOptionalPositional(positionals, 2) ?? session.selectedInstanceId,
661
879
  );
662
880
  if (!instanceId) {
663
881
  ui.status("error", "Instance id is required.", { stream: "stderr" });
@@ -672,7 +890,7 @@ async function handleWorkshopUpdateInstance(io, ui, env, positionals, flags, dep
672
890
  if (!hasWorkshopMetadataInput(payload)) {
673
891
  ui.status(
674
892
  "error",
675
- "At least one metadata field is required. Use flags such as --event-title, --date-range, --venue-name, or --room-name.",
893
+ "At least one metadata field is required. Use flags such as --content-lang, --event-title, --date-range, --venue-name, or --room-name.",
676
894
  { stream: "stderr" },
677
895
  );
678
896
  return 1;
@@ -707,7 +925,7 @@ async function handleWorkshopPrepare(io, ui, env, positionals, flags, deps) {
707
925
  flags,
708
926
  ["id", "instance-id"],
709
927
  "Instance id: ",
710
- readOptionalPositional(positionals, 2),
928
+ readOptionalPositional(positionals, 2) ?? session.selectedInstanceId,
711
929
  );
712
930
  if (!instanceId) {
713
931
  ui.status("error", "Instance id is required.", { stream: "stderr" });
@@ -743,7 +961,7 @@ async function handleWorkshopResetInstance(io, ui, env, positionals, flags, deps
743
961
  flags,
744
962
  ["id", "instance-id"],
745
963
  "Instance id: ",
746
- readOptionalPositional(positionals, 2),
964
+ readOptionalPositional(positionals, 2) ?? session.selectedInstanceId,
747
965
  );
748
966
  if (!instanceId) {
749
967
  ui.status("error", "Instance id is required.", { stream: "stderr" });
@@ -778,7 +996,7 @@ async function handleWorkshopRemoveInstance(io, ui, env, positionals, flags, dep
778
996
  flags,
779
997
  ["id", "instance-id"],
780
998
  "Instance id: ",
781
- readOptionalPositional(positionals, 2),
999
+ readOptionalPositional(positionals, 2) ?? session.selectedInstanceId,
782
1000
  );
783
1001
  if (!instanceId) {
784
1002
  ui.status("error", "Instance id is required.", { stream: "stderr" });
@@ -817,7 +1035,11 @@ async function handleWorkshopPhaseSet(io, ui, env, positionals, deps) {
817
1035
 
818
1036
  try {
819
1037
  const client = createHarnessClient({ fetchFn: deps.fetchFn, session });
820
- const result = await client.setCurrentPhase(phaseId);
1038
+ const target = resolveCurrentInstanceTarget(session, env);
1039
+ const result =
1040
+ target.source === "session" && target.instanceId
1041
+ ? await client.setCurrentPhaseForInstance(target.instanceId, phaseId)
1042
+ : await client.setCurrentPhase(phaseId);
821
1043
  ui.json("Workshop Phase", result);
822
1044
  return 0;
823
1045
  } catch (error) {
@@ -832,8 +1054,8 @@ async function handleWorkshopPhaseSet(io, ui, env, positionals, deps) {
832
1054
  export async function runCli(argv, io, deps = {}) {
833
1055
  const fetchFn = deps.fetchFn ?? globalThis.fetch;
834
1056
  const mergedDeps = { fetchFn, sleepFn: deps.sleepFn, openUrl: deps.openUrl, cwd: deps.cwd };
835
- const ui = createCliUi(io);
836
1057
  const { positionals, flags } = parseArgs(argv);
1058
+ const ui = createCliUi(io, { jsonMode: flags.json === true || flags.output === "json" });
837
1059
  const [scope, action, subaction] = positionals;
838
1060
 
839
1061
  if (flags.help === true) {
@@ -876,10 +1098,26 @@ export async function runCli(argv, io, deps = {}) {
876
1098
  return handleSkillInstall(io, ui, mergedDeps, flags);
877
1099
  }
878
1100
 
1101
+ if (scope === "workshop" && action === "current-instance") {
1102
+ return handleWorkshopCurrentInstance(io, ui, io.env, mergedDeps);
1103
+ }
1104
+
1105
+ if (scope === "workshop" && action === "select-instance") {
1106
+ return handleWorkshopSelectInstance(io, ui, io.env, positionals, flags, mergedDeps);
1107
+ }
1108
+
879
1109
  if (scope === "workshop" && action === "status") {
880
1110
  return handleWorkshopStatus(io, ui, io.env, mergedDeps);
881
1111
  }
882
1112
 
1113
+ if (scope === "workshop" && action === "list-instances") {
1114
+ return handleWorkshopListInstances(io, ui, io.env, mergedDeps);
1115
+ }
1116
+
1117
+ if (scope === "workshop" && action === "show-instance") {
1118
+ return handleWorkshopShowInstance(io, ui, io.env, positionals, flags, mergedDeps);
1119
+ }
1120
+
883
1121
  if (scope === "workshop" && action === "archive") {
884
1122
  return handleWorkshopArchive(io, ui, io.env, flags, mergedDeps);
885
1123
  }
@@ -314,6 +314,7 @@ export function sanitizeSession(session, env) {
314
314
  authType: session.authType,
315
315
  username: session.username ?? null,
316
316
  email: session.email ?? null,
317
+ selectedInstanceId: session.selectedInstanceId ?? null,
317
318
  loggedInAt: session.loggedInAt,
318
319
  expiresAt: session.expiresAt ?? null,
319
320
  mode: session.mode ?? "local-dev",
@@ -225,6 +225,20 @@ async function writeWorkshopBundleManifest(bundleRoot, manifest) {
225
225
  );
226
226
  }
227
227
 
228
+ async function pruneBundleFiles(bundleRoot, manifest) {
229
+ const expectedFiles = new Set([
230
+ WORKSHOP_BUNDLE_MANIFEST,
231
+ ...manifest.files.map((file) => file.path),
232
+ ]);
233
+ const currentFiles = await listFilesRecursive(bundleRoot);
234
+
235
+ for (const file of currentFiles) {
236
+ if (!expectedFiles.has(file.relativePath)) {
237
+ await fs.rm(file.absolutePath, { force: true });
238
+ }
239
+ }
240
+ }
241
+
228
242
  export async function createWorkshopBundleFromSource(sourceRoot, targetRoot, options = {}) {
229
243
  if (options.clean === true) {
230
244
  await fs.rm(targetRoot, { recursive: true, force: true });
@@ -234,6 +248,9 @@ export async function createWorkshopBundleFromSource(sourceRoot, targetRoot, opt
234
248
  await fs.rm(path.join(targetRoot, "workshop-skill", "SKILL.md"), { force: true });
235
249
  await copyBundleFiles(sourceRoot, targetRoot);
236
250
  const manifest = await createWorkshopBundleManifestFromSource(sourceRoot);
251
+ if (options.prune === true) {
252
+ await pruneBundleFiles(targetRoot, manifest);
253
+ }
237
254
  await writeWorkshopBundleManifest(targetRoot, manifest);
238
255
  }
239
256
 
@@ -250,7 +267,7 @@ export async function syncPackagedWorkshopBundle() {
250
267
  export async function syncRepoBundledWorkshopSkill() {
251
268
  const sourceRoot = getRepoWorkshopSourceRoot();
252
269
  const bundleRoot = getRepoBundledWorkshopSkillPath();
253
- await createWorkshopBundleFromSource(sourceRoot, bundleRoot, { clean: true });
270
+ await createWorkshopBundleFromSource(sourceRoot, bundleRoot, { prune: true });
254
271
  return {
255
272
  sourceRoot,
256
273
  bundleRoot,