@ours.network/cli 0.1.0 → 0.2.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/README.md +49 -6
- package/dist/args.d.ts +1 -1
- package/dist/args.js +1 -1
- package/dist/{chunk-AXXFFER2.js → chunk-6NFATG6P.js} +2 -2
- package/dist/chunk-HNBWRI4O.js +399 -0
- package/dist/chunk-QRNZFPNJ.js +379 -0
- package/dist/chunk-YSHMCQ2B.js +174 -0
- package/dist/commands.d.ts +178 -0
- package/dist/commands.js +14 -0
- package/dist/help.d.ts +4 -0
- package/dist/help.js +15 -0
- package/dist/main.js +72 -97
- package/dist/operations.d.ts +203 -3
- package/dist/operations.js +8 -2
- package/package.json +1 -1
- package/dist/chunk-IUZOQT4D.js +0 -152
package/README.md
CHANGED
|
@@ -10,13 +10,45 @@ separate package above `@ours.network/sdk`: lifecycle commands call
|
|
|
10
10
|
```bash
|
|
11
11
|
npm install --global @ours.network/cli
|
|
12
12
|
ours version
|
|
13
|
+
ours help
|
|
13
14
|
ours config show --json
|
|
14
15
|
ours daemon status --json
|
|
15
16
|
```
|
|
16
17
|
|
|
17
|
-
Node 20 or newer is required. `--json` emits one JSON value on stdout
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
Node 20 or newer is required. `--json` emits one JSON value on stdout, except
|
|
19
|
+
`daemon watch`, which streams one JSON event per line (JSONL); errors are JSON
|
|
20
|
+
on stderr. Status exits `0` while running and `3` while stopped. Usage errors
|
|
21
|
+
exit `2`; other failures exit `1`.
|
|
22
|
+
|
|
23
|
+
For a new host, configure safe daemon settings, start the shared daemon, create
|
|
24
|
+
the Human identity, and then add agent roles:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
ours config setup --port 3070 --state-dir "$HOME/.ours/state"
|
|
28
|
+
ours daemon start
|
|
29
|
+
ours daemon status --json
|
|
30
|
+
ours identity create-root --name Alice@laptop
|
|
31
|
+
ours identity create --name BuildBot --bio "Build coordinator"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Help and command discovery
|
|
35
|
+
|
|
36
|
+
The CLI help is the complete operator reference. A bare command group shows its
|
|
37
|
+
subcommands, and both help forms reach the same command-specific page without
|
|
38
|
+
connecting to a daemon or performing the requested action:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
ours identity
|
|
42
|
+
ours identity create --help
|
|
43
|
+
ours help identity create
|
|
44
|
+
ours help api send-file
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Specific help lists every accepted flag, required fields, defaults, enum and
|
|
48
|
+
XOR rules, side effects, safety confirmation, and a credential-free example.
|
|
49
|
+
Parser-compatible flags that an action does not consume are labeled
|
|
50
|
+
`compatibility-only/ignored`; they are not setup knobs. Unknown commands and
|
|
51
|
+
malformed options point to the narrowest relevant help page.
|
|
20
52
|
|
|
21
53
|
## Daemon lifecycle
|
|
22
54
|
|
|
@@ -32,6 +64,9 @@ ours daemon restart
|
|
|
32
64
|
CLI-owned PID record, the PID reported by the selected daemon, and its expected
|
|
33
65
|
state directory/port. A daemon started by ours-mcp, a container, systemd, or any
|
|
34
66
|
other launcher remains fully attachable, but these commands refuse to signal it.
|
|
67
|
+
`start`, `serve`, and `restart` reject `--endpoint` because they must launch a
|
|
68
|
+
locally selected daemon; this rejection happens before `restart` probes or stops
|
|
69
|
+
anything.
|
|
35
70
|
|
|
36
71
|
Selection is coherent and credential-safe. Use a single config file or select
|
|
37
72
|
both the endpoint and its state directory:
|
|
@@ -66,9 +101,11 @@ secret mechanism or the existing SDK configuration, not CLI arguments.
|
|
|
66
101
|
|
|
67
102
|
## Operator commands
|
|
68
103
|
|
|
69
|
-
|
|
70
|
-
`--state-dir`; identity-scoped commands also accept `--identity
|
|
71
|
-
binds that identity without force for the current process.
|
|
104
|
+
Grouped operation commands accept the selection flags `--config`, `--endpoint`,
|
|
105
|
+
`--port`, and `--state-dir`; identity-scoped commands also accept `--identity
|
|
106
|
+
NAME`, which binds that identity without force for the current process. Run the
|
|
107
|
+
specific command help for lifecycle/config applicability and compatibility-only
|
|
108
|
+
flags.
|
|
72
109
|
|
|
73
110
|
```text
|
|
74
111
|
identity create | create-root | create-temporary | close-temporary
|
|
@@ -86,11 +123,17 @@ Input fields become kebab-case flags. For example:
|
|
|
86
123
|
|
|
87
124
|
```bash
|
|
88
125
|
ours identity create --name BuildBot --bio "Build coordinator"
|
|
126
|
+
ours profile set-bio --identity BuildBot --bio= # clear the bio
|
|
127
|
+
ours profile set-persona --identity BuildBot --persona= # clear the persona
|
|
89
128
|
ours message send --identity BuildBot --contact Peer --text "done" --json
|
|
90
129
|
ours file get --identity BuildBot --wire-ids ID1,ID2 --json
|
|
91
130
|
ours conversation policy --identity Human --keep-history true
|
|
92
131
|
```
|
|
93
132
|
|
|
133
|
+
An explicit file retrieval accepts 1–32 unique 64-hex wire IDs. `daemon watch`
|
|
134
|
+
first binds its `--identity` without force, changing the CLI lease; it can fail
|
|
135
|
+
with `BOUND_ELSEWHERE` when another live session holds that identity.
|
|
136
|
+
|
|
94
137
|
Malformed JSON, unknown input fields, invalid booleans/integers, and incomplete
|
|
95
138
|
file inputs fail closed before the request. Destructive identity/contact/invite
|
|
96
139
|
operations require `--yes`.
|
package/dist/args.d.ts
CHANGED
|
@@ -7,6 +7,6 @@ export interface ParsedFlags {
|
|
|
7
7
|
values: Record<string, string>;
|
|
8
8
|
booleans: Set<string>;
|
|
9
9
|
}
|
|
10
|
-
export declare function parseFlags(argv: string[], valueFlags: ReadonlySet<string>, booleanFlags: ReadonlySet<string>): ParsedFlags;
|
|
10
|
+
export declare function parseFlags(argv: string[], valueFlags: ReadonlySet<string>, booleanFlags: ReadonlySet<string>, emptyValueFlags?: ReadonlySet<string>): ParsedFlags;
|
|
11
11
|
export declare function parseInteger(value: string, flag: string, min?: number, max?: number): number;
|
|
12
12
|
export declare function parseBoolean(value: string, flag: string): boolean;
|
package/dist/args.js
CHANGED
|
@@ -6,7 +6,7 @@ var CliUsageError = class extends Error {
|
|
|
6
6
|
this.name = "CliUsageError";
|
|
7
7
|
}
|
|
8
8
|
};
|
|
9
|
-
function parseFlags(argv, valueFlags, booleanFlags) {
|
|
9
|
+
function parseFlags(argv, valueFlags, booleanFlags, emptyValueFlags = /* @__PURE__ */ new Set()) {
|
|
10
10
|
const positionals = [];
|
|
11
11
|
const values = {};
|
|
12
12
|
const booleans = /* @__PURE__ */ new Set();
|
|
@@ -35,7 +35,7 @@ function parseFlags(argv, valueFlags, booleanFlags) {
|
|
|
35
35
|
}
|
|
36
36
|
if (!valueFlags.has(name)) throw new CliUsageError(`unknown option: ${name}`);
|
|
37
37
|
const value = equal >= 0 ? token.slice(equal + 1) : argv[++i];
|
|
38
|
-
if (value === void 0 || value === "") throw new CliUsageError(`${name} requires a value`);
|
|
38
|
+
if (value === void 0 || value === "" && !emptyValueFlags.has(name)) throw new CliUsageError(`${name} requires a value`);
|
|
39
39
|
if (Object.prototype.hasOwnProperty.call(values, name)) throw new CliUsageError(`${name} may be given only once`);
|
|
40
40
|
values[name] = value;
|
|
41
41
|
}
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
import {
|
|
2
|
+
fieldFlag,
|
|
3
|
+
isOperationName,
|
|
4
|
+
operationNames,
|
|
5
|
+
operationSpec
|
|
6
|
+
} from "./chunk-QRNZFPNJ.js";
|
|
7
|
+
import {
|
|
8
|
+
CliUsageError
|
|
9
|
+
} from "./chunk-6NFATG6P.js";
|
|
10
|
+
import {
|
|
11
|
+
COMMAND_GROUPS,
|
|
12
|
+
DESTRUCTIVE_OPERATIONS,
|
|
13
|
+
IDENTITY_PREBIND_EXCEPTIONS,
|
|
14
|
+
commandSpec,
|
|
15
|
+
isCommandGroup
|
|
16
|
+
} from "./chunk-YSHMCQ2B.js";
|
|
17
|
+
|
|
18
|
+
// src/help.ts
|
|
19
|
+
var pad = (value, width = 32) => value.length >= width ? `${value} ` : value.padEnd(width);
|
|
20
|
+
var option = (syntax, description) => ` ${pad(syntax)}${description}`;
|
|
21
|
+
var heading = (name, body) => ["", `${name}:`, ...body];
|
|
22
|
+
function operationSummary(spec) {
|
|
23
|
+
return spec.kind === "operation" ? operationSpec(spec.operation).summary : spec.summary;
|
|
24
|
+
}
|
|
25
|
+
function shellQuote(value) {
|
|
26
|
+
if (/^[A-Za-z0-9_./:@+-]+$/.test(value)) return value;
|
|
27
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
28
|
+
}
|
|
29
|
+
function displayDefault(value) {
|
|
30
|
+
if (value === "") return "empty string";
|
|
31
|
+
return JSON.stringify(value);
|
|
32
|
+
}
|
|
33
|
+
function fieldType(field, context) {
|
|
34
|
+
if (field.values) return `Accepted values: ${field.values.join(", ")}.`;
|
|
35
|
+
if (field.kind === "boolean") return context === "json" ? "Use a JSON boolean (true or false)." : "Use true or false (1 and 0 are also accepted).";
|
|
36
|
+
if (field.kind === "integer") return field.positive ? "Use an integer of 1 or greater." : context === "json" ? "Use a safe integer." : "Use a non-negative integer.";
|
|
37
|
+
if (field.kind === "integer[]") return "Command flags use comma-separated integers; API JSON uses an integer array.";
|
|
38
|
+
if (field.kind === "string[]") return "Command flags use comma-separated values; API JSON uses a string array.";
|
|
39
|
+
return void 0;
|
|
40
|
+
}
|
|
41
|
+
function fieldDescription(field, name, defaults, context) {
|
|
42
|
+
const details = [field.help];
|
|
43
|
+
if (Object.prototype.hasOwnProperty.call(defaults, name)) details.push(`Default: ${displayDefault(defaults[name])}.`);
|
|
44
|
+
else if (field.required) details.push("Required.");
|
|
45
|
+
else details.push("Optional.");
|
|
46
|
+
const type = fieldType(field, context);
|
|
47
|
+
if (type) details.push(type);
|
|
48
|
+
return details.join(" ");
|
|
49
|
+
}
|
|
50
|
+
function operationFieldOptions(spec, defaults) {
|
|
51
|
+
return Object.entries(spec.fields).map(([name, field]) => option(`${fieldFlag(name)} ${field.valueName}`, fieldDescription(field, name, defaults, "flags")));
|
|
52
|
+
}
|
|
53
|
+
function apiFieldOptions(spec) {
|
|
54
|
+
const entries = Object.entries(spec.fields);
|
|
55
|
+
if (entries.length === 0) return [option("(none)", "Omit --input, or provide an empty JSON object.")];
|
|
56
|
+
return entries.map(([name, field]) => option(name, fieldDescription(field, name, {}, "json")));
|
|
57
|
+
}
|
|
58
|
+
function operationUsage(group, action, command) {
|
|
59
|
+
const spec = operationSpec(command.operation);
|
|
60
|
+
const fields = Object.entries(spec.fields).map(([name, field]) => {
|
|
61
|
+
const flag = `${fieldFlag(name)} ${field.valueName}`;
|
|
62
|
+
return field.required && !Object.prototype.hasOwnProperty.call(command.defaults ?? {}, name) ? flag : `[${flag}]`;
|
|
63
|
+
});
|
|
64
|
+
return [`ours ${group} ${action}`, ...fields, "[selection/output options]"].join(" ");
|
|
65
|
+
}
|
|
66
|
+
function cliExample(group, action, command) {
|
|
67
|
+
const spec = operationSpec(command.operation);
|
|
68
|
+
const args = [`ours ${group} ${action}`];
|
|
69
|
+
for (const [name, value] of Object.entries(spec.exampleInput ?? {})) {
|
|
70
|
+
if (Object.prototype.hasOwnProperty.call(command.defaults ?? {}, name) && command.defaults?.[name] === value) continue;
|
|
71
|
+
const rendered = Array.isArray(value) ? value.join(",") : String(value);
|
|
72
|
+
args.push(fieldFlag(name), shellQuote(rendered));
|
|
73
|
+
}
|
|
74
|
+
if (["profile", "invite", "contact", "message", "file", "conversation"].includes(group) && command.operation !== "list-local-contact-book") {
|
|
75
|
+
args.push("--identity", "BuildBot");
|
|
76
|
+
}
|
|
77
|
+
if (group === "identity" && ["show", "release"].includes(action)) args.push("--identity", "BuildBot");
|
|
78
|
+
if (DESTRUCTIVE_OPERATIONS.has(command.operation)) args.push("--yes");
|
|
79
|
+
args.push("--json");
|
|
80
|
+
return args.join(" ");
|
|
81
|
+
}
|
|
82
|
+
function commonOperationOptions(operation, fieldFlags) {
|
|
83
|
+
const out = [
|
|
84
|
+
option("--endpoint URL", "Connect to this running daemon URL; pair it with the matching --state-dir."),
|
|
85
|
+
option("--port N", "Select daemon port 1\u201365535."),
|
|
86
|
+
option("--state-dir PATH", "Select the daemon state directory used for endpoint identity verification."),
|
|
87
|
+
option("--config PATH", "Read daemon selection and credentials from this config file.")
|
|
88
|
+
];
|
|
89
|
+
if (!fieldFlags.has("--identity")) {
|
|
90
|
+
out.push(option("--identity NAME", IDENTITY_PREBIND_EXCEPTIONS.has(operation) ? "Compatibility-only/ignored for this operation; use the operation\u2019s --name option instead." : "Bind this identity without force before invoking the operation."));
|
|
91
|
+
}
|
|
92
|
+
out.push(
|
|
93
|
+
option("--json", operation === "watch-notifications" ? "Compatibility-only: watch always streams JSON Lines (one event per line); errors remain on stderr as JSON." : "Write one JSON value to stdout; errors remain on stderr as JSON."),
|
|
94
|
+
option("--yes", DESTRUCTIVE_OPERATIONS.has(operation) ? "Required confirmation for this destructive operation." : "Compatibility-only/ignored for this non-destructive operation."),
|
|
95
|
+
option("--help", "Show this help and exit without connecting to a daemon.")
|
|
96
|
+
);
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
function nativeDaemonOptions(action) {
|
|
100
|
+
const lifecycle = /* @__PURE__ */ new Set(["serve", "start", "stop", "restart", "status"]);
|
|
101
|
+
const service = /* @__PURE__ */ new Set(["install-service", "uninstall-service"]);
|
|
102
|
+
const selected = lifecycle.has(action);
|
|
103
|
+
const install = action === "install-service";
|
|
104
|
+
const rows = [
|
|
105
|
+
["--endpoint URL", selected ? action === "serve" || action === "start" || action === "restart" ? "Rejected: this action must start a local selected daemon." : "Select a running endpoint; pair it with --state-dir." : "Compatibility-only/ignored for service management."],
|
|
106
|
+
["--port N", selected ? "Select daemon port 1\u201365535." : "Compatibility-only/ignored for service management."],
|
|
107
|
+
["--state-dir PATH", selected ? "Select the daemon state directory." : "Compatibility-only/ignored for service management."],
|
|
108
|
+
["--config PATH", selected ? "Use this daemon config for lifecycle selection." : action === "install-service" ? "Embed this config file\u2019s absolute path in the installed unit." : "Compatibility-only/ignored by uninstall-service."],
|
|
109
|
+
["--identity NAME", "Compatibility-only/ignored by daemon lifecycle and service commands."],
|
|
110
|
+
["--json", action === "serve" ? "Compatibility-only/ignored because serve runs until interrupted." : "Write one JSON result to stdout."],
|
|
111
|
+
["--yes", service.has(action) ? "Confirm the service-manager change; not needed with --dry-run." : "Compatibility-only/ignored for this action."],
|
|
112
|
+
["--managed", action === "serve" ? "Write the CLI-owned PID record after the foreground daemon starts." : "Compatibility-only/ignored for this action."],
|
|
113
|
+
["--dry-run", service.has(action) ? "Return the planned service-manager changes without writing or running systemctl." : "Compatibility-only/ignored for this action."],
|
|
114
|
+
["--force", install ? "Allow replacement of an existing user unit not marked as CLI-managed." : "Compatibility-only/ignored for this action."],
|
|
115
|
+
["--help", "Show this help and exit without starting, stopping, probing, or installing anything."]
|
|
116
|
+
];
|
|
117
|
+
return rows.map(([syntax, description]) => option(syntax, description));
|
|
118
|
+
}
|
|
119
|
+
function nativeConfigOptions(action) {
|
|
120
|
+
const show = action === "show";
|
|
121
|
+
const rows = [
|
|
122
|
+
["--config PATH", show ? "Read and report this config file." : "Write this config file (default: $OURS_CONFIG or ~/.ours/config.json)."],
|
|
123
|
+
["--endpoint URL", show ? "Resolve and report this endpoint selection." : "Compatibility-only/ignored by config setup."],
|
|
124
|
+
["--port N", show ? "Resolve and report port 1\u201365535." : "Set daemon port 1\u201365535."],
|
|
125
|
+
["--state-dir PATH", show ? "Resolve and report this state directory." : "Set the absolute daemon state directory."],
|
|
126
|
+
["--identity NAME", "Compatibility-only/ignored by config commands."],
|
|
127
|
+
["--broker-url URL", show ? "Compatibility-only/ignored by config show." : "Set the broker WebSocket URL."],
|
|
128
|
+
["--gc-interval-ms N", show ? "Compatibility-only/ignored by config show." : "Set a positive garbage-collection interval in milliseconds."],
|
|
129
|
+
["--auto-start true|false", show ? "Compatibility-only/ignored by config show." : "Set automatic daemon startup (1/0 are also accepted)."],
|
|
130
|
+
["--api-visibility MODE", show ? "Compatibility-only/ignored by config show." : "Set owner, shared, or open API visibility."],
|
|
131
|
+
["--dry-run", show ? "Compatibility-only/ignored by config show." : "Return the merged redacted result without writing the file."],
|
|
132
|
+
["--json", "Write one JSON value to stdout."],
|
|
133
|
+
["--help", "Show this help and exit without reading or writing configuration."]
|
|
134
|
+
];
|
|
135
|
+
return rows.map(([syntax, description]) => option(syntax, description));
|
|
136
|
+
}
|
|
137
|
+
function renderNativeHelp(version, group, action, spec) {
|
|
138
|
+
const options = spec.kind === "daemon" ? nativeDaemonOptions(action) : nativeConfigOptions(action);
|
|
139
|
+
const usage = spec.kind === "daemon" ? `ours daemon ${action} [options]` : action === "setup" ? "ours config setup SETTING... [--dry-run] [--json]" : "ours config show [selection options] [--json]";
|
|
140
|
+
return [
|
|
141
|
+
`ours ${version} \u2014 ${group} ${action}`,
|
|
142
|
+
"",
|
|
143
|
+
spec.summary,
|
|
144
|
+
"",
|
|
145
|
+
`Usage: ${usage}`,
|
|
146
|
+
...heading("Options", options),
|
|
147
|
+
...heading("Behavior", [` ${spec.effects}`]),
|
|
148
|
+
...heading("Example", [` ${spec.example}`]),
|
|
149
|
+
"",
|
|
150
|
+
`More help: ours help ${group}`,
|
|
151
|
+
""
|
|
152
|
+
].join("\n");
|
|
153
|
+
}
|
|
154
|
+
function renderOperationHelp(version, group, action, command) {
|
|
155
|
+
const spec = operationSpec(command.operation);
|
|
156
|
+
const defaults = command.defaults ?? {};
|
|
157
|
+
const fields = operationFieldOptions(spec, defaults);
|
|
158
|
+
const fieldFlags = new Set(Object.keys(spec.fields).map(fieldFlag));
|
|
159
|
+
const options = [...fields, ...commonOperationOptions(command.operation, fieldFlags)];
|
|
160
|
+
const behavior = [` ${spec.effects}`];
|
|
161
|
+
if (group === "daemon" && command.operation === "watch-notifications") {
|
|
162
|
+
behavior.push(" Before streaming, --identity attempts a non-force bind that changes this CLI lease; it fails with BOUND_ELSEWHERE when another live session holds the identity.");
|
|
163
|
+
}
|
|
164
|
+
return [
|
|
165
|
+
`ours ${version} \u2014 ${group} ${action}`,
|
|
166
|
+
"",
|
|
167
|
+
spec.summary,
|
|
168
|
+
"",
|
|
169
|
+
`Usage: ${operationUsage(group, action, command)}`,
|
|
170
|
+
"",
|
|
171
|
+
"Positional arguments: none after the command name.",
|
|
172
|
+
...heading("Options", options),
|
|
173
|
+
...heading("Behavior", behavior),
|
|
174
|
+
...heading("Example", [` ${cliExample(group, action, command)}`]),
|
|
175
|
+
"",
|
|
176
|
+
`Expert equivalent: ours help api ${command.operation}`,
|
|
177
|
+
`More help: ours help ${group}`,
|
|
178
|
+
""
|
|
179
|
+
].join("\n");
|
|
180
|
+
}
|
|
181
|
+
function renderGroupHelp(version, group) {
|
|
182
|
+
const spec = COMMAND_GROUPS[group];
|
|
183
|
+
const commands = Object.entries(spec.commands).map(([name, command]) => option(name, operationSummary(command)));
|
|
184
|
+
const shared = group === "config" ? [
|
|
185
|
+
"Use `ours config <command> --help` or `ours help config <command>` for exact settings and compatibility-only flags."
|
|
186
|
+
] : group === "daemon" ? [
|
|
187
|
+
"Use `ours daemon <command> --help` or `ours help daemon <command>` for exact lifecycle, service, selection, and compatibility-only flags."
|
|
188
|
+
] : [
|
|
189
|
+
"Operation commands accept --endpoint, --port, --state-dir, --config, --identity, --json, --yes, and --help.",
|
|
190
|
+
"--yes is meaningful only where the specific help marks an operation destructive; otherwise it is compatibility-only/ignored."
|
|
191
|
+
];
|
|
192
|
+
return [
|
|
193
|
+
`ours ${version} \u2014 ${group} commands`,
|
|
194
|
+
"",
|
|
195
|
+
spec.summary,
|
|
196
|
+
"",
|
|
197
|
+
`Usage: ours ${group} <command> [options]`,
|
|
198
|
+
` ours help ${group} <command>`,
|
|
199
|
+
...heading("Commands", commands),
|
|
200
|
+
...heading("Discovery", shared.map((line) => ` ${line}`)),
|
|
201
|
+
...heading("Example", [` ours help ${group} ${Object.keys(spec.commands)[0]}`]),
|
|
202
|
+
""
|
|
203
|
+
].join("\n");
|
|
204
|
+
}
|
|
205
|
+
function apiExample(operation, spec) {
|
|
206
|
+
const args = [`ours api ${operation}`];
|
|
207
|
+
if (Object.keys(spec.fields).length > 0) args.push("--input", shellQuote(JSON.stringify(spec.exampleInput ?? {})));
|
|
208
|
+
const noBindNeeded = /* @__PURE__ */ new Set([
|
|
209
|
+
"create-identity",
|
|
210
|
+
"create-temporary-identity",
|
|
211
|
+
"create-root-identity",
|
|
212
|
+
"define-local-identity-file",
|
|
213
|
+
"choose-identity",
|
|
214
|
+
"list-identities",
|
|
215
|
+
"remove-identity",
|
|
216
|
+
"version",
|
|
217
|
+
"state-dir",
|
|
218
|
+
"identities",
|
|
219
|
+
"unread",
|
|
220
|
+
"watch-notifications",
|
|
221
|
+
"list-local-contact-book"
|
|
222
|
+
]);
|
|
223
|
+
if (!noBindNeeded.has(operation)) args.push("--identity", "BuildBot");
|
|
224
|
+
if (DESTRUCTIVE_OPERATIONS.has(operation)) args.push("--yes");
|
|
225
|
+
args.push("--json");
|
|
226
|
+
return args.join(" ");
|
|
227
|
+
}
|
|
228
|
+
function renderApiOperationHelp(version, operation) {
|
|
229
|
+
const spec = operationSpec(operation);
|
|
230
|
+
const options = [
|
|
231
|
+
option("--input JSON", "Operation input as one JSON object."),
|
|
232
|
+
option("--input-file PATH", "Read the JSON object from PATH, or use - for stdin. Mutually exclusive with --input."),
|
|
233
|
+
...commonOperationOptions(operation, /* @__PURE__ */ new Set())
|
|
234
|
+
];
|
|
235
|
+
return [
|
|
236
|
+
`ours ${version} \u2014 api ${operation}`,
|
|
237
|
+
"",
|
|
238
|
+
spec.summary,
|
|
239
|
+
"",
|
|
240
|
+
`Usage: ours api ${operation} [--input JSON | --input-file PATH] [selection/output options]`,
|
|
241
|
+
"",
|
|
242
|
+
"Positional arguments: exactly one allowlisted operation name after `api`.",
|
|
243
|
+
...heading("JSON fields", apiFieldOptions(spec)),
|
|
244
|
+
...heading("Options", options),
|
|
245
|
+
...heading("Behavior", [` ${spec.effects}`, " Unknown JSON fields and invalid types fail before the operation is invoked."]),
|
|
246
|
+
...heading("Example", [` ${apiExample(operation, spec)}`]),
|
|
247
|
+
"",
|
|
248
|
+
"List all expert operations: ours api list",
|
|
249
|
+
""
|
|
250
|
+
].join("\n");
|
|
251
|
+
}
|
|
252
|
+
function renderApiListHelp(version) {
|
|
253
|
+
return [
|
|
254
|
+
`ours ${version} \u2014 api list`,
|
|
255
|
+
"",
|
|
256
|
+
"List every expert operation accepted by `ours api` in deterministic order.",
|
|
257
|
+
"",
|
|
258
|
+
"Usage: ours api list [--json] [--help]",
|
|
259
|
+
...heading("Options", [
|
|
260
|
+
option("--json", "Write the operation names as one JSON array instead of text."),
|
|
261
|
+
option("--help", "Show this help without connecting to a daemon.")
|
|
262
|
+
]),
|
|
263
|
+
...heading("Behavior", [" Read-only and local: this command does not connect to a daemon."]),
|
|
264
|
+
...heading("Examples", [" ours api list", " ours api list --json"]),
|
|
265
|
+
""
|
|
266
|
+
].join("\n");
|
|
267
|
+
}
|
|
268
|
+
function renderApiHelp(version) {
|
|
269
|
+
const operations = operationNames().map((name) => option(name, operationSpec(name).summary));
|
|
270
|
+
return [
|
|
271
|
+
`ours ${version} \u2014 expert API operations`,
|
|
272
|
+
"",
|
|
273
|
+
"Invoke the CLI\u2019s explicit OursClient allowlist with a typed JSON object. Grouped commands are easier for routine operator work.",
|
|
274
|
+
"",
|
|
275
|
+
"Usage: ours api list [--json]",
|
|
276
|
+
" ours api <operation> [--input JSON | --input-file PATH] [options]",
|
|
277
|
+
" ours help api <operation>",
|
|
278
|
+
...heading("Operations", operations),
|
|
279
|
+
...heading("Input", [
|
|
280
|
+
option("--input JSON", "Inline JSON object; quote it so the shell passes one argument."),
|
|
281
|
+
option("--input-file PATH", "Read JSON from a file, or use - for stdin. Mutually exclusive with --input."),
|
|
282
|
+
option("--json", "For `api list`, return the operation-name array as JSON."),
|
|
283
|
+
option("--help", "Show help without connecting to a daemon.")
|
|
284
|
+
]),
|
|
285
|
+
...heading("Examples", [
|
|
286
|
+
" ours api list --json",
|
|
287
|
+
" ours help api send-message"
|
|
288
|
+
]),
|
|
289
|
+
""
|
|
290
|
+
].join("\n");
|
|
291
|
+
}
|
|
292
|
+
function renderVersionHelp(version) {
|
|
293
|
+
return [
|
|
294
|
+
`ours ${version} \u2014 CLI version`,
|
|
295
|
+
"",
|
|
296
|
+
"Show the installed @ours.network/cli package version.",
|
|
297
|
+
"",
|
|
298
|
+
"Usage: ours version [--json] [--help]",
|
|
299
|
+
...heading("Options", [
|
|
300
|
+
option("--json", 'Write {"name":"@ours.network/cli","version":"\u2026"} to stdout.'),
|
|
301
|
+
option("--help", "Show this help.")
|
|
302
|
+
]),
|
|
303
|
+
...heading("Compatibility", [" Additional tokens are currently ignored; they are not setup knobs and should not be relied on."]),
|
|
304
|
+
...heading("Example", [" ours version --json"]),
|
|
305
|
+
""
|
|
306
|
+
].join("\n");
|
|
307
|
+
}
|
|
308
|
+
function renderTopHelp(version) {
|
|
309
|
+
const groups = Object.entries(COMMAND_GROUPS).map(([name, spec]) => option(name, spec.summary));
|
|
310
|
+
return [
|
|
311
|
+
`ours ${version} \u2014 operator CLI for the shared ours daemon`,
|
|
312
|
+
"",
|
|
313
|
+
"Create and manage identities, encrypted contacts/messages/files, and daemon lifecycle through the transport-neutral @ours.network/sdk client.",
|
|
314
|
+
"",
|
|
315
|
+
"Usage: ours <command> [options]",
|
|
316
|
+
" ours help [command [subcommand]]",
|
|
317
|
+
" ours <command> [subcommand] --help",
|
|
318
|
+
...heading("First-time setup", [
|
|
319
|
+
" 1. Install Node.js 20 or newer and `npm install --global @ours.network/cli`.",
|
|
320
|
+
" 2. Inspect redacted defaults with `ours config show --json`.",
|
|
321
|
+
' 3. Configure safe daemon settings, for example `ours config setup --port 3070 --state-dir "$HOME/.ours/state"`.',
|
|
322
|
+
" 4. Start and verify the shared daemon with `ours daemon start` and `ours daemon status --json`.",
|
|
323
|
+
" 5. Create the Human identity first: `ours identity create-root --name Alice@laptop`.",
|
|
324
|
+
' 6. Add agent roles with `ours identity create --name BuildBot --bio "Build coordinator"`.'
|
|
325
|
+
]),
|
|
326
|
+
...heading("Commands", [
|
|
327
|
+
...groups,
|
|
328
|
+
option("api", "Invoke the expert operation allowlist with typed JSON input."),
|
|
329
|
+
option("version", "Show the installed CLI version."),
|
|
330
|
+
option("help", "Show top-level, group, command, or API-operation help.")
|
|
331
|
+
]),
|
|
332
|
+
...heading("Connection and configuration", [
|
|
333
|
+
" Selection flags are --config PATH, or a coherent --endpoint URL / --port N / --state-dir PATH selection.",
|
|
334
|
+
" The default config is $OURS_CONFIG when set, otherwise ~/.ours/config.json when present.",
|
|
335
|
+
" Pair an explicit endpoint with its matching state directory; the CLI verifies /state-dir before sending a discovered API token.",
|
|
336
|
+
" There is intentionally no token flag. Keep credentials in operator-owned environment/config secret handling."
|
|
337
|
+
]),
|
|
338
|
+
...heading("Output and exit status", [
|
|
339
|
+
" Results go to stdout; diagnostics go to stderr. --json writes one JSON value and JSON-formatted errors, except watch streams JSON Lines.",
|
|
340
|
+
" Exit 0 means success, 2 means command/option usage error, and 1 means another failure.",
|
|
341
|
+
" `ours daemon status` uses exit 3 when the selected daemon is stopped.",
|
|
342
|
+
" Help is plain text, exits 0, and performs no daemon, file, or service-manager action."
|
|
343
|
+
]),
|
|
344
|
+
...heading("Examples", [
|
|
345
|
+
" ours help identity create",
|
|
346
|
+
' ours message send --identity BuildBot --contact Peer --text "Build finished." --json',
|
|
347
|
+
" ours file list --identity BuildBot --json",
|
|
348
|
+
" ours api list --json"
|
|
349
|
+
]),
|
|
350
|
+
"",
|
|
351
|
+
"Run `ours help <command>` to see its commands, then `ours help <command> <subcommand>` for every accepted option and side effect.",
|
|
352
|
+
""
|
|
353
|
+
].join("\n");
|
|
354
|
+
}
|
|
355
|
+
function helpRequestPath(argv) {
|
|
356
|
+
if (argv.length === 0) return [];
|
|
357
|
+
if (argv[0] === "help") return argv.slice(1).filter((value) => value !== "--help");
|
|
358
|
+
if (argv.includes("--help")) {
|
|
359
|
+
if (argv[0].startsWith("--")) return [];
|
|
360
|
+
const path = [argv[0]];
|
|
361
|
+
if ((isCommandGroup(argv[0]) || argv[0] === "api") && argv[1] !== void 0 && !argv[1].startsWith("--")) path.push(argv[1]);
|
|
362
|
+
return path;
|
|
363
|
+
}
|
|
364
|
+
if (argv.length === 1 && (isCommandGroup(argv[0]) || argv[0] === "api")) return [argv[0]];
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
function renderHelp(path, version) {
|
|
368
|
+
if (path.length === 0 || path.length === 1 && path[0] === "help") return renderTopHelp(version);
|
|
369
|
+
if (path.length === 1 && path[0] === "api") return renderApiHelp(version);
|
|
370
|
+
if (path.length === 1 && path[0] === "version") return renderVersionHelp(version);
|
|
371
|
+
if (path[0] === "api") {
|
|
372
|
+
if (path.length === 2 && path[1] === "list") return renderApiListHelp(version);
|
|
373
|
+
if (path.length !== 2 || !isOperationName(path[1])) {
|
|
374
|
+
throw new CliUsageError(`unknown API help topic ${JSON.stringify(path.slice(1).join(" "))}; run \`ours help api\``);
|
|
375
|
+
}
|
|
376
|
+
return renderApiOperationHelp(version, path[1]);
|
|
377
|
+
}
|
|
378
|
+
if (!isCommandGroup(path[0])) throw new CliUsageError(`unknown help topic ${JSON.stringify(path.join(" "))}; run \`ours help\``);
|
|
379
|
+
if (path.length === 1) return renderGroupHelp(version, path[0]);
|
|
380
|
+
if (path.length !== 2) throw new CliUsageError(`too many help topics; run \`ours help ${path[0]}\``);
|
|
381
|
+
const spec = commandSpec(path[0], path[1]);
|
|
382
|
+
if (!spec) throw new CliUsageError(`unknown ${path[0]} help topic ${JSON.stringify(path[1])}; run \`ours help ${path[0]}\``);
|
|
383
|
+
return spec.kind === "operation" ? renderOperationHelp(version, path[0], path[1], spec) : renderNativeHelp(version, path[0], path[1], spec);
|
|
384
|
+
}
|
|
385
|
+
function helpHint(group, action) {
|
|
386
|
+
if (group && isCommandGroup(group)) {
|
|
387
|
+
if (action && commandSpec(group, action)) return `Run \`ours help ${group} ${action}\` for usage.`;
|
|
388
|
+
return `Run \`ours help ${group}\` to list valid commands.`;
|
|
389
|
+
}
|
|
390
|
+
if (group === "api") return action === "list" ? "Run `ours help api list` for usage." : action && isOperationName(action) ? `Run \`ours help api ${action}\` for usage.` : "Run `ours help api` to list valid operations.";
|
|
391
|
+
return "Run `ours help` to list commands.";
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export {
|
|
395
|
+
renderTopHelp,
|
|
396
|
+
helpRequestPath,
|
|
397
|
+
renderHelp,
|
|
398
|
+
helpHint
|
|
399
|
+
};
|