buckyos 0.7.116 → 0.7.119

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.
Files changed (46) hide show
  1. package/cli/README.md +14 -10
  2. package/cli/core/argv.ts +0 -4
  3. package/cli/core/auth.ts +104 -20
  4. package/cli/core/config.ts +0 -38
  5. package/cli/core/development_mode.ts +103 -0
  6. package/cli/core/identity.ts +39 -31
  7. package/cli/core/registry.ts +0 -2
  8. package/cli/dist/cli.mjs +320 -230
  9. package/cli/dist/cli.mjs.map +1 -1
  10. package/cli/runtime/host.ts +8 -10
  11. package/cli/version.ts +1 -1
  12. package/dist/browser.cjs +1 -1
  13. package/dist/browser.mjs +59 -58
  14. package/dist/index.cjs +1 -1
  15. package/dist/index.mjs +59 -58
  16. package/dist/namelib.d.ts +4 -0
  17. package/dist/namelib.d.ts.map +1 -1
  18. package/dist/ndm_proxy-68354a89.js +13 -0
  19. package/dist/ndm_proxy-68354a89.js.map +1 -0
  20. package/dist/{ndm_proxy-a719bc58.mjs → ndm_proxy-c00bc561.mjs} +75 -58
  21. package/dist/ndm_proxy-c00bc561.mjs.map +1 -0
  22. package/dist/ndn_types-859bbd48.js +2 -0
  23. package/dist/ndn_types-859bbd48.js.map +1 -0
  24. package/dist/{ndn_types-ca94286b.mjs → ndn_types-f6c08d20.mjs} +43 -7
  25. package/dist/ndn_types-f6c08d20.mjs.map +1 -0
  26. package/dist/node.cjs +1 -1
  27. package/dist/node.mjs +59 -58
  28. package/dist/provision.cjs +1 -1
  29. package/dist/provision.mjs +1 -1
  30. package/dist/sn_client.d.ts +16 -0
  31. package/dist/sn_client.d.ts.map +1 -1
  32. package/dist/task_mgr_client.d.ts +10 -0
  33. package/dist/task_mgr_client.d.ts.map +1 -1
  34. package/dist/{tus_client-f1532e14.mjs → tus_client-573066b3.mjs} +2 -2
  35. package/dist/{tus_client-f1532e14.mjs.map → tus_client-573066b3.mjs.map} +1 -1
  36. package/dist/{tus_client-32d016c4.js → tus_client-83ad0f4f.js} +2 -2
  37. package/dist/{tus_client-32d016c4.js.map → tus_client-83ad0f4f.js.map} +1 -1
  38. package/dist/types.d.ts +1 -0
  39. package/dist/types.d.ts.map +1 -1
  40. package/package.json +1 -1
  41. package/dist/ndm_proxy-36879436.js +0 -13
  42. package/dist/ndm_proxy-36879436.js.map +0 -1
  43. package/dist/ndm_proxy-a719bc58.mjs.map +0 -1
  44. package/dist/ndn_types-c4672b04.js +0 -2
  45. package/dist/ndn_types-c4672b04.js.map +0 -1
  46. package/dist/ndn_types-ca94286b.mjs.map +0 -1
package/cli/README.md CHANGED
@@ -44,16 +44,20 @@ build scripts. Set `SOURCE_DATE_EPOCH` for byte-reproducible Node/Deno PIKG buil
44
44
 
45
45
  ## Policy and identity
46
46
 
