@nanobpm/nano-workforce 0.153.0 → 0.154.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +17 -0
- package/install.sh +236 -12
- package/package.json +1 -1
- package/test/install-smoke.sh +113 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
## [0.154.0](https://github.com/nanobpm/nano-workforce/compare/v0.153.0...v0.154.0) (2026-08-29)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
* **onboarding:** three-state GitHub credential preflight in install.sh ([#599](https://github.com/nanobpm/nano-workforce/issues/599)) ([7e9f592](https://github.com/nanobpm/nano-workforce/commit/7e9f592632fc85cfea9f89843252c0128cec6c8d)), closes [#588](https://github.com/nanobpm/nano-workforce/issues/588)
|
|
6
|
+
|
|
1
7
|
## [0.153.0](https://github.com/nanobpm/nano-workforce/compare/v0.152.0...v0.153.0) (2026-08-29)
|
|
2
8
|
|
|
3
9
|
### Features
|
package/README.md
CHANGED
|
@@ -97,6 +97,23 @@ instance count each), hires each with `--rank senior --protocol acp --permission
|
|
|
97
97
|
yolo`, composes a declarative **workforce manifest** (`c8 nano workforce add`),
|
|
98
98
|
and brings it up (`c8 nano start` then `c8 nano workforce start`).
|
|
99
99
|
|
|
100
|
+
**GitHub access preflight.** Because a workforce with no GitHub credential can
|
|
101
|
+
neither review, push, nor merge, the preflight checks for a **usable** credential
|
|
102
|
+
on this host before hiring: a `GITHUB_TOKEN`/`GH_TOKEN` in the environment
|
|
103
|
+
*(validated with `gh api user` / a raw `api.github.com/user` probe, not trusted
|
|
104
|
+
merely for being set — in the rare case where neither `gh` nor `curl` is present
|
|
105
|
+
to probe with, it cannot validate and warns that it is proceeding on trust)*, or
|
|
106
|
+
the `gh` CLI *installed, authenticated, and proven with
|
|
107
|
+
`gh api user`*. It
|
|
108
|
+
distinguishes gh missing, installed-but-unauthenticated, and
|
|
109
|
+
authenticated-but-unusable (expired token, missing `repo` scope, SAML SSO), warns
|
|
110
|
+
(but does not fail) on a missing `workflow` scope, and checks `git` too. It prints
|
|
111
|
+
a platform-aware install hint and the `gh auth login` / `GITHUB_TOKEN` remediation
|
|
112
|
+
— it never runs `gh auth login` for you. Interactive runs let you fix it and
|
|
113
|
+
re-check, or confirm-to-continue (default no); non-interactive runs **fail** unless
|
|
114
|
+
you pass `--allow-no-github`. The check covers **this host only** — remote worker
|
|
115
|
+
hosts each need their own credential.
|
|
116
|
+
|
|
100
117
|
**Phase 2 — the app.** It then talks to the nano console the engine brought up to
|
|
101
118
|
install the `@nanobpm/nano-workforce` extension, scaffold a **Workforce** project
|
|
102
119
|
from the `nano-workforce` template, write its `ProjectConfig.env` (including
|
package/install.sh
CHANGED
|
@@ -79,6 +79,8 @@ DRY_RUN=0
|
|
|
79
79
|
ASSUME_YES=0
|
|
80
80
|
SHELL_OVERRIDE=''
|
|
81
81
|
INSTALL_ADAPTERS=0 # auto-install missing adapters without prompting
|
|
82
|
+
ALLOW_NO_GITHUB=0 # non-interactive: proceed even without usable GitHub access
|
|
83
|
+
GITHUB_DEGRADED='' # non-empty => the GitHub preflight passed in a degraded state; repeated in the final summary
|
|
82
84
|
SKIP_APP=0 # phase 1 only: bring up engine + workforce, don't install the app
|
|
83
85
|
PROJECT_NAME='' # console project name for the Nano Workforce app (default: Workforce)
|
|
84
86
|
CLI_HARNESS_SPECS='' # newline-separated name[:model][:instances] from --harness
|
|
@@ -97,6 +99,19 @@ ADAPTERS_PRESENT="${NANO_INSTALL_ADAPTERS_PRESENT:-}"
|
|
|
97
99
|
if [ "${NANO_INSTALL_HARNESSES_OVERRIDE+set}" = set ]; then HARNESS_OVERRIDE_SET=1; else HARNESS_OVERRIDE_SET=0; fi
|
|
98
100
|
if [ "${NANO_INSTALL_ADAPTERS_PRESENT+set}" = set ]; then ADAPTERS_PRESENT_SET=1; else ADAPTERS_PRESENT_SET=0; fi
|
|
99
101
|
|
|
102
|
+
# Test hooks for the GitHub credential preflight (undocumented; keep the smoke
|
|
103
|
+
# test hermetic on runners that may or may not ship an authenticated gh):
|
|
104
|
+
# NANO_INSTALL_GH_STATE — force gh state: missing|unauthed|unusable|ok
|
|
105
|
+
# NANO_INSTALL_GH_SCOPES — comma/space list of token scopes gh reports
|
|
106
|
+
# NANO_INSTALL_GIT_STATE — force git state: missing|present
|
|
107
|
+
# NANO_INSTALL_TOKEN_STATE — force env-token validity: ok|bad (skips the real
|
|
108
|
+
# gh/api.github.com probe so tests need no network)
|
|
109
|
+
# A set-but-empty override still counts as set, so the probe never falls back to
|
|
110
|
+
# the real command -v / gh and the smoke test stays hermetic.
|
|
111
|
+
if [ "${NANO_INSTALL_GH_STATE+set}" = set ]; then GH_STATE_OVERRIDE_SET=1; else GH_STATE_OVERRIDE_SET=0; fi
|
|
112
|
+
if [ "${NANO_INSTALL_GIT_STATE+set}" = set ]; then GIT_STATE_OVERRIDE_SET=1; else GIT_STATE_OVERRIDE_SET=0; fi
|
|
113
|
+
if [ "${NANO_INSTALL_TOKEN_STATE+set}" = set ]; then TOKEN_STATE_OVERRIDE_SET=1; else TOKEN_STATE_OVERRIDE_SET=0; fi
|
|
114
|
+
|
|
100
115
|
CLI='' # resolved c8ctl / c8 binary
|
|
101
116
|
TTY='' # /dev/tty if usable, else empty
|
|
102
117
|
|
|
@@ -219,6 +234,10 @@ Options:
|
|
|
219
234
|
-y, --yes Skip the confirmation summary.
|
|
220
235
|
--install-adapters Auto-install a selected harness's missing ACP adapter
|
|
221
236
|
(claude/pi) instead of prompting/skipping.
|
|
237
|
+
--allow-no-github Proceed even when no usable GitHub access is detected. In a
|
|
238
|
+
non-interactive run the preflight otherwise fails, since a
|
|
239
|
+
workforce with no GitHub credential cannot review, push, or
|
|
240
|
+
merge. Interactive runs prompt instead.
|
|
222
241
|
--project-name <name>
|
|
223
242
|
Console project name for the Nano Workforce app
|
|
224
243
|
(default: Workforce). [A-Za-z0-9._-] only.
|
|
@@ -261,6 +280,7 @@ parse_args() {
|
|
|
261
280
|
shift ;;
|
|
262
281
|
--yes|-y) ASSUME_YES=1; shift ;;
|
|
263
282
|
--install-adapters) INSTALL_ADAPTERS=1; shift ;;
|
|
283
|
+
--allow-no-github) ALLOW_NO_GITHUB=1; shift ;;
|
|
264
284
|
--skip-app) SKIP_APP=1; shift ;;
|
|
265
285
|
--project-name)
|
|
266
286
|
[ $# -ge 2 ] || die "--project-name requires a value"
|
|
@@ -277,6 +297,210 @@ parse_args() {
|
|
|
277
297
|
done
|
|
278
298
|
}
|
|
279
299
|
|
|
300
|
+
# ---------------------------------------------------------------------------
|
|
301
|
+
# GitHub credential preflight (nanobpm/nano-workforce#588)
|
|
302
|
+
#
|
|
303
|
+
# Two consumers need GitHub access and fail differently: the nwf app (review
|
|
304
|
+
# poller + merge stage) and each hired agent worker (shells out to gh/git). The
|
|
305
|
+
# real predicate is "a usable credential exists on this host", not "gh is
|
|
306
|
+
# installed" — a token in GITHUB_TOKEN/GH_TOKEN satisfies both without gh.
|
|
307
|
+
#
|
|
308
|
+
# Detection distinguishes three states beyond present/absent: not installed,
|
|
309
|
+
# installed-but-unauthenticated, and authenticated-but-unusable (a push/PR that
|
|
310
|
+
# 403s only later — expired token, missing scope, SAML SSO, fine-grained PAT
|
|
311
|
+
# without repo access). We prove usability with `gh api user`, never trusting
|
|
312
|
+
# `gh auth status` alone.
|
|
313
|
+
# ---------------------------------------------------------------------------
|
|
314
|
+
gh_present() { # 0 if the gh CLI is available (honours the test hook)
|
|
315
|
+
if [ "$GH_STATE_OVERRIDE_SET" -eq 1 ]; then
|
|
316
|
+
case "$NANO_INSTALL_GH_STATE" in missing) return 1 ;; *) return 0 ;; esac
|
|
317
|
+
fi
|
|
318
|
+
command -v gh >/dev/null 2>&1
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
gh_authed() { # 0 if gh auth status succeeds (honours the test hook)
|
|
322
|
+
if [ "$GH_STATE_OVERRIDE_SET" -eq 1 ]; then
|
|
323
|
+
case "$NANO_INSTALL_GH_STATE" in missing|unauthed) return 1 ;; *) return 0 ;; esac
|
|
324
|
+
fi
|
|
325
|
+
gh auth status >/dev/null 2>&1
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
gh_usable() { # 0 if gh api user actually works — validates token + network + SSO
|
|
329
|
+
if [ "$GH_STATE_OVERRIDE_SET" -eq 1 ]; then
|
|
330
|
+
case "$NANO_INSTALL_GH_STATE" in ok) return 0 ;; *) return 1 ;; esac
|
|
331
|
+
fi
|
|
332
|
+
gh api user --jq .login >/dev/null 2>&1
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
gh_token_usable() { # 0 if the env GITHUB_TOKEN/GH_TOKEN actually authenticates
|
|
336
|
+
# An expired/revoked/underscoped token is the *exact* late failure this
|
|
337
|
+
# preflight exists to catch, so a non-empty token is not trusted blindly — we
|
|
338
|
+
# prove it works (gh api user, or a raw api.github.com/user probe when gh is
|
|
339
|
+
# absent). Honours the test hook so the smoke suite needs no network.
|
|
340
|
+
if [ "$TOKEN_STATE_OVERRIDE_SET" -eq 1 ]; then
|
|
341
|
+
case "$NANO_INSTALL_TOKEN_STATE" in ok) return 0 ;; *) return 1 ;; esac
|
|
342
|
+
fi
|
|
343
|
+
_tok="${GITHUB_TOKEN:-${GH_TOKEN:-}}"
|
|
344
|
+
if gh_present; then
|
|
345
|
+
GITHUB_TOKEN="$_tok" GH_TOKEN="$_tok" gh api user --jq .login >/dev/null 2>&1
|
|
346
|
+
elif command -v curl >/dev/null 2>&1; then
|
|
347
|
+
# Pass the token via --config on stdin so it never lands in curl's argv
|
|
348
|
+
# (readable by other users via `ps` on a multi-user host).
|
|
349
|
+
printf 'header = "Authorization: Bearer %s"\n' "$_tok" | \
|
|
350
|
+
curl -fsS --connect-timeout 5 --max-time 10 --config - \
|
|
351
|
+
-H "User-Agent: nano-workforce-install" \
|
|
352
|
+
https://api.github.com/user >/dev/null 2>&1
|
|
353
|
+
else
|
|
354
|
+
# No gh and no curl to validate with — we cannot prove the token works,
|
|
355
|
+
# so warn explicitly rather than let an unvalidated token look "usable".
|
|
356
|
+
warn "cannot validate GITHUB_TOKEN/GH_TOKEN — neither gh nor curl is available on this host; proceeding on trust (an expired or under-scoped token will only fail later)."
|
|
357
|
+
return 0
|
|
358
|
+
fi
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
gh_scopes() { # print space-separated token scopes gh reports (honours the test hook)
|
|
362
|
+
if [ "$GH_STATE_OVERRIDE_SET" -eq 1 ]; then
|
|
363
|
+
printf '%s' "${NANO_INSTALL_GH_SCOPES:-}" | tr ',' ' '
|
|
364
|
+
return 0
|
|
365
|
+
fi
|
|
366
|
+
# gh auth status prints e.g.: - Token scopes: 'gist', 'read:org', 'repo', 'workflow'
|
|
367
|
+
gh auth status 2>&1 | sed -n "s/.*Token scopes:[ ]*//p" | tr -d "'" | tr ',' ' '
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
git_present() { # 0 if git is available (honours the test hook)
|
|
371
|
+
if [ "$GIT_STATE_OVERRIDE_SET" -eq 1 ]; then
|
|
372
|
+
case "$NANO_INSTALL_GIT_STATE" in missing) return 1 ;; *) return 0 ;; esac
|
|
373
|
+
fi
|
|
374
|
+
command -v git >/dev/null 2>&1
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
# Platform-aware install + remediation hint. We print the command; we NEVER run
|
|
378
|
+
# `gh auth login` for the user (it is an interactive device/OAuth flow that
|
|
379
|
+
# should stay in their hands).
|
|
380
|
+
gh_remediation_hint() {
|
|
381
|
+
note "GitHub access not detected. The workforce cannot review, push, or merge without it."
|
|
382
|
+
note ""
|
|
383
|
+
note " macOS brew install gh"
|
|
384
|
+
note " Debian/Ubuntu sudo apt install gh"
|
|
385
|
+
note " Fedora sudo dnf install gh"
|
|
386
|
+
note " Arch sudo pacman -S gh"
|
|
387
|
+
note " other https://github.com/cli/cli#installation"
|
|
388
|
+
note ""
|
|
389
|
+
note "Then: gh auth login (or export GITHUB_TOKEN=…)"
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
# One probe pass. Sets GH_PROBE_MSG (human summary) and returns 0 when a usable
|
|
393
|
+
# credential exists, 1 otherwise. Warns (non-fatal) when the workflow scope is
|
|
394
|
+
# absent, since that only bites on workflow-file changes.
|
|
395
|
+
probe_github() {
|
|
396
|
+
GH_PROBE_MSG=''
|
|
397
|
+
# 1. token in env → validate it (don't trust a non-empty value), skip gh
|
|
398
|
+
# scope checks (a raw token exposes no scopes here) once it authenticates.
|
|
399
|
+
if [ -n "${GITHUB_TOKEN:-${GH_TOKEN:-}}" ]; then
|
|
400
|
+
if ! gh_token_usable; then
|
|
401
|
+
GH_PROBE_MSG="a GITHUB_TOKEN/GH_TOKEN is set but it failed validation (gh api user / api.github.com/user) — it is expired, revoked, or lacks access. Fix or unset it."
|
|
402
|
+
return 1
|
|
403
|
+
fi
|
|
404
|
+
if gh_present; then
|
|
405
|
+
GH_PROBE_MSG="GitHub token detected in the environment (GITHUB_TOKEN/GH_TOKEN); gh CLI also present."
|
|
406
|
+
else
|
|
407
|
+
GH_PROBE_MSG="GitHub token detected in the environment (GITHUB_TOKEN/GH_TOKEN)."
|
|
408
|
+
note "A token is set, so the nwf app is fine (NANO_PR_GITHUB_TRANSPORT=token)."
|
|
409
|
+
note "But gh is NOT installed on this host — harnesses that shell out to 'gh' still won't work. Install gh too."
|
|
410
|
+
fi
|
|
411
|
+
return 0
|
|
412
|
+
fi
|
|
413
|
+
# 2. otherwise the host CLI must be present, authenticated, and usable.
|
|
414
|
+
if ! gh_present; then
|
|
415
|
+
GH_PROBE_MSG="no gh CLI and no GITHUB_TOKEN/GH_TOKEN in the environment."
|
|
416
|
+
return 1
|
|
417
|
+
fi
|
|
418
|
+
if ! gh_authed; then
|
|
419
|
+
GH_PROBE_MSG="gh is installed but not authenticated (gh auth status failed)."
|
|
420
|
+
return 1
|
|
421
|
+
fi
|
|
422
|
+
# 3. prove it actually works — don't just trust status.
|
|
423
|
+
if ! gh_usable; then
|
|
424
|
+
GH_PROBE_MSG="gh reports authenticated but 'gh api user' failed — the credential is unusable (expired/revoked token, network, or SAML SSO not authorized for the target org)."
|
|
425
|
+
return 1
|
|
426
|
+
fi
|
|
427
|
+
# 4. authenticated + usable: the repo scope is required, workflow only warns.
|
|
428
|
+
_scopes=$(gh_scopes)
|
|
429
|
+
case " $_scopes " in
|
|
430
|
+
*" repo "*) : ;;
|
|
431
|
+
*)
|
|
432
|
+
GH_PROBE_MSG="gh is authenticated but the token is missing the 'repo' scope — pushes and PR creation will 403. Re-auth with: gh auth refresh -s repo"
|
|
433
|
+
return 1 ;;
|
|
434
|
+
esac
|
|
435
|
+
case " $_scopes " in
|
|
436
|
+
*" workflow "*) : ;;
|
|
437
|
+
*) warn "gh token is missing the 'workflow' scope — harmless unless agents edit workflow files; add it with: gh auth refresh -s workflow" ;;
|
|
438
|
+
esac
|
|
439
|
+
GH_PROBE_MSG="GitHub access OK via the host gh CLI."
|
|
440
|
+
return 0
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
# The full preflight: probe, remediate, and decide whether to continue. Records
|
|
444
|
+
# a degraded state (repeated in the final summary) whenever we proceed without a
|
|
445
|
+
# usable credential, so a degraded install never *looks* complete.
|
|
446
|
+
add_degraded() { # accumulate a degraded reason so a later one never masks an earlier
|
|
447
|
+
if [ -n "$GITHUB_DEGRADED" ]; then
|
|
448
|
+
GITHUB_DEGRADED="${GITHUB_DEGRADED}; $1"
|
|
449
|
+
else
|
|
450
|
+
GITHUB_DEGRADED="$1"
|
|
451
|
+
fi
|
|
452
|
+
}
|
|
453
|
+
github_preflight() {
|
|
454
|
+
info "Checking GitHub access (this host only)"
|
|
455
|
+
|
|
456
|
+
# git is needed by every worker (clone/commit/push) — check it alongside gh.
|
|
457
|
+
if git_present; then
|
|
458
|
+
ok "git present"
|
|
459
|
+
else
|
|
460
|
+
warn "git not found on this host — agents cannot clone, commit, or push without it."
|
|
461
|
+
note "Install git from https://git-scm.com/downloads, then re-run."
|
|
462
|
+
add_degraded "git is not installed on this host"
|
|
463
|
+
fi
|
|
464
|
+
|
|
465
|
+
while : ; do
|
|
466
|
+
if probe_github; then
|
|
467
|
+
ok "$GH_PROBE_MSG"
|
|
468
|
+
note "This check covers THIS host only — remote worker hosts each need their own gh auth / token."
|
|
469
|
+
break
|
|
470
|
+
fi
|
|
471
|
+
|
|
472
|
+
warn "$GH_PROBE_MSG"
|
|
473
|
+
gh_remediation_hint
|
|
474
|
+
note "This check covers THIS host only — remote worker hosts each need their own gh auth / token."
|
|
475
|
+
|
|
476
|
+
# Non-interactive (--yes, no /dev/tty, or --dry-run): no prompt is possible.
|
|
477
|
+
if [ "$ASSUME_YES" -eq 1 ] || [ "$DRY_RUN" -eq 1 ] || [ -z "$TTY" ]; then
|
|
478
|
+
if [ "$ALLOW_NO_GITHUB" -eq 1 ]; then
|
|
479
|
+
warn "continuing without GitHub access (--allow-no-github) — the agents will not be able to do GitHub work."
|
|
480
|
+
add_degraded "$GH_PROBE_MSG"
|
|
481
|
+
break
|
|
482
|
+
fi
|
|
483
|
+
# Dry-run changes nothing, so never fail on it — just report what a real run would do.
|
|
484
|
+
if [ "$DRY_RUN" -eq 1 ]; then
|
|
485
|
+
note "dry-run: not blocking on GitHub access (a real run would fail here without --allow-no-github)."
|
|
486
|
+
break
|
|
487
|
+
fi
|
|
488
|
+
die "GitHub access not detected and running non-interactively — fix it (see above) and re-run, or pass --allow-no-github to proceed anyway."
|
|
489
|
+
fi
|
|
490
|
+
|
|
491
|
+
# Interactive: let the user fix it and re-check without re-running the whole
|
|
492
|
+
# script, else confirm-to-continue (default no).
|
|
493
|
+
if confirm "I've set up GitHub access (installed gh / ran gh auth login / exported a token). Re-check now?"; then
|
|
494
|
+
continue
|
|
495
|
+
fi
|
|
496
|
+
if confirm "Continue anyway? The agents won't be able to do GitHub work."; then
|
|
497
|
+
add_degraded "$GH_PROBE_MSG"
|
|
498
|
+
break
|
|
499
|
+
fi
|
|
500
|
+
die "aborted — set up GitHub access (see above) and re-run."
|
|
501
|
+
done
|
|
502
|
+
}
|
|
503
|
+
|
|
280
504
|
# ---------------------------------------------------------------------------
|
|
281
505
|
# Step 1 — Preflight
|
|
282
506
|
# ---------------------------------------------------------------------------
|
|
@@ -290,18 +514,10 @@ preflight() {
|
|
|
290
514
|
fi
|
|
291
515
|
ok "node $_nv (>= ${MIN_NODE}), npm present"
|
|
292
516
|
|
|
293
|
-
#
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
warn "gh is installed but not authenticated, and no GITHUB_TOKEN/GH_TOKEN is set."
|
|
298
|
-
note "Agents need GitHub access — run 'gh auth login' or export GITHUB_TOKEN before they start pulling work."
|
|
299
|
-
fi
|
|
300
|
-
else
|
|
301
|
-
warn "no gh CLI and no GITHUB_TOKEN/GH_TOKEN in the environment."
|
|
302
|
-
note "Agents need GitHub access — install gh (https://cli.github.com) and 'gh auth login', or export GITHUB_TOKEN."
|
|
303
|
-
fi
|
|
304
|
-
fi
|
|
517
|
+
# GitHub access — a workforce with no usable credential can do nothing useful,
|
|
518
|
+
# so this is a real three-state check with remediation (nanobpm/nano-workforce#588),
|
|
519
|
+
# not a passing mention. Covers this host only; remote worker hosts need their own.
|
|
520
|
+
github_preflight
|
|
305
521
|
}
|
|
306
522
|
|
|
307
523
|
# ---------------------------------------------------------------------------
|
|
@@ -939,6 +1155,9 @@ confirm_app() {
|
|
|
939
1155
|
note "project : ${PROJECT} (from template 'nano-workforce')"
|
|
940
1156
|
if [ -n "${GITHUB_TOKEN:-${GH_TOKEN:-}}" ]; then _ghkey='GITHUB_TOKEN'; else _ghkey='NANO_PR_GITHUB_TRANSPORT'; fi
|
|
941
1157
|
note "env keys written : ${_ghkey}, NANOBPMN_BASE_URL, NANO_WORKFORCE_BASE_URL, PR_REVIEW_PORT (values not shown)"
|
|
1158
|
+
if [ -z "${GITHUB_TOKEN:-${GH_TOKEN:-}}" ]; then
|
|
1159
|
+
note "no token in env : the app relies on the host gh CLI (NANO_PR_GITHUB_TRANSPORT=auto)."
|
|
1160
|
+
fi
|
|
942
1161
|
note "app-view URL : ${APPVIEW_BASE}/"
|
|
943
1162
|
if [ "$ASSUME_YES" -eq 1 ] || [ "$DRY_RUN" -eq 1 ]; then
|
|
944
1163
|
[ "$DRY_RUN" -eq 1 ] && note "dry-run: not asking for confirmation."
|
|
@@ -1147,6 +1366,11 @@ install_app() {
|
|
|
1147
1366
|
# Partial-success report
|
|
1148
1367
|
# ---------------------------------------------------------------------------
|
|
1149
1368
|
report_and_exit() {
|
|
1369
|
+
# A degraded GitHub-access install must never *look* complete — repeat the
|
|
1370
|
+
# warning here and exit non-zero so the failure is visible, not deferred.
|
|
1371
|
+
if [ -n "$GITHUB_DEGRADED" ] && [ "$DRY_RUN" -eq 0 ]; then
|
|
1372
|
+
record_failure "GitHub/git preflight degraded (this host only): ${GITHUB_DEGRADED} — the workforce cannot review, push, or merge until it is fixed"
|
|
1373
|
+
fi
|
|
1150
1374
|
if [ -n "$FAILURES" ]; then
|
|
1151
1375
|
warn "Completed with some steps skipped or failed:"
|
|
1152
1376
|
printf '%s\n' "$FAILURES" | sed '/^$/d' | while IFS= read -r _f; do note "- $_f"; done
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nanobpm/nano-workforce",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.154.0",
|
|
4
4
|
"description": "Nano Workforce — an Agent Graph Orchestration application for Agentic SDLC: durable BPMN processes that coordinate a graph of AI agents across the software delivery lifecycle.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "main.ts",
|
package/test/install-smoke.sh
CHANGED
|
@@ -99,6 +99,115 @@ assert_contains "s5: later selection defaults to 1 instance" \
|
|
|
99
99
|
assert_contains "s5: default model => bare adapter command, empty --model" \
|
|
100
100
|
"--command 'claude-code-acp' --model ''" "$OUT"
|
|
101
101
|
|
|
102
|
+
# ===========================================================================
|
|
103
|
+
# GitHub credential preflight — three-state check (nano-workforce#588)
|
|
104
|
+
# ===========================================================================
|
|
105
|
+
# The probe is stubbed hermetically via NANO_INSTALL_GH_STATE / NANO_INSTALL_GH_SCOPES
|
|
106
|
+
# / NANO_INSTALL_GIT_STATE so no real gh/git auth is needed. All run under
|
|
107
|
+
# --dry-run (never blocks) unless a scenario specifically exercises the
|
|
108
|
+
# non-interactive fail path.
|
|
109
|
+
|
|
110
|
+
# --- Scenario G1: token in env is usable, gh absent → app-fine caveat -------
|
|
111
|
+
OUT=$(NANO_INSTALL_HARNESSES_OVERRIDE="copilot" NANO_INSTALL_GH_STATE="missing" \
|
|
112
|
+
NANO_INSTALL_TOKEN_STATE="ok" GITHUB_TOKEN="fake-usable-token" \
|
|
113
|
+
sh "$SCRIPT" --harness copilot:gpt-5.4:1 --yes --dry-run 2>&1)
|
|
114
|
+
assert_contains "sG1: token detected treated as usable" \
|
|
115
|
+
"GitHub token detected in the environment" "$OUT"
|
|
116
|
+
assert_contains "sG1: token+no-gh caveat about harnesses shelling out to gh" \
|
|
117
|
+
"harnesses that shell out to 'gh' still won't work" "$OUT"
|
|
118
|
+
|
|
119
|
+
# --- Scenario G1b: a set-but-invalid token FAILS validation (not trusted) ---
|
|
120
|
+
# A non-empty token is not trusted blindly: an expired/revoked/underscoped
|
|
121
|
+
# token is the exact late failure this preflight exists to surface early.
|
|
122
|
+
OUT=$(NANO_INSTALL_HARNESSES_OVERRIDE="copilot" NANO_INSTALL_GH_STATE="missing" \
|
|
123
|
+
NANO_INSTALL_TOKEN_STATE="bad" GITHUB_TOKEN="fake-invalid-token" \
|
|
124
|
+
sh "$SCRIPT" --harness copilot:gpt-5.4:1 --yes --dry-run 2>&1)
|
|
125
|
+
assert_contains "sG1b: invalid token reported as failing validation" \
|
|
126
|
+
"failed validation" "$OUT"
|
|
127
|
+
assert_contains "sG1b: invalid token names remediation (fix or unset)" \
|
|
128
|
+
"expired, revoked, or lacks access" "$OUT"
|
|
129
|
+
|
|
130
|
+
# --- Scenario G2: gh missing, no token → not-detected + platform hint -------
|
|
131
|
+
OUT=$(NANO_INSTALL_HARNESSES_OVERRIDE="copilot" NANO_INSTALL_GH_STATE="missing" \
|
|
132
|
+
sh "$SCRIPT" --harness copilot:gpt-5.4:1 --yes --dry-run 2>&1)
|
|
133
|
+
assert_contains "sG2: no gh + no token reported" \
|
|
134
|
+
"no gh CLI and no GITHUB_TOKEN/GH_TOKEN" "$OUT"
|
|
135
|
+
assert_contains "sG2: not-detected headline" \
|
|
136
|
+
"GitHub access not detected" "$OUT"
|
|
137
|
+
assert_contains "sG2: Debian/Ubuntu install hint" "sudo apt install gh" "$OUT"
|
|
138
|
+
assert_contains "sG2: macOS install hint" "brew install gh" "$OUT"
|
|
139
|
+
assert_contains "sG2: gh auth login remediation printed" "gh auth login" "$OUT"
|
|
140
|
+
assert_contains "sG2: this-host-only caveat" "covers THIS host only" "$OUT"
|
|
141
|
+
# The script must NEVER run gh auth login itself — only print it.
|
|
142
|
+
assert_not_contains "sG2: never runs gh auth login (no 'Running' style exec)" \
|
|
143
|
+
"would run: gh auth login" "$OUT"
|
|
144
|
+
|
|
145
|
+
# --- Scenario G3: gh present but unauthenticated ---------------------------
|
|
146
|
+
OUT=$(NANO_INSTALL_HARNESSES_OVERRIDE="copilot" NANO_INSTALL_GH_STATE="unauthed" \
|
|
147
|
+
sh "$SCRIPT" --harness copilot:gpt-5.4:1 --yes --dry-run 2>&1)
|
|
148
|
+
assert_contains "sG3: installed-but-unauthenticated state" \
|
|
149
|
+
"gh is installed but not authenticated" "$OUT"
|
|
150
|
+
|
|
151
|
+
# --- Scenario G4: gh authenticated but unusable (gh api user fails) ---------
|
|
152
|
+
OUT=$(NANO_INSTALL_HARNESSES_OVERRIDE="copilot" NANO_INSTALL_GH_STATE="unusable" \
|
|
153
|
+
sh "$SCRIPT" --harness copilot:gpt-5.4:1 --yes --dry-run 2>&1)
|
|
154
|
+
assert_contains "sG4: authenticated-but-unusable state (gh api user failed)" \
|
|
155
|
+
"'gh api user' failed" "$OUT"
|
|
156
|
+
assert_contains "sG4: names SAML SSO as a likely cause" "SAML SSO" "$OUT"
|
|
157
|
+
|
|
158
|
+
# --- Scenario G5: authenticated + usable, missing repo scope FAILS ----------
|
|
159
|
+
OUT=$(NANO_INSTALL_HARNESSES_OVERRIDE="copilot" NANO_INSTALL_GH_STATE="ok" \
|
|
160
|
+
NANO_INSTALL_GH_SCOPES="gist,read:org" \
|
|
161
|
+
sh "$SCRIPT" --harness copilot:gpt-5.4:1 --yes --dry-run 2>&1)
|
|
162
|
+
assert_contains "sG5: missing repo scope fails the check" \
|
|
163
|
+
"missing the 'repo' scope" "$OUT"
|
|
164
|
+
|
|
165
|
+
# --- Scenario G6: usable with repo but missing workflow scope only WARNS ----
|
|
166
|
+
OUT=$(NANO_INSTALL_HARNESSES_OVERRIDE="copilot" NANO_INSTALL_GH_STATE="ok" \
|
|
167
|
+
NANO_INSTALL_GH_SCOPES="repo,read:org" \
|
|
168
|
+
sh "$SCRIPT" --harness copilot:gpt-5.4:1 --yes --dry-run 2>&1)
|
|
169
|
+
assert_contains "sG6: missing workflow scope warns (non-fatal)" \
|
|
170
|
+
"missing the 'workflow' scope" "$OUT"
|
|
171
|
+
assert_contains "sG6: repo+ passes the check" "GitHub access OK via the host gh CLI" "$OUT"
|
|
172
|
+
|
|
173
|
+
# --- Scenario G7: git absence is flagged -----------------------------------
|
|
174
|
+
OUT=$(NANO_INSTALL_HARNESSES_OVERRIDE="copilot" NANO_INSTALL_GH_STATE="ok" \
|
|
175
|
+
NANO_INSTALL_GH_SCOPES="repo,workflow" NANO_INSTALL_GIT_STATE="missing" \
|
|
176
|
+
sh "$SCRIPT" --harness copilot:gpt-5.4:1 --yes --dry-run 2>&1)
|
|
177
|
+
assert_contains "sG7: git presence checked alongside gh" \
|
|
178
|
+
"git not found on this host" "$OUT"
|
|
179
|
+
|
|
180
|
+
# --- Scenario G8: non-interactive (--yes) FAILS without --allow-no-github ----
|
|
181
|
+
if NANO_INSTALL_HARNESSES_OVERRIDE="copilot" NANO_INSTALL_GH_STATE="missing" \
|
|
182
|
+
sh "$SCRIPT" --harness copilot:gpt-5.4:1 --yes >/dev/null 2>&1; then
|
|
183
|
+
fail "sG8: non-interactive run with no GitHub access should exit non-zero"
|
|
184
|
+
else
|
|
185
|
+
pass "sG8: non-interactive run with no GitHub access exits non-zero"
|
|
186
|
+
fi
|
|
187
|
+
|
|
188
|
+
# --- Scenario G9: --allow-no-github lets a non-interactive run proceed -------
|
|
189
|
+
# Under --dry-run this exercises the --allow-no-github *continue* decision (the
|
|
190
|
+
# preflight records GITHUB_DEGRADED and breaks instead of dying). The final
|
|
191
|
+
# summary's degraded-failure line is intentionally NOT emitted here: dry-run
|
|
192
|
+
# makes no changes, so report_and_exit() only records the degraded failure when
|
|
193
|
+
# DRY_RUN=0 — that real-run summary path is a deliberate no-op under --dry-run.
|
|
194
|
+
OUT=$(NANO_INSTALL_HARNESSES_OVERRIDE="copilot" NANO_INSTALL_GH_STATE="missing" \
|
|
195
|
+
sh "$SCRIPT" --harness copilot:gpt-5.4:1 --yes --dry-run --allow-no-github 2>&1)
|
|
196
|
+
assert_contains "sG9: --allow-no-github continues" \
|
|
197
|
+
"continuing without GitHub access" "$OUT"
|
|
198
|
+
|
|
199
|
+
# --- Scenario G9b: git-missing AND github-missing both surface (no masking) --
|
|
200
|
+
# The GitHub-degraded reason must ACCUMULATE onto the earlier git-missing one,
|
|
201
|
+
# not overwrite it — so a host lacking both never loses the git problem behind
|
|
202
|
+
# the GitHub message. Both warnings must appear in the same run.
|
|
203
|
+
OUT=$(NANO_INSTALL_HARNESSES_OVERRIDE="copilot" NANO_INSTALL_GH_STATE="missing" \
|
|
204
|
+
NANO_INSTALL_GIT_STATE="missing" \
|
|
205
|
+
sh "$SCRIPT" --harness copilot:gpt-5.4:1 --yes --dry-run --allow-no-github 2>&1)
|
|
206
|
+
assert_contains "sG9b: git-missing warning still shown alongside github" \
|
|
207
|
+
"git not found on this host" "$OUT"
|
|
208
|
+
assert_contains "sG9b: github-missing continue also shown" \
|
|
209
|
+
"continuing without GitHub access" "$OUT"
|
|
210
|
+
|
|
102
211
|
# ===========================================================================
|
|
103
212
|
# Phase 2 — install & run the Nano Workforce app (nano-workforce#583)
|
|
104
213
|
# ===========================================================================
|
|
@@ -157,10 +266,11 @@ assert_contains "s6b: project name in the run URL" \
|
|
|
157
266
|
"POST http://localhost:8080/console/api/projects/Fleet/run" "$OUT"
|
|
158
267
|
|
|
159
268
|
# --- Scenario 6c: GITHUB_TOKEN is redacted in dry-run, never printed --------
|
|
160
|
-
OUT=$(NANO_INSTALL_HARNESSES_OVERRIDE="copilot" GITHUB_TOKEN="
|
|
269
|
+
OUT=$(NANO_INSTALL_HARNESSES_OVERRIDE="copilot" GITHUB_TOKEN="SHOULD-NOT-APPEAR-TOKEN" \
|
|
270
|
+
NANO_INSTALL_TOKEN_STATE="ok" \
|
|
161
271
|
sh "$SCRIPT" --harness copilot:gpt-5.4:1 --yes --dry-run 2>&1)
|
|
162
272
|
assert_contains "s6c: token key present, masked" '"GITHUB_TOKEN":"***"' "$OUT"
|
|
163
|
-
assert_not_contains "s6c: token value never printed" "
|
|
273
|
+
assert_not_contains "s6c: token value never printed" "SHOULD-NOT-APPEAR-TOKEN" "$OUT"
|
|
164
274
|
|
|
165
275
|
# --- Scenario 7: --skip-app runs phase 1 only, emits no console calls -------
|
|
166
276
|
OUT=$(NANO_INSTALL_HARNESSES_OVERRIDE="copilot" \
|
|
@@ -303,7 +413,7 @@ CJS
|
|
|
303
413
|
# newline) must be rejected with a clear error before any JSON config body
|
|
304
414
|
# is emitted, never producing invalid JSON that fails the console API
|
|
305
415
|
# opaquely. The token flows through json_str() unredacted on the live PUT.
|
|
306
|
-
_ctrl_token="$(printf '
|
|
416
|
+
_ctrl_token="$(printf 'fake-bad\ntoken')"
|
|
307
417
|
export GITHUB_TOKEN="$_ctrl_token"
|
|
308
418
|
run_phase2 happy
|
|
309
419
|
unset GITHUB_TOKEN
|