okstra 0.30.1 → 0.30.3

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/bin/okstra CHANGED
@@ -17,6 +17,7 @@ const COMMANDS = new Map([
17
17
  ["render-bundle", () => import("../src/render-bundle.mjs").then((m) => m.run)],
18
18
  ["render-views", () => import("../src/render-views.mjs").then((m) => m.run)],
19
19
  ["wizard", () => import("../src/wizard.mjs").then((m) => m.run)],
20
+ ["token-usage", () => import("../src/token-usage.mjs").then((m) => m.run)],
20
21
  ]);
21
22
 
22
23
  const USAGE = `okstra — multi-agent cross-verification orchestrator for Claude Code
@@ -55,6 +56,9 @@ Introspection commands (JSON output, used by skills to avoid python heredocs):
55
56
  python3 -m okstra_ctl.run --render-only)
56
57
  wizard Drive the okstra-run interactive input state machine
57
58
  (init / step / render-args / confirmation)
59
+ token-usage Collect token usage for a run (wraps the installed
60
+ okstra-token-usage.py so skills avoid emitting
61
+ python3 "$HOME/..." invocations).
58
62
 
59
63
  Global options:
60
64
  --version Print okstra version and exit
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "okstra",
3
- "version": "0.30.1",
3
+ "version": "0.30.3",
4
4
  "description": "Multi-agent cross-verification orchestrator runtime + Claude Code skills.",
5
5
  "license": "MIT",
6
6
  "author": "devonshin",
@@ -1,5 +1,5 @@
1
1
  {
2
- "package": "0.30.1",
3
- "builtAt": "2026-05-17T11:12:55.482Z",
2
+ "package": "0.30.3",
3
+ "builtAt": "2026-05-19T08:41:16.438Z",
4
4
  "repoRoot": "/home/runner/work/okstra/okstra"
5
5
  }
@@ -107,26 +107,24 @@ at external `docs/adr/`.
107
107
 
108
108
  ## Step 0: Resolve project root
109
109
 
110
- Reuse the same disk-only resolution rule as `okstra-run`:
110
+ Reuse the same literal-token-first rule as `okstra-run`. Each command below is a **separate Bash tool call** starting with the literal token `okstra` so the `Bash(okstra:*)` permission match succeeds. Do **not** wrap any of them in `if`, `eval`, `export`, `$(...)`, `VAR=...`, `||`, or `&&`, and do **not** introduce a `$OKSTRA_CMD` variable or an `npx -y okstra@latest` fallback — those leading tokens defeat the permission match and force a confirmation prompt on every call.
111
111
 
112
112
  ```bash