47
- The developer policy grants the package root, cwd, Tool config, and explicit input/output paths. It
48
- does not include `$BUCKYOS_ROOT` or scan system credentials. The system policy additionally grants
49
- its paired BuckyOS root. Both policies constrain environment names and subprocesses; only PIKG
50
- Docker inputs may invoke `docker`.
51
-
52
- An explicitly selected identity is tried once. Without an explicit identity, readable candidate
53
- roots are scanned in the frozen source order `explicit`, `tool`, `environment`, `buckyos-root`, with
54
- directories sorted and a maximum of eight usable candidates. Rotation occurs only during session
55
- creation and only for `IDENTITY_KIND_NOT_ACCEPTED` or `AUTHENTICATION_REJECTED`; timeouts, network
56
- errors, capability mismatches and RBAC denial never rotate. Private keys and tokens are never shown.
47
+ The developer policy grants the package root, cwd, Tool config, the read-only `~/.buckyos` and
48
+ `~/.buckycli` roots, and explicit non-identity input/output paths. It does not include
49
+ `$BUCKYOS_ROOT` or scan system credentials. The system policy additionally grants its paired BuckyOS
50
+ root. Both policies constrain environment names and subprocesses; only PIKG Docker inputs may invoke
51
+ `docker`.
52
+
53
+ `~/.buckyos` is the regular operations identity root and is always eligible. Before touching the
54
+ developer-only `~/.buckycli` root, the Tool reads `system.dev_mode.get` from the target Zone and
55
+ requires a valid `BuckyOSDevConfig` with `enabled: true`. Automatic discovery tries `~/.buckyos`
56
+ first and consults developer mode only if no operations identity succeeds; the combined search is
57
+ limited to eight usable candidates. An explicitly selected identity is tried once. Rotation occurs
58
+ only during session creation and only for
59
+ `IDENTITY_KIND_NOT_ACCEPTED` or `AUTHENTICATION_REJECTED`; timeouts, network errors, capability
60
+ mismatches and RBAC denial never rotate. Private keys and tokens are never shown.
57
61
 
58
62
  ## Verification
59
63
 
