@hediet/linkrpc-cli 0.0.3-next.20261022.3 → 0.0.3-next.20261022.5
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 +19 -0
- package/dist/chunks/{cli-DJREb0Dm.js → cli-B93twHKF.js} +76 -15
- package/dist/chunks/cli-B93twHKF.js.map +1 -0
- package/dist/chunks/{runApprovalUi-CdC5_Qoo.js → runApprovalUi-CNp2DhKG.js} +3 -3
- package/dist/chunks/{runApprovalUi-CdC5_Qoo.js.map → runApprovalUi-CNp2DhKG.js.map} +1 -1
- package/dist/chunks/{runUi-Bb6VATfB.js → runUi-B4rV3ZfF.js} +3 -3
- package/dist/chunks/{runUi-Bb6VATfB.js.map → runUi-B4rV3ZfF.js.map} +1 -1
- package/dist/chunks/{scroll-BSzq6cdj.js → scroll-DGHNxpYN.js} +2 -2
- package/dist/chunks/{scroll-BSzq6cdj.js.map → scroll-DGHNxpYN.js.map} +1 -1
- package/dist/hub.js +1 -1
- package/dist/linkrpc.js +1 -1
- package/dist/rpc.js +1 -1
- package/package.json +7 -7
- package/dist/chunks/cli-DJREb0Dm.js.map +0 -1
package/README.md
CHANGED
|
@@ -9,6 +9,25 @@ interfaces (`hubrpc.defaults`, `hubrpc.directory`, `hubrpc.schemas`) are
|
|
|
9
9
|
used to discover what the endpoint exposes. Generic RPC commands work with any
|
|
10
10
|
reflected interface; compiled-in views add specialized CLI and TUI renderers.
|
|
11
11
|
|
|
12
|
+
## Directory discovery and consent
|
|
13
|
+
|
|
14
|
+
`linkrpc hub ls` first tries directory reads using its current identity and
|
|
15
|
+
capabilities. On `permissionRequired`, it asks the connected hub's approval
|
|
16
|
+
service for short-lived access to `hubrpc.directory::list` on that exact
|
|
17
|
+
directory service, then retries once. Watch mode also requests `watch`;
|
|
18
|
+
schema/member dump modes additionally request `hubrpc.schemas::get`.
|
|
19
|
+
Unsigned connections (`--no-sign`) do not request capabilities.
|
|
20
|
+
|
|
21
|
+
Timeouts, missing methods, and transport errors never trigger consent. Plain
|
|
22
|
+
output shows inaccessible directories and their errors. `--format json` and
|
|
23
|
+
`--format jsonl` include an `inaccessible` report; legacy `--json` preserves its
|
|
24
|
+
flat array and reports failures on stderr.
|
|
25
|
+
|
|
26
|
+
Approval is not automatic authorization: the destination must accept the
|
|
27
|
+
issued capability's root. If the connected hub cannot obtain a grant from an
|
|
28
|
+
accepted issuer, discovery still reports that directory as inaccessible.
|
|
29
|
+
No trust roots are added and no service is made public.
|
|
30
|
+
|
|
12
31
|
## Daemon-owned endpoints (`hub services`)
|
|
13
32
|
|
|
14
33
|
`hub services` manages processes and existing connections **through the daemon
|
|
@@ -2239,7 +2239,7 @@ async function lsCommand(channel, opts = {}) {
|
|
|
2239
2239
|
validateLsOptions(opts);
|
|
2240
2240
|
const legacyOutput = opts.format === void 0;
|
|
2241
2241
|
if (opts.dumpPath !== void 0) {
|
|
2242
|
-
const dump = await createHubDump(channel,
|
|
2242
|
+
const dump = await createHubDump(channel, opts);
|
|
2243
2243
|
const dumpPath = path.resolve(opts.dumpPath);
|
|
2244
2244
|
await mkdir(path.dirname(dumpPath), { recursive: true });
|
|
2245
2245
|
await writeFile(dumpPath, `${formatJson(dump)}\n`, "utf8");
|
|
@@ -2254,9 +2254,38 @@ async function lsCommand(channel, opts = {}) {
|
|
|
2254
2254
|
const state = createLsState(explorer.graphSnapshot, opts);
|
|
2255
2255
|
explorer.dispose();
|
|
2256
2256
|
const all = state.listings;
|
|
2257
|
-
const output = opts.withMembers ? await addMembers(channel, all, (listing) => listing.discoveredFrom) : all;
|
|
2258
|
-
if (format === "json")
|
|
2259
|
-
|
|
2257
|
+
const output = opts.withMembers ? await addMembers(createSchemaFetcher(channel, opts), all, (listing) => listing.discoveredFrom) : all;
|
|
2258
|
+
if (format === "json") {
|
|
2259
|
+
for (const item of state.inaccessible) opts.log?.(`Inaccessible directory ${item.serviceId || "(root)"}: ${item.reason}`);
|
|
2260
|
+
return formatJson(legacyOutput ? output : state);
|
|
2261
|
+
}
|
|
2262
|
+
return [renderTree(output, opts.interfaceId), ...state.inaccessible.map((item) => `! Inaccessible directory ${item.serviceId || "(root)"}: ${item.reason}`)].join("\n");
|
|
2263
|
+
}
|
|
2264
|
+
async function requestDirectoryAccess(session, serviceId, options) {
|
|
2265
|
+
const result = await session.requestAccess({
|
|
2266
|
+
consumer: {
|
|
2267
|
+
name: "linkrpc-cli",
|
|
2268
|
+
purpose: `Discover interfaces in ${serviceId}.`
|
|
2269
|
+
},
|
|
2270
|
+
permissions: [{
|
|
2271
|
+
target: {
|
|
2272
|
+
serviceId: { exact: serviceId },
|
|
2273
|
+
interfaceId: { exact: "hubrpc.directory" },
|
|
2274
|
+
members: [{ exact: "list" }, ...options.watch ? [{ exact: "watch" }] : []]
|
|
2275
|
+
},
|
|
2276
|
+
canInvoke: true
|
|
2277
|
+
}, ...options.withMembers || options.dumpPath !== void 0 || options.dumpPatchesPath !== void 0 ? [{
|
|
2278
|
+
target: {
|
|
2279
|
+
serviceId: { exact: serviceId },
|
|
2280
|
+
interfaceId: { exact: "hubrpc.schemas" },
|
|
2281
|
+
members: [{ exact: "get" }]
|
|
2282
|
+
},
|
|
2283
|
+
canInvoke: true
|
|
2284
|
+
}] : []],
|
|
2285
|
+
duration: "shortLived"
|
|
2286
|
+
});
|
|
2287
|
+
if (result.status !== "granted") throw new Error(`Directory access ${result.status}${"reason" in result && result.reason ? `: ${result.reason}` : ""}`);
|
|
2288
|
+
return true;
|
|
2260
2289
|
}
|
|
2261
2290
|
function validateLsOptions(opts) {
|
|
2262
2291
|
validateSearchExpression(opts.search, "ls");
|
|
@@ -2282,6 +2311,8 @@ function validateSearchExpression(search, command) {
|
|
|
2282
2311
|
}
|
|
2283
2312
|
function explorerOptions(opts) {
|
|
2284
2313
|
return {
|
|
2314
|
+
unlockGatedDirectory: opts.requestDirectoryAccess,
|
|
2315
|
+
log: opts.log,
|
|
2285
2316
|
maxDepth: opts.serviceId === void 0 ? opts.maxDepth ?? DEFAULT_WALK_DEPTH : 0,
|
|
2286
2317
|
...opts.interfaceId !== void 0 ? { interfaceId: opts.interfaceId } : {},
|
|
2287
2318
|
...opts.interfacePrefix !== void 0 ? { interfaceIdPrefix: opts.interfacePrefix } : {},
|
|
@@ -2426,14 +2457,31 @@ function directoryTargetLabel(target) {
|
|
|
2426
2457
|
function formatScopes(scopes) {
|
|
2427
2458
|
return scopes.length === 0 ? "" : ` via ${scopes.join(", ")}`;
|
|
2428
2459
|
}
|
|
2429
|
-
|
|
2460
|
+
function createSchemaFetcher(channel, opts) {
|
|
2461
|
+
const approvals = /* @__PURE__ */ new Map();
|
|
2462
|
+
return async (interfaceId, hash, target) => {
|
|
2463
|
+
try {
|
|
2464
|
+
return await fetchSchema(channel, interfaceId, hash, target);
|
|
2465
|
+
} catch (error) {
|
|
2466
|
+
if (typeof error !== "object" || error === null || !("code" in error) || error.code !== ErrorCode.permissionRequired || !target || !opts.requestDirectoryAccess) throw error;
|
|
2467
|
+
let approval = approvals.get(target);
|
|
2468
|
+
if (!approval) {
|
|
2469
|
+
approval = opts.requestDirectoryAccess(target);
|
|
2470
|
+
approvals.set(target, approval);
|
|
2471
|
+
}
|
|
2472
|
+
if (!await approval) throw error;
|
|
2473
|
+
return fetchSchema(channel, interfaceId, hash, target);
|
|
2474
|
+
}
|
|
2475
|
+
};
|
|
2476
|
+
}
|
|
2477
|
+
async function addMembers(getSchema, listings, getTarget) {
|
|
2430
2478
|
const cache = /* @__PURE__ */ new Map();
|
|
2431
2479
|
return Promise.all(listings.map(async (listing) => {
|
|
2432
2480
|
const target = getTarget(listing);
|
|
2433
2481
|
const cacheKey = `${target ?? ""}\0${listing.interfaceId}\0${listing.hash}`;
|
|
2434
2482
|
let members = cache.get(cacheKey);
|
|
2435
2483
|
if (members === void 0) {
|
|
2436
|
-
members =
|
|
2484
|
+
members = getSchema(listing.interfaceId, listing.hash, target === "" ? void 0 : target).then((schema) => Object.entries(schema.methods).map(([name, method]) => ({
|
|
2437
2485
|
name,
|
|
2438
2486
|
kind: method.result === void 0 ? "notification" : "request"
|
|
2439
2487
|
})).sort((a, b) => a.name.localeCompare(b.name)));
|
|
@@ -2469,14 +2517,14 @@ function createInitialHubDump() {
|
|
|
2469
2517
|
};
|
|
2470
2518
|
}
|
|
2471
2519
|
async function createHubDump(channel, opts = {}) {
|
|
2472
|
-
const explorer = new HubDirectoryExplorer(channel,
|
|
2520
|
+
const explorer = new HubDirectoryExplorer(channel, explorerOptions(opts));
|
|
2473
2521
|
await explorer.explore();
|
|
2474
|
-
const cache = await fetchDumpSchemas(channel, explorer.graphSnapshot);
|
|
2522
|
+
const cache = await fetchDumpSchemas(createSchemaFetcher(channel, opts), explorer.graphSnapshot);
|
|
2475
2523
|
const dump = buildHubDump(explorer.graphSnapshot, cache, true);
|
|
2476
2524
|
explorer.dispose();
|
|
2477
2525
|
return dump;
|
|
2478
2526
|
}
|
|
2479
|
-
async function fetchDumpSchemas(
|
|
2527
|
+
async function fetchDumpSchemas(getSchema, snapshot, cache = {
|
|
2480
2528
|
schemas: /* @__PURE__ */ new Map(),
|
|
2481
2529
|
errors: /* @__PURE__ */ new Map()
|
|
2482
2530
|
}) {
|
|
@@ -2486,7 +2534,7 @@ async function fetchDumpSchemas(channel, snapshot, cache = {
|
|
|
2486
2534
|
cache.errors.delete(hash);
|
|
2487
2535
|
const attempts = [];
|
|
2488
2536
|
for (const candidate of candidates) try {
|
|
2489
|
-
const schema = await
|
|
2537
|
+
const schema = await getSchema(candidate.interfaceId, hash, candidate.target);
|
|
2490
2538
|
cache.schemas.set(hash, schema);
|
|
2491
2539
|
break;
|
|
2492
2540
|
} catch (error) {
|
|
@@ -2542,6 +2590,7 @@ function collectSchemaCandidates(snapshot) {
|
|
|
2542
2590
|
return result;
|
|
2543
2591
|
}
|
|
2544
2592
|
async function runStreamingLs(channel, opts) {
|
|
2593
|
+
const getSchema = createSchemaFetcher(channel, opts);
|
|
2545
2594
|
const explorer = new HubDirectoryExplorer(channel, explorerOptions(opts));
|
|
2546
2595
|
const patchMode = opts.dumpPatchesPath !== void 0;
|
|
2547
2596
|
const patchPath = patchMode && opts.dumpPatchesPath !== "-" ? path.resolve(opts.dumpPatchesPath) : void 0;
|
|
@@ -2565,7 +2614,7 @@ async function runStreamingLs(channel, opts) {
|
|
|
2565
2614
|
const unsubscribe = explorer.subscribe((event) => {
|
|
2566
2615
|
queue = queue.then(async () => {
|
|
2567
2616
|
if (patchMode) {
|
|
2568
|
-
if (event.type === "settled") await fetchDumpSchemas(
|
|
2617
|
+
if (event.type === "settled") await fetchDumpSchemas(getSchema, event.snapshot, schemaCache);
|
|
2569
2618
|
const schemasComplete = event.snapshot.complete && [...collectSchemaCandidates(event.snapshot).keys()].every((hash) => schemaCache.schemas.has(hash) || schemaCache.errors.has(hash));
|
|
2570
2619
|
const next = buildHubDump(event.snapshot, schemaCache, schemasComplete);
|
|
2571
2620
|
for (const operation of createJsonPatch(toJsonValue(currentDump), toJsonValue(next))) await writeLine(JSON.stringify(operation));
|
|
@@ -8476,7 +8525,7 @@ async function main(rawArgv, executableName = "linkrpc") {
|
|
|
8476
8525
|
}, { autoNegotiatePerCall: true });
|
|
8477
8526
|
});
|
|
8478
8527
|
program.command("ls").description("List services and interfaces. Walks the bus recursively, following `hubrpc.directory` references.").option("--interface <id>", "filter by interfaceId").option("--interface-prefix <prefix>", "filter by interfaceId prefix").option("--service <id>", "list a single service (no recursion)").option("--service-prefix <prefix>", "filter by serviceId prefix").option("--search <regexp>", "case-insensitive regexp over listings").option("--depth <n>", "max recursion depth (default 5)", (v) => Number.parseInt(v, 10)).option("--with-members", "include request and notification members from interface schemas").option("--json", "legacy flat listing JSON; use --format json for graph state").option("--format <format>", "pretty, json, or jsonl").option("--dump <file>", "write topology and all schemas to one JSON fixture file").option("--dump-patches <file>", "write progressive RFC 6902 operations as JSON Lines; use - for stdout").option("--stream", "legacy patch-log JSONL; use --format jsonl for reconstructable graph state").option("--watch", "continue observing directory changes until interrupted").action(async (opts) => {
|
|
8479
|
-
await withChannel(endpoint, getPrincipalSpec(), async (channel) => {
|
|
8528
|
+
await withChannel(endpoint, getPrincipalSpec(), async (channel, session) => {
|
|
8480
8529
|
const format = resolveGraphFormat(opts.format, {
|
|
8481
8530
|
json: opts.json === true,
|
|
8482
8531
|
stream: opts.stream === true,
|
|
@@ -8484,6 +8533,15 @@ async function main(rawArgv, executableName = "linkrpc") {
|
|
|
8484
8533
|
});
|
|
8485
8534
|
const legacyOutput = opts.format === void 0 && opts.watch !== true;
|
|
8486
8535
|
const out = await lsCommand(channel, {
|
|
8536
|
+
requestDirectoryAccess: session === void 0 ? void 0 : (serviceId) => requestDirectoryAccess(session, serviceId, {
|
|
8537
|
+
watch: opts.watch,
|
|
8538
|
+
withMembers: opts.withMembers,
|
|
8539
|
+
dumpPath: opts.dump,
|
|
8540
|
+
dumpPatchesPath: opts.dumpPatches
|
|
8541
|
+
}),
|
|
8542
|
+
log: (message) => {
|
|
8543
|
+
process.stderr.write(`linkrpc: ${message}\n`);
|
|
8544
|
+
},
|
|
8487
8545
|
interfaceId: opts.interface,
|
|
8488
8546
|
interfacePrefix: opts.interfacePrefix,
|
|
8489
8547
|
serviceId: opts.service,
|
|
@@ -8506,6 +8564,9 @@ async function main(rawArgv, executableName = "linkrpc") {
|
|
|
8506
8564
|
maxDepth: typeof opts.depth === "number" && !Number.isNaN(opts.depth) ? opts.depth : void 0
|
|
8507
8565
|
});
|
|
8508
8566
|
if (out.length > 0) process.stdout.write(out + "\n");
|
|
8567
|
+
}, {
|
|
8568
|
+
requestReflectionAccess: false,
|
|
8569
|
+
autoNegotiatePerCall: false
|
|
8509
8570
|
});
|
|
8510
8571
|
});
|
|
8511
8572
|
if (profile === "hub") {
|
|
@@ -9125,7 +9186,7 @@ Batch options:
|
|
|
9125
9186
|
process.once("SIGINT", onSignal);
|
|
9126
9187
|
process.once("SIGTERM", onSignal);
|
|
9127
9188
|
try {
|
|
9128
|
-
const { runApprovalUi } = await import("./runApprovalUi-
|
|
9189
|
+
const { runApprovalUi } = await import("./runApprovalUi-CNp2DhKG.js");
|
|
9129
9190
|
await runApprovalUi({
|
|
9130
9191
|
client,
|
|
9131
9192
|
signal: stop.signal
|
|
@@ -9140,7 +9201,7 @@ Batch options:
|
|
|
9140
9201
|
registerViewCommands(program, (action) => withChannel(endpoint, getPrincipalSpec(), action));
|
|
9141
9202
|
program.command("ui").description("Launch the terminal UI (form-based browser of services + methods).").action(async () => {
|
|
9142
9203
|
const acquired = await acquireConnection(endpoint);
|
|
9143
|
-
const { runUi } = await import("./runUi-
|
|
9204
|
+
const { runUi } = await import("./runUi-B4rV3ZfF.js");
|
|
9144
9205
|
try {
|
|
9145
9206
|
await runUi({
|
|
9146
9207
|
endpoint: acquired.ep,
|
|
@@ -9676,4 +9737,4 @@ function safeJson(value) {
|
|
|
9676
9737
|
//#endregion
|
|
9677
9738
|
export { formatCondition as _, formatCommandLegend as a, targetId as b, safeTerminalText as c, ObservablePromise as d, observableValue as f, resolveViewTarget as g, Disposable as h, dispatchHostKey as i, approvalDetailLines as l, derived as m, views as n, hostCommands as o, autorun as p, ViewFrame as r, matchesKey as s, runCli as t, ObservableLazyPromise as u, interfaceTarget as v, matchesTarget as y };
|
|
9678
9739
|
|
|
9679
|
-
//# sourceMappingURL=cli-
|
|
9740
|
+
//# sourceMappingURL=cli-B93twHKF.js.map
|