113
- if command -v okstra >/dev/null 2>&1; then
114
- OKSTRA_CMD="okstra"
115
- else
116
- # `~/.okstra/bin/okstra.sh` exists on every installed machine but it is
117
- # the bash run-wrapper, NOT the Node CLI — it does not implement
118
- # `check-project`. Do not branch into it as a fallback. Go straight to
119
- # `npx` so the user always reaches a working `check-project` even when
120
- # the Node CLI is off-$PATH.
121
- OKSTRA_CMD="npx -y okstra@latest"
122
- fi
123
- $OKSTRA_CMD check-project --cwd "$(pwd)"
113
+ okstra check-project --json
124
114
  ```
125
115
 
126
- - `ok: true` use `projectRoot`.
127
- - `ok: false` → `AskUserQuestion` (free text) for an absolute project root, then
128
- re-run `okstra check-project --cwd <input>`. If still not ok, tell the user
129
- to run `okstra-setup` and stop.
116
+ Parse the JSON from stdout:
117
+
118
+ - `ok: true` use `projectRoot` from the JSON output. Carry it as a literal string into the steps below.
119
+ - `ok: false` → ask the user with a **plain text prompt** for an absolute project-root path, then rerun as a separate tool call with the literal absolute path (no `$(pwd)`, no shell variables):
120
+
121
+ ```bash
122
+ okstra check-project --cwd /abs/path/from/user --json
123
+ ```
124
+
125
+ If still `ok: false`, tell the user to run `okstra-setup` and stop.
126
+
127
+ > If the `okstra` binary is not on `PATH` at all, the command above will not run. In that case tell the user verbatim: "okstra not installed — run `npx okstra@latest install` once, then retry this skill." Do **not** try to invoke `npx -y okstra@latest ...` from this skill — `npx` is not on the literal-token allow-list this skill targets and will force a confirmation prompt on every subsequent call.
130
128
 
131
129
  ## Step 1: Choose input source
132
130
 
@@ -26,27 +26,18 @@ If the user is ambiguous, ask. Defaulting to the wrong one either wastes a fresh
26
26
 
27
27
  ## Step 0: Verify okstra runtime + project setup
28
28
 
29
- ```bash
30
- if command -v okstra >/dev/null 2>&1; then
31
- OKSTRA_CMD="okstra"
32
- else
33
- OKSTRA_CMD="npx -y okstra@latest"
34
- fi
35
- $OKSTRA_CMD ensure-installed >/dev/null 2>&1 || {
36
- echo "FAIL: okstra not installed; tell the user to run: npx okstra@latest install" >&2
37
- exit 1
38
- }
39
- eval "$($OKSTRA_CMD paths --shell)"
40
- export PYTHONPATH="$OKSTRA_PYTHONPATH"
41
- OKSTRA_PROJECT_INFO="$($OKSTRA_CMD check-project --json)" || {
42
- echo "FAIL: this project has no okstra setup. Tell the user to run /okstra-setup first." >&2
43
- echo "$OKSTRA_PROJECT_INFO" >&2
44
- exit 1
45
- }
46
- ```
29
+ Run each of the following commands as a **separate Bash tool call**. Each command starts with the literal token `okstra` so the `Bash(okstra:*)` permission match succeeds. Do **not** wrap any of them in `if`, `eval`, `export`, `$(...)`, `VAR=...`, `||`, or `&&`, and do **not** introduce a `$OKSTRA_CMD` variable or an `npx -y okstra@latest` fallback — those leading tokens defeat the permission match and force a confirmation prompt on every call. The LLM (you) inspects each command's output and decides what to do next in natural language — never in shell.
30
+
31
+ 1. `okstra ensure-installed`
32
+ If this exits non-zero, tell the user: "okstra not installed — run `npx okstra@latest install` once, then retry this skill." Then stop. Do **not** try to invoke `npx -y okstra@latest ...` as a fallback.
33
+
34
+ 2. `okstra check-project --json`
35
+ Reads the project from the current working directory. Parse the JSON from stdout. The shape is `{ok, projectRoot, projectJsonPath, projectId}`.
36
+
37
+ - `ok: false` → tell the user: "this project has no okstra setup. Run `/okstra-setup` first." Then stop.
38
+ - `ok: true` → carry `projectRoot` as a literal string and use it to locate `.project-docs/okstra/discovery/task-catalog.json`.
47
39
 
48
- `$OKSTRA_PROJECT_INFO` is JSON `{ok, projectRoot, projectJsonPath, projectId}`
49
- use `projectRoot` to locate `.project-docs/okstra/discovery/task-catalog.json`.
40
+ Subsequent `okstra <subcmd>` calls self-bootstrap their Python path, so this skill never needs `okstra paths --shell` / `export PYTHONPATH=...`.
50
41
 
51
42
  ## Step 1: Read the Task Catalog
52
43
 
@@ -36,57 +36,41 @@ multi-MB logs; analysis-phase dispatches are typically smaller.
36
36
 
37
37
  ## Step 0: Verify okstra runtime + project setup
38
38
 
39
- Before any other step, ensure both the okstra runtime and the current
40
- project's okstra metadata are in place:
39
+ Before any other step, run each of the following commands as a **separate Bash tool call**. Each command starts with the literal token `okstra` so the `Bash(okstra:*)` permission match succeeds. Do **not** wrap any of them in `if`, `eval`, `export`, `$(...)`, `VAR=...`, `||`, or `&&`, and do **not** introduce a `$OKSTRA_CMD` variable or an `npx -y okstra@latest` fallback — those leading tokens defeat the permission match and force a confirmation prompt on every call. The LLM (you) inspects each command's output and decides what to do next in natural language — never in shell.
41
40
 
42
- ```bash
43
- if command -v okstra >/dev/null 2>&1; then
44
- OKSTRA_CMD="okstra"
45
- else
46
- OKSTRA_CMD="npx -y okstra@latest"
47
- fi
48
- $OKSTRA_CMD ensure-installed >/dev/null 2>&1 || {
49
- echo "FAIL: okstra not installed; tell the user to run: npx okstra@latest install" >&2
50
- exit 1
51
- }
52
- eval "$($OKSTRA_CMD paths --shell)"
53
- export PYTHONPATH="$OKSTRA_PYTHONPATH"
54
- OKSTRA_PROJECT_INFO="$($OKSTRA_CMD check-project --json)" || {
55
- echo "FAIL: this project has no okstra setup. Tell the user to run /okstra-setup first." >&2
56
- echo "$OKSTRA_PROJECT_INFO" >&2
57
- exit 1
58
- }
59
- ```
41
+ 1. `okstra ensure-installed`
42
+ If this exits non-zero, tell the user: "okstra not installed — run `npx okstra@latest install` once, then retry this skill." Then stop. Do **not** try to invoke `npx -y okstra@latest ...` as a fallback.
43
+
44
+ 2. `okstra check-project --json`
45
+ Reads the project from the current working directory. Parse the JSON from stdout. The shape is `{ok, projectRoot, projectJsonPath, projectId}`.
46
+
47
+ - `ok: false` → tell the user: "this project has no okstra setup. Run `/okstra-setup` first." Then stop.
48
+ - `ok: true` carry `projectRoot` as a literal string and use it as the search root for the steps below.
60
49
 
61
- Parse `projectRoot` from the JSON and use it as the search root for the
62
- steps below.
50
+ Subsequent `okstra <subcmd>` calls self-bootstrap their Python path, so this skill never needs `okstra paths --shell` / `export PYTHONPATH=...`.
63
51
 
64
52
  ## Step 1: Inventory
65
53
 
66
54
  Find all wrapper log files and collect metadata. Use a single `find` to
67
55
  keep the I/O cost predictable, then format the results.
68
56
 
69
- ```bash
70
- PROJECT_ROOT=$(echo "$OKSTRA_PROJECT_INFO" | python3 -c 'import sys,json;print(json.load(sys.stdin)["projectRoot"])')
71
- LOGS_ROOT="$PROJECT_ROOT/.project-docs/okstra/tasks"
57
+ Construct the logs root by appending `/.project-docs/okstra/tasks` to the literal `projectRoot` value parsed in Step 0, and paste it as a literal absolute path in place of `<LOGS_ROOT>` below (no shell variables, no `$(...)`):
72
58
 
