@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 +29 -3
- package/assets/workshop-bundle/SKILL.md +36 -0
- package/assets/workshop-bundle/bundle-manifest.json +11 -11
- package/assets/workshop-bundle/content/facilitation/master-guide.md +8 -5
- package/assets/workshop-bundle/content/project-briefs/code-review-helper.md +10 -10
- package/assets/workshop-bundle/content/talks/context-is-king.md +4 -1
- package/assets/workshop-bundle/docs/harness-cli-foundation.md +26 -9
- package/assets/workshop-bundle/workshop-blueprint/README.md +3 -2
- package/assets/workshop-bundle/workshop-blueprint/agenda.json +58 -16
- package/assets/workshop-bundle/workshop-blueprint/day-structure.md +3 -3
- package/assets/workshop-bundle/workshop-skill/facilitator.md +116 -0
- package/package.json +1 -1
- package/src/client.js +15 -0
- package/src/io.js +10 -2
- package/src/run-cli.js +247 -9
- package/src/session-store.js +1 -0
- package/src/workshop-bundle.js +18 -1
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
|
|
126
|
-
harness workshop
|
|
127
|
-
harness workshop
|
|
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.
|
|
5
|
-
"contentHash": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
101
|
+
"sha256": "0bb48497b458474a546c351118ad455c00ad997c9efc0e227b388f81dd0b55e3"
|
|
102
102
|
},
|
|
103
103
|
{
|
|
104
104
|
"path": "docs/harness-cli-foundation.md",
|
|
105
|
-
"sha256": "
|
|
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": "
|
|
137
|
+
"sha256": "d1e242a857f61bc2fae29c74a9e0efbd1ba0ed5beb25087dbaeffabc5f53b4a5"
|
|
138
138
|
},
|
|
139
139
|
{
|
|
140
140
|
"path": "workshop-blueprint/agenda.json",
|
|
141
|
-
"sha256": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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
|
-
-
|
|
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
|
|
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 →
|
|
18
|
-
- Musí být zřejmé, jaké vstupy nástroj
|
|
19
|
-
- Přidejte seed diff nebo `examples/`, aby šlo workflow lokálně ověřit.
|
|
20
|
-
-
|
|
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
|
|
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š
|
|
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
|
-
|
|
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
|
|
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
|
|
93
|
-
- `harness workshop
|
|
94
|
-
- `harness workshop
|
|
95
|
-
- `harness workshop
|
|
96
|
-
|
|
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
|
|
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":
|
|
2
|
+
"version": 2,
|
|
3
3
|
"blueprintId": "harness-lab-core-day",
|
|
4
4
|
"title": "Harness Lab",
|
|
5
|
-
"subtitle": "
|
|
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
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
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": "
|
|
17
|
+
"label": "Opening and framing",
|
|
16
18
|
"startTime": "09:10",
|
|
17
19
|
"kind": "shared",
|
|
18
|
-
"goal": "
|
|
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": "
|
|
28
|
+
"goal": "Turn context into a precise thesis: AGENTS.md, plans, and checks are working infrastructure, not polish."
|
|
27
29
|
},
|
|
28
30
|
{
|
|
29
|
-
"id": "
|
|
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": "
|
|
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":
|
|
39
|
-
"label": "
|
|
64
|
+
"order": 7,
|
|
65
|
+
"label": "Continuation Shift",
|
|
40
66
|
"startTime": "13:30",
|
|
41
67
|
"kind": "team",
|
|
42
|
-
"goal": "
|
|
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":
|
|
47
|
-
"label": "Reveal
|
|
88
|
+
"order": 10,
|
|
89
|
+
"label": "Reveal and reflection",
|
|
48
90
|
"startTime": "15:45",
|
|
49
91
|
"kind": "shared",
|
|
50
|
-
"goal": "
|
|
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
|
-
##
|
|
150
|
+
## Agenda Mirror
|
|
151
151
|
|
|
152
|
-
The
|
|
152
|
+
The public-readable 10-phase mirror lives in [`agenda.json`](agenda.json).
|
|
153
153
|
|
|
154
|
-
Runtime workshop instances import from
|
|
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
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
|
-
|
|
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
|
|
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
|
}
|
package/src/session-store.js
CHANGED
|
@@ -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",
|
package/src/workshop-bundle.js
CHANGED
|
@@ -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, {
|
|
270
|
+
await createWorkshopBundleFromSource(sourceRoot, bundleRoot, { prune: true });
|
|
254
271
|
return {
|
|
255
272
|
sourceRoot,
|
|
256
273
|
bundleRoot,
|