nawabari 0.1.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/LICENSE +21 -0
- package/README.md +120 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.js +298 -0
- package/dist/cli.js.map +1 -0
- package/dist/domain/doctor.d.ts +20 -0
- package/dist/domain/doctor.js +167 -0
- package/dist/domain/doctor.js.map +1 -0
- package/dist/domain/errors.d.ts +29 -0
- package/dist/domain/errors.js +42 -0
- package/dist/domain/errors.js.map +1 -0
- package/dist/domain/session-backend.d.ts +24 -0
- package/dist/domain/session-backend.js +206 -0
- package/dist/domain/session-backend.js.map +1 -0
- package/dist/domain/session.d.ts +89 -0
- package/dist/domain/session.js +47 -0
- package/dist/domain/session.js.map +1 -0
- package/dist/errors.d.ts +8 -0
- package/dist/errors.js +14 -0
- package/dist/errors.js.map +1 -0
- package/dist/git.d.ts +33 -0
- package/dist/git.js +157 -0
- package/dist/git.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/presentation.d.ts +9 -0
- package/dist/presentation.js +61 -0
- package/dist/presentation.js.map +1 -0
- package/dist/registry/atomic.d.ts +20 -0
- package/dist/registry/atomic.js +128 -0
- package/dist/registry/atomic.js.map +1 -0
- package/dist/registry/errors.d.ts +8 -0
- package/dist/registry/errors.js +30 -0
- package/dist/registry/errors.js.map +1 -0
- package/dist/registry/lock.d.ts +60 -0
- package/dist/registry/lock.js +727 -0
- package/dist/registry/lock.js.map +1 -0
- package/dist/registry/store.d.ts +38 -0
- package/dist/registry/store.js +156 -0
- package/dist/registry/store.js.map +1 -0
- package/dist/registry/types.d.ts +18 -0
- package/dist/registry/types.js +3 -0
- package/dist/registry/types.js.map +1 -0
- package/dist/registry.d.ts +5 -0
- package/dist/registry.js +6 -0
- package/dist/registry.js.map +1 -0
- package/dist/session-id.d.ts +7 -0
- package/dist/session-id.js +32 -0
- package/dist/session-id.js.map +1 -0
- package/dist/session-registry.d.ts +169 -0
- package/dist/session-registry.js +1190 -0
- package/dist/session-registry.js.map +1 -0
- package/package.json +57 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 yohn-jp
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Nawabari
|
|
2
|
+
|
|
3
|
+
Nawabari is a standalone local Git/session ownership layer for parallel coding
|
|
4
|
+
agents. It gives each active session one exclusively owned worktree and one
|
|
5
|
+
mutable branch. It works without GitHub, `gh`, a network connection, Mottainai,
|
|
6
|
+
or a particular agent runtime.
|
|
7
|
+
|
|
8
|
+
Nawabari governs operations routed through Nawabari. It is an authorization and
|
|
9
|
+
ownership boundary, not an operating-system or filesystem sandbox: a process
|
|
10
|
+
that already has filesystem permissions can still edit another worktree
|
|
11
|
+
directly.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install -g nawabari
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The package installs `nawabari` for direct use and `git-nawabari` for Git's
|
|
20
|
+
`git nawabari` external subcommand. Both names use the same entry point.
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
git nawabari --help
|
|
24
|
+
nawabari --version
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Session lifecycle
|
|
28
|
+
|
|
29
|
+
Session IDs are generated automatically as UUIDv7 values. They are immutable
|
|
30
|
+
machine identities; labels and branch names are separate display metadata.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
git nawabari session create --branch feature/example --worktree ../example-worktree --json
|
|
34
|
+
git nawabari session id --json
|
|
35
|
+
git nawabari session show --json
|
|
36
|
+
git nawabari session list --json
|
|
37
|
+
git nawabari status --json
|
|
38
|
+
git nawabari guard --json
|
|
39
|
+
git nawabari session close --json
|
|
40
|
+
git nawabari gc --dry-run --json
|
|
41
|
+
git nawabari doctor --json
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`session create` provisions a dedicated worktree and mutable branch atomically
|
|
45
|
+
under the repository-scoped mutation lock. The default/integration worktree
|
|
46
|
+
and its protected branch cannot be session resources. `session id` and the
|
|
47
|
+
other current-session commands resolve ownership from the current worktree;
|
|
48
|
+
callers do not need to repeat the session ID.
|
|
49
|
+
|
|
50
|
+
Close is conservative. Dirty worktrees, ambiguous ownership, mismatched Git
|
|
51
|
+
state, and commits not proven reachable from the integration branch block
|
|
52
|
+
destructive cleanup. A clean close releases only the owned worktree and
|
|
53
|
+
branch, and repeating close is idempotent. `gc` detects stale or interrupted
|
|
54
|
+
sessions; `--apply` uses the same close safety checks and reports blocked
|
|
55
|
+
sessions instead of guessing.
|
|
56
|
+
|
|
57
|
+
## Ownership guard
|
|
58
|
+
|
|
59
|
+
`git nawabari guard` is a cheap, side-effect-free authorization decision for a
|
|
60
|
+
Nawabari-governed mutation. It reads the current repository/worktree/branch and
|
|
61
|
+
the same authoritative session registry used by provisioning and lifecycle
|
|
62
|
+
operations. An optional `--session` asserts the caller's session identity.
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
decision=$(git nawabari guard --session "$NAWABARI_SESSION_ID" --json) || {
|
|
66
|
+
printf '%s\n' "$decision" >&2
|
|
67
|
+
exit 1
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
An allowed decision has `allowed: true` and `code: "ALLOWED"`. A denied
|
|
72
|
+
decision has `allowed: false`, a stable code such as
|
|
73
|
+
`WORKTREE_OWNED_BY_OTHER_SESSION`, `PROTECTED_WORKTREE`, or
|
|
74
|
+
`OWNERSHIP_MISMATCH`, and a non-zero exit status. Detached, corrupt, missing,
|
|
75
|
+
or conflicting state fails closed. The guard does not install hooks and does
|
|
76
|
+
not prevent direct filesystem writes outside Nawabari.
|
|
77
|
+
|
|
78
|
+
## Orchestrator integration
|
|
79
|
+
|
|
80
|
+
An external orchestrator can create a session, capture the returned
|
|
81
|
+
`session_id`, launch its worker with the returned `worktree` as `cwd`, and
|
|
82
|
+
check the guard before each Nawabari-governed mutation:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
created=$(git nawabari session create --branch feature/task --json)
|
|
86
|
+
session_id=$(printf '%s' "$created" | jq -r .session_id)
|
|
87
|
+
worktree=$(printf '%s' "$created" | jq -r .worktree)
|
|
88
|
+
|
|
89
|
+
(cd "$worktree" && git nawabari guard --session "$session_id" --json)
|
|
90
|
+
# run the worker in "$worktree"
|
|
91
|
+
git nawabari session close --session "$session_id" --json
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The orchestrator owns scheduling, prompts, and worker lifetime; Nawabari owns
|
|
95
|
+
only local session identity, worktree/branch ownership, and safe cleanup. No
|
|
96
|
+
Mottainai, GitHub, `gh`, network, or agent-runtime dependency is required.
|
|
97
|
+
|
|
98
|
+
## Repository state and concurrency
|
|
99
|
+
|
|
100
|
+
The authoritative registry is stored in the repository-common Git directory at
|
|
101
|
+
`.git/nawabari/session-registry.json`; linked worktrees therefore share one
|
|
102
|
+
registry. It records the schema version, repository identity, immutable session
|
|
103
|
+
ID, canonical worktree and branch identities, lifecycle state, and timestamps.
|
|
104
|
+
|
|
105
|
+
Ownership-changing writes use an exclusive repository-local lock and a synced
|
|
106
|
+
temporary file followed by atomic replacement. Concurrent creation cannot
|
|
107
|
+
silently duplicate an active worktree or branch. Lock recovery is conservative:
|
|
108
|
+
the lock records a random token, PID, host, and process-start identity; an
|
|
109
|
+
owner is reclaimed only when the same host proves that exact process identity
|
|
110
|
+
is dead. Invalid, remote, or otherwise unverifiable lock metadata is never
|
|
111
|
+
stolen and fails closed so an operator can inspect or remove it deliberately.
|
|
112
|
+
|
|
113
|
+
## Development
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
pnpm install --frozen-lockfile
|
|
117
|
+
pnpm run verify
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) and [LICENSE](LICENSE).
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type SessionBackend } from "./domain/session.js";
|
|
2
|
+
import { type CliIO } from "./presentation.js";
|
|
3
|
+
export type CliDependencies = {
|
|
4
|
+
backend?: SessionBackend;
|
|
5
|
+
cwd?: string;
|
|
6
|
+
io?: CliIO;
|
|
7
|
+
version?: string;
|
|
8
|
+
};
|
|
9
|
+
export declare function runCli(argv: string[], dependencies?: CliDependencies): Promise<number>;
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { runDoctor } from "./domain/doctor.js";
|
|
3
|
+
import { DomainError, EXIT_CODES, failure } from "./domain/errors.js";
|
|
4
|
+
import { createLocalSessionBackend } from "./domain/session-backend.js";
|
|
5
|
+
import { defaultCliIO, renderFailure, renderSuccess } from "./presentation.js";
|
|
6
|
+
const CLI_NAME = "nawabari";
|
|
7
|
+
const packageMetadata = createRequire(import.meta.url)("../package.json");
|
|
8
|
+
const VERSION = packageMetadata.version;
|
|
9
|
+
const HELP_TEXT = [
|
|
10
|
+
`Usage: ${CLI_NAME} <command> [options]`,
|
|
11
|
+
"",
|
|
12
|
+
"Commands:",
|
|
13
|
+
" session create Request a new Nawabari session",
|
|
14
|
+
" session id Resolve the current session identity",
|
|
15
|
+
" session show Show the current or selected session",
|
|
16
|
+
" session list List repository sessions",
|
|
17
|
+
" session close Close the current or selected session",
|
|
18
|
+
" status Show Nawabari session status",
|
|
19
|
+
" guard [--session id] Authorize the current worktree for mutation",
|
|
20
|
+
" gc Detect or clean eligible stale sessions",
|
|
21
|
+
" doctor Check local Nawabari prerequisites",
|
|
22
|
+
" --help Show this help",
|
|
23
|
+
" --version Print the installed version",
|
|
24
|
+
"",
|
|
25
|
+
"Global options:",
|
|
26
|
+
" --json Emit one stable JSON document on stdout",
|
|
27
|
+
" -h, --help Show this help",
|
|
28
|
+
"",
|
|
29
|
+
"Session options:",
|
|
30
|
+
" session create --branch <name> --worktree <path> --base <ref> --label <text>",
|
|
31
|
+
" session show|close --session <id>",
|
|
32
|
+
" gc [--dry-run|--apply]",
|
|
33
|
+
].join("\n");
|
|
34
|
+
const HELP_DATA = {
|
|
35
|
+
usage: `Usage: ${CLI_NAME} <command> [options]`,
|
|
36
|
+
commands: [
|
|
37
|
+
"session create",
|
|
38
|
+
"session id",
|
|
39
|
+
"session show",
|
|
40
|
+
"session list",
|
|
41
|
+
"session close",
|
|
42
|
+
"status",
|
|
43
|
+
"guard",
|
|
44
|
+
"gc",
|
|
45
|
+
"doctor",
|
|
46
|
+
],
|
|
47
|
+
options: ["--json", "--help", "--version"],
|
|
48
|
+
session_options: ["--branch", "--worktree", "--base", "--label", "--session"],
|
|
49
|
+
gc_options: ["--apply", "--dry-run"],
|
|
50
|
+
};
|
|
51
|
+
function usageError(code, message, details = null) {
|
|
52
|
+
return new DomainError(code, message, details);
|
|
53
|
+
}
|
|
54
|
+
function parseGlobalArguments(argv) {
|
|
55
|
+
let json = false;
|
|
56
|
+
let help = false;
|
|
57
|
+
let version = false;
|
|
58
|
+
const commandArguments = [];
|
|
59
|
+
for (const argument of argv) {
|
|
60
|
+
if (argument === "--json") {
|
|
61
|
+
json = true;
|
|
62
|
+
}
|
|
63
|
+
else if (argument === "-h" || argument === "--help") {
|
|
64
|
+
help = true;
|
|
65
|
+
}
|
|
66
|
+
else if (argument === "--version") {
|
|
67
|
+
version = true;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
commandArguments.push(argument);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (help && version) {
|
|
74
|
+
return failure(usageError("INVALID_ARGUMENT", "--help and --version cannot be used together."));
|
|
75
|
+
}
|
|
76
|
+
if (version && commandArguments.length > 0) {
|
|
77
|
+
return failure(usageError("INVALID_ARGUMENT", "--version cannot be combined with a command."));
|
|
78
|
+
}
|
|
79
|
+
return { ok: true, value: { json, help, version, commandArguments } };
|
|
80
|
+
}
|
|
81
|
+
function optionParts(argument) {
|
|
82
|
+
const separator = argument.indexOf("=");
|
|
83
|
+
if (separator === -1)
|
|
84
|
+
return { name: argument, inlineValue: null };
|
|
85
|
+
return { name: argument.slice(0, separator), inlineValue: argument.slice(separator + 1) };
|
|
86
|
+
}
|
|
87
|
+
function parseOptions(arguments_, allowed) {
|
|
88
|
+
const options = {
|
|
89
|
+
session_id: null,
|
|
90
|
+
branch: null,
|
|
91
|
+
worktree: null,
|
|
92
|
+
base: null,
|
|
93
|
+
label: null,
|
|
94
|
+
apply: false,
|
|
95
|
+
};
|
|
96
|
+
let dryRun = false;
|
|
97
|
+
for (let index = 0; index < arguments_.length; index += 1) {
|
|
98
|
+
const { name, inlineValue } = optionParts(arguments_[index]);
|
|
99
|
+
if (!allowed.has(name)) {
|
|
100
|
+
return failure(usageError("INVALID_ARGUMENT", `Unknown option: ${name}.`, { option: name }));
|
|
101
|
+
}
|
|
102
|
+
if (name === "--apply" || name === "--dry-run") {
|
|
103
|
+
if (inlineValue !== null) {
|
|
104
|
+
return failure(usageError("INVALID_ARGUMENT", `${name} does not accept a value.`, { option: name }));
|
|
105
|
+
}
|
|
106
|
+
if (name === "--apply")
|
|
107
|
+
options.apply = true;
|
|
108
|
+
else
|
|
109
|
+
dryRun = true;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
const value = inlineValue ?? arguments_[index + 1];
|
|
113
|
+
if (value === undefined || value === "" || (inlineValue === null && value.startsWith("-"))) {
|
|
114
|
+
return failure(usageError("MISSING_ARGUMENT", `${name} requires a value.`, { option: name }));
|
|
115
|
+
}
|
|
116
|
+
if (inlineValue === null)
|
|
117
|
+
index += 1;
|
|
118
|
+
if (name === "--session")
|
|
119
|
+
options.session_id = value;
|
|
120
|
+
else if (name === "--branch")
|
|
121
|
+
options.branch = value;
|
|
122
|
+
else if (name === "--worktree")
|
|
123
|
+
options.worktree = value;
|
|
124
|
+
else if (name === "--base")
|
|
125
|
+
options.base = value;
|
|
126
|
+
else if (name === "--label")
|
|
127
|
+
options.label = value;
|
|
128
|
+
}
|
|
129
|
+
if (options.apply && dryRun) {
|
|
130
|
+
return failure(usageError("INVALID_ARGUMENT", "--apply and --dry-run cannot be used together."));
|
|
131
|
+
}
|
|
132
|
+
return { ok: true, value: options };
|
|
133
|
+
}
|
|
134
|
+
function noOptions(arguments_) {
|
|
135
|
+
return parseOptions(arguments_, new Set());
|
|
136
|
+
}
|
|
137
|
+
function sessionContext(cwd) {
|
|
138
|
+
return { cwd };
|
|
139
|
+
}
|
|
140
|
+
async function resolveSelectedSession(backend, context, sessionId) {
|
|
141
|
+
return sessionId === null ? backend.resolveCurrentSession(context) : backend.getSession(context, sessionId);
|
|
142
|
+
}
|
|
143
|
+
async function executeCommand(commandArguments, dependencies) {
|
|
144
|
+
const [command, subcommand, ...rest] = commandArguments;
|
|
145
|
+
const context = sessionContext(dependencies.cwd);
|
|
146
|
+
if (command === "session") {
|
|
147
|
+
if (subcommand === undefined) {
|
|
148
|
+
return failure(usageError("MISSING_ARGUMENT", "session requires a subcommand."));
|
|
149
|
+
}
|
|
150
|
+
if (subcommand === "create") {
|
|
151
|
+
const parsed = parseOptions(rest, new Set(["--branch", "--worktree", "--base", "--label"]));
|
|
152
|
+
if (!parsed.ok)
|
|
153
|
+
return parsed;
|
|
154
|
+
const options = {
|
|
155
|
+
branch: parsed.value.branch,
|
|
156
|
+
worktree: parsed.value.worktree,
|
|
157
|
+
base: parsed.value.base,
|
|
158
|
+
label: parsed.value.label,
|
|
159
|
+
};
|
|
160
|
+
const result = await dependencies.backend.createSession(context, options);
|
|
161
|
+
return result.ok ? { ok: true, value: result.value } : result;
|
|
162
|
+
}
|
|
163
|
+
if (subcommand === "id" || subcommand === "show" || subcommand === "close") {
|
|
164
|
+
if (subcommand === "id") {
|
|
165
|
+
const parsed = noOptions(rest);
|
|
166
|
+
if (!parsed.ok)
|
|
167
|
+
return parsed;
|
|
168
|
+
const selected = await dependencies.backend.resolveCurrentSession(context);
|
|
169
|
+
if (!selected.ok)
|
|
170
|
+
return selected;
|
|
171
|
+
return { ok: true, value: { session_id: selected.value.session_id } };
|
|
172
|
+
}
|
|
173
|
+
const parsed = parseOptions(rest, new Set(["--session"]));
|
|
174
|
+
if (!parsed.ok)
|
|
175
|
+
return parsed;
|
|
176
|
+
if (subcommand === "close") {
|
|
177
|
+
const closeOptions = { session_id: parsed.value.session_id };
|
|
178
|
+
const selected = await dependencies.backend.closeSession(context, closeOptions);
|
|
179
|
+
return selected.ok ? { ok: true, value: selected.value } : selected;
|
|
180
|
+
}
|
|
181
|
+
const selected = await resolveSelectedSession(dependencies.backend, context, parsed.value.session_id);
|
|
182
|
+
return selected.ok ? { ok: true, value: selected.value } : selected;
|
|
183
|
+
}
|
|
184
|
+
if (subcommand === "list") {
|
|
185
|
+
const parsed = noOptions(rest);
|
|
186
|
+
if (!parsed.ok)
|
|
187
|
+
return parsed;
|
|
188
|
+
const result = await dependencies.backend.listSessions(context);
|
|
189
|
+
return result.ok ? { ok: true, value: result.value } : result;
|
|
190
|
+
}
|
|
191
|
+
return failure(new DomainError("UNKNOWN_COMMAND", `Unknown session subcommand: ${subcommand}.`, { subcommand }));
|
|
192
|
+
}
|
|
193
|
+
if (command === "status") {
|
|
194
|
+
const parsed = noOptions([subcommand, ...rest].filter((argument) => argument !== undefined));
|
|
195
|
+
if (!parsed.ok)
|
|
196
|
+
return parsed;
|
|
197
|
+
const result = await dependencies.backend.status(context);
|
|
198
|
+
return result.ok ? { ok: true, value: result.value } : result;
|
|
199
|
+
}
|
|
200
|
+
if (command === "guard") {
|
|
201
|
+
const parsed = parseOptions([subcommand, ...rest].filter((argument) => argument !== undefined), new Set(["--session"]));
|
|
202
|
+
if (!parsed.ok)
|
|
203
|
+
return parsed;
|
|
204
|
+
const result = await dependencies.backend.guard(context, { session_id: parsed.value.session_id });
|
|
205
|
+
if (!result.ok)
|
|
206
|
+
return result;
|
|
207
|
+
if (result.value.allowed)
|
|
208
|
+
return { ok: true, value: result.value };
|
|
209
|
+
const code = result.value.code === "ALLOWED" ? "OPERATION_REJECTED" : result.value.code;
|
|
210
|
+
return failure(new DomainError(code, `Guard denied the current worktree: ${code}.`, {
|
|
211
|
+
allowed: false,
|
|
212
|
+
repository: result.value.repository,
|
|
213
|
+
worktree: result.value.worktree,
|
|
214
|
+
branch: result.value.branch,
|
|
215
|
+
session_id: result.value.session_id,
|
|
216
|
+
owner_session_id: result.value.owner_session_id,
|
|
217
|
+
requested_session_id: result.value.requested_session_id,
|
|
218
|
+
state: result.value.state,
|
|
219
|
+
details: result.value.details,
|
|
220
|
+
}));
|
|
221
|
+
}
|
|
222
|
+
if (command === "gc") {
|
|
223
|
+
const parsed = parseOptions([subcommand, ...rest].filter((argument) => argument !== undefined), new Set(["--apply", "--dry-run"]));
|
|
224
|
+
if (!parsed.ok)
|
|
225
|
+
return parsed;
|
|
226
|
+
const options = { apply: parsed.value.apply };
|
|
227
|
+
const result = await dependencies.backend.garbageCollect(context, options);
|
|
228
|
+
return result.ok ? { ok: true, value: result.value } : result;
|
|
229
|
+
}
|
|
230
|
+
if (command === "doctor") {
|
|
231
|
+
const parsed = noOptions([subcommand, ...rest].filter((argument) => argument !== undefined));
|
|
232
|
+
if (!parsed.ok)
|
|
233
|
+
return parsed;
|
|
234
|
+
const report = await runDoctor(dependencies.cwd);
|
|
235
|
+
if (!report.ok)
|
|
236
|
+
return report;
|
|
237
|
+
if (report.value.ok)
|
|
238
|
+
return { ok: true, value: report.value };
|
|
239
|
+
return failure(new DomainError("DOCTOR_FAILED", "One or more local Nawabari checks failed.", {
|
|
240
|
+
checks: report.value.checks,
|
|
241
|
+
repository: report.value.repository,
|
|
242
|
+
}));
|
|
243
|
+
}
|
|
244
|
+
if (command === undefined)
|
|
245
|
+
return failure(new DomainError("NO_COMMAND", "A command is required."));
|
|
246
|
+
return failure(new DomainError("UNKNOWN_COMMAND", `Unknown command: ${command}.`, { command }));
|
|
247
|
+
}
|
|
248
|
+
function commandName(commandArguments) {
|
|
249
|
+
if (commandArguments[0] === "session")
|
|
250
|
+
return commandArguments.slice(0, 2).join(" ");
|
|
251
|
+
return commandArguments[0] ?? "cli";
|
|
252
|
+
}
|
|
253
|
+
function emitFailure(mode, command, error, io) {
|
|
254
|
+
io[mode === "json" ? "stdout" : "stderr"](renderFailure(mode, command, error));
|
|
255
|
+
return error.exitCode;
|
|
256
|
+
}
|
|
257
|
+
export async function runCli(argv, dependencies = {}) {
|
|
258
|
+
const io = dependencies.io ?? defaultCliIO();
|
|
259
|
+
const mode = argv.includes("--json") ? "json" : "human";
|
|
260
|
+
const parsed = parseGlobalArguments(argv);
|
|
261
|
+
if (!parsed.ok)
|
|
262
|
+
return emitFailure(mode, "cli", parsed.error, io);
|
|
263
|
+
if (parsed.value.help) {
|
|
264
|
+
if (mode === "json")
|
|
265
|
+
io.stdout(renderSuccess(mode, "help", HELP_DATA));
|
|
266
|
+
else
|
|
267
|
+
io.stdout(HELP_TEXT);
|
|
268
|
+
return EXIT_CODES.success;
|
|
269
|
+
}
|
|
270
|
+
if (parsed.value.version) {
|
|
271
|
+
const payload = { version: dependencies.version ?? VERSION };
|
|
272
|
+
if (mode === "json")
|
|
273
|
+
io.stdout(renderSuccess(mode, "version", payload));
|
|
274
|
+
else
|
|
275
|
+
io.stdout(String(payload.version));
|
|
276
|
+
return EXIT_CODES.success;
|
|
277
|
+
}
|
|
278
|
+
if (parsed.value.commandArguments.length === 0) {
|
|
279
|
+
if (mode === "json")
|
|
280
|
+
return emitFailure(mode, "cli", new DomainError("NO_COMMAND", "A command is required."), io);
|
|
281
|
+
io.stdout(HELP_TEXT);
|
|
282
|
+
return EXIT_CODES.usage;
|
|
283
|
+
}
|
|
284
|
+
const command = commandName(parsed.value.commandArguments);
|
|
285
|
+
const cwd = dependencies.cwd ?? process.cwd();
|
|
286
|
+
try {
|
|
287
|
+
const backend = dependencies.backend ?? createLocalSessionBackend();
|
|
288
|
+
const result = await executeCommand(parsed.value.commandArguments, { backend, cwd });
|
|
289
|
+
if (!result.ok)
|
|
290
|
+
return emitFailure(mode, command, result.error, io);
|
|
291
|
+
io.stdout(renderSuccess(mode, command, result.value));
|
|
292
|
+
return EXIT_CODES.success;
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
return emitFailure(mode, command, new DomainError("INTERNAL_ERROR", "An unexpected internal error occurred."), io);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,OAAO,EAAsC,MAAM,oBAAoB,CAAC;AAQ1G,OAAO,EAAE,yBAAyB,EAAE,MAAM,6BAA6B,CAAC;AACxE,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAA4B,MAAM,mBAAmB,CAAC;AAEzG,MAAM,QAAQ,GAAG,UAAU,CAAC;AAC5B,MAAM,eAAe,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,iBAAiB,CAAwB,CAAC;AACjG,MAAM,OAAO,GAAG,eAAe,CAAC,OAAO,CAAC;AAExC,MAAM,SAAS,GAAG;IAChB,UAAU,QAAQ,sBAAsB;IACxC,EAAE;IACF,WAAW;IACX,uDAAuD;IACvD,6DAA6D;IAC7D,6DAA6D;IAC7D,iDAAiD;IACjD,8DAA8D;IAC9D,qDAAqD;IACrD,oEAAoE;IACpE,gEAAgE;IAChE,2DAA2D;IAC3D,uCAAuC;IACvC,oDAAoD;IACpD,EAAE;IACF,iBAAiB;IACjB,gEAAgE;IAChE,uCAAuC;IACvC,EAAE;IACF,kBAAkB;IAClB,gFAAgF;IAChF,qCAAqC;IACrC,0BAA0B;CAC3B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEb,MAAM,SAAS,GAAe;IAC5B,KAAK,EAAE,UAAU,QAAQ,sBAAsB;IAC/C,QAAQ,EAAE;QACR,gBAAgB;QAChB,YAAY;QACZ,cAAc;QACd,cAAc;QACd,eAAe;QACf,QAAQ;QACR,OAAO;QACP,IAAI;QACJ,QAAQ;KACT;IACD,OAAO,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,WAAW,CAAC;IAC1C,eAAe,EAAE,CAAC,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,CAAC;IAC7E,UAAU,EAAE,CAAC,SAAS,EAAE,WAAW,CAAC;CACrC,CAAC;AAyBF,SAAS,UAAU,CACjB,IAA4D,EAC5D,OAAe,EACf,UAA6B,IAAI;IAEjC,OAAO,IAAI,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AACjD,CAAC;AAED,SAAS,oBAAoB,CAAC,IAAc;IAC1C,IAAI,IAAI,GAAG,KAAK,CAAC;IACjB,IAAI,IAAI,GAAG,KAAK,CAAC;IACjB,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,MAAM,gBAAgB,GAAa,EAAE,CAAC;IAEtC,KAAK,MAAM,QAAQ,IAAI,IAAI,EAAE,CAAC;QAC5B,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAC1B,IAAI,GAAG,IAAI,CAAC;QACd,CAAC;aAAM,IAAI,QAAQ,KAAK,IAAI,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;YACtD,IAAI,GAAG,IAAI,CAAC;QACd,CAAC;aAAM,IAAI,QAAQ,KAAK,WAAW,EAAE,CAAC;YACpC,OAAO,GAAG,IAAI,CAAC;QACjB,CAAC;aAAM,CAAC;YACN,gBAAgB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAClC,CAAC;IACH,CAAC;IAED,IAAI,IAAI,IAAI,OAAO,EAAE,CAAC;QACpB,OAAO,OAAO,CAAC,UAAU,CAAC,kBAAkB,EAAE,+CAA+C,CAAC,CAAC,CAAC;IAClG,CAAC;IACD,IAAI,OAAO,IAAI,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3C,OAAO,OAAO,CAAC,UAAU,CAAC,kBAAkB,EAAE,8CAA8C,CAAC,CAAC,CAAC;IACjG,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,gBAAgB,EAAE,EAAE,CAAC;AACxE,CAAC;AAED,SAAS,WAAW,CAAC,QAAgB;IACnC,MAAM,SAAS,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACxC,IAAI,SAAS,KAAK,CAAC,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;IACnE,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,EAAE,WAAW,EAAE,QAAQ,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,EAAE,CAAC;AAC5F,CAAC;AAED,SAAS,YAAY,CAAC,UAAoB,EAAE,OAA4B;IACtE,MAAM,OAAO,GAAkB;QAC7B,UAAU,EAAE,IAAI;QAChB,MAAM,EAAE,IAAI;QACZ,QAAQ,EAAE,IAAI;QACd,IAAI,EAAE,IAAI;QACV,KAAK,EAAE,IAAI;QACX,KAAK,EAAE,KAAK;KACb,CAAC;IACF,IAAI,MAAM,GAAG,KAAK,CAAC;IAEnB,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,UAAU,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QAC1D,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,WAAW,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC;QAC7D,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YACvB,OAAO,OAAO,CAAC,UAAU,CAAC,kBAAkB,EAAE,mBAAmB,IAAI,GAAG,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QAC/F,CAAC;QAED,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,WAAW,EAAE,CAAC;YAC/C,IAAI,WAAW,KAAK,IAAI,EAAE,CAAC;gBACzB,OAAO,OAAO,CAAC,UAAU,CAAC,kBAAkB,EAAE,GAAG,IAAI,2BAA2B,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;YACvG,CAAC;YACD,IAAI,IAAI,KAAK,SAAS;gBAAE,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC;;gBACxC,MAAM,GAAG,IAAI,CAAC;YACnB,SAAS;QACX,CAAC;QAED,MAAM,KAAK,GAAG,WAAW,IAAI,UAAU,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;QACnD,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE,IAAI,CAAC,WAAW,KAAK,IAAI,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YAC3F,OAAO,OAAO,CAAC,UAAU,CAAC,kBAAkB,EAAE,GAAG,IAAI,oBAAoB,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QAChG,CAAC;QACD,IAAI,WAAW,KAAK,IAAI;YAAE,KAAK,IAAI,CAAC,CAAC;QAErC,IAAI,IAAI,KAAK,WAAW;YAAE,OAAO,CAAC,UAAU,GAAG,KAAK,CAAC;aAChD,IAAI,IAAI,KAAK,UAAU;YAAE,OAAO,CAAC,MAAM,GAAG,KAAK,CAAC;aAChD,IAAI,IAAI,KAAK,YAAY;YAAE,OAAO,CAAC,QAAQ,GAAG,KAAK,CAAC;aACpD,IAAI,IAAI,KAAK,QAAQ;YAAE,OAAO,CAAC,IAAI,GAAG,KAAK,CAAC;aAC5C,IAAI,IAAI,KAAK,SAAS;YAAE,OAAO,CAAC,KAAK,GAAG,KAAK,CAAC;IACrD,CAAC;IAED,IAAI,OAAO,CAAC,KAAK,IAAI,MAAM,EAAE,CAAC;QAC5B,OAAO,OAAO,CAAC,UAAU,CAAC,kBAAkB,EAAE,gDAAgD,CAAC,CAAC,CAAC;IACnG,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;AACtC,CAAC;AAED,SAAS,SAAS,CAAC,UAAoB;IACrC,OAAO,YAAY,CAAC,UAAU,EAAE,IAAI,GAAG,EAAE,CAAC,CAAC;AAC7C,CAAC;AAED,SAAS,cAAc,CAAC,GAAW;IACjC,OAAO,EAAE,GAAG,EAAE,CAAC;AACjB,CAAC;AAED,KAAK,UAAU,sBAAsB,CACnC,OAAuB,EACvB,OAAuB,EACvB,SAAwB;IAExB,OAAO,SAAS,KAAK,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;AAC9G,CAAC;AAED,KAAK,UAAU,cAAc,CAC3B,gBAA0B,EAC1B,YAAgE;IAEhE,MAAM,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC,GAAG,gBAAgB,CAAC;IACxD,MAAM,OAAO,GAAG,cAAc,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;IAEjD,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;YAC7B,OAAO,OAAO,CAAC,UAAU,CAAC,kBAAkB,EAAE,gCAAgC,CAAC,CAAC,CAAC;QACnF,CAAC;QACD,IAAI,UAAU,KAAK,QAAQ,EAAE,CAAC;YAC5B,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,EAAE,IAAI,GAAG,CAAC,CAAC,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC;YAC5F,IAAI,CAAC,MAAM,CAAC,EAAE;gBAAE,OAAO,MAAM,CAAC;YAC9B,MAAM,OAAO,GAAyB;gBACpC,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM;gBAC3B,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,QAAQ;gBAC/B,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI;gBACvB,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,KAAK;aAC1B,CAAC;YACF,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,aAAa,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC1E,OAAO,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;QAChE,CAAC;QACD,IAAI,UAAU,KAAK,IAAI,IAAI,UAAU,KAAK,MAAM,IAAI,UAAU,KAAK,OAAO,EAAE,CAAC;YAC3E,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;gBACxB,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;gBAC/B,IAAI,CAAC,MAAM,CAAC,EAAE;oBAAE,OAAO,MAAM,CAAC;gBAC9B,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC;gBAC3E,IAAI,CAAC,QAAQ,CAAC,EAAE;oBAAE,OAAO,QAAQ,CAAC;gBAClC,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,UAAU,EAAE,QAAQ,CAAC,KAAK,CAAC,UAAU,EAAE,EAAE,CAAC;YACxE,CAAC;YAED,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,EAAE,IAAI,GAAG,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;YAC1D,IAAI,CAAC,MAAM,CAAC,EAAE;gBAAE,OAAO,MAAM,CAAC;YAC9B,IAAI,UAAU,KAAK,OAAO,EAAE,CAAC;gBAC3B,MAAM,YAAY,GAAwB,EAAE,UAAU,EAAE,MAAM,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC;gBAClF,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,YAAY,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;gBAChF,OAAO,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC;YACtE,CAAC;YACD,MAAM,QAAQ,GAAG,MAAM,sBAAsB,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;YACtG,OAAO,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC;QACtE,CAAC;QACD,IAAI,UAAU,KAAK,MAAM,EAAE,CAAC;YAC1B,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;YAC/B,IAAI,CAAC,MAAM,CAAC,EAAE;gBAAE,OAAO,MAAM,CAAC;YAC9B,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;YAChE,OAAO,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;QAChE,CAAC;QACD,OAAO,OAAO,CAAC,IAAI,WAAW,CAAC,iBAAiB,EAAE,+BAA+B,UAAU,GAAG,EAAE,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC;IACnH,CAAC;IAED,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC,UAAU,EAAE,GAAG,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAsB,EAAE,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC;QACjH,IAAI,CAAC,MAAM,CAAC,EAAE;YAAE,OAAO,MAAM,CAAC;QAC9B,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC1D,OAAO,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;IAChE,CAAC;IAED,IAAI,OAAO,KAAK,OAAO,EAAE,CAAC;QACxB,MAAM,MAAM,GAAG,YAAY,CACzB,CAAC,UAAU,EAAE,GAAG,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAsB,EAAE,CAAC,QAAQ,KAAK,SAAS,CAAC,EACtF,IAAI,GAAG,CAAC,CAAC,WAAW,CAAC,CAAC,CACvB,CAAC;QACF,IAAI,CAAC,MAAM,CAAC,EAAE;YAAE,OAAO,MAAM,CAAC;QAC9B,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,UAAU,EAAE,MAAM,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC;QAClG,IAAI,CAAC,MAAM,CAAC,EAAE;YAAE,OAAO,MAAM,CAAC;QAC9B,IAAI,MAAM,CAAC,KAAK,CAAC,OAAO;YAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,KAA8B,EAAE,CAAC;QAE5F,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC;QACxF,OAAO,OAAO,CACZ,IAAI,WAAW,CAAC,IAAI,EAAE,sCAAsC,IAAI,GAAG,EAAE;YACnE,OAAO,EAAE,KAAK;YACd,UAAU,EAAE,MAAM,CAAC,KAAK,CAAC,UAAU;YACnC,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,QAAQ;YAC/B,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM;YAC3B,UAAU,EAAE,MAAM,CAAC,KAAK,CAAC,UAAU;YACnC,gBAAgB,EAAE,MAAM,CAAC,KAAK,CAAC,gBAAgB;YAC/C,oBAAoB,EAAE,MAAM,CAAC,KAAK,CAAC,oBAAoB;YACvD,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,KAAK;YACzB,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,OAAO;SAC9B,CAAC,CACH,CAAC;IACJ,CAAC;IAED,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QACrB,MAAM,MAAM,GAAG,YAAY,CACzB,CAAC,UAAU,EAAE,GAAG,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAsB,EAAE,CAAC,QAAQ,KAAK,SAAS,CAAC,EACtF,IAAI,GAAG,CAAC,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC,CAClC,CAAC;QACF,IAAI,CAAC,MAAM,CAAC,EAAE;YAAE,OAAO,MAAM,CAAC;QAC9B,MAAM,OAAO,GAA0B,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACrE,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,cAAc,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC3E,OAAO,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;IAChE,CAAC;IAED,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC,UAAU,EAAE,GAAG,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAsB,EAAE,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC;QACjH,IAAI,CAAC,MAAM,CAAC,EAAE;YAAE,OAAO,MAAM,CAAC;QAC9B,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;QACjD,IAAI,CAAC,MAAM,CAAC,EAAE;YAAE,OAAO,MAAM,CAAC;QAC9B,IAAI,MAAM,CAAC,KAAK,CAAC,EAAE;YAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,KAAmB,EAAE,CAAC;QAC5E,OAAO,OAAO,CACZ,IAAI,WAAW,CAAC,eAAe,EAAE,2CAA2C,EAAE;YAC5E,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM;YAC3B,UAAU,EAAE,MAAM,CAAC,KAAK,CAAC,UAAU;SACpC,CAAC,CACH,CAAC;IACJ,CAAC;IAED,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,OAAO,CAAC,IAAI,WAAW,CAAC,YAAY,EAAE,wBAAwB,CAAC,CAAC,CAAC;IACnG,OAAO,OAAO,CAAC,IAAI,WAAW,CAAC,iBAAiB,EAAE,oBAAoB,OAAO,GAAG,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;AAClG,CAAC;AAED,SAAS,WAAW,CAAC,gBAA0B;IAC7C,IAAI,gBAAgB,CAAC,CAAC,CAAC,KAAK,SAAS;QAAE,OAAO,gBAAgB,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACrF,OAAO,gBAAgB,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC;AACtC,CAAC;AAED,SAAS,WAAW,CAAC,IAAa,EAAE,OAAe,EAAE,KAAkB,EAAE,EAAS;IAChF,EAAE,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,aAAa,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;IAC/E,OAAO,KAAK,CAAC,QAAQ,CAAC;AACxB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,IAAc,EAAE,eAAgC,EAAE;IAC7E,MAAM,EAAE,GAAG,YAAY,CAAC,EAAE,IAAI,YAAY,EAAE,CAAC;IAC7C,MAAM,IAAI,GAAY,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;IACjE,MAAM,MAAM,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC;IAC1C,IAAI,CAAC,MAAM,CAAC,EAAE;QAAE,OAAO,WAAW,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAElE,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;QACtB,IAAI,IAAI,KAAK,MAAM;YAAE,EAAE,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;;YAClE,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAC1B,OAAO,UAAU,CAAC,OAAO,CAAC;IAC5B,CAAC;IACD,IAAI,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;QACzB,MAAM,OAAO,GAAe,EAAE,OAAO,EAAE,YAAY,CAAC,OAAO,IAAI,OAAO,EAAE,CAAC;QACzE,IAAI,IAAI,KAAK,MAAM;YAAE,EAAE,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC;;YACnE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;QACxC,OAAO,UAAU,CAAC,OAAO,CAAC;IAC5B,CAAC;IACD,IAAI,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/C,IAAI,IAAI,KAAK,MAAM;YAAE,OAAO,WAAW,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,WAAW,CAAC,YAAY,EAAE,wBAAwB,CAAC,EAAE,EAAE,CAAC,CAAC;QAClH,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QACrB,OAAO,UAAU,CAAC,KAAK,CAAC;IAC1B,CAAC;IAED,MAAM,OAAO,GAAG,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;IAC3D,MAAM,GAAG,GAAG,YAAY,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IAC9C,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,YAAY,CAAC,OAAO,IAAI,yBAAyB,EAAE,CAAC;QACpE,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,MAAM,CAAC,KAAK,CAAC,gBAAgB,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC;QACrF,IAAI,CAAC,MAAM,CAAC,EAAE;YAAE,OAAO,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACpE,EAAE,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QACtD,OAAO,UAAU,CAAC,OAAO,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,WAAW,CAAC,gBAAgB,EAAE,wCAAwC,CAAC,EAAE,EAAE,CAAC,CAAC;IACrH,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type DomainResult, type ErrorCode, type JsonObject } from "./errors.js";
|
|
2
|
+
export type DoctorCheckStatus = "ok" | "warning" | "error" | "not_configured" | "not_applicable";
|
|
3
|
+
export type DoctorCheck = {
|
|
4
|
+
name: "git" | "repository" | "registry" | "runtime";
|
|
5
|
+
status: DoctorCheckStatus;
|
|
6
|
+
code: ErrorCode | null;
|
|
7
|
+
message: string;
|
|
8
|
+
details: JsonObject;
|
|
9
|
+
};
|
|
10
|
+
export type RepositoryInfo = {
|
|
11
|
+
top_level: string;
|
|
12
|
+
common_dir: string;
|
|
13
|
+
registry_path: string;
|
|
14
|
+
};
|
|
15
|
+
export type DoctorReport = {
|
|
16
|
+
ok: boolean;
|
|
17
|
+
checks: DoctorCheck[];
|
|
18
|
+
repository: RepositoryInfo | null;
|
|
19
|
+
};
|
|
20
|
+
export declare function runDoctor(cwd?: string): Promise<DomainResult<DoctorReport>>;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
import { SessionRegistry } from "../session-registry.js";
|
|
6
|
+
import { isSessionRegistryError } from "../errors.js";
|
|
7
|
+
import { success } from "./errors.js";
|
|
8
|
+
function runGit(args, cwd) {
|
|
9
|
+
return new Promise((resolve) => {
|
|
10
|
+
const child = spawn("git", args, {
|
|
11
|
+
cwd,
|
|
12
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
13
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: "0", GIT_OPTIONAL_LOCKS: "0" },
|
|
14
|
+
});
|
|
15
|
+
let stdout = "";
|
|
16
|
+
let stderr = "";
|
|
17
|
+
let settled = false;
|
|
18
|
+
const timer = setTimeout(() => {
|
|
19
|
+
if (settled)
|
|
20
|
+
return;
|
|
21
|
+
settled = true;
|
|
22
|
+
child.kill("SIGKILL");
|
|
23
|
+
resolve({ exit_code: null, stdout, stderr, error: "git command timed out" });
|
|
24
|
+
}, 10_000);
|
|
25
|
+
child.stdout.setEncoding("utf8");
|
|
26
|
+
child.stderr.setEncoding("utf8");
|
|
27
|
+
child.stdout.on("data", (chunk) => {
|
|
28
|
+
stdout += chunk;
|
|
29
|
+
});
|
|
30
|
+
child.stderr.on("data", (chunk) => {
|
|
31
|
+
stderr += chunk;
|
|
32
|
+
});
|
|
33
|
+
child.on("error", (error) => {
|
|
34
|
+
if (settled)
|
|
35
|
+
return;
|
|
36
|
+
settled = true;
|
|
37
|
+
clearTimeout(timer);
|
|
38
|
+
resolve({ exit_code: null, stdout, stderr, error: error.message });
|
|
39
|
+
});
|
|
40
|
+
child.on("close", (exitCode) => {
|
|
41
|
+
if (settled)
|
|
42
|
+
return;
|
|
43
|
+
settled = true;
|
|
44
|
+
clearTimeout(timer);
|
|
45
|
+
resolve({ exit_code: exitCode, stdout, stderr, error: null });
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
function supportsRuntime(version) {
|
|
50
|
+
const [majorText, minorText] = version.split(".");
|
|
51
|
+
const major = Number(majorText);
|
|
52
|
+
const minor = Number(minorText);
|
|
53
|
+
return Number.isFinite(major) && Number.isFinite(minor) && (major > 22 || (major === 22 && minor >= 13));
|
|
54
|
+
}
|
|
55
|
+
function check(name, status, code, message, details = {}) {
|
|
56
|
+
return { name, status, code, message, details };
|
|
57
|
+
}
|
|
58
|
+
function parseRepository(stdout, cwd) {
|
|
59
|
+
const lines = stdout
|
|
60
|
+
.trim()
|
|
61
|
+
.split(/\r?\n/u)
|
|
62
|
+
.map((line) => line.trim());
|
|
63
|
+
if (lines.length < 2 || lines[0] === "" || lines[1] === "")
|
|
64
|
+
return null;
|
|
65
|
+
const topLevel = path.resolve(cwd, lines[0]);
|
|
66
|
+
const commonDir = path.resolve(cwd, lines[1]);
|
|
67
|
+
return {
|
|
68
|
+
top_level: topLevel,
|
|
69
|
+
common_dir: commonDir,
|
|
70
|
+
registry_path: path.join(commonDir, "nawabari", "session-registry.json"),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
async function inspectRegistry(repository) {
|
|
74
|
+
try {
|
|
75
|
+
const contents = await readFile(repository.registry_path, "utf8");
|
|
76
|
+
let parsed;
|
|
77
|
+
try {
|
|
78
|
+
parsed = JSON.parse(contents);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return check("registry", "error", "REGISTRY_CORRUPT", "The Nawabari registry is not valid JSON.", {
|
|
82
|
+
path: repository.registry_path,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
86
|
+
return check("registry", "error", "INVALID_REGISTRY", "The Nawabari registry must contain a JSON object.", {
|
|
87
|
+
path: repository.registry_path,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
const registry = new SessionRegistry({ cwd: repository.top_level });
|
|
92
|
+
const sessions = registry.list();
|
|
93
|
+
return check("registry", "ok", null, "The Nawabari registry is readable and valid.", {
|
|
94
|
+
path: repository.registry_path,
|
|
95
|
+
bytes: contents.length,
|
|
96
|
+
sessions: sessions.length,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
const code = isSessionRegistryError(error) && error.code === "UNSUPPORTED_SCHEMA_VERSION"
|
|
101
|
+
? "REGISTRY_CORRUPT"
|
|
102
|
+
: isSessionRegistryError(error) && error.code === "REGISTRY_REPOSITORY_MISMATCH"
|
|
103
|
+
? "INVALID_REGISTRY"
|
|
104
|
+
: "REGISTRY_UNREADABLE";
|
|
105
|
+
return check("registry", "error", code, "The Nawabari registry failed authoritative validation.", {
|
|
106
|
+
path: repository.registry_path,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
if (isFileNotFound(error)) {
|
|
112
|
+
return check("registry", "not_configured", null, "The Nawabari session registry is not initialized.", {
|
|
113
|
+
path: repository.registry_path,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
return check("registry", "error", "REGISTRY_UNREADABLE", "The Nawabari registry cannot be read.", {
|
|
117
|
+
path: repository.registry_path,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function isFileNotFound(error) {
|
|
122
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
123
|
+
}
|
|
124
|
+
export async function runDoctor(cwd = process.cwd()) {
|
|
125
|
+
const checks = [];
|
|
126
|
+
const runtimeOk = supportsRuntime(process.versions.node);
|
|
127
|
+
checks.push(runtimeOk
|
|
128
|
+
? check("runtime", "ok", null, "The Nawabari runtime meets the supported Node.js version.", {
|
|
129
|
+
node: process.versions.node,
|
|
130
|
+
})
|
|
131
|
+
: check("runtime", "error", "UNSUPPORTED_RUNTIME", "The Node.js runtime is below the supported version.", {
|
|
132
|
+
node: process.versions.node,
|
|
133
|
+
}));
|
|
134
|
+
const gitVersion = await runGit(["--version"], cwd);
|
|
135
|
+
if (gitVersion.exit_code !== 0) {
|
|
136
|
+
checks.push(check("git", "error", "GIT_UNAVAILABLE", "Git is not available to the local CLI.", {
|
|
137
|
+
command: "git --version",
|
|
138
|
+
reason: gitVersion.error ?? gitVersion.stderr.trim(),
|
|
139
|
+
}));
|
|
140
|
+
checks.push(check("repository", "not_applicable", null, "Repository resolution was skipped because Git is unavailable."));
|
|
141
|
+
checks.push(check("registry", "not_applicable", null, "Registry inspection was skipped because Git is unavailable."));
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
checks.push(check("git", "ok", null, "Git is available.", { version: gitVersion.stdout.trim() }));
|
|
145
|
+
const repositoryResult = await runGit(["rev-parse", "--show-toplevel", "--git-common-dir"], cwd);
|
|
146
|
+
const repository = repositoryResult.exit_code === 0 ? parseRepository(repositoryResult.stdout, cwd) : null;
|
|
147
|
+
if (repository === null) {
|
|
148
|
+
checks.push(check("repository", "error", "NOT_GIT_REPOSITORY", "The current directory is not a Git repository.", {
|
|
149
|
+
command: "git rev-parse --show-toplevel --git-common-dir",
|
|
150
|
+
reason: repositoryResult.stderr.trim(),
|
|
151
|
+
}));
|
|
152
|
+
checks.push(check("registry", "not_applicable", null, "Registry inspection was skipped outside a Git repository."));
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
checks.push(check("repository", "ok", null, "Repository and common Git directory resolved.", {
|
|
156
|
+
top_level: repository.top_level,
|
|
157
|
+
common_dir: repository.common_dir,
|
|
158
|
+
}));
|
|
159
|
+
checks.push(await inspectRegistry(repository));
|
|
160
|
+
}
|
|
161
|
+
const hasError = checks.some((item) => item.status === "error");
|
|
162
|
+
return success({ ok: !hasError, checks, repository });
|
|
163
|
+
}
|
|
164
|
+
const hasError = checks.some((item) => item.status === "error");
|
|
165
|
+
return success({ ok: !hasError, checks, repository: null });
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=doctor.js.map
|