73
- # columns: size_bytes | mtime_epoch | path
74
- find "$LOGS_ROOT" -type f -path '*/runs/*/prompts/*.log' \
59
+ ```bash
60
+ find <LOGS_ROOT> -type f -path '*/runs/*/prompts/*.log' \
75
61
  -printf '%s\t%T@\t%p\n' 2>/dev/null \
76
62
  | sort -k1,1nr
77
63
  ```
78
64
 
79
- On macOS, `find -printf` is unavailable. Fall back to `stat`:
65
+ The columns produced are `size_bytes | mtime_epoch | path`.
66
+
67
+ On macOS, `find -printf` is unavailable. Fall back to `-exec stat` — again substitute the literal `<LOGS_ROOT>`. The `-exec ... {} +` form contains no shell variables and no `$(...)`, so the `Bash(find:*)` permission match holds:
80
68
 
81
69
  ```bash
82
- find "$LOGS_ROOT" -type f -path '*/runs/*/prompts/*.log' 2>/dev/null \
83
- | while IFS= read -r p; do
84
- stat -f '%z%t%m%t%N' "$p"
85
- done \
86
- | sort -k1,1nr
70
+ find <LOGS_ROOT> -type f -path '*/runs/*/prompts/*.log' -exec stat -f '%z%t%m%t%N' {} + 2>/dev/null | sort -k1,1nr
87
71
  ```
88
72
 
89
- If the result is empty, report `No wrapper log files found under <PROJECT_ROOT>` and exit.
73
+ If the result is empty, report `No wrapper log files found under <projectRoot>` and exit.
90
74
 
91
75
  ## Step 2: Summary table
92
76
 
@@ -14,27 +14,18 @@ user-invocable: false
14
14
 
15
15
  ## Step 0: Verify okstra runtime + project setup
16
16
 
