buckyos 0.7.115 → 0.7.118

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/cli/dist/cli.mjs CHANGED
@@ -2,7 +2,7 @@ import process from "node:process";
2
2
  import { fileURLToPath } from "node:url";
3
3
  import * as nodePath from "node:path";
4
4
  import { resolve as resolve$5, dirname as dirname$3 } from "node:path";
5
- import { namelib, BuckyOSSDK, RuntimeType, buckyos, VerifyHubClient, createAppInstanceId, parseSessionTokenClaims, ndn, ndm_proxy } from "buckyos/node";
5
+ import { BuckyOSSDK, RuntimeType, buckyos, namelib, VerifyHubClient, createAppInstanceId, parseSessionTokenClaims, ndn, ndm_proxy } from "buckyos/node";
6
6
  import { createHash } from "node:crypto";
7
7
  import { readFile, writeFile, readdir, stat, lstat, realpath, mkdtemp, rename, rm, mkdir, chmod, symlink, copyFile, open } from "node:fs/promises";
8
8
  import { createReadStream, createWriteStream } from "node:fs";
@@ -94,8 +94,6 @@ const STRING_GLOBALS = /* @__PURE__ */ new Map([
94
94
  ["zone", "zone"],
95
95
  ["endpoint", "endpoint"],
96
96
  ["identity", "identity"],
97
- ["identity-root", "identityRoot"],
98
- ["security-root", "securityRoot"],
99
97
  ["session-token", "sessionToken"],
100
98
  ["session-token-file", "sessionTokenFile"],
101
99
  ["output", "output"],
@@ -104,6 +102,9 @@ const STRING_GLOBALS = /* @__PURE__ */ new Map([
104
102
  ["trace-id", "traceId"],
105
103
  ["idempotency-key", "idempotencyKey"]
106
104
  ]);
105
+ const REPEATABLE_STRING_GLOBALS = /* @__PURE__ */ new Map([
106
+ ["allow-read", "allowRead"]
107
+ ]);
107
108
  const BOOLEAN_GLOBALS = /* @__PURE__ */ new Map([
108
109
  ["cli", "cli"],
109
110
  ["wait", "wait"],
@@ -149,11 +150,16 @@ function parseInvocation(argv) {
149
150
  continue;
150
151
  }
151
152
  const stringProperty = STRING_GLOBALS.get(parsed.name);
152
- if (!stringProperty) {
153
+ const repeatableStringProperty = REPEATABLE_STRING_GLOBALS.get(parsed.name);
154
+ if (!stringProperty && !repeatableStringProperty) {
153
155
  throw new UsageError("UNKNOWN_OPTION", `unknown global option: --${parsed.name}`);
154
156
  }
155
157
  const { value, consumed } = readOptionValue(argv, index, parsed);
156
- setGlobal(global, stringProperty, normalizeGlobalValue(parsed.name, value));
158
+ if (repeatableStringProperty === "allowRead") {
159
+ global.allowRead = [...global.allowRead ?? [], value];
160
+ } else {
161
+ setGlobal(global, stringProperty, normalizeGlobalValue(parsed.name, value));
162
+ }
157
163
  index += consumed;
158
164
  }
159
165
  const module = argv[index];
@@ -214,7 +220,7 @@ function parseCommandArgs(command, argv, inputObject, allowReplScopedGlobals = f
214
220
  index += consumed2;
215
221
  continue;
216
222
  }
217
- if (allowReplScopedGlobals && (STRING_GLOBALS.has(parsed.name) || BOOLEAN_GLOBALS.has(parsed.name))) {
223
+ if (allowReplScopedGlobals && (STRING_GLOBALS.has(parsed.name) || REPEATABLE_STRING_GLOBALS.has(parsed.name) || BOOLEAN_GLOBALS.has(parsed.name))) {
218
224
  throw new UsageError(
219
225
  "SESSION_OPTION_FROZEN",
220
226
  `--${parsed.name} is frozen for the interactive session`
@@ -477,8 +483,6 @@ const TOOL_ENVIRONMENT_NAMES = [
477
483
  "BUCKYOS_TOOL_ENDPOINT",
478
484
  "BUCKYOS_TOOL_IDENTITY",
479
485
  "BUCKYOS_TOOL_OUTPUT",
480
- "BUCKYOS_IDENTITY_ROOT",
481
- "BUCKYOS_SECURITY_ROOT",
482
486
  "BUCKYOS_APPCLIENT_SESSION_TOKEN",
483
487
  "BUCKYOS_ROOT",
484
488
  "SOURCE_DATE_EPOCH"
@@ -491,14 +495,16 @@ function buildDistributionPolicy(options) {
491
495
  const configRoot = path.resolve(
492
496
  environment2.BUCKYOS_TOOL_CONFIG_DIR ?? path.join(homeDir, ".buckyos_tool")
493
497
  );
494
- const readPaths = /* @__PURE__ */ new Set([path.resolve(packageRoot2), path.resolve(cwd), configRoot]);
498
+ const readPaths = /* @__PURE__ */ new Set([
499
+ path.resolve(packageRoot2),
500
+ path.resolve(cwd),
501
+ configRoot,
502
+ path.resolve(path.join(homeDir, ".buckyos")),
503
+ path.resolve(path.join(homeDir, ".buckycli"))
504
+ ]);
495
505
  const writePaths = /* @__PURE__ */ new Set([path.resolve(cwd), configRoot]);
496
506
  if (options.distribution === "system")
497
507
  readPaths.add(buckyosRoot);
498
- if (environment2.BUCKYOS_IDENTITY_ROOT && environment2.BUCKYOS_SECURITY_ROOT) {
499
- readPaths.add(path.resolve(environment2.BUCKYOS_IDENTITY_ROOT));
500
- readPaths.add(path.resolve(environment2.BUCKYOS_SECURITY_ROOT));
501
- }
502
508
  const parsed = collectArgumentPaths(argv);
503
509
  for (const candidate of parsed.read)
504
510
  readPaths.add(resolveInputPath(candidate, cwd, path));
@@ -593,7 +599,9 @@ function collectArgumentPaths(argv) {
593
599
  if (name === "config-dir") {
594
600
  read.push(value);
595
601
  write.push(value);
596
- } else if (["input", "session-token-file", "identity-root", "security-root", "pikg"].includes(name)) {
602
+ } else if (name === "allow-read") {
603
+ read.push(value);
604
+ } else if (["input", "session-token-file", "pikg"].includes(name)) {
597
605
  if (value !== "-")
598
606
  read.push(value);
599
607
  } else if (name === "source" && module === "pikg") {
@@ -676,8 +684,6 @@ const ENVIRONMENT_NAMES = [
676
684
  "BUCKYOS_TOOL_ENDPOINT",
677
685
  "BUCKYOS_TOOL_IDENTITY",
678
686
  "BUCKYOS_TOOL_OUTPUT",
679
- "BUCKYOS_IDENTITY_ROOT",
680
- "BUCKYOS_SECURITY_ROOT",
681
687
  "BUCKYOS_APPCLIENT_SESSION_TOKEN",
682
688
  "BUCKYOS_ROOT"
683
689
  ];
@@ -820,34 +826,6 @@ async function resolveConfig(explicit, environment2 = readEnvironment(), options
820
826
  void 0,
821
827
  sources
822
828
  );
823
- const identityRoot = select(
824
- "identity_root",
825
- explicit.identityRoot,
826
- environment2.BUCKYOS_IDENTITY_ROOT,
827
- void 0,
828
- void 0,
829
- sources
830
- );
831
- const securityRoot = select(
832
- "security_root",
833
- explicit.securityRoot,
834
- environment2.BUCKYOS_SECURITY_ROOT,
835
- void 0,
836
- void 0,
837
- sources
838
- );
839
- if (!!explicit.identityRoot !== !!explicit.securityRoot) {
840
- throw new UsageError(
841
- "IDENTITY_ROOT_PAIR_REQUIRED",
842
- "--identity-root and --security-root must be provided together"
843
- );
844
- }
845
- if (!!environment2.BUCKYOS_IDENTITY_ROOT !== !!environment2.BUCKYOS_SECURITY_ROOT && !explicit.identityRoot) {
846
- throw new UsageError(
847
- "IDENTITY_ROOT_PAIR_REQUIRED",
848
- "BUCKYOS_IDENTITY_ROOT and BUCKYOS_SECURITY_ROOT must be provided together"
849
- );
850
- }
851
829
  let output;
852
830
  if (options.interactive && explicit.output === void 0) {
853
831
  output = "table";
@@ -880,8 +858,6 @@ async function resolveConfig(explicit, environment2 = readEnvironment(), options
880
858
  zone,
881
859
  endpoint,
882
860
  identity,
883
- identityRoot,
884
- securityRoot,
885
861
  sessionToken,
886
862
  sessionTokenFile: explicit.sessionTokenFile,
887
863
  output,
@@ -915,8 +891,6 @@ function localResolvedConfig(explicit, environment2 = readEnvironment(), options
915
891
  zone: explicit.zone ?? environment2.BUCKYOS_TOOL_ZONE,
916
892
  endpoint: explicit.endpoint ?? environment2.BUCKYOS_TOOL_ENDPOINT,
917
893
  identity: explicit.identity ?? environment2.BUCKYOS_TOOL_IDENTITY,
918
- identityRoot: explicit.identityRoot ?? environment2.BUCKYOS_IDENTITY_ROOT,
919
- securityRoot: explicit.securityRoot ?? environment2.BUCKYOS_SECURITY_ROOT,
920
894
  sessionToken: explicit.sessionToken ?? environment2.BUCKYOS_APPCLIENT_SESSION_TOKEN,
921
895
  sessionTokenFile: explicit.sessionTokenFile,
922
896
  output,
@@ -1081,6 +1055,196 @@ function redactUrl(value) {
1081
1055
  function configValueError(message) {
1082
1056
  return new ToolError("INVALID_CONFIG_VALUE", message, 2);
1083
1057
  }
1058
+ class BuckyOSRuntimeAdapter {
1059
+ #sdk = new BuckyOSSDK("node");
1060
+ async initialize(config, session) {
1061
+ const authenticated = session.current();
1062
+ const zoneHost = resolveZoneHost(config);
1063
+ const initialize = this.#sdk.initBuckyOS;
1064
+ await initialize.call(this.#sdk, authenticated.principal.appId, {
1065
+ appId: authenticated.principal.appId,
1066
+ ownerUserId: authenticated.principal.id,
1067
+ runtimeType: RuntimeType.AppClient,
1068
+ zoneHost,
1069
+ defaultProtocol: config.defaultProtocol,
1070
+ sessionToken: authenticated.token,
1071
+ privateKeySearchPaths: [],
1072
+ autoRenew: false,
1073
+ verifyHubServiceUrl: resolveServiceUrl(config, "verify-hub")
1074
+ });
1075
+ return {
1076
+ zone: config.zone ?? zoneHost,
1077
+ endpoint: config.endpoint ?? `${config.defaultProtocol}${zoneHost}`,
1078
+ defaultProtocol: config.defaultProtocol
1079
+ };
1080
+ }
1081
+ }
1082
+ class BuckyOSServiceClientRegistry {
1083
+ #config;
1084
+ #session;
1085
+ #clients = /* @__PURE__ */ new Map();
1086
+ constructor(config, session) {
1087
+ this.#config = config;
1088
+ this.#session = session;
1089
+ }
1090
+ async call(service, method, params, options) {
1091
+ let client = this.#clients.get(service);
1092
+ if (!client) {
1093
+ const RpcClient = buckyos.kRPCClient;
1094
+ client = new RpcClient(resolveServiceUrl(this.#config, service), null, null, {
1095
+ sessionTokenProvider: async () => (await this.#session.ensureValid()).token
1096
+ });
1097
+ this.#clients.set(service, client);
1098
+ }
1099
+ const request = client.call(method, params, { traceId: options.traceId });
1100
+ return await withDeadline(request, options.timeoutMs, options.signal);
1101
+ }
1102
+ async createEventReader(pattern, signal) {
1103
+ return await buckyos.createEventReader(pattern, { keepaliveMs: 5e3, signal });
1104
+ }
1105
+ }
1106
+ class InteractiveSession {
1107
+ constructor(authentication, clients, connection) {
1108
+ this.authentication = authentication;
1109
+ this.clients = clients;
1110
+ this.connection = connection;
1111
+ this.startedAt = Date.now();
1112
+ }
1113
+ static async create(config, authentication, runtime = new BuckyOSRuntimeAdapter(), clients) {
1114
+ await authentication.connect();
1115
+ const connection = await runtime.initialize(config, authentication);
1116
+ return new InteractiveSession(
1117
+ authentication,
1118
+ clients ?? new BuckyOSServiceClientRegistry(config, authentication),
1119
+ connection
1120
+ );
1121
+ }
1122
+ async reconnect() {
1123
+ await this.authentication.reconnect();
1124
+ }
1125
+ }
1126
+ function resolveServiceUrl(config, service) {
1127
+ if (config.endpoint) {
1128
+ const endpoint = new URL(config.endpoint);
1129
+ const marker = endpoint.pathname.indexOf("/kapi/");
1130
+ if (marker >= 0)
1131
+ endpoint.pathname = `${endpoint.pathname.slice(0, marker)}/kapi/${service}`;
1132
+ else if (endpoint.pathname.endsWith("/kapi")) {
1133
+ endpoint.pathname = `${endpoint.pathname}/${service}`;
1134
+ } else
1135
+ endpoint.pathname = `${endpoint.pathname.replace(/\/$/, "")}/kapi/${service}`;
1136
+ endpoint.search = "";
1137
+ endpoint.hash = "";
1138
+ return endpoint.toString().replace(/\/$/, "");
1139
+ }
1140
+ const host2 = resolveZoneHost(config);
1141
+ return `${config.defaultProtocol}${host2}/kapi/${service}`;
1142
+ }
1143
+ function resolveZoneHost(config) {
1144
+ if (config.zone) {
1145
+ try {
1146
+ return namelib.DID.fromStr(config.zone).toRawHostName();
1147
+ } catch {
1148
+ throw new UsageError("INVALID_ZONE", `invalid zone host or DID: ${config.zone}`);
1149
+ }
1150
+ }
1151
+ if (config.endpoint)
1152
+ return new URL(config.endpoint).host;
1153
+ throw new UsageError("CONNECTION_REQUIRED", "an endpoint or zone is required for online commands");
1154
+ }
1155
+ async function withDeadline(promise, timeoutMs, signal) {
1156
+ if (signal?.aborted)
1157
+ throw new ToolError("CANCELED", "operation canceled", 8);
1158
+ let timer;
1159
+ let abortHandler;
1160
+ const guards = [
1161
+ new Promise((_, reject) => {
1162
+ timer = setTimeout(
1163
+ () => reject(new ToolError("TIMEOUT", "operation timed out", 8, true)),
1164
+ timeoutMs
1165
+ );
1166
+ })
1167
+ ];
1168
+ if (signal) {
1169
+ guards.push(
1170
+ new Promise((_, reject) => {
1171
+ abortHandler = () => reject(new ToolError("CANCELED", "operation canceled", 8));
1172
+ signal.addEventListener("abort", abortHandler, { once: true });
1173
+ })
1174
+ );
1175
+ }
1176
+ try {
1177
+ return await Promise.race([promise, ...guards]);
1178
+ } finally {
1179
+ if (timer !== void 0)
1180
+ clearTimeout(timer);
1181
+ if (signal && abortHandler)
1182
+ signal.removeEventListener("abort", abortHandler);
1183
+ }
1184
+ }
1185
+ const BUCKYOS_DEV_CONFIG_SCHEMA_VERSION = 1;
1186
+ async function readTargetDevelopmentMode(config) {
1187
+ try {
1188
+ const url = resolveServiceUrl(config, "control-panel");
1189
+ const client = new buckyos.kRPCClient(url);
1190
+ const value = await withTimeout(
1191
+ client.call("system.dev_mode.get", {}),
1192
+ config.timeoutMs
1193
+ );
1194
+ const parsed = parseBuckyOSDevConfig(value);
1195
+ return parsed.enabled ? { state: "enabled", config: parsed } : { state: "disabled", config: parsed };
1196
+ } catch (error) {
1197
+ return {
1198
+ state: "unavailable",
1199
+ reason: error instanceof Error ? error.message : String(error)
1200
+ };
1201
+ }
1202
+ }
1203
+ function parseBuckyOSDevConfig(value) {
1204
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1205
+ throw new Error("BuckyOSDevConfig must be an object");
1206
+ }
1207
+ const config = value;
1208
+ const keys = Object.keys(config).sort();
1209
+ const expectedKeys = ["enabled", "enabled_at", "enabled_by", "schema_version"];
1210
+ if (JSON.stringify(keys) !== JSON.stringify(expectedKeys)) {
1211
+ throw new Error("BuckyOSDevConfig contains missing or unsupported fields");
1212
+ }
1213
+ if (config.schema_version !== BUCKYOS_DEV_CONFIG_SCHEMA_VERSION) {
1214
+ throw new Error(`unsupported BuckyOSDevConfig schema_version ${String(config.schema_version)}`);
1215
+ }
1216
+ if (typeof config.enabled !== "boolean") {
1217
+ throw new Error("BuckyOSDevConfig enabled must be a boolean");
1218
+ }
1219
+ const enabledAt = config.enabled_at;
1220
+ const enabledBy = config.enabled_by;
1221
+ const hasAuditFields = Number.isSafeInteger(enabledAt) && enabledAt > 0 && typeof enabledBy === "string" && enabledBy.trim().length > 0;
1222
+ const hasNoAuditFields = enabledAt === null && enabledBy === null;
1223
+ if (!hasAuditFields && !hasNoAuditFields) {
1224
+ throw new Error("BuckyOSDevConfig enabled_at and enabled_by must be valid and present together");
1225
+ }
1226
+ if (config.enabled && !hasAuditFields) {
1227
+ throw new Error("enabled BuckyOSDevConfig requires enable audit fields");
1228
+ }
1229
+ return {
1230
+ schema_version: 1,
1231
+ enabled: config.enabled,
1232
+ enabled_at: enabledAt,
1233
+ enabled_by: enabledBy
1234
+ };
1235
+ }
1236
+ async function withTimeout(promise, timeoutMs) {
1237
+ let timer;
1238
+ const timeout = new Promise((_, reject) => {
1239
+ timer = setTimeout(() => reject(new Error("developer-mode lookup timed out")), timeoutMs);
1240
+ });
1241
+ try {
1242
+ return await Promise.race([promise, timeout]);
1243
+ } finally {
1244
+ if (timer !== void 0)
1245
+ clearTimeout(timer);
1246
+ }
1247
+ }
1084
1248
  const join$2 = (...parts) => getHost().path.join(...parts);
1085
1249
  const LOCAL_NODE_GATEWAY_ENDPOINT = "http://127.0.0.1:3180";
1086
1250
  const IDENTITY_CANDIDATE_LIMIT = 8;
@@ -1164,45 +1328,38 @@ async function readCurrentDeviceIdentity(environment2 = readEnvironment()) {
1164
1328
  securityRoot: join$2(buckyosRoot, "security")
1165
1329
  };
1166
1330
  }
1167
- function identityRootPairs(config, environment2 = readEnvironment()) {
1331
+ function identityRootPairs(config, environment2 = readEnvironment(), options = {}) {
1332
+ const device = config.implicitDeviceIdentity;
1333
+ if (device) {
1334
+ return [{
1335
+ publicRoot: device.publicRoot,
1336
+ securityRoot: device.securityRoot,
1337
+ source: "current-device"
1338
+ }];
1339
+ }
1340
+ const home = environment2.HOME ?? environment2.USERPROFILE ?? getHost().homeDir();
1168
1341
  const pairs = [];
1169
- if (config.identityRoot && config.securityRoot) {
1342
+ if (options.includeOperations ?? true) {
1170
1343
  pairs.push({
1171
- publicRoot: config.identityRoot,
1172
- securityRoot: config.securityRoot,
1173
- source: "explicit"
1344
+ publicRoot: join$2(home, ".buckyos", "local", "identity"),
1345
+ securityRoot: join$2(home, ".buckyos", "security"),
1346
+ source: "buckyos-home"
1174
1347
  });
1175
1348
  }
1176
- pairs.push({
1177
- publicRoot: join$2(config.configDir, "local", "identity"),
1178
- securityRoot: join$2(config.configDir, "security"),
1179
- source: "tool"
1180
- });
1181
- if (environment2.BUCKYOS_IDENTITY_ROOT && environment2.BUCKYOS_SECURITY_ROOT) {
1182
- const duplicate = config.identityRoot === environment2.BUCKYOS_IDENTITY_ROOT && config.securityRoot === environment2.BUCKYOS_SECURITY_ROOT;
1183
- if (!duplicate) {
1184
- pairs.push({
1185
- publicRoot: environment2.BUCKYOS_IDENTITY_ROOT,
1186
- securityRoot: environment2.BUCKYOS_SECURITY_ROOT,
1187
- source: "environment"
1188
- });
1189
- }
1190
- }
1191
- if (getHost().policy.distribution === "system") {
1192
- const buckyosRoot = environment2.BUCKYOS_ROOT ?? defaultBuckyOSRoot(environment2);
1349
+ if (options.includeDeveloper ?? false) {
1193
1350
  pairs.push({
1194
- publicRoot: join$2(buckyosRoot, "local", "identity"),
1195
- securityRoot: join$2(buckyosRoot, "security"),
1196
- source: "buckyos-root"
1351
+ publicRoot: join$2(home, ".buckycli", "local", "identity"),
1352
+ securityRoot: join$2(home, ".buckycli", "security"),
1353
+ source: "buckycli-home"
1197
1354
  });
1198
1355
  }
1199
1356
  return deduplicatePairs(pairs);
1200
1357
  }
1201
- async function resolveIdentityMaterial(selectedIdentity, config, environment2 = readEnvironment()) {
1358
+ async function resolveIdentityMaterial(selectedIdentity, config, environment2 = readEnvironment(), options = {}) {
1202
1359
  const selected = selectedIdentity.trim();
1203
1360
  if (!selected)
1204
1361
  throw new UsageError("IDENTITY_REQUIRED", "identity is empty");
1205
- for (const roots of identityRootPairs(config, environment2)) {
1362
+ for (const roots of identityRootPairs(config, environment2, options)) {
1206
1363
  for (const directory of await candidateDirectories(roots.publicRoot, selected)) {
1207
1364
  const material = await loadIdentityMaterial(roots, directory, selected);
1208
1365
  if (material)
@@ -1215,11 +1372,11 @@ async function resolveIdentityMaterial(selectedIdentity, config, environment2 =
1215
1372
  EXIT_AUTH
1216
1373
  );
1217
1374
  }
1218
- async function scanIdentityCandidates(config, environment2 = readEnvironment()) {
1375
+ async function scanIdentityCandidates(config, environment2 = readEnvironment(), options = {}) {
1219
1376
  const candidates = [];
1220
1377
  const skipped = [];
1221
1378
  const seen = /* @__PURE__ */ new Set();
1222
- const roots = identityRootPairs(config, environment2);
1379
+ const roots = identityRootPairs(config, environment2, options);
1223
1380
  for (const pair of roots) {
1224
1381
  let directories;
1225
1382
  try {
@@ -1273,16 +1430,18 @@ async function scanIdentityCandidates(config, environment2 = readEnvironment())
1273
1430
  break;
1274
1431
  }
1275
1432
  return {
1276
- order: Object.freeze(["explicit", "tool", "environment", "buckyos-root"]),
1433
+ order: Object.freeze(roots.map((pair) => pair.source)),
1277
1434
  limit: IDENTITY_CANDIDATE_LIMIT,
1278
1435
  candidates,
1279
1436
  skipped
1280
1437
  };
1281
1438
  }
1282
- async function identityCandidateView(config, environment2 = readEnvironment()) {
1439
+ async function identityCandidateView(config, environment2 = readEnvironment(), developerModeEnabled = false) {
1283
1440
  if (config.identity) {
1284
1441
  try {
1285
- const material = await resolveIdentityMaterial(config.identity, config, environment2);
1442
+ const material = await resolveIdentityMaterial(config.identity, config, environment2, {
1443
+ includeDeveloper: developerModeEnabled
1444
+ });
1286
1445
  return {
1287
1446
  mode: "explicit",
1288
1447
  order: [material.did],
@@ -1307,7 +1466,9 @@ async function identityCandidateView(config, environment2 = readEnvironment()) {
1307
1466
  };
1308
1467
  }
1309
1468
  }
1310
- const scan = await scanIdentityCandidates(config, environment2);
1469
+ const scan = await scanIdentityCandidates(config, environment2, {
1470
+ includeDeveloper: developerModeEnabled
1471
+ });
1311
1472
  return {
1312
1473
  mode: "search",
1313
1474
  order: scan.order,
@@ -1343,7 +1504,8 @@ async function loadIdentityMaterial(roots, directory, selected) {
1343
1504
  securityRoot: roots.securityRoot,
1344
1505
  documentPath,
1345
1506
  privateKeyPath,
1346
- privateKeyPem
1507
+ privateKeyPem,
1508
+ source: roots.source
1347
1509
  };
1348
1510
  } catch (error) {
1349
1511
  if (!isHostError(error, "NotFound"))
@@ -1428,133 +1590,6 @@ function deduplicatePairs(pairs) {
1428
1590
  return true;
1429
1591
  });
1430
1592
  }
1431
- class BuckyOSRuntimeAdapter {
1432
- #sdk = new BuckyOSSDK("node");
1433
- async initialize(config, session) {
1434
- const authenticated = session.current();
1435
- const zoneHost = resolveZoneHost(config);
1436
- const initialize = this.#sdk.initBuckyOS;
1437
- await initialize.call(this.#sdk, authenticated.principal.appId, {
1438
- appId: authenticated.principal.appId,
1439
- ownerUserId: authenticated.principal.id,
1440
- runtimeType: RuntimeType.AppClient,
1441
- zoneHost,
1442
- defaultProtocol: config.defaultProtocol,
1443
- sessionToken: authenticated.token,
1444
- privateKeySearchPaths: [],
1445
- autoRenew: false,
1446
- verifyHubServiceUrl: resolveServiceUrl(config, "verify-hub")
1447
- });
1448
- return {
1449
- zone: config.zone ?? zoneHost,
1450
- endpoint: config.endpoint ?? `${config.defaultProtocol}${zoneHost}`,
1451
- defaultProtocol: config.defaultProtocol
1452
- };
1453
- }
1454
- }
1455
- class BuckyOSServiceClientRegistry {
1456
- #config;
1457
- #session;
1458
- #clients = /* @__PURE__ */ new Map();
1459
- constructor(config, session) {
1460
- this.#config = config;
1461
- this.#session = session;
1462
- }
1463
- async call(service, method, params, options) {
1464
- let client = this.#clients.get(service);
1465
- if (!client) {
1466
- const RpcClient = buckyos.kRPCClient;
1467
- client = new RpcClient(resolveServiceUrl(this.#config, service), null, null, {
1468
- sessionTokenProvider: async () => (await this.#session.ensureValid()).token
1469
- });
1470
- this.#clients.set(service, client);
1471
- }
1472
- const request = client.call(method, params, { traceId: options.traceId });
1473
- return await withDeadline(request, options.timeoutMs, options.signal);
1474
- }
1475
- async createEventReader(pattern, signal) {
1476
- return await buckyos.createEventReader(pattern, { keepaliveMs: 5e3, signal });
1477
- }
1478
- }
1479
- class InteractiveSession {
1480
- constructor(authentication, clients, connection) {
1481
- this.authentication = authentication;
1482
- this.clients = clients;
1483
- this.connection = connection;
1484
- this.startedAt = Date.now();
1485
- }
1486
- static async create(config, authentication, runtime = new BuckyOSRuntimeAdapter(), clients) {
1487
- await authentication.connect();
1488
- const connection = await runtime.initialize(config, authentication);
1489
- return new InteractiveSession(
1490
- authentication,
1491
- clients ?? new BuckyOSServiceClientRegistry(config, authentication),
1492
- connection
1493
- );
1494
- }
1495
- async reconnect() {
1496
- await this.authentication.reconnect();
1497
- }
1498
- }
1499
- function resolveServiceUrl(config, service) {
1500
- if (config.endpoint) {
1501
- const endpoint = new URL(config.endpoint);
1502
- const marker = endpoint.pathname.indexOf("/kapi/");
1503
- if (marker >= 0)
1504
- endpoint.pathname = `${endpoint.pathname.slice(0, marker)}/kapi/${service}`;
1505
- else if (endpoint.pathname.endsWith("/kapi")) {
1506
- endpoint.pathname = `${endpoint.pathname}/${service}`;
1507
- } else
1508
- endpoint.pathname = `${endpoint.pathname.replace(/\/$/, "")}/kapi/${service}`;
1509
- endpoint.search = "";
1510
- endpoint.hash = "";
1511
- return endpoint.toString().replace(/\/$/, "");
1512
- }
1513
- const host2 = resolveZoneHost(config);
1514
- return `${config.defaultProtocol}${host2}/kapi/${service}`;
1515
- }
1516
- function resolveZoneHost(config) {
1517
- if (config.zone) {
1518
- try {
1519
- return namelib.DID.fromStr(config.zone).toRawHostName();
1520
- } catch {
1521
- throw new UsageError("INVALID_ZONE", `invalid zone host or DID: ${config.zone}`);
1522
- }
1523
- }
1524
- if (config.endpoint)
1525
- return new URL(config.endpoint).host;
1526
- throw new UsageError("CONNECTION_REQUIRED", "an endpoint or zone is required for online commands");
1527
- }
1528
- async function withDeadline(promise, timeoutMs, signal) {
1529
- if (signal?.aborted)
1530
- throw new ToolError("CANCELED", "operation canceled", 8);
1531
- let timer;
1532
- let abortHandler;
1533
- const guards = [
1534
- new Promise((_, reject) => {
1535
- timer = setTimeout(
1536
- () => reject(new ToolError("TIMEOUT", "operation timed out", 8, true)),
1537
- timeoutMs
1538
- );
1539
- })
1540
- ];
1541
- if (signal) {
1542
- guards.push(
1543
- new Promise((_, reject) => {
1544
- abortHandler = () => reject(new ToolError("CANCELED", "operation canceled", 8));
1545
- signal.addEventListener("abort", abortHandler, { once: true });
1546
- })
1547
- );
1548
- }
1549
- try {
1550
- return await Promise.race([promise, ...guards]);
1551
- } finally {
1552
- if (timer !== void 0)
1553
- clearTimeout(timer);
1554
- if (signal && abortHandler)
1555
- signal.removeEventListener("abort", abortHandler);
1556
- }
1557
- }
1558
1593
  const LOGIN_APP_ID = "buckycli";
1559
1594
  const LOGIN_TOKEN_TTL_SECONDS = 10 * 60;
1560
1595
  class AuthenticationSession {
@@ -1565,15 +1600,18 @@ class AuthenticationSession {
1565
1600
  this.#transport = dependencies.transport ?? new SdkAuthenticationTransport();
1566
1601
  this.#readPassword = dependencies.readPassword ?? readSecret;
1567
1602
  this.#readUsername = dependencies.readUsername ?? readVisible;
1603
+ this.#readDevelopmentMode = dependencies.readDevelopmentMode ?? readTargetDevelopmentMode;
1568
1604
  this.#now = dependencies.now ?? Date.now;
1569
1605
  }
1570
1606
  #environment;
1571
1607
  #transport;
1572
1608
  #readPassword;
1573
1609
  #readUsername;
1610
+ #readDevelopmentMode;
1574
1611
  #now;
1575
1612
  #session;
1576
1613
  #acceptedIdentity;
1614
+ #developmentMode;
1577
1615
  #identityAttempts;
1578
1616
  async connect() {
1579
1617
  if (this.#session)
@@ -1621,6 +1659,7 @@ class AuthenticationSession {
1621
1659
  renewable: session.renewable,
1622
1660
  expires_at: expiresAt ?? null,
1623
1661
  remaining_seconds: remainingSeconds,
1662
+ developer_mode: this.#developmentMode?.state ?? null,
1624
1663
  identity_attempts: this.#identityAttempts
1625
1664
  };
1626
1665
  }
@@ -1648,34 +1687,39 @@ class AuthenticationSession {
1648
1687
  const injected = this.#environment.BUCKYOS_APPCLIENT_SESSION_TOKEN?.trim();
1649
1688
  if (injected)
1650
1689
  return externalSession(injected, "environment", this.#now());
1651
- if (this.#acceptedIdentity)
1690
+ if (this.#acceptedIdentity) {
1691
+ if (this.#acceptedIdentity.source === "buckycli-home") {
1692
+ await this.#requireDeveloperIdentityAccess();
1693
+ }
1652
1694
  return await this.#loginWithIdentity(this.#acceptedIdentity);
1695
+ }
1653
1696
  if (this.config.identity) {
1654
- const material = await resolveIdentityMaterial(
1655
- this.config.identity,
1656
- this.config,
1657
- this.#environment
1658
- );
1697
+ const material = await this.#resolveExplicitIdentity(this.config.identity);
1659
1698
  const session = await this.#loginWithIdentity(material);
1660
1699
  this.#acceptedIdentity = material;
1661
1700
  return session;
1662
1701
  }
1663
- const candidates = await scanIdentityCandidates(this.config, this.#environment);
1664
1702
  this.#identityAttempts = [];
1665
- for (const candidate of candidates.candidates) {
1666
- try {
1667
- const session = await this.#loginWithIdentity(candidate.material);
1668
- this.#acceptedIdentity = candidate.material;
1669
- return session;
1670
- } catch (error) {
1671
- if (!isIdentityRejection(error))
1672
- throw error;
1673
- this.#identityAttempts.push({
1674
- identity: candidate.material.did,
1675
- code: error.code,
1676
- message: sanitizeMessage(error.message)
1677
- });
1678
- }
1703
+ const operations = await scanIdentityCandidates(this.config, this.#environment);
1704
+ const operationsSession = await this.#tryIdentityCandidates(operations.candidates);
1705
+ if (operationsSession)
1706
+ return operationsSession;
1707
+ const developmentMode = await this.#checkDevelopmentMode();
1708
+ if (developmentMode.state === "enabled" && operations.candidates.length < IDENTITY_CANDIDATE_LIMIT) {
1709
+ const operationsDids = new Set(
1710
+ operations.candidates.map((candidate) => candidate.material.did)
1711
+ );
1712
+ const developer = await scanIdentityCandidates(this.config, this.#environment, {
1713
+ includeOperations: false,
1714
+ includeDeveloper: true
1715
+ });
1716
+ const remaining = IDENTITY_CANDIDATE_LIMIT - operations.candidates.length;
1717
+ const candidates = developer.candidates.filter(
1718
+ (candidate) => !operationsDids.has(candidate.material.did)
1719
+ ).slice(0, remaining);
1720
+ const developerSession = await this.#tryIdentityCandidates(candidates);
1721
+ if (developerSession)
1722
+ return developerSession;
1679
1723
  }
1680
1724
  if (this.#identityAttempts.length > 0) {
1681
1725
  throw new ToolError(
@@ -1706,6 +1750,40 @@ class AuthenticationSession {
1706
1750
  );
1707
1751
  return authenticatedSession(token, "password", true, this.#now());
1708
1752
  }
1753
+ async #resolveExplicitIdentity(selectedIdentity) {
1754
+ if (this.config.implicitDeviceIdentity) {
1755
+ return await resolveIdentityMaterial(selectedIdentity, this.config, this.#environment);
1756
+ }
1757
+ try {
1758
+ return await resolveIdentityMaterial(selectedIdentity, this.config, this.#environment);
1759
+ } catch (error) {
1760
+ if (!(error instanceof ToolError) || error.code !== "IDENTITY_NOT_FOUND")
1761
+ throw error;
1762
+ }
1763
+ await this.#requireDeveloperIdentityAccess();
1764
+ return await resolveIdentityMaterial(selectedIdentity, this.config, this.#environment, {
1765
+ includeOperations: false,
1766
+ includeDeveloper: true
1767
+ });
1768
+ }
1769
+ async #tryIdentityCandidates(candidates) {
1770
+ for (const candidate of candidates) {
1771
+ try {
1772
+ const session = await this.#loginWithIdentity(candidate.material);
1773
+ this.#acceptedIdentity = candidate.material;
1774
+ return session;
1775
+ } catch (error) {
1776
+ if (!isIdentityRejection(error))
1777
+ throw error;
1778
+ this.#identityAttempts.push({
1779
+ identity: candidate.material.did,
1780
+ code: error.code,
1781
+ message: sanitizeMessage(error.message)
1782
+ });
1783
+ }
1784
+ }
1785
+ return void 0;
1786
+ }
1709
1787
  async #loginWithIdentity(material) {
1710
1788
  const loginJwt = await createLoginJwt(
1711
1789
  material.subject,
@@ -1721,6 +1799,30 @@ class AuthenticationSession {
1721
1799
  );
1722
1800
  return authenticatedSession(token, "identity", true, this.#now());
1723
1801
  }
1802
+ async #checkDevelopmentMode() {
1803
+ this.#developmentMode = await this.#readDevelopmentMode(this.config);
1804
+ return this.#developmentMode;
1805
+ }
1806
+ async #requireDeveloperIdentityAccess() {
1807
+ const decision = await this.#checkDevelopmentMode();
1808
+ if (decision.state === "enabled")
1809
+ return;
1810
+ this.#acceptedIdentity = void 0;
1811
+ if (decision.state === "disabled") {
1812
+ throw new ToolError(
1813
+ "DEVELOPER_IDENTITY_DISABLED",
1814
+ "the target Zone has developer mode disabled; local developer identities are unavailable",
1815
+ EXIT_AUTH
1816
+ );
1817
+ }
1818
+ throw new ToolError(
1819
+ "DEVELOPER_MODE_UNAVAILABLE",
1820
+ "the target Zone developer-mode configuration could not be verified",
1821
+ EXIT_AUTH,
1822
+ true,
1823
+ { reason: sanitizeMessage(decision.reason) }
1824
+ );
1825
+ }
1724
1826
  }
1725
1827
  function isIdentityRejection(error) {
1726
1828
  return error instanceof ToolError && IDENTITY_REJECTION_CODES.includes(error.code);
@@ -1984,12 +2086,17 @@ function displayValue(value) {
1984
2086
  const KEBAB_CASE = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
1985
2087
  const GLOBAL_OPTIONS = [
1986
2088
  { name: "config-dir", property: "configDir", type: "string", scope: "process" },
2089
+ {
2090
+ name: "allow-read",
2091
+ property: "allowRead",
2092
+ type: "string",
2093
+ scope: "process",
2094
+ repeatable: true
2095
+ },
1987
2096
  { name: "profile", property: "profile", type: "string", scope: "session" },
1988
2097
  { name: "zone", property: "zone", type: "string", scope: "session" },
1989
2098
  { name: "endpoint", property: "endpoint", type: "string", scope: "session" },
1990
2099
  { name: "identity", property: "identity", type: "string", scope: "session" },
1991
- { name: "identity-root", property: "identityRoot", type: "string", scope: "session" },
1992
- { name: "security-root", property: "securityRoot", type: "string", scope: "session" },
1993
2100
  {
1994
2101
  name: "session-token",
1995
2102
  property: "sessionToken",
@@ -4741,7 +4848,7 @@ function invalid(stage, message, entry) {
4741
4848
  function stableJsonDigest(value) {
4742
4849
  return sha256Bytes(new TextEncoder().encode(ndn.toCanonicalJsonString(value)));
4743
4850
  }
4744
- const PACKAGE_VERSION = "0.7.115";
4851
+ const PACKAGE_VERSION = "0.7.118";
4745
4852
  const TOOL_VERSION = PACKAGE_VERSION;
4746
4853
  const SDK_VERSION = PACKAGE_VERSION;
4747
4854
  const PROTOCOL_VERSION = "1";
@@ -8795,6 +8902,7 @@ function topLevelHelp(registry) {
8795
8902
  "",
8796
8903
  "Global options:",
8797
8904
  " --config-dir <path> --profile <name> --zone <host-or-did>",
8905
+ " --allow-read <path> Add a readable filesystem root (repeatable)",
8798
8906
  " --endpoint <url> --identity <did-or-name>",
8799
8907
  " --session-token <token> | --session-token-file <path>",
8800
8908
  " Prefer --session-token-file for automation; argv tokens may appear in process listings.",