@nodii/dev 0.6.28 → 0.6.30
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 +74 -1
- package/dist/api.js +4 -1
- package/dist/cli.js +49 -5
- package/dist/core.js +38 -15
- package/dist/devops-openapi.json +1 -1
- package/dist/doctor.js +19 -2
- package/dist/freeze.js +328 -0
- package/dist/gather.js +241 -0
- package/dist/hooks.js +25 -25
- package/dist/lanes.js +224 -17
- package/dist/nodii-dev.js +12 -2
- package/dist/runtime-launcher.js +270 -0
- package/dist/safety.js +19 -0
- package/dist/setup.js +25 -8
- package/dist/starting.js +435 -0
- package/dist/upload.js +11 -1
- package/dist/watchdog.js +308 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -87,7 +87,7 @@ nodii-dev login [--no-browser] [--scope KEYS] [--force]
|
|
|
87
87
|
nodii-dev logout
|
|
88
88
|
nodii-dev whoami # OAuth identity plus the registered agent
|
|
89
89
|
nodii-dev config show
|
|
90
|
-
nodii-dev doctor [--figma-file KEY] [--json] # preflight:
|
|
90
|
+
nodii-dev doctor [--figma-file KEY] [--json] # preflight: Dev, hooks, local Ollama + Gather model, optional Figma
|
|
91
91
|
|
|
92
92
|
nodii-dev api list [--tag TAG] [--json]
|
|
93
93
|
nodii-dev api OPERATION_ID [--param name=value ...] [--body @file|-|JSON]
|
|
@@ -97,6 +97,8 @@ nodii-dev agent heartbeat
|
|
|
97
97
|
nodii-dev agents spawn SERVICE [--repo-root DIR]
|
|
98
98
|
nodii-dev agents cowork-tick [--json]
|
|
99
99
|
nodii-dev agents cowork-tick --plist [--interval SECONDS]
|
|
100
|
+
nodii-dev watchdog install [--interval SECONDS]
|
|
101
|
+
nodii-dev watchdog sweep
|
|
100
102
|
|
|
101
103
|
nodii-dev issues list [--service ID] [--project KEY] [--status STATUS]
|
|
102
104
|
[--kind KIND] [--priority PRIORITY] [--claimed true|false]
|
|
@@ -127,6 +129,15 @@ nodii-dev decisions get D123
|
|
|
127
129
|
nodii-dev decisions search WORDS
|
|
128
130
|
nodii-dev decisions create|lock|supersede|amend
|
|
129
131
|
|
|
132
|
+
nodii-dev lane gather [KEY] [--delta] [--objective TEXT] [--done-check TEXT]
|
|
133
|
+
[--paths A,B] [--components K,L]
|
|
134
|
+
nodii-dev lane freeze add KEY --section SECTION --file ITEM.json --reason TEXT
|
|
135
|
+
nodii-dev lane freeze drop KEY --section SECTION --item ID --reason TEXT
|
|
136
|
+
nodii-dev lane freeze swap KEY --section SECTION [--item ID] --file VALUE.json --reason TEXT
|
|
137
|
+
nodii-dev lane freeze show KEY
|
|
138
|
+
nodii-dev lane freeze history KEY
|
|
139
|
+
nodii-dev rules find "QUESTION" [--limit N]
|
|
140
|
+
|
|
130
141
|
nodii-dev open-questions list|show|answer
|
|
131
142
|
nodii-dev feature-docs list|show|claim|release|status
|
|
132
143
|
nodii-dev contracts grpc|rest|outbox|saga|table publish|status
|
|
@@ -312,6 +323,7 @@ nodii-dev lane step KEY --side backend|frontend --start|--done|--block STEP [--r
|
|
|
312
323
|
nodii-dev lane evidence KEY --pr|--commit|--figma|--deploy|--dev-check REF
|
|
313
324
|
nodii-dev lane subagent-quota KEY N
|
|
314
325
|
nodii-dev lane heartbeat KEY [--note TEXT]
|
|
326
|
+
nodii-dev lane resume KEY
|
|
315
327
|
nodii-dev lane review KEY --file REVIEW.json
|
|
316
328
|
nodii-dev lane reviews KEY
|
|
317
329
|
nodii-dev lane land KEY
|
|
@@ -358,8 +370,22 @@ nodii-dev harness install [--include-drafts] [--adopt PATH,...] [--dry-run] [--s
|
|
|
358
370
|
nodii-dev harness update [--include-drafts] [--adopt PATH,...] [--dry-run] [--system]
|
|
359
371
|
nodii-dev harness check [--json] # 0 current, 1 differs from Dev, 2 no harness
|
|
360
372
|
nodii-dev harness update --check # the same check
|
|
373
|
+
nodii-dev watchdog install [--interval SECONDS] # install/update com.nodii.watchdog in ~/Library/LaunchAgents
|
|
374
|
+
nodii-dev watchdog sweep # launchd entrypoint; normally not run by hand
|
|
361
375
|
```
|
|
362
376
|
|
|
377
|
+
The per-Mac watchdog runs outside agent sessions. Run `watchdog install` once
|
|
378
|
+
with the machine coordinator's config; it refuses a lane or unassigned agent.
|
|
379
|
+
Every sweep reports three host facts to Dev: watched run processes that exited
|
|
380
|
+
without ending their run, `/tmp/cc-socks/<pid>.sock` sockets whose process is
|
|
381
|
+
gone, and watched worktrees whose HEAD has commits absent from its configured
|
|
382
|
+
remote branch. Each observation is posted to the watched run by the machine
|
|
383
|
+
coordinator; `process_dead` transitions it to explicit `orphan` immediately.
|
|
384
|
+
Run launchers and wrappers persist one owner-only record per process under
|
|
385
|
+
`~/.config/nodii-dev/watchdog/runs/`; the plist itself contains only absolute
|
|
386
|
+
command/config paths, its schedule, and log paths, while OAuth credentials
|
|
387
|
+
remain in the machine keychain.
|
|
388
|
+
|
|
363
389
|
`harness install` and `harness update` merge the safety hooks into both
|
|
364
390
|
`.claude/settings.json` and `.codex/hooks.json`; every installed hook invokes
|
|
365
391
|
only `nodii-dev`. The
|
|
@@ -718,6 +744,42 @@ from AGENT-HARNESS-14 is available.
|
|
|
718
744
|
|
|
719
745
|
## Build lanes and repository setup
|
|
720
746
|
|
|
747
|
+
`coordinator start --machine NAME` registers the machine's one watched
|
|
748
|
+
coordinator run and shows its running lanes, orphaned runs and unclaimed
|
|
749
|
+
results. A live second coordinator is refused; a coordinator whose heartbeat
|
|
750
|
+
watchdog has recorded it orphaned is recorded as the predecessor of the
|
|
751
|
+
takeover.
|
|
752
|
+
|
|
753
|
+
`lane launch KEY` replaces the manual worktree/start/process sequence. It
|
|
754
|
+
requires the issue (whose title supplies both lane title and objective), done
|
|
755
|
+
check, build kind and side, runtime profile and model, subagent quota, and
|
|
756
|
+
repeated `--repo NAME=/SOURCE` plus
|
|
757
|
+
`--branch NAME=BRANCH` pairs. The Hub preflight checks the coordinator waiter,
|
|
758
|
+
target agent and machine, fresh account-capacity telemetry, repository grants,
|
|
759
|
+
lane limit, open issues and (when `--figma` is present) the authenticated Figma
|
|
760
|
+
account. Only after that passes does the CLI create one worktree per repository,
|
|
761
|
+
atomically register the lane and watched run, and launch the runtime with the
|
|
762
|
+
single-line brief `You are lane KEY. Run nodii-dev lane gather.`. Ordinary
|
|
763
|
+
notes use `--notes`; a scope-narrowing note uses `--narrowing-note` and must
|
|
764
|
+
also name the remainder's destination with `--narrowing-remainder`.
|
|
765
|
+
The launched process uses the already-provisioned lowercase identity (for
|
|
766
|
+
example `~/.config/nodii-dev/agents/lane-ah-12.json`), never the coordinator's
|
|
767
|
+
Dev identity.
|
|
768
|
+
|
|
769
|
+
Objective, scope and rule changes are durable `lane amend` records, never chat
|
|
770
|
+
messages. Every CLI call from a launched worktree checks the current watched
|
|
771
|
+
run's acknowledgement version. A pending change is printed and blocks the
|
|
772
|
+
call until `lane gather --delta` serves the current rules and acknowledges the
|
|
773
|
+
newest displayed version. `NODII_DEV_RUN_ID` makes this check per subagent run,
|
|
774
|
+
so one child cannot acknowledge for its siblings.
|
|
775
|
+
|
|
776
|
+
Gather also persists the lane's bounded Freeze: the objective and done check,
|
|
777
|
+
strict repository/Figma boundaries, the current pinned clauses, and the lane's
|
|
778
|
+
lookups, decisions, and unconfirmed questions. Ordinary CLI calls print and
|
|
779
|
+
acknowledge a changed Freeze before continuing, and compact SessionStart uses
|
|
780
|
+
the same backstop. `lane freeze add|drop|swap` makes a reasoned, version-checked
|
|
781
|
+
change; `show|history` reads the current contract or its append-only history.
|
|
782
|
+
|
|
721
783
|
`lane start` creates and starts a lane. It infers the repository (by short
|
|
722
784
|
name, as Dev compares it), branch and worktree from git unless overridden, and resolves `--issue` keys to the UUIDs
|
|
723
785
|
required by Dev. Lane keys are passed directly to every lane and Built
|
|
@@ -733,6 +795,17 @@ slot; Dev refuses another registration with `SUBAGENT_QUOTA_REACHED` when the
|
|
|
733
795
|
lane is full. A `done`, `handed_off`, or `failed` subagent run no longer counts,
|
|
734
796
|
so the watchdog marking a killed process failed frees the slot.
|
|
735
797
|
|
|
798
|
+
`lane resume` asks Dev to reserve a fresh run for an orphaned lane, then launches
|
|
799
|
+
the allowlisted Codex or Claude runtime as a detached headless process in the
|
|
800
|
+
same worktree. Dev keeps the lane's finished steps intact and returns the next
|
|
801
|
+
unfinished step together with its bounded recovery packet. The CLI sends that
|
|
802
|
+
packet over the new process's stdin (never its argv), saves its exact PID and
|
|
803
|
+
append-only log under `~/.config/nodii-dev/runs/<run-id>/`, and reports a local
|
|
804
|
+
spawn failure back to Dev so the reservation is not left running. Human output
|
|
805
|
+
identifies the replacement run, PID, step and last pushed commit without
|
|
806
|
+
echoing the dead run's log tail; `--json` retains the complete server response.
|
|
807
|
+
Dev refuses a third attempt after two identical deaths.
|
|
808
|
+
|
|
736
809
|
`setup` installs the Claude Code and Codex workflow harnesses in the current repository.
|
|
737
810
|
It writes `.claude/skills/nodii-build-workflow/SKILL.md`, merges SessionStart,
|
|
738
811
|
Stop and tool hooks into `.claude/settings.json` and `.codex/hooks.json`, and maintains one
|
package/dist/api.js
CHANGED
|
@@ -99,7 +99,8 @@ export const GROUPS = {
|
|
|
99
99
|
"approval-kinds": "ApprovalKinds", "notification-registry": "Notification registry", skills: "Skills", "open-questions": "OpenQuestions",
|
|
100
100
|
decisions: "Decisions", drift: "Drift", requests: "Requests", pipelines: "Pipelines", slices: "Slices", "sql-lookups": "SqlLookups",
|
|
101
101
|
runners: "Runners", routing: "Routing", usage: "Usage", audit: "AuditLog", reconciliation: "Reconciliation", cron: "Cron", health: "Health",
|
|
102
|
-
composite: "Composite", "service-edges": "ServiceEdges", lanes: "Lanes",
|
|
102
|
+
composite: "Composite", "service-edges": "ServiceEdges", lanes: "Lanes", runs: "Runs",
|
|
103
|
+
releases: "Releases", operate: "Operate",
|
|
103
104
|
swaps: "Swaps", ideas: "Ideas", terminology: "Terminology", credentials: "Credentials",
|
|
104
105
|
rulebook: "ad-documents", "ad-decisions": "ad-decisions", domains: "ad-domains", standardizations: "standardizations",
|
|
105
106
|
archive: "archive", harness: "harness", utils: "utils",
|
|
@@ -154,6 +155,8 @@ export const ALIASES = {
|
|
|
154
155
|
"standardizations:list": "listStandardizations", "standardizations:show": "getStandardization", "standardizations:update": "updateStandardization",
|
|
155
156
|
"archive:search": "searchPreDevArchive", "archive:resolve": "resolvePreCitation",
|
|
156
157
|
"harness:show": "generateHarness", "harness:methodology": "getHarnessMethodologyVersion",
|
|
158
|
+
"runs:register": "registerRun", "runs:show": "getRun", "runs:heartbeat": "heartbeatRun", "runs:end": "endRun",
|
|
159
|
+
"runs:results": "listRunResults", "runs:acknowledge": "acknowledgeResult",
|
|
157
160
|
"terminology:list": "listTerminology", "terminology:show": "getTerminologyTerm", "terminology:create": "createTerminologyTerm", "terminology:update": "updateTerminologyTerm",
|
|
158
161
|
};
|
|
159
162
|
|
package/dist/cli.js
CHANGED
|
@@ -26,6 +26,7 @@ import { harnessCommand } from "./harness.js";
|
|
|
26
26
|
import { hookRuntime, recordHookFire, safetyPostHook, safetyPreHook } from "./safety.js";
|
|
27
27
|
import { doctorCommand } from "./doctor.js";
|
|
28
28
|
import { dispatchManagedHook } from "./managed-hooks.js";
|
|
29
|
+
import { freezeCommand } from "./freeze.js";
|
|
29
30
|
import {
|
|
30
31
|
figmaChangeSetShow,
|
|
31
32
|
figmaChangeSetText,
|
|
@@ -53,6 +54,7 @@ export { figmaLock, submitMode } from "./figma.js";
|
|
|
53
54
|
import { docsCommand, rulebookCommand, rulesCommand } from "./upload.js";
|
|
54
55
|
import { changeSetCommand, guidelineCommand } from "./guidelines.js";
|
|
55
56
|
import { designCheck, designCheckText, designRoles, designRolesText } from "./design.js";
|
|
57
|
+
import { watchdogCommand } from "./watchdog.js";
|
|
56
58
|
import {
|
|
57
59
|
ALIASES,
|
|
58
60
|
GROUPS,
|
|
@@ -74,6 +76,13 @@ import {
|
|
|
74
76
|
nextCommand,
|
|
75
77
|
peopleCommand,
|
|
76
78
|
} from "./lanes.js";
|
|
79
|
+
import {
|
|
80
|
+
amendmentGuard,
|
|
81
|
+
amendLaneCommand,
|
|
82
|
+
coordinatorCommand,
|
|
83
|
+
gatherLaneCommand,
|
|
84
|
+
launchLaneCommand,
|
|
85
|
+
} from "./starting.js";
|
|
77
86
|
import {
|
|
78
87
|
launchRunSupervisor,
|
|
79
88
|
showWaitingResults,
|
|
@@ -87,8 +96,8 @@ Usage: nodii-dev <command> [options] [--json]
|
|
|
87
96
|
login [--force] | logout | whoami | config show
|
|
88
97
|
# login says so when the machine is already signed in; --force
|
|
89
98
|
# replaces that one sign-in (never from a lane)
|
|
90
|
-
doctor [--figma-file KEY] [--json] # preflight: sign-in, agent, permissions, machine, repo, lane slots
|
|
91
|
-
#
|
|
99
|
+
doctor [--figma-file KEY] [--json] # preflight: sign-in, agent, permissions, machine, repo, lane slots,
|
|
100
|
+
# local Ollama + pinned Gather model, and optional Figma registration
|
|
92
101
|
agent register --name NAME | agent heartbeat
|
|
93
102
|
issues list|show|create|update|claim|release
|
|
94
103
|
issues comment ISSUE --body TEXT # or --body-file FILE; issues update --comment TEXT|--comment-file FILE adds one too
|
|
@@ -127,11 +136,13 @@ Usage: nodii-dev <command> [options] [--json]
|
|
|
127
136
|
figma file add FILE_KEY --name TEXT --kind design-system|surfaces|prototype [--module M]
|
|
128
137
|
# register a Figma file in Dev so it can be locked
|
|
129
138
|
hook claude-figma-pre|claude-figma-post|safety-pre|safety-post|session-start|managed-dispatch|print-settings
|
|
130
|
-
setup [--yes|--check] # dry-run by default
|
|
139
|
+
setup [--yes|--check] # dry-run by default; installs/checks Ollama and pulls the Gather model
|
|
131
140
|
harness install|update [--include-drafts[=false]] [--adopt PATH,...] [--dry-run] [--system]
|
|
132
141
|
# --system stages managed hooks and only prints operator-reviewed sudo commands
|
|
133
142
|
harness check # 0 current, 1 differs from Dev, 2 not installed
|
|
134
143
|
harness update --check # the same check
|
|
144
|
+
watchdog install [--interval SECONDS] # install/update the per-Mac launchd liveness sweep (default 60s)
|
|
145
|
+
watchdog sweep # launchd entrypoint: report dead runs, stale sockets, unpushed worktrees
|
|
135
146
|
docs check|validate|upload <path...> --class ruling|conformance|editorial
|
|
136
147
|
[--base CODE=VERSION] [--decision FILE] [--pending] [--conformance-ref REF]
|
|
137
148
|
[--meaning-unchanged REASON] [--wait]
|
|
@@ -140,8 +151,21 @@ Usage: nodii-dev <command> [options] [--json]
|
|
|
140
151
|
rulebook list|show CODE [--version V]|diff CODE
|
|
141
152
|
rules core # the generated core (M05-33); refused over the cap
|
|
142
153
|
rules report [--section S] [--lane KEY] # delivery totals per document; rules served per lane
|
|
154
|
+
rules find "QUESTION" [--limit N] # answer an ad-hoc rules question from the indexed sections
|
|
143
155
|
rules cap TOKENS # operator: set the core token cap (default 3000)
|
|
144
156
|
|
|
157
|
+
coordinator start --machine NAME # register this machine's one watched coordinator; refuses a live second
|
|
158
|
+
lane launch <key> --done-check TEXT --kind KIND --side SIDE
|
|
159
|
+
--issue KEY --repo NAME=/SOURCE --branch NAME=BRANCH [--repo ... --branch ...]
|
|
160
|
+
--machine NAME --runtime claude|codex --profile PATH --model MODEL --subagents N
|
|
161
|
+
[--base NAME=REF] [--worktree NAME=PATH] [--owned-path NAME=PATH]
|
|
162
|
+
[--figma FILE[=COMPONENT_SET[,SET]]] [--notes TEXT]
|
|
163
|
+
[--narrowing-note TEXT --narrowing-remainder DESTINATION]
|
|
164
|
+
# authoritative preflight, one worktree per repo, watched launch
|
|
165
|
+
# uses the provisioned agents/lane-<key>.json Dev identity
|
|
166
|
+
lane amend <key> --objective|--scope|--rules TEXT --reason TEXT
|
|
167
|
+
lane gather [<key>] [--delta] [--objective TEXT] [--done-check TEXT]
|
|
168
|
+
# infers a launched worktree; explicit contract inputs support manual lane start
|
|
145
169
|
run [--kind build|test_run|subagent|deploy] [--name TEXT] -- <command> [args...]
|
|
146
170
|
# watched command: registers with NODII_DEV_RUN_ID,
|
|
147
171
|
# heartbeats, and records exit code + redacted log tail
|
|
@@ -151,15 +175,21 @@ Usage: nodii-dev <command> [options] [--json]
|
|
|
151
175
|
# --block --reason on a blocked step records its new reason
|
|
152
176
|
lane evidence <key> --pr|--commit|--figma|--deploy|--dev-check REF
|
|
153
177
|
lane subagent-quota <key> <n> # coordinator assigns 0-100 subagent slots
|
|
154
|
-
lane heartbeat|land|verify|prioritize|deprioritize|stop <key>
|
|
178
|
+
lane heartbeat|resume|land|verify|prioritize|deprioritize|stop <key>
|
|
155
179
|
lane review <key> --file REVIEW.json # base/head, outcome and clause-cited findings
|
|
156
180
|
lane reviews <key> # stored review history for the lane
|
|
181
|
+
lane freeze add <key> --section SECTION --file ITEM.json --reason TEXT
|
|
182
|
+
lane freeze drop <key> --section SECTION --item ID --reason TEXT
|
|
183
|
+
lane freeze swap <key> --section SECTION [--item ID] --file VALUE.json --reason TEXT
|
|
184
|
+
lane freeze show|history <key> # durable compact-safe contract and all versions
|
|
157
185
|
lane non-issue <key> --reason TEXT --evidence TEXT --reroute KEY[,KEY]|none
|
|
158
186
|
--branch carried:KEY|dropped[:REASON] [--commits SHA[,SHA]]
|
|
159
187
|
# DEC-0011: terminal non_issue; issue -> wont_do; slot frees
|
|
160
188
|
lane list [--kind KIND] [--status STATUS] [--mine] [--cursor CURSOR]
|
|
161
189
|
lane show <key> | next <key> [--paths A,B] [--components K,L] [--role R]
|
|
162
190
|
# next step, plus the rules Dev serves for it (DEC-0020)
|
|
191
|
+
lane gather [key] [--delta] [--paths A,B] [--components K,L]
|
|
192
|
+
# local document pick; prints Lane contract then Clause map
|
|
163
193
|
built list | built ack <key...>
|
|
164
194
|
machine list|show|set-coordinator|assign|report
|
|
165
195
|
agent lane-limit|grant|revoke
|
|
@@ -412,8 +442,12 @@ export async function figmaFileAdd(value, flags, { request = devRequest } = {})
|
|
|
412
442
|
}
|
|
413
443
|
|
|
414
444
|
export async function main(argv = process.argv.slice(2)) {
|
|
415
|
-
|
|
445
|
+
// Amendment acknowledgement is a lane-wide backstop, including otherwise
|
|
446
|
+
// local commands such as help and version.
|
|
447
|
+
if (argv[0] !== "__run-supervisor") {
|
|
448
|
+
await amendmentGuard(argv);
|
|
416
449
|
await showWaitingResults({ acknowledge: argv[0] !== "hook" });
|
|
450
|
+
}
|
|
417
451
|
if (argv.length === 1 && ["--version", "-v"].includes(argv[0]))
|
|
418
452
|
return console.log(VERSION);
|
|
419
453
|
if (argv.length === 1 && ["--help", "-h"].includes(argv[0]))
|
|
@@ -523,6 +557,10 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
523
557
|
if (rest.length) throw new CliError("USAGE", `harness ${action} takes no arguments, got: ${rest.join(" ")}. Write a flag value as --include-drafts=false.`, 2);
|
|
524
558
|
return emitCommand(await harnessCommand(action, f));
|
|
525
559
|
}
|
|
560
|
+
if (command === "watchdog") {
|
|
561
|
+
if (rest.length) throw new CliError("USAGE", `watchdog ${action ?? ""} takes no arguments, got: ${rest.join(" ")}.`, 2);
|
|
562
|
+
return emitCommand(await watchdogCommand(action, f));
|
|
563
|
+
}
|
|
526
564
|
if (command === "docs") return emitCommand(await docsCommand(action, rest, f));
|
|
527
565
|
if (command === "rulebook" && ["list", "show", "diff"].includes(action)) return emitCommand(await rulebookCommand(action, rest, f));
|
|
528
566
|
if (command === "rules") return emitCommand(await rulesCommand(action, rest, f));
|
|
@@ -541,6 +579,12 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
541
579
|
}
|
|
542
580
|
return;
|
|
543
581
|
}
|
|
582
|
+
if (command === "coordinator") return emitCommand(await coordinatorCommand(action, f));
|
|
583
|
+
if (command === "lane" && action === "launch") return emitCommand(await launchLaneCommand(rest[0], f));
|
|
584
|
+
if (command === "lane" && action === "amend") return emitCommand(await amendLaneCommand(rest[0], f));
|
|
585
|
+
if (command === "lane" && action === "gather") return emitCommand(await gatherLaneCommand(rest[0], f));
|
|
586
|
+
if (command === "lane" && action === "freeze")
|
|
587
|
+
return emitCommand(await freezeCommand(rest[0], rest.slice(1), f));
|
|
544
588
|
if (command === "lane") return emitCommand(await laneCommand(action, rest, f));
|
|
545
589
|
if (command === "next") return emitCommand(await nextCommand(action, f));
|
|
546
590
|
if (command === "built") return emitCommand(await builtCommand(action, rest, f));
|
package/dist/core.js
CHANGED
|
@@ -4,7 +4,7 @@ import { chmod, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
|
4
4
|
import { homedir, platform } from "node:os";
|
|
5
5
|
import { dirname, join } from "node:path";
|
|
6
6
|
|
|
7
|
-
export const VERSION = "0.6.
|
|
7
|
+
export const VERSION = "0.6.30";
|
|
8
8
|
export const configPath = () =>
|
|
9
9
|
process.env.NODII_DEV_CONFIG_PATH ??
|
|
10
10
|
join(homedir(), ".config/nodii-dev/config.json");
|
|
@@ -119,9 +119,30 @@ const staleCode = (raw) => {
|
|
|
119
119
|
}
|
|
120
120
|
};
|
|
121
121
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
122
|
+
const withSignal = (promise, signal) => {
|
|
123
|
+
if (!signal) return promise;
|
|
124
|
+
if (signal.aborted) return Promise.reject(signal.reason ?? new Error("aborted"));
|
|
125
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
126
|
+
const abort = () => rejectPromise(signal.reason ?? new Error("aborted"));
|
|
127
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
128
|
+
Promise.resolve(promise).then(
|
|
129
|
+
(value) => { signal.removeEventListener("abort", abort); resolvePromise(value); },
|
|
130
|
+
(error) => { signal.removeEventListener("abort", abort); rejectPromise(error); },
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
async function devFetch(method, path, bodyText, contentType, accept, extra = {}, signal) {
|
|
136
|
+
const wait = async (promise) => {
|
|
137
|
+
try {
|
|
138
|
+
return await withSignal(promise, signal);
|
|
139
|
+
} catch (error) {
|
|
140
|
+
if (signal?.aborted) throw new CliError("DEV_TIMEOUT", `Dev request timed out: ${method} ${path}`, 1);
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
const config = await wait(readConfig());
|
|
145
|
+
const auth = await wait(authProvider());
|
|
125
146
|
const url = requestUrl(config, path);
|
|
126
147
|
// The edge refuses a token minted before the principal's claims epoch with
|
|
127
148
|
// 401 token_stale (refresh and retry once). The request never reached Dev,
|
|
@@ -132,26 +153,28 @@ async function devFetch(method, path, bodyText, contentType, accept, extra = {})
|
|
|
132
153
|
for (;;) {
|
|
133
154
|
const headers = { accept, ...extra };
|
|
134
155
|
if (bodyText !== undefined) headers["content-type"] = contentType;
|
|
135
|
-
Object.assign(headers, await auth.headers({ method, path, body: bodyText ?? "" }));
|
|
156
|
+
Object.assign(headers, await wait(auth.headers({ method, path, body: bodyText ?? "" })));
|
|
136
157
|
let response;
|
|
137
158
|
try {
|
|
138
|
-
response = await fetch(url, { method, headers, body: bodyText });
|
|
159
|
+
response = await wait(fetch(url, { method, headers, body: bodyText, signal }));
|
|
139
160
|
} catch (error) {
|
|
161
|
+
if (error?.code === "DEV_TIMEOUT") throw error;
|
|
162
|
+
if (signal?.aborted) throw new CliError("DEV_TIMEOUT", `Dev request timed out: ${method} ${path}`, 1);
|
|
140
163
|
throw new CliError("DEV_UNREACHABLE", `Cannot reach Dev at ${new URL(url).origin}: ${error.message}`);
|
|
141
164
|
}
|
|
142
|
-
const raw = await response.text();
|
|
165
|
+
const raw = await wait(response.text());
|
|
143
166
|
if (response.status !== 401 || typeof auth.refreshStale !== "function" || !staleCode(raw)) return { response, raw };
|
|
144
167
|
if (refreshed) return { response, raw };
|
|
145
168
|
// A cached token that is stale too is not adopted twice: the next one is refreshed.
|
|
146
169
|
const used = String(headers.authorization ?? "").replace(/^Bearer /, "");
|
|
147
|
-
if ((await auth.refreshStale(used, { force: adopted })) === "refreshed") refreshed = true;
|
|
170
|
+
if ((await wait(auth.refreshStale(used, { force: adopted }))) === "refreshed") refreshed = true;
|
|
148
171
|
else adopted = true;
|
|
149
172
|
}
|
|
150
173
|
}
|
|
151
174
|
|
|
152
175
|
/** A JSON call that keeps the status: `{ status, payload }`. Errors throw as devRawRequest's do. */
|
|
153
|
-
export async function devExchange(method, path, bodyText, { contentType = "application/json", headers = {} } = {}) {
|
|
154
|
-
const { response, raw } = await devFetch(method, path, bodyText, contentType, "application/json", headers);
|
|
176
|
+
export async function devExchange(method, path, bodyText, { contentType = "application/json", headers = {}, signal } = {}) {
|
|
177
|
+
const { response, raw } = await devFetch(method, path, bodyText, contentType, "application/json", headers, signal);
|
|
155
178
|
let payload;
|
|
156
179
|
try {
|
|
157
180
|
payload = raw ? JSON.parse(raw) : {};
|
|
@@ -168,8 +191,8 @@ export async function devExchange(method, path, bodyText, { contentType = "appli
|
|
|
168
191
|
return { status: response.status, payload };
|
|
169
192
|
}
|
|
170
193
|
|
|
171
|
-
export async function devRawRequest(method, path, bodyText, contentType = "application/json") {
|
|
172
|
-
return (await devExchange(method, path, bodyText, { contentType })).payload;
|
|
194
|
+
export async function devRawRequest(method, path, bodyText, contentType = "application/json", options = {}) {
|
|
195
|
+
return (await devExchange(method, path, bodyText, { ...options, contentType })).payload;
|
|
173
196
|
}
|
|
174
197
|
|
|
175
198
|
/** A Dev route that answers with text (e.g. a document body as text/markdown). Errors are Dev's JSON. */
|
|
@@ -181,8 +204,8 @@ export async function devTextRequest(method, path) {
|
|
|
181
204
|
throw new CliError(payload.code ?? `HTTP_${response.status}`, payload.message ?? `Dev returned HTTP ${response.status}`, 1, payload.details);
|
|
182
205
|
}
|
|
183
206
|
|
|
184
|
-
export const devRequest = (method, path, body) =>
|
|
185
|
-
devRawRequest(method, path, body === undefined ? undefined : JSON.stringify(body));
|
|
207
|
+
export const devRequest = (method, path, body, options) =>
|
|
208
|
+
devRawRequest(method, path, body === undefined ? undefined : JSON.stringify(body), "application/json", options);
|
|
186
209
|
|
|
187
210
|
/** A JSON call sent with `Prefer: respond-async` (RFC 7240): `{ status, payload }`, 202 when Dev took it as a job. */
|
|
188
211
|
export const devAsyncRequest = async (method, path, body) =>
|
|
@@ -199,7 +222,7 @@ export async function checkLogin() {
|
|
|
199
222
|
export const encode = (value) => encodeURIComponent(value);
|
|
200
223
|
// Switches never take the next token as their value, so
|
|
201
224
|
// `docs upload --wait rulebook/` keeps rulebook/ as a path.
|
|
202
|
-
const SWITCHES = new Set(["json", "wait", "pending", "dry-run", "check", "all", "fleet", "help", "yes", "mine", "no-browser", "include-drafts", "drafted", "library", "async", "sync", "force", "system"]);
|
|
225
|
+
const SWITCHES = new Set(["json", "wait", "pending", "dry-run", "check", "all", "fleet", "help", "yes", "mine", "no-browser", "include-drafts", "drafted", "library", "async", "sync", "force", "system", "delta"]);
|
|
203
226
|
export function parseArgs(argv) {
|
|
204
227
|
const flags = {};
|
|
205
228
|
const args = [];
|