17
- ```bash
18
- if command -v okstra >/dev/null 2>&1; then
19
- OKSTRA_CMD="okstra"
20
- else
21
- OKSTRA_CMD="npx -y okstra@latest"
22
- fi
23
- $OKSTRA_CMD ensure-installed >/dev/null 2>&1 || {
24
- echo "FAIL: okstra not installed; tell the user to run: npx okstra@latest install" >&2
25
- exit 1
26
- }
27
- eval "$($OKSTRA_CMD paths --shell)"
28
- export PYTHONPATH="$OKSTRA_PYTHONPATH"
29
- OKSTRA_PROJECT_INFO="$($OKSTRA_CMD check-project --json)" || {
30
- echo "FAIL: this project has no okstra setup. Tell the user to run /okstra-setup first." >&2
31
- echo "$OKSTRA_PROJECT_INFO" >&2
32
- exit 1
33
- }
34
- ```
17
+ 각 명령은 **별도 Bash tool call** 로 실행한다. 모든 명령은 리터럴 `okstra` 토큰으로 시작해 `Bash(okstra:*)` 권한 매칭을 통과해야 한다. `if`, `eval`, `export`, `$(...)`, `VAR=...`, `||`, `&&` 로 감싸지 말고, `$OKSTRA_CMD` 변수나 `npx -y okstra@latest` 폴백도 도입하지 않는다 — 첫 토큰이 비리터럴이면 권한 매칭이 깨져 호출마다 확인 프롬프트가 뜬다. 각 명령의 출력은 LLM(너) 이 보고 자연어로 분기 판단을 한다 — shell 분기는 쓰지 않는다.
18
+
19
+ 1. `okstra ensure-installed`
20
+ 비정상 종료 → 사용자에게 그대로 안내: "okstra not installed — run `npx okstra@latest install` once, then retry this skill." 그리고 중단. 폴백으로 `npx -y okstra@latest ...` 를 호출하지 않는다.
21
+
22
+ 2. `okstra check-project --json`
23
+ 현재 작업 디렉터리 기준 프로젝트를 읽는다. stdout 의 JSON shape: `{ok, projectRoot, projectJsonPath, projectId}`.
24
+
25
+ - `ok: false` → 사용자에게 "this project has no okstra setup. Run `/okstra-setup` first." 안내 후 중단.
26
+ - `ok: true` → `projectRoot` 를 리터럴 문자열로 들고 catalog/manifest 위치를 잡는다.
35
27
 
36
- `$OKSTRA_PROJECT_INFO` (JSON `{ok, projectRoot, projectJsonPath, projectId}`)
37
- `projectRoot` 로 catalog/manifest 위치를 잡는다.
28
+ 후속 `okstra <subcmd>` 호출은 Python path 를 자체 부트스트랩하므로 본 스킬은 `okstra paths --shell` / `export PYTHONPATH=...` 를 필요로 하지 않는다.
38
29
 
39
30
  ## Step 1: Task Key로 Report 경로 찾기
40
31
 