package/cli/core/argv.ts CHANGED
@@ -12,8 +12,6 @@ export interface GlobalOptions {
12
12
  zone?: string
13
13
  endpoint?: string
14
14
  identity?: string
15
- identityRoot?: string
16
- securityRoot?: string
17
15
  sessionToken?: string
18
16
  sessionTokenFile?: string
19
17
  cli?: boolean
@@ -50,8 +48,6 @@ const STRING_GLOBALS = new Map<string, keyof GlobalOptions>([
50
48
  ['zone', 'zone'],
51
49
  ['endpoint', 'endpoint'],
52
50
  ['identity', 'identity'],
53
- ['identity-root', 'identityRoot'],
54
- ['security-root', 'securityRoot'],
55
51
  ['session-token', 'sessionToken'],
56
52
  ['session-token-file', 'sessionTokenFile'],
57
53
  ['output', 'output'],
package/cli/core/auth.ts CHANGED
@@ -5,9 +5,16 @@ import {
5
5
  VerifyHubClient,
6
6
  } from 'buckyos/node'
7
7
  import { type Environment, readEnvironment, type ResolvedConfig } from './config.ts'
8
+ import {
9
+ type DevelopmentModeDecision,
10
+ type DevelopmentModeReader,
11
+ readTargetDevelopmentMode,
12
+ } from './development_mode.ts'
8
13
  import { EXIT_AUTH, sanitizeMessage, ToolError } from './errors.ts'
9
14
  import {
15
+ IDENTITY_CANDIDATE_LIMIT,
10
16
  IDENTITY_REJECTION_CODES,
17
+ type IdentityCandidate,
11
18
  type IdentityMaterial,
12
19
  resolveIdentityMaterial,
13
20
  scanIdentityCandidates,
@@ -57,6 +64,7 @@ export interface AuthenticationDependencies {
57
64
  transport?: AuthenticationTransport
58
65
  readPassword?: (prompt: string) => Promise<string>
59
66
  readUsername?: (prompt: string) => Promise<string>
67
+ readDevelopmentMode?: DevelopmentModeReader
60
68
  now?: () => number
61
69
  }
62
70
 
@@ -78,9 +86,11 @@ export class AuthenticationSession implements SessionController {
78
86
  readonly #transport: AuthenticationTransport
79
87
  readonly #readPassword: (prompt: string) => Promise<string>
80
88
  readonly #readUsername: (prompt: string) => Promise<string>
89
+ readonly #readDevelopmentMode: DevelopmentModeReader
81
90
  readonly #now: () => number
82
91
  #session?: AuthenticatedSession
83
92
  #acceptedIdentity?: IdentityMaterial
93
+ #developmentMode?: DevelopmentModeDecision
84
94
  #identityAttempts: Array<{ identity: string; code: string; message: string }> = []
85
95
 
86
96
  constructor(
@@ -93,6 +103,7 @@ export class AuthenticationSession implements SessionController {
93
103
  this.#transport = dependencies.transport ?? new SdkAuthenticationTransport()
94
104
  this.#readPassword = dependencies.readPassword ?? readSecret
95
105
  this.#readUsername = dependencies.readUsername ?? readVisible
106
+ this.#readDevelopmentMode = dependencies.readDevelopmentMode ?? readTargetDevelopmentMode
96
107
  this.#now = dependencies.now ?? Date.now
97
108
  }
98
109
 
@@ -146,6 +157,7 @@ export class AuthenticationSession implements SessionController {
146
157
  renewable: session.renewable,
147
158
  expires_at: expiresAt ?? null,
148
159
  remaining_seconds: remainingSeconds,
160
+ developer_mode: this.#developmentMode?.state ?? null,
149
161
  identity_attempts: this.#identityAttempts,
150
162
  }
151
163
  }
@@ -180,34 +192,43 @@ export class AuthenticationSession implements SessionController {
180
192
  const injected = this.#environment.BUCKYOS_APPCLIENT_SESSION_TOKEN?.trim()
181
193
  if (injected) return externalSession(injected, 'environment', this.#now())
182
194
 
183
- if (this.#acceptedIdentity) return await this.#loginWithIdentity(this.#acceptedIdentity)
195
+ if (this.#acceptedIdentity) {
196
+ if (this.#acceptedIdentity.source === 'buckycli-home') {
197
+ await this.#requireDeveloperIdentityAccess()
198
+ }
199
+ return await this.#loginWithIdentity(this.#acceptedIdentity)
200
+ }
184
201
 
185
202
  if (this.config.identity) {
186
- const material = await resolveIdentityMaterial(
187
- this.config.identity,
188
- this.config,
189
- this.#environment,
190
- )
203
+ const material = await this.#resolveExplicitIdentity(this.config.identity)
191
204
  const session = await this.#loginWithIdentity(material)
192
205
  this.#acceptedIdentity = material
193
206
  return session
194
207
  }
195
208
 
196
- const candidates = await scanIdentityCandidates(this.config, this.#environment)
197
209
  this.#identityAttempts = []
198
- for (const candidate of candidates.candidates) {
199
- try {
200
- const session = await this.#loginWithIdentity(candidate.material)
201
- this.#acceptedIdentity = candidate.material
202
- return session
203
- } catch (error) {
204
- if (!isIdentityRejection(error)) throw error
205
- this.#identityAttempts.push({
206
- identity: candidate.material.did,
207
- code: error.code,
208
- message: sanitizeMessage(error.message),
209
- })
210
- }
210
+ const operations = await scanIdentityCandidates(this.config, this.#environment)
211
+ const operationsSession = await this.#tryIdentityCandidates(operations.candidates)
212
+ if (operationsSession) return operationsSession
213
+
214
+ const developmentMode = await this.#checkDevelopmentMode()
215
+ if (
216
+ developmentMode.state === 'enabled' &&
217
+ operations.candidates.length < IDENTITY_CANDIDATE_LIMIT
218
+ ) {
219
+ const operationsDids = new Set(
220
+ operations.candidates.map((candidate) => candidate.material.did),
221
+ )
222
+ const developer = await scanIdentityCandidates(this.config, this.#environment, {
223
+ includeOperations: false,
224
+ includeDeveloper: true,
225
+ })
226
+ const remaining = IDENTITY_CANDIDATE_LIMIT - operations.candidates.length
227
+ const candidates = developer.candidates.filter((candidate) =>
228
+ !operationsDids.has(candidate.material.did)
229
+ ).slice(0, remaining)
230
+ const developerSession = await this.#tryIdentityCandidates(candidates)
231
+ if (developerSession) return developerSession
211
232
  }
212
233
  if (this.#identityAttempts.length > 0) {
213
234
  throw new ToolError(
@@ -241,6 +262,44 @@ export class AuthenticationSession implements SessionController {
241
262
  return authenticatedSession(token, 'password', true, this.#now())
242
263
  }
243
264
 
265
+ async #resolveExplicitIdentity(selectedIdentity: string): Promise<IdentityMaterial> {
266
+ if (this.config.implicitDeviceIdentity) {
267
+ return await resolveIdentityMaterial(selectedIdentity, this.config, this.#environment)
268
+ }
269
+
270
+ try {
271
+ return await resolveIdentityMaterial(selectedIdentity, this.config, this.#environment)
272
+ } catch (error) {
273
+ if (!(error instanceof ToolError) || error.code !== 'IDENTITY_NOT_FOUND') throw error
274
+ }
275
+
276
+ await this.#requireDeveloperIdentityAccess()
277
+ return await resolveIdentityMaterial(selectedIdentity, this.config, this.#environment, {
278
+ includeOperations: false,
279
+ includeDeveloper: true,
280
+ })
281
+ }
282
+
283
+ async #tryIdentityCandidates(
284
+ candidates: readonly IdentityCandidate[],
285
+ ): Promise<AuthenticatedSession | undefined> {
286
+ for (const candidate of candidates) {
287
+ try {
288
+ const session = await this.#loginWithIdentity(candidate.material)
289
+ this.#acceptedIdentity = candidate.material
290
+ return session
291
+ } catch (error) {
292
+ if (!isIdentityRejection(error)) throw error
293
+ this.#identityAttempts.push({
294
+ identity: candidate.material.did,
295
+ code: error.code,
296
+ message: sanitizeMessage(error.message),
297
+ })
298
+ }
299
+ }
300
+ return undefined
301
+ }
302
+
244
303
  async #loginWithIdentity(material: IdentityMaterial): Promise<AuthenticatedSession> {
245
304
  const loginJwt = await createLoginJwt(
246
305
  material.subject,
@@ -256,6 +315,31 @@ export class AuthenticationSession implements SessionController {
256
315
  )
257
316
  return authenticatedSession(token, 'identity', true, this.#now())
258
317
  }
318
+
319
+ async #checkDevelopmentMode(): Promise<DevelopmentModeDecision> {
320
+ this.#developmentMode = await this.#readDevelopmentMode(this.config)
321
+ return this.#developmentMode
322
+ }
323
+
324
+ async #requireDeveloperIdentityAccess(): Promise<void> {
325
+ const decision = await this.#checkDevelopmentMode()
326
+ if (decision.state === 'enabled') return
327
+ this.#acceptedIdentity = undefined
328
+ if (decision.state === 'disabled') {
329
+ throw new ToolError(
330
+ 'DEVELOPER_IDENTITY_DISABLED',
331
+ 'the target Zone has developer mode disabled; local developer identities are unavailable',
332
+ EXIT_AUTH,
333
+ )
334
+ }
335
+ throw new ToolError(
336
+ 'DEVELOPER_MODE_UNAVAILABLE',
337
+ 'the target Zone developer-mode configuration could not be verified',
338
+ EXIT_AUTH,
339
+ true,
340
+ { reason: sanitizeMessage(decision.reason) },
341
+ )
342
+ }
259
343
  }
260
344
 
261
345
  function isIdentityRejection(error: unknown): error is ToolError {
@@ -69,8 +69,6 @@ export const ENVIRONMENT_NAMES = [
69
69
  'BUCKYOS_TOOL_ENDPOINT',
70
70
  'BUCKYOS_TOOL_IDENTITY',
71
71
  'BUCKYOS_TOOL_OUTPUT',
72
- 'BUCKYOS_IDENTITY_ROOT',
73
- 'BUCKYOS_SECURITY_ROOT',
74
72
  'BUCKYOS_APPCLIENT_SESSION_TOKEN',
75
73
  'BUCKYOS_ROOT',
76
74
  ] as const
@@ -229,38 +227,6 @@ export async function resolveConfig(
229
227
  undefined,
230
228
  sources,
231
229
  )
232
- const identityRoot = select(
233
- 'identity_root',
234
- explicit.identityRoot,
235
- environment.BUCKYOS_IDENTITY_ROOT,
236
- undefined,
237
- undefined,
238
- sources,
239
- )
240
- const securityRoot = select(
241
- 'security_root',
242
- explicit.securityRoot,
243
- environment.BUCKYOS_SECURITY_ROOT,
244
- undefined,
245
- undefined,
246
- sources,
247
- )
248
- if (!!explicit.identityRoot !== !!explicit.securityRoot) {
249
- throw new UsageError(
250
- 'IDENTITY_ROOT_PAIR_REQUIRED',
251
- '--identity-root and --security-root must be provided together',
252
- )
253
- }
254
- if (
255
- !!environment.BUCKYOS_IDENTITY_ROOT !== !!environment.BUCKYOS_SECURITY_ROOT &&
256
- !explicit.identityRoot
257
- ) {
258
- throw new UsageError(
259
- 'IDENTITY_ROOT_PAIR_REQUIRED',
260
- 'BUCKYOS_IDENTITY_ROOT and BUCKYOS_SECURITY_ROOT must be provided together',
261
- )
262
- }
263
-
264
230
  let output: OutputFormat
265
231
  if (options.interactive && explicit.output === undefined) {
266
232
  output = 'table'
@@ -297,8 +263,6 @@ export async function resolveConfig(
297
263
  zone,
298
264
  endpoint,
299
265
  identity,
300
- identityRoot,
301
- securityRoot,
302
266
  sessionToken,
303
267
  sessionTokenFile: explicit.sessionTokenFile,
304
268
  output,
@@ -347,8 +311,6 @@ export function localResolvedConfig(
347
311
  zone: explicit.zone ?? environment.BUCKYOS_TOOL_ZONE,
348
312
  endpoint: explicit.endpoint ?? environment.BUCKYOS_TOOL_ENDPOINT,
349
313
  identity: explicit.identity ?? environment.BUCKYOS_TOOL_IDENTITY,
350
- identityRoot: explicit.identityRoot ?? environment.BUCKYOS_IDENTITY_ROOT,
351
- securityRoot: explicit.securityRoot ?? environment.BUCKYOS_SECURITY_ROOT,
352
314
  sessionToken: explicit.sessionToken ?? environment.BUCKYOS_APPCLIENT_SESSION_TOKEN,
353
315
  sessionTokenFile: explicit.sessionTokenFile,
354
316
  output,
@@ -0,0 +1,103 @@
1
+ import { buckyos } from 'buckyos/node'
2
+ import type { ResolvedConfig } from './config.ts'
3
+ import { resolveServiceUrl } from './runtime.ts'
4
+
5
+ export const BUCKYOS_DEV_CONFIG_KEY = 'system/buckyos_dev_config'
6
+ export const BUCKYOS_DEV_CONFIG_SCHEMA_VERSION = 1
7
+
8
+ export interface BuckyOSDevConfig {
9
+ schema_version: 1
10
+ enabled: boolean
11
+ enabled_at: number | null
12
+ enabled_by: string | null
13
+ }
14
+
15
+ export type DevelopmentModeDecision =
16
+ | { state: 'enabled'; config: BuckyOSDevConfig }
17
+ | { state: 'disabled'; config: BuckyOSDevConfig }
18
+ | { state: 'unavailable'; reason: string }
19
+
20
+ export type DevelopmentModeReader = (
21
+ config: ResolvedConfig,
22
+ ) => Promise<DevelopmentModeDecision>
23
+
24
+ interface PublicRpcClient {
25
+ call<T>(method: string, params: Record<string, unknown>): Promise<T>
26
+ }
27
+
28
+ /**
29
+ * Read the target Zone's public developer-mode setting.
30
+ *
31
+ * This deliberately uses control-panel's unauthenticated `system.dev_mode.get`
32
+ * endpoint: developer mode must be known before a local identity is touched or
33
+ * exchanged at verify-hub. Every error is converted to `unavailable`, which is
34
+ * a fail-closed result for the identity resolver.
35
+ */
36
+ export async function readTargetDevelopmentMode(
37
+ config: ResolvedConfig,
38
+ ): Promise<DevelopmentModeDecision> {
39
+ try {
40
+ const url = resolveServiceUrl(config, 'control-panel')
41
+ const client = new buckyos.kRPCClient(url) as unknown as PublicRpcClient
42
+ const value = await withTimeout(
43
+ client.call<unknown>('system.dev_mode.get', {}),
44
+ config.timeoutMs,
45
+ )
46
+ const parsed = parseBuckyOSDevConfig(value)
47
+ return parsed.enabled
48
+ ? { state: 'enabled', config: parsed }
49
+ : { state: 'disabled', config: parsed }
50
+ } catch (error) {
51
+ return {
52
+ state: 'unavailable',
53
+ reason: error instanceof Error ? error.message : String(error),
54
+ }
55
+ }
56
+ }
57
+
58
+ export function parseBuckyOSDevConfig(value: unknown): BuckyOSDevConfig {
59
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
60
+ throw new Error('BuckyOSDevConfig must be an object')
61
+ }
62
+ const config = value as Record<string, unknown>
63
+ const keys = Object.keys(config).sort()
64
+ const expectedKeys = ['enabled', 'enabled_at', 'enabled_by', 'schema_version']
65
+ if (JSON.stringify(keys) !== JSON.stringify(expectedKeys)) {
66
+ throw new Error('BuckyOSDevConfig contains missing or unsupported fields')
67
+ }
68
+ if (config.schema_version !== BUCKYOS_DEV_CONFIG_SCHEMA_VERSION) {
69
+ throw new Error(`unsupported BuckyOSDevConfig schema_version ${String(config.schema_version)}`)
70
+ }
71
+ if (typeof config.enabled !== 'boolean') {
72
+ throw new Error('BuckyOSDevConfig enabled must be a boolean')
73
+ }
74
+ const enabledAt = config.enabled_at
75
+ const enabledBy = config.enabled_by
76
+ const hasAuditFields = Number.isSafeInteger(enabledAt) && (enabledAt as number) > 0 &&
77
+ typeof enabledBy === 'string' && enabledBy.trim().length > 0
78
+ const hasNoAuditFields = enabledAt === null && enabledBy === null
79
+ if (!hasAuditFields && !hasNoAuditFields) {
80
+ throw new Error('BuckyOSDevConfig enabled_at and enabled_by must be valid and present together')
81
+ }
82
+ if (config.enabled && !hasAuditFields) {
83
+ throw new Error('enabled BuckyOSDevConfig requires enable audit fields')
84
+ }
85
+ return {
86
+ schema_version: 1,
87
+ enabled: config.enabled,
88
+ enabled_at: enabledAt as number | null,
89
+ enabled_by: enabledBy as string | null,
90
+ }
91
+ }
92
+
93
+ async function withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T> {
94
+ let timer: ReturnType<typeof setTimeout> | undefined
95
+ const timeout = new Promise<never>((_, reject) => {
96
+ timer = setTimeout(() => reject(new Error('developer-mode lookup timed out')), timeoutMs)
97
+ })
98
+ try {
99
+ return await Promise.race([promise, timeout])
100
+ } finally {
101
+ if (timer !== undefined) clearTimeout(timer)
102
+ }
103
+ }
@@ -17,12 +17,18 @@ export interface IdentityMaterial {
17
17
  documentPath: string
18
18
  privateKeyPath: string
19
19
  privateKeyPem: string
20
+ source: IdentityRootPair['source']
20
21
  }
21
22
 
22
23
  export interface IdentityRootPair {
23
24
  publicRoot: string
24
25
  securityRoot: string
25
- source: 'explicit' | 'tool' | 'environment' | 'buckyos-root'
26
+ source: 'buckyos-home' | 'buckycli-home' | 'current-device'
27
+ }
28
+
29
+ export interface IdentityRootOptions {
30
+ includeOperations?: boolean
31
+ includeDeveloper?: boolean
26
32
  }
27
33
 
28
34
  export const IDENTITY_CANDIDATE_LIMIT = 8
@@ -137,37 +143,31 @@ export async function readCurrentDeviceIdentity(
137
143
  export function identityRootPairs(
138
144
  config: ResolvedConfig,
139
145
  environment: Environment = readEnvironment(),
146
+ options: IdentityRootOptions = {},
140
147
  ): IdentityRootPair[] {
148
+ const device = config.implicitDeviceIdentity
149
+ if (device) {
150
+ return [{
151
+ publicRoot: device.publicRoot,
152
+ securityRoot: device.securityRoot,
153
+ source: 'current-device',
154
+ }]
155
+ }
156
+
157
+ const home = environment.HOME ?? environment.USERPROFILE ?? getHost().homeDir()
141
158
  const pairs: IdentityRootPair[] = []
142
- if (config.identityRoot && config.securityRoot) {
159
+ if (options.includeOperations ?? true) {
143
160
  pairs.push({
144
- publicRoot: config.identityRoot,
145
- securityRoot: config.securityRoot,
146
- source: 'explicit',
161
+ publicRoot: join(home, '.buckyos', 'local', 'identity'),
162
+ securityRoot: join(home, '.buckyos', 'security'),
163
+ source: 'buckyos-home',
147
164
  })
148
165
  }
149
- pairs.push({
150
- publicRoot: join(config.configDir, 'local', 'identity'),
151
- securityRoot: join(config.configDir, 'security'),
152
- source: 'tool',
153
- })
154
- if (environment.BUCKYOS_IDENTITY_ROOT && environment.BUCKYOS_SECURITY_ROOT) {
155
- const duplicate = config.identityRoot === environment.BUCKYOS_IDENTITY_ROOT &&
156
- config.securityRoot === environment.BUCKYOS_SECURITY_ROOT
157
- if (!duplicate) {
158
- pairs.push({
159
- publicRoot: environment.BUCKYOS_IDENTITY_ROOT,
160
- securityRoot: environment.BUCKYOS_SECURITY_ROOT,
161
- source: 'environment',
162
- })
163
- }
164
- }
165
- if (getHost().policy.distribution === 'system') {
166
- const buckyosRoot = environment.BUCKYOS_ROOT ?? defaultBuckyOSRoot(environment)
166
+ if (options.includeDeveloper ?? false) {
167
167
  pairs.push({
168
- publicRoot: join(buckyosRoot, 'local', 'identity'),
169
- securityRoot: join(buckyosRoot, 'security'),
170
- source: 'buckyos-root',
168
+ publicRoot: join(home, '.buckycli', 'local', 'identity'),
169
+ securityRoot: join(home, '.buckycli', 'security'),
170
+ source: 'buckycli-home',
171
171
  })
172
172
  }
173
173
  return deduplicatePairs(pairs)
@@ -177,11 +177,12 @@ export async function resolveIdentityMaterial(
177
177
  selectedIdentity: string,
178
178
  config: ResolvedConfig,
179
179
  environment: Environment = readEnvironment(),
180
+ options: IdentityRootOptions = {},
180
181
  ): Promise<IdentityMaterial> {
181
182
  const selected = selectedIdentity.trim()
182
183
  if (!selected) throw new UsageError('IDENTITY_REQUIRED', 'identity is empty')
183
184
 
184
- for (const roots of identityRootPairs(config, environment)) {
185
+ for (const roots of identityRootPairs(config, environment, options)) {
185
186
  for (const directory of await candidateDirectories(roots.publicRoot, selected)) {
186
187
  const material = await loadIdentityMaterial(roots, directory, selected)
187
188
  if (material) return material
@@ -198,11 +199,12 @@ export async function resolveIdentityMaterial(
198
199
  export async function scanIdentityCandidates(
199
200
  config: ResolvedConfig,
200
201
  environment: Environment = readEnvironment(),
202
+ options: IdentityRootOptions = {},
201
203
  ): Promise<IdentityCandidateScan> {
202
204
  const candidates: IdentityCandidate[] = []
203
205
  const skipped: IdentityCandidateScan['skipped'] = []
204
206
  const seen = new Set<string>()
205
- const roots = identityRootPairs(config, environment)
207
+ const roots = identityRootPairs(config, environment, options)
206
208
  for (const pair of roots) {
207
209
  let directories: string[]
208
210
  try {
@@ -256,7 +258,7 @@ export async function scanIdentityCandidates(
256
258
  if (candidates.length >= IDENTITY_CANDIDATE_LIMIT) break
257
259
  }
258
260
  return {
259
- order: Object.freeze(['explicit', 'tool', 'environment', 'buckyos-root']),
261
+ order: Object.freeze(roots.map((pair) => pair.source)),
260
262
  limit: IDENTITY_CANDIDATE_LIMIT,
261
263
  candidates,
262
264
  skipped,
@@ -266,10 +268,13 @@ export async function scanIdentityCandidates(
266
268
  export async function identityCandidateView(
267
269
  config: ResolvedConfig,
268
270
  environment: Environment = readEnvironment(),
271
+ developerModeEnabled = false,
269
272
  ): Promise<Record<string, unknown>> {
270
273
  if (config.identity) {
271
274
  try {
272
- const material = await resolveIdentityMaterial(config.identity, config, environment)
275
+ const material = await resolveIdentityMaterial(config.identity, config, environment, {
276
+ includeDeveloper: developerModeEnabled,
277
+ })
273
278
  return {
274
279
  mode: 'explicit',
275
280
  order: [material.did],
@@ -294,7 +299,9 @@ export async function identityCandidateView(
294
299
  }
295
300
  }
296
301
  }
297
- const scan = await scanIdentityCandidates(config, environment)
302
+ const scan = await scanIdentityCandidates(config, environment, {
303
+ includeDeveloper: developerModeEnabled,
304
+ })
298
305
  return {
299
306
  mode: 'search',
300
307
  order: scan.order,
@@ -336,6 +343,7 @@ async function loadIdentityMaterial(
336
343
  documentPath,
337
344
  privateKeyPath,
338
345
  privateKeyPem,
346
+ source: roots.source,
339
347
  }
340
348
  } catch (error) {
341
349
  if (!isHostError(error, 'NotFound')) throw error
@@ -17,8 +17,6 @@ const GLOBAL_OPTIONS = [
17
17
  { name: 'zone', property: 'zone', type: 'string', scope: 'session' },
18
18
  { name: 'endpoint', property: 'endpoint', type: 'string', scope: 'session' },
19
19
  { name: 'identity', property: 'identity', type: 'string', scope: 'session' },
20
- { name: 'identity-root', property: 'identityRoot', type: 'string', scope: 'session' },
21
- { name: 'security-root', property: 'securityRoot', type: 'string', scope: 'session' },
22
20
  {
23
21
  name: 'session-token',
24
22
  property: 'sessionToken',