@@ -37,30 +37,18 @@ If `--title` is omitted, derive a default title from `task-group` (e.g. `uploadF
37
37
 
38
38
  ## Preflight: Verify okstra runtime + project setup
39
39
 
40
- Run before anything else in this skill:
40
+ Before anything else in this skill, run each of the following commands as a **separate Bash tool call**. Each command starts with the literal token `okstra` so the `Bash(okstra:*)` permission match succeeds. Do **not** wrap any of them in `if`, `eval`, `export`, `$(...)`, `VAR=...`, `||`, or `&&`, and do **not** introduce a `$OKSTRA_CMD` variable or an `npx -y okstra@latest` fallback — those leading tokens defeat the permission match and force a confirmation prompt on every call. The LLM (you) inspects each command's output and decides what to do next in natural language — never in shell.
41
41
 
42
- ```bash
43
- if command -v okstra >/dev/null 2>&1; then
44
- OKSTRA_CMD="okstra"
45
- else
46
- OKSTRA_CMD="npx -y okstra@latest"
47
- fi
48
- $OKSTRA_CMD ensure-installed >/dev/null 2>&1 || {
49
- echo "FAIL: okstra not installed; tell the user to run: npx okstra@latest install" >&2
50
- exit 1
51
- }
52
- eval "$($OKSTRA_CMD paths --shell)"
53
- export PYTHONPATH="$OKSTRA_PYTHONPATH"
54
- OKSTRA_PROJECT_INFO="$($OKSTRA_CMD check-project --json)" || {
55
- echo "FAIL: this project has no okstra setup. Tell the user to run /okstra-setup first." >&2
56
- echo "$OKSTRA_PROJECT_INFO" >&2
57
- exit 1
58
- }
59
- ```
42
+ 1. `okstra ensure-installed`
43
+ If this exits non-zero, tell the user: "okstra not installed — run `npx okstra@latest install` once, then retry this skill." Then stop. Do **not** try to invoke `npx -y okstra@latest ...` as a fallback.
44
+
45
+ 2. `okstra check-project --json`
46
+ Reads the project from the current working directory. Parse the JSON from stdout. The shape is `{ok, projectRoot, projectJsonPath, projectId}`.
47
+
48
+ - `ok: false` → tell the user: "this project has no okstra setup. Run `/okstra-setup` first." Then stop.
49
+ - `ok: true` carry `projectRoot` as a literal string and use it to locate `.project-docs/okstra/discovery/task-catalog.json` and the task-group directory.
60
50
 
61
- `$OKSTRA_PROJECT_INFO` is JSON `{ok, projectRoot, projectJsonPath, projectId}`
62
- use `projectRoot` to locate `.project-docs/okstra/discovery/task-catalog.json`
63
- and the task-group directory.
51
+ Subsequent `okstra <subcmd>` calls self-bootstrap their Python path, so this skill never needs `okstra paths --shell` / `export PYTHONPATH=...`.
64
52
 
65
53
  ## Process Procedure
66
54
 
@@ -53,44 +53,32 @@ Show the final summary line back to the user (`version stamp: x.y.z`).
53
53
  If install fails, surface the stderr verbatim. Do NOT try to "fix" it by
54
54
  running the legacy `okstra-install.sh` — that path is dev-only.
55
55
 
56
- ## Step 2: Load runtime paths
56
+ ## Step 2: Load runtime paths (no-op)
57
57
 
58
- ```bash
59
- # Prefer PATH-resolved okstra (npm-installed) over npx — avoids per-call registry lookup.
60
- if command -v okstra >/dev/null 2>&1; then
61
- OKSTRA_CMD="okstra"
62
- else
63
- OKSTRA_CMD="npx -y okstra@latest"
64
- fi
65
- eval "$($OKSTRA_CMD paths --shell)"
66
- export PYTHONPATH="$OKSTRA_PYTHONPATH"
67
- ```
58
+ After `okstra install`, every `okstra <subcmd>` call self-bootstraps its Python path, so this skill does NOT run `okstra paths --shell` / `eval ...` / `export PYTHONPATH=...`. The previously-set `$OKSTRA_WORKSPACE`, `$OKSTRA_AGENTS_DIR`, `$OKSTRA_PYTHONPATH`, `$OKSTRA_BIN`, `$OKSTRA_HOME` env vars are NOT set by this skill. If a later step needs one of those paths, call `okstra paths --json` as a **separate Bash tool call** (literal-token-first) and read the value from JSON output.
68
59
 
69
- After this, `$OKSTRA_WORKSPACE`, `$OKSTRA_AGENTS_DIR`, `$OKSTRA_PYTHONPATH`,
70
- `$OKSTRA_BIN`, `$OKSTRA_HOME` are all set. Do not hardcode any of these — read
71
- them from the env vars.
60
+ **Bash invocation rule (permission-friendly)**: from this step on, every Bash command in this skill MUST begin with the literal token `okstra` and pass literal argument values. Do not introduce shell variables (`$PROJECT_ROOT`, `$PROJECT_ID`, ...), `$(...)` command substitution, leading `VAR=...` assignments, or wrap commands in `if`/`eval`/`||`/`&&` — any of those make the leading token non-literal, defeat the `Bash(okstra:*)` permission match, and force a confirmation prompt on every call. When a prior tool call emitted a path or value, read it from the tool output and paste the literal string into the next command.
72
61
 
73
62
  ## Step 3: Resolve PROJECT_ROOT
74
63
 
75
64
  ```bash
76
- PROJECT_ROOT=$(okstra check-project --cwd "$(pwd)" | jq -r '.projectRoot')
65
+ okstra check-project --json
77
66
  ```
78
67
 
79
- The JSON includes `projectRoot` on success. Bind it into the shell so
80
- Step 4 onwards can expand `$PROJECT_ROOT`. On failure (`ok: false`,
81
- `stage: "resolve"`) ask the user (`AskUserQuestion`, free text) for an
82
- absolute project root and rerun with `--cwd <their answer>`.
68
+ Parse the JSON from stdout. The shape is `{ok, projectRoot, projectJsonPath, projectId, stage?}`.
69
+
70
+ - `ok: true` carry `projectRoot` as a literal absolute string and paste it into every subsequent command in this skill.
71
+ - `ok: false`, `stage: "resolve"` → ask the user (`AskUserQuestion`, free text) for an absolute project root and rerun as a separate Bash tool call with the literal absolute path:
72
+
73
+ ```bash
74
+ okstra check-project --cwd /abs/path/from/user --json
75
+ ```
83
76
 
84
77
  ## Step 4: Inspect or create `project.json`
85
78
 
86
- ```bash
87
- PROJECT_JSON="$PROJECT_ROOT/.project-docs/okstra/project.json"
88
- if [ -f "$PROJECT_JSON" ]; then
89
- cat "$PROJECT_JSON"
90
- fi
91
- ```
79
+ Use the `Read` tool on the literal absolute path `<projectRoot>/.project-docs/okstra/project.json` (substitute the literal `projectRoot` value parsed in Step 3). If `Read` errors with "file does not exist", treat that as the "create" branch below; otherwise the file exists and you can inspect its contents inline.
92
80
 
93
- If the file exists, print its `projectId`/`projectRoot` and ask whether to
81
+ If the file exists, surface its `projectId`/`projectRoot` and ask whether to
94
82
  keep or overwrite. Default is to keep — okstra refuses to change `projectId`
95
83
  on an existing project (see `okstra_project.resolver.upsert_project_json`),
96
84
  so overwriting requires manually deleting the file first.
@@ -104,10 +92,10 @@ If the file does NOT exist, ask via `AskUserQuestion`:
104
92
  `error: --project-id is required (no existing project.json, not a TTY)`,
105
93
  so passing the user's empty answer through is a silent failure path.
106
94
 
107
- Then create the file:
95
+ Then create the file — paste the literal `projectRoot` from Step 3 and the literal `projectId` from the user's answer (no shell variables):
108
96
 
109
97
  ```bash
110
- okstra setup --yes --project-root "$PROJECT_ROOT" --project-id "$PROJECT_ID"
98
+ okstra setup --yes --project-root /abs/path/to/projectRoot --project-id my-project-id
111
99
  ```
112
100
 
113
101
  > After this, **Steps 4.5–4.8 are all optional**. The built-in defaults
@@ -266,7 +254,7 @@ If the user chose `나중에`, tell them they can register later with one of:
266
254
  ## Step 5: Verify
267
255
 
268
256
  ```bash
269
- $OKSTRA_CMD doctor
257
+ okstra doctor
270
258
  ```
271
259
 
272
260
  If all checks return `OK`, the setup is complete. If any check fails, surface
@@ -13,30 +13,18 @@ description: Use when the user asks for overall okstra task status, current life
13
13
 
14
14
  ## Step 0: Verify okstra runtime + project setup
15
15
 
16
- Before any other step, ensure both the okstra runtime and the current
17
- project's okstra metadata are in place:
18
-
19
- ```bash
20
- if command -v okstra >/dev/null 2>&1; then
21
- OKSTRA_CMD="okstra"
22
- else
23
- OKSTRA_CMD="npx -y okstra@latest"
24
- fi
25
- $OKSTRA_CMD ensure-installed >/dev/null 2>&1 || {
26
- echo "FAIL: okstra not installed; tell the user to run: npx okstra@latest install" >&2
27
- exit 1
28
- }
29
- eval "$($OKSTRA_CMD paths --shell)"
30
- export PYTHONPATH="$OKSTRA_PYTHONPATH"
31
- OKSTRA_PROJECT_INFO="$($OKSTRA_CMD check-project --json)" || {
32
- echo "FAIL: this project has no okstra setup. Tell the user to run /okstra-setup first." >&2
33
- echo "$OKSTRA_PROJECT_INFO" >&2
34
- exit 1
35
- }
36
- ```
16
+ Before any other step, run each of the following commands as a **separate Bash tool call**. Each command starts with the literal token `okstra` so the `Bash(okstra:*)` permission match succeeds. Do **not** wrap any of them in `if`, `eval`, `export`, `$(...)`, `VAR=...`, `||`, or `&&`, and do **not** introduce a `$OKSTRA_CMD` variable or an `npx -y okstra@latest` fallback — those leading tokens defeat the permission match and force a confirmation prompt on every call. The LLM (you) inspects each command's output and decides what to do next in natural language — never in shell.
17
+
18
+ 1. `okstra ensure-installed`
19
+ If this exits non-zero, tell the user: "okstra not installed — run `npx okstra@latest install` once, then retry this skill." Then stop. Do **not** try to invoke `npx -y okstra@latest ...` as a fallback.
20
+
21
+ 2. `okstra check-project --json`
22
+ Reads the project from the current working directory. Parse the JSON from stdout. The shape is `{ok, projectRoot, projectJsonPath, projectId}`.
23
+
24
+ - `ok: false` → tell the user: "this project has no okstra setup. Run `/okstra-setup` first." Then stop.
25
+ - `ok: true` carry `projectRoot` as a literal string into the steps below; reuse it instead of re-resolving.
37
26
 
38
- `$OKSTRA_PROJECT_INFO` is JSON `{ok, projectRoot, projectJsonPath, projectId}`
39
- parse and reuse it instead of re-resolving in the steps below.
27
+ Subsequent `okstra <subcmd>` calls self-bootstrap their Python path, so this skill never needs `okstra paths --shell` / `export PYTHONPATH=...`.
40
28
 
41
29
  ## Step 1: Overall Project Status
42
30
 
@@ -255,4 +243,4 @@ This skill updates `task-manifest.json` only. `discovery/task-catalog.json` may
255
243
 
256
244
  ## Out-of-Scope Backlog
257
245
 
258
- - **Step 0 boilerplate duplication.** The `ensure-installed` + `paths --shell` + `check-project --json` preamble is byte-identical across every user-facing okstra skill. The Claude Code skill framework has no include/snippet mechanism today, so each skill duplicates the block. A future change should either (a) extract the preamble into a single `okstra preflight` subcommand the skill can call in one line, or (b) ship the block as a shared SKILL fragment if the framework gains include support. Not actionable inside this skill alone.
246
+ - **Step 0 boilerplate duplication.** The `ensure-installed` + `check-project --json` preamble (literal-token-first, separate Bash calls) is near-identical across every user-facing okstra skill. The Claude Code skill framework has no include/snippet mechanism today, so each skill duplicates the prose. A future change should either (a) extract the preamble into a single `okstra preflight` subcommand the skill can call in one line, or (b) ship the block as a shared SKILL fragment if the framework gains include support. Not actionable inside this skill alone.
@@ -432,15 +432,13 @@ Token usage is collected from agent session transcripts after the run, NOT from
432
432
 
433
433
  ### How to Collect
434
434
 
435
- At the **start of Phase 7** (persistence), run the helper script with the path to this run's `team-state.json`:
435
+ At the **start of Phase 7** (persistence), run the helper via the okstra CLI with the path to this run's `team-state.json`. Substitute `<runDirectoryPath>` with a literal absolute path (no shell variables, no `$(...)`) so the `Bash(okstra:*)` permission match holds:
436
436
 
437
437
  ```bash
438
- python3 "$HOME/.okstra/lib/python/okstra-token-usage.py" \
439
- <runDirectoryPath>/state/team-state-<task-type>-<seq>.json \
440
- --write --summary
438
+ okstra token-usage /abs/path/to/run/state/team-state-<task-type>-<seq>.json --write --summary
441
439
  ```
442
440
 
443
- The script is installed at `$HOME/.okstra/lib/python/okstra-token-usage.py` by `okstra install`. The previous repo-relative path (`scripts/okstra-token-usage.py`) only exists in a working clone of the okstra repo and is not appropriate for end-user-deployed runs.
441
+ `okstra token-usage` is a thin Node-side wrapper around the python helper installed at `~/.okstra/bin/okstra-token-usage.py`. Calling the python script directly with `python3 "$HOME/..."` is forbidden the `$HOME` expansion breaks the literal-token permission match and forces a confirmation prompt every call.
444
442
 
445
443
  The script reads:
446
444
  - `~/.claude/projects/<encoded-cwd>/<sessionId>.jsonl` for the lead and every Claude-side worker (Claude worker, Report writer worker, plus the Claude wrappers around Codex/Gemini workers). Sessions are discovered by `teamName: okstra-<task-id>`, lead is identified by `lead.sessionId`, and other workers are identified by `agentName` (e.g. `claude-worker`, `codex-worker`, `gemini-worker`, `report-writer`).
@@ -29,27 +29,18 @@ If a run never reached Phase 7, its `team-state` will not have `durationMs` fill
29
29
 
30
30
  ## Step 0: Verify okstra runtime + project setup
31
31
 
32
- ```bash
33
- if command -v okstra >/dev/null 2>&1; then
34
- OKSTRA_CMD="okstra"
35
- else
36
- OKSTRA_CMD="npx -y okstra@latest"
37
- fi
38
- $OKSTRA_CMD ensure-installed >/dev/null 2>&1 || {
39
- echo "FAIL: okstra not installed; tell the user to run: npx okstra@latest install" >&2
40
- exit 1
41
- }
42
- eval "$($OKSTRA_CMD paths --shell)"
43
- export PYTHONPATH="$OKSTRA_PYTHONPATH"
44
- OKSTRA_PROJECT_INFO="$($OKSTRA_CMD check-project --json)" || {
45
- echo "FAIL: this project has no okstra setup. Tell the user to run /okstra-setup first." >&2
46
- echo "$OKSTRA_PROJECT_INFO" >&2
47
- exit 1
48
- }
49
- ```
32
+ Run each of the following commands as a **separate Bash tool call**. Each command starts with the literal token `okstra` so the `Bash(okstra:*)` permission match succeeds. Do **not** wrap any of them in `if`, `eval`, `export`, `$(...)`, `VAR=...`, `||`, or `&&`, and do **not** introduce a `$OKSTRA_CMD` variable or an `npx -y okstra@latest` fallback — those leading tokens defeat the permission match and force a confirmation prompt on every call. The LLM (you) inspects each command's output and decides what to do next in natural language — never in shell.
33
+
34
+ 1. `okstra ensure-installed`
35
+ If this exits non-zero, tell the user: "okstra not installed — run `npx okstra@latest install` once, then retry this skill." Then stop. Do **not** try to invoke `npx -y okstra@latest ...` as a fallback.
36
+
37
+ 2. `okstra check-project --json`
38
+ Reads the project from the current working directory. Parse the JSON from stdout. The shape is `{ok, projectRoot, projectJsonPath, projectId}`.
39
+
40
+ - `ok: false` → tell the user: "this project has no okstra setup. Run `/okstra-setup` first." Then stop.
41
+ - `ok: true` → carry `projectRoot` as a literal string and use it to locate `.project-docs/okstra/discovery/task-catalog.json`.
50
42
 
51
- `$OKSTRA_PROJECT_INFO` (JSON `{ok, projectRoot, projectJsonPath, projectId}`)
52
- gives `projectRoot` for locating `.project-docs/okstra/discovery/task-catalog.json`.
43
+ Subsequent `okstra <subcmd>` calls self-bootstrap their Python path, so this skill never needs `okstra paths --shell` / `export PYTHONPATH=...`.
53
44
 
54
45
  ## Step 1: Resolve task-id → timeline path
55
46
 
@@ -0,0 +1,51 @@
1
+ import { spawn } from "node:child_process";
2
+ import { join } from "node:path";
3
+ import { promises as fs } from "node:fs";
4
+ import { resolvePaths } from "./paths.mjs";
5
+
6
+ const USAGE = `okstra token-usage — collect token usage for a run
7
+
8
+ Wraps the python helper (\`okstra-token-usage.py\`) installed under
9
+ \`~/.okstra/bin/\` so skills can call this command without emitting a
10
+ shell-expansion-bearing \`python3 "$HOME/..."\` invocation (which would
11
+ break \`Bash(okstra:*)\` permission matching and force a confirmation
12
+ prompt every call).
13
+
14
+ Usage:
15
+ okstra token-usage <state-file> [--write] [--summary] [...]
16
+
17
+ Arguments and flags after the state-file path are forwarded verbatim to
18
+ the python helper. See \`python3 ~/.okstra/bin/okstra-token-usage.py --help\`
19
+ for the full option list.
20
+ `;
21
+
22
+ export async function run(args) {
23
+ if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
24
+ process.stdout.write(USAGE);
25
+ return args.length === 0 ? 2 : 0;
26
+ }
27
+
28
+ const paths = await resolvePaths();
29
+ const script = join(paths.bin, "okstra-token-usage.py");
30
+
31
+ try {
32
+ await fs.access(script);
33
+ } catch {
34
+ process.stderr.write(
35
+ `error: ${script} not found — run 'okstra install' (or 'okstra ensure-installed') first\n`,
36
+ );
37
+ return 1;
38
+ }
39
+
40
+ return await new Promise((resolve) => {
41
+ const child = spawn("python3", [script, ...args], {
42
+ stdio: "inherit",
43
+ env: { ...process.env, PYTHONPATH: paths.pythonpath },
44
+ });
45
+ child.on("error", (err) => {
46
+ process.stderr.write(`error: failed to spawn python3: ${err.message}\n`);
47
+ resolve(1);
48
+ });
49
+ child.on("close", (code) => resolve(typeof code === "number" ? code : 1));
50
+ });
51
+ }