@knpkv/alchemy-console 0.3.2 → 0.4.1

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 (80) hide show
  1. package/README.md +78 -23
  2. package/dist/api.d.ts +16 -2
  3. package/dist/api.d.ts.map +1 -1
  4. package/dist/client/assets/empty-state-creature-CWMFWDpa.svg +60 -0
  5. package/dist/client/assets/index-C5EGKyzJ.js +58 -0
  6. package/dist/client/assets/{index-CrA-MUdX.css → index-CHwWf6VU.css} +1 -1
  7. package/dist/client/index.html +2 -2
  8. package/dist/index.d.ts +1 -1
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +1 -1
  11. package/dist/index.js.map +1 -1
  12. package/dist/internal/resource-identity.d.ts +7 -0
  13. package/dist/internal/resource-identity.d.ts.map +1 -0
  14. package/dist/internal/resource-identity.js +180 -0
  15. package/dist/internal/resource-identity.js.map +1 -0
  16. package/dist/local-state.d.ts.map +1 -1
  17. package/dist/local-state.js +11 -1
  18. package/dist/local-state.js.map +1 -1
  19. package/dist/outputs.d.ts +5 -0
  20. package/dist/outputs.d.ts.map +1 -0
  21. package/dist/outputs.js +15 -0
  22. package/dist/outputs.js.map +1 -0
  23. package/dist/presentation/ResourceDetailView.d.ts +13 -0
  24. package/dist/presentation/ResourceDetailView.d.ts.map +1 -0
  25. package/dist/presentation/ResourceDetailView.js +12 -0
  26. package/dist/presentation/ResourceDetailView.js.map +1 -0
  27. package/dist/presentation/ResourceTree.d.ts +18 -0
  28. package/dist/presentation/ResourceTree.d.ts.map +1 -0
  29. package/dist/presentation/ResourceTree.js +36 -0
  30. package/dist/presentation/ResourceTree.js.map +1 -0
  31. package/dist/presentation/StackDetailView.d.ts +15 -0
  32. package/dist/presentation/StackDetailView.d.ts.map +1 -0
  33. package/dist/presentation/StackDetailView.js +36 -0
  34. package/dist/presentation/StackDetailView.js.map +1 -0
  35. package/dist/presentation/StackListView.d.ts +4 -2
  36. package/dist/presentation/StackListView.d.ts.map +1 -1
  37. package/dist/presentation/StackListView.js +4 -4
  38. package/dist/presentation/StackListView.js.map +1 -1
  39. package/dist/presentation/detail-model.d.ts +40 -0
  40. package/dist/presentation/detail-model.d.ts.map +1 -0
  41. package/dist/presentation/detail-model.js +2 -0
  42. package/dist/presentation/detail-model.js.map +1 -0
  43. package/dist/presentation/detail-parts.d.ts +28 -0
  44. package/dist/presentation/detail-parts.d.ts.map +1 -0
  45. package/dist/presentation/detail-parts.js +42 -0
  46. package/dist/presentation/detail-parts.js.map +1 -0
  47. package/dist/presentation/resource-graph.d.ts +4 -0
  48. package/dist/presentation/resource-graph.d.ts.map +1 -0
  49. package/dist/presentation/resource-graph.js +25 -0
  50. package/dist/presentation/resource-graph.js.map +1 -0
  51. package/dist/s3-live.d.ts +1 -0
  52. package/dist/s3-live.d.ts.map +1 -1
  53. package/dist/s3-live.js +20 -14
  54. package/dist/s3-live.js.map +1 -1
  55. package/dist/s3-reader.d.ts +2 -1
  56. package/dist/s3-reader.d.ts.map +1 -1
  57. package/dist/s3-reader.js.map +1 -1
  58. package/dist/s3-state.d.ts.map +1 -1
  59. package/dist/s3-state.js +13 -1
  60. package/dist/s3-state.js.map +1 -1
  61. package/dist/schema.d.ts +22 -3
  62. package/dist/schema.d.ts.map +1 -1
  63. package/dist/schema.js +16 -5
  64. package/dist/schema.js.map +1 -1
  65. package/dist/server/Catalog.d.ts.map +1 -1
  66. package/dist/server/Catalog.js +15 -2
  67. package/dist/server/Catalog.js.map +1 -1
  68. package/dist/state.d.ts +2 -0
  69. package/dist/state.d.ts.map +1 -1
  70. package/dist/state.js +8 -8
  71. package/dist/state.js.map +1 -1
  72. package/dist/views/empty-state-creature.svg +60 -0
  73. package/dist/views/index.js +705 -105
  74. package/dist/views/style.css +1 -1
  75. package/dist/views.d.ts +5 -0
  76. package/dist/views.d.ts.map +1 -1
  77. package/dist/views.js +2 -1
  78. package/dist/views.js.map +1 -1
  79. package/package.json +3 -3
  80. package/dist/client/assets/index-CNQi3IQH.js +0 -58
package/README.md CHANGED
@@ -12,8 +12,12 @@ The browser shows stacks grouped by app, with desktop columns and compact phone
12
12
  rows. Read again starts a fresh discovery request and clears the previous rows.
13
13
  Loading, empty, incomplete discovery and owner sign-in failures are explicit.
14
14
  Missing metadata reads Unknown; zero resources remains zero. No polling, cached
15
- rows, plan, apply or destroy runs. Stage names and resource counts are plain text;
16
- the detail and resources screens are a later change.
15
+ rows, plan, apply or destroy runs. Stage names and known resource counts open the
16
+ stack UI. Resources lists actual returned resource metadata; State groups the
17
+ same resources by their returned construct parent and exact type. Resource rows
18
+ open identity and dependency details. Outputs distinguishes Hidden values, proven
19
+ empty state and unavailable formats. No raw JSON click target, Plan tab or Preview
20
+ changes action is present.
17
21
 
18
22
  The standalone wrapper shows Alchemy console, the actual local listener address
19
23
  and owner session status. It adds no Relay host inventory or shell navigation.
@@ -55,7 +59,7 @@ process-scoped identifiers with no encoded locator. They remain stable across
55
59
  scans while a stack remains present, then expire when it disappears or the server
56
60
  restarts. They are normalized, authenticated client-visible identifiers; the
57
61
  server never persists them. Do not emit them or stack metadata through public
58
- routes or telemetry. Browsers must not persist private provider coordinates.
62
+ routes or telemetry. Browsers must not persist provider coordinates or authenticated stack/resource metadata.
59
63
 
60
64
  Discovery reports sanitized reason codes for failed projects, SSO profiles and
61
65
  buckets while continuing other sources. A discovered entrypoint with no state
@@ -76,13 +80,18 @@ requests. Listener and request Host are restricted to the bound loopback address
76
80
 
77
81
  ## Browser views
78
82
 
79
- Import `StackListView` from `@knpkv/alchemy-console/views` and its stylesheet from
80
- `@knpkv/alchemy-console/views/style.css`. Mount it inside a Rly style/theme boundary.
83
+ Import `StackListView`, `StackDetailView` and `ResourceDetailView` from `@knpkv/alchemy-console/views` and its stylesheet from
84
+ `@knpkv/alchemy-console/views/style.css`. Mount them inside a Rly style/theme boundary.
81
85
  Views consumers supply the optional React, React DOM and Rly peers; the standalone
82
86
  browser shell already bundles them.
83
- The application supplies `state`, a safe product label and host sign-in instruction,
84
- and `onReadAgain`. The views fetch nothing, read no
85
- configuration, and retain no data. `Ready.checkedAt` describes the actual completed
87
+ The application supplies normalized presentation data, a product label, host
88
+ sign-in instruction, read actions and real UI destinations. `StackListView` accepts
89
+ optional `stackHref`; without a destination, stages and counts remain text.
90
+ `StackDetailView` has controlled Resources/Outputs/State tabs and `resourceHref`.
91
+ `ResourceDetailView` accepts `stackHref` and `resourceHref`. Views fetch nothing,
92
+ read no configuration and persist no data. The standalone detail wrapper adds
93
+ breadcrumbs and Read again outside the referenced module content. Phone list
94
+ links use separated stage/count lines to keep their 44px targets disjoint. `Ready.checkedAt` describes the actual completed
86
95
  request. Replace state with `Loading` before reading; failures and unauthorized
87
96
  states contain no previous rows. Only normalized authenticated client-visible
88
97
  stack metadata belongs here, never raw provider locators or credentials.
@@ -90,9 +99,15 @@ stack metadata belongs here, never raw provider locators or credentials.
90
99
  The standalone client uses the typed API and sends same-origin cookies with
91
100
  no-store requests. The fragment is removed from history before its bootstrap
92
101
  POST. Tokens never enter storage, logs or telemetry. CSS and font assets are
93
- served from the same listener with a self-only CSP, without inline styles. The
102
+ served from the same listener with a self-only CSP and no application-authored inline
103
+ style markup. Pinned Rly/Radix tabs assign `outline: none` on their tab list and
104
+ `animation-duration: 0s` on an initially mounted panel through trusted CSSOM
105
+ properties. Those framework effects require no CSP relaxation; serialized inline
106
+ style markup remains blocked. The
94
107
  decorative empty-state SVG uses the approved Stacks illustration from this MIT
95
- repository, with presentation attributes rather than inline styles.
108
+ repository, with presentation attributes rather than inline styles. The State empty panel uses
109
+ the original decorative 56px Stacks2c procedural creature from the same MIT repo;
110
+ both SVGs remain external same-origin assets.
96
111
 
97
112
  ## Data readers
98
113
 
@@ -113,7 +128,7 @@ unresolved Alchemy version return `null`. Invalid lockfiles fail explicitly.
113
128
  `.alchemy/state/<app>/<stage>/<encoded-fqn>.json` for beta.74 and beta.77. Other
114
129
  versions, including unknown versions, fail with `unsupported-version`. Resource
115
130
  metadata includes FQN, logical ID, type, status, and namespace parent. Action rows
116
- are validated and excluded. Stack output and temporary files are ignored. Missing
131
+ are validated and excluded. Temporary files are ignored. Missing
117
132
  state returns `available: false`; an existing empty stage returns an empty resource
118
133
  list with `available: true`. JSON names must match the stored FQN encoding.
119
134
 
@@ -148,10 +163,11 @@ An API can report these failures per profile while continuing other profiles.
148
163
  keys. `listS3Versions(bucket, prefix)` returns persisted object version coordinates
149
164
  and deletion markers. Both S3 continuation markers are retained; repeated markers
150
165
  fail. `readS3State(stack, alchemyVersion?)` reads each latest non-deleted resource
151
- using its listed version ID. Stack outputs and temporary files are excluded.
166
+ using its listed version ID. Temporary files are excluded; supported-version stack outputs use their selected
167
+ current version and the key-only decoder below.
152
168
  `readS3Resource(stack, key, versionId)` reads one historical version after checking
153
169
  that the key belongs to that stack. JSON names must match the resource FQN.
154
- Latest deletion markers never resurrect an older resource. Null versions from
170
+ Latest deletion markers never resurrect an older resource or output dictionary. Null versions from
155
171
  unversioned or suspended buckets are supported. Multiple resource reads are not
156
172
  an atomic snapshot of a deployment.
157
173
 
@@ -168,10 +184,41 @@ Read-only IAM access needs `s3:ListAllMyBuckets`, `s3:ListBucketVersions`, and
168
184
  and [object reading](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObject.html)
169
185
  references. No live AWS or Alchemy stack is used by the tests.
170
186
 
171
- The flat resource list retains namespace parents, including parents without their
172
- own resource row. A consumer can group these into a tree. Provider IDs, account,
173
- region, console links, and last deploy are currently `null`. Local file timestamps
174
- do not prove a successful deployment. Readers do not invent missing metadata.
187
+ The flat resource list retains construct parents, including parents without their
188
+ own resource row. Resource-own identity is selected only from documented beta.74
189
+ and beta.77 `attr` fields. The AWS allowlist is Lambda Function, S3 Bucket,
190
+ DynamoDB Table, SQS Queue, IAM Role/Policy, CloudFront Distribution, Logs LogGroup,
191
+ ACM Certificate and CloudWatch Alarm/Dashboard. Commercial `aws` ARNs must match
192
+ the exact resource type and own name. Console links use the AWS-owned
193
+ [toolkit resource resolver](https://github.com/aws/aws-toolkit-vscode/blob/e1b0f9bc573bac1830f0b9aa7ced25a6c69bb477/packages/core/src/awsService/cloudformation/consoleLinksUtils.ts).
194
+ Other partitions and unknown/custom types have no link. S3 resource region/account
195
+ must come from that resource, never the backend bucket or SSO selector.
196
+
197
+ `Cloudflare.Worker` uses its own validated account, worker ID and worker name in
198
+ the documented service route. Local/dev identities, dispatch namespaces and
199
+ parent-owned versions have no regular service link. R2 Bucket and DNS Record may
200
+ expose validated own identity but have no console link when an official route or
201
+ required own routing identity is unproven. DNS `modifiedOn` alone supplies Last
202
+ updated, preserving valid complete UTC precision. No other generic updated time
203
+ or successful deployment time is inferred. Missing metadata remains null.
204
+ Absent provider mode can represent legacy cloud state; explicit local or invalid
205
+ modes never authorize a provider link.
206
+
207
+ `downstream` records dependents, not dependencies. The view builds a bounded
208
+ reverse index for the complete returned stack. Missing edges, missing rows,
209
+ duplicates or an incomplete graph make dependency knowledge unavailable rather
210
+ than inventing an empty dependency list. Local file timestamps never prove a
211
+ successful deployment.
212
+
213
+ The supported stack output file is a JSON root dictionary. Only validated keys
214
+ cross normalization; each value is `Hidden`, with a null description. Plaintext,
215
+ including a URL, never proves safety. Exact successfully read `{}` proves no
216
+ outputs. Missing, unreadable, malformed, scalar/array or root encoding-marker
217
+ state is unavailable, including reserved envelopes with sibling fields. Unknown
218
+ remote version cannot establish an output format. Output values are never
219
+ traversed or transported. The pure exported view additionally accepts `SafeText`
220
+ with `proof: "explicit-non-sensitive"` only from a trusted host projection with
221
+ independent proof. The persisted readers and standalone API never produce it.
175
222
 
176
223
  ## Private data and redaction
177
224
 
@@ -183,23 +230,31 @@ appear on unauthenticated routes or public telemetry.
183
230
 
184
231
  Persisted Alchemy state can contain plaintext secret payloads inside
185
232
  `__redacted__` marker objects. This package never invokes Alchemy's reviver or
186
- decrypts anything. It discards props, attrs, old state, action inputs, and outputs.
233
+ decrypts anything. It discards raw props, attrs, old state, action inputs and output values. Only the
234
+ explicit resource-own attr allowlist and output keys are normalized.
187
235
  Marker objects in metadata become `<redacted>`, including sibling fields. Parsing
188
236
  errors include only a tagged reason, never raw contents or parser diagnostics.
189
- Reads are capped at 8 MiB per file and directory listings at 50,000 entries.
237
+ Reads are capped at 8 MiB per file and directory listings at 50,000 entries. Resource identities are bounded to 1024
238
+ characters, dependency graphs to 100,000 edges, and output dictionaries to 1000
239
+ keys of at most 256 characters.
190
240
 
191
241
  SSO profiles retain server-private names, config paths, account/role selectors,
192
242
  session names, start URLs, and routing regions. S3 bucket names, state prefixes,
193
243
  object keys, version IDs, and pagination markers are also server-private. Their
194
244
  persisted representation is AWS config or S3 object coordinates; console code
195
245
  holds them in server memory only. They must not cross HTTP, browser storage,
196
- logs, telemetry, or public errors. Only normalized `StackState` and
197
- `ResourceSummary` fields may cross the authenticated owner API. Credential-bearing
246
+ logs, telemetry, or public errors. Only normalized `StackState`, `ResourceSummary` and `OutputSummary` fields may
247
+ cross the authenticated owner API. Resource FQNs, construct parents, provider IDs,
248
+ own account/region, allowlisted console URLs, documented updated timestamps,
249
+ downstream identities and output keys are authenticated client-visible metadata.
250
+ Stack IDs and resource FQNs also appear in validated UI query routes and browser
251
+ history. They are not backend locators. Never emit them on public routes, in logs
252
+ or telemetry, or persist them in browser storage. Credential-bearing
198
253
  `SsoCredentials` results remain solely inside the signing boundary. S3 reads have
199
254
  the same 8 MiB payload cap and a 50,000-entry / 100-page listing cap.
200
255
 
201
- Cloudflare profiles will also remain server-side. The UI will never apply or
202
- destroy. Plan diffs remain on demand, with no scheduled execution.
256
+ Cloudflare profile configuration remains server-side. This phase has no plan,
257
+ apply or destroy execution.
203
258
 
204
259
  ## Format references
205
260
 
package/dist/api.d.ts CHANGED
@@ -49,12 +49,19 @@ export declare const StackDetail: Schema.Struct<{
49
49
  readonly logicalId: Schema.NonEmptyString;
50
50
  readonly resourceType: Schema.NonEmptyString;
51
51
  readonly status: Schema.Literals<readonly ["creating", "created", "updating", "updated", "deleting", "replacing", "replaced"]>;
52
- readonly parent: Schema.NullOr<Schema.String>;
52
+ readonly parent: Schema.NullOr<Schema.NonEmptyString>;
53
53
  readonly providerId: Schema.NullOr<Schema.String>;
54
54
  readonly account: Schema.NullOr<Schema.String>;
55
55
  readonly region: Schema.NullOr<Schema.String>;
56
56
  readonly consoleUrl: Schema.NullOr<Schema.String>;
57
+ readonly updatedAt: Schema.NullOr<Schema.String>;
58
+ readonly downstream: Schema.NullOr<Schema.$Array<Schema.NonEmptyString>>;
57
59
  }>>;
60
+ readonly outputs: Schema.NullOr<Schema.$Array<Schema.Struct<{
61
+ readonly key: Schema.NonEmptyString;
62
+ readonly value: Schema.TaggedStruct<"Hidden", {}>;
63
+ readonly description: Schema.Null;
64
+ }>>>;
58
65
  }>;
59
66
  }>;
60
67
  export type StackDetail = typeof StackDetail.Type;
@@ -112,12 +119,19 @@ declare const AlchemyConsoleApi_base: HttpApi.HttpApi<"AlchemyConsoleApi", HttpA
112
119
  readonly logicalId: Schema.NonEmptyString;
113
120
  readonly resourceType: Schema.NonEmptyString;
114
121
  readonly status: Schema.Literals<readonly ["creating", "created", "updating", "updated", "deleting", "replacing", "replaced"]>;
115
- readonly parent: Schema.NullOr<Schema.String>;
122
+ readonly parent: Schema.NullOr<Schema.NonEmptyString>;
116
123
  readonly providerId: Schema.NullOr<Schema.String>;
117
124
  readonly account: Schema.NullOr<Schema.String>;
118
125
  readonly region: Schema.NullOr<Schema.String>;
119
126
  readonly consoleUrl: Schema.NullOr<Schema.String>;
127
+ readonly updatedAt: Schema.NullOr<Schema.String>;
128
+ readonly downstream: Schema.NullOr<Schema.$Array<Schema.NonEmptyString>>;
120
129
  }>>;
130
+ readonly outputs: Schema.NullOr<Schema.$Array<Schema.Struct<{
131
+ readonly key: Schema.NonEmptyString;
132
+ readonly value: Schema.TaggedStruct<"Hidden", {}>;
133
+ readonly description: Schema.Null;
134
+ }>>>;
121
135
  }>;
122
136
  }>>, Schema.toCodecJson<typeof CatalogReadError | typeof StackNotFound>, OwnerAuth, never>, false>>;
123
137
  export declare class AlchemyConsoleApi extends AlchemyConsoleApi_base {
package/dist/api.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,8GAA8G;AAC9G,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC/B,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAG3F,0GAA0G;AAC1G,eAAO,MAAM,OAAO,eAAuC,CAAA;AAC3D,MAAM,MAAM,OAAO,GAAG,OAAO,OAAO,CAAC,IAAI,CAAA;AAEzC,eAAO,MAAM,mBAAmB,oOAc9B,CAAA;AAEF,eAAO,MAAM,cAAc;;;EAGzB,CAAA;AACF,MAAM,MAAM,cAAc,GAAG,OAAO,cAAc,CAAC,IAAI,CAAA;AAEvD,eAAO,MAAM,YAAY;;;;;;;;EAQvB,CAAA;AACF,MAAM,MAAM,YAAY,GAAG,OAAO,YAAY,CAAC,IAAI,CAAA;AAEnD,eAAO,MAAM,SAAS;;;;;;;;;;;;;;EAA8F,CAAA;AACpH,MAAM,MAAM,SAAS,GAAG,OAAO,SAAS,CAAC,IAAI,CAAA;AAC7C,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;EAAoD,CAAA;AAC5E,MAAM,MAAM,WAAW,GAAG,OAAO,WAAW,CAAC,IAAI,CAAA;;;;AAEjD,qBAAa,gBAAiB,SAAQ,qBAEZ;CAAG;;AAE7B,qBAAa,aAAc,SAAQ,kBAAgF;CAAG;;AAEtH,qBAAa,YAAa,SAAQ,iBAA8E;CAAG;;AAEnH,qBAAa,SAAU,SAAQ,cAAwE;CAAG;;;;;;;;;AAE1G,6EAA6E;AAC7E,qBAAa,SAAU,SAAQ,cAE7B;CAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAWL,qBAAa,iBAAkB,SAAQ,sBAAmE;CAAG"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,8GAA8G;AAC9G,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC/B,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAG3F,0GAA0G;AAC1G,eAAO,MAAM,OAAO,eAAuC,CAAA;AAC3D,MAAM,MAAM,OAAO,GAAG,OAAO,OAAO,CAAC,IAAI,CAAA;AAEzC,eAAO,MAAM,mBAAmB,oOAc9B,CAAA;AAEF,eAAO,MAAM,cAAc;;;EAGzB,CAAA;AACF,MAAM,MAAM,cAAc,GAAG,OAAO,cAAc,CAAC,IAAI,CAAA;AAEvD,eAAO,MAAM,YAAY;;;;;;;;EAQvB,CAAA;AACF,MAAM,MAAM,YAAY,GAAG,OAAO,YAAY,CAAC,IAAI,CAAA;AAEnD,eAAO,MAAM,SAAS;;;;;;;;;;;;;;EAA8F,CAAA;AACpH,MAAM,MAAM,SAAS,GAAG,OAAO,SAAS,CAAC,IAAI,CAAA;AAC7C,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;EAAoD,CAAA;AAC5E,MAAM,MAAM,WAAW,GAAG,OAAO,WAAW,CAAC,IAAI,CAAA;;;;AAEjD,qBAAa,gBAAiB,SAAQ,qBAEZ;CAAG;;AAE7B,qBAAa,aAAc,SAAQ,kBAAgF;CAAG;;AAEtH,qBAAa,YAAa,SAAQ,iBAA8E;CAAG;;AAEnH,qBAAa,SAAU,SAAQ,cAAwE;CAAG;;;;;;;;;AAE1G,6EAA6E;AAC7E,qBAAa,SAAU,SAAQ,cAE7B;CAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAWL,qBAAa,iBAAkB,SAAQ,sBAAmE;CAAG"}
@@ -0,0 +1,60 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="56" height="56" aria-hidden="true" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter">
2
+ <defs fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter">
3
+ <radialGradient id="bpkgsrc-56-dc-38" cx="38%" cy="30%" r="80%" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter"><stop offset="0" stop-color="oklch(0.86 0.12 273)" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter" /><stop offset="0.55" stop-color="oklch(0.66 0.17 273)" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter" /><stop offset="1" stop-color="oklch(0.46 0.17 291)" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter" /></radialGradient>
4
+ <radialGradient id="spkgsrc-56-dc-38" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter"><stop offset="0" stop-color="black" stop-opacity="0.28" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter" /><stop offset="1" stop-color="black" stop-opacity="0" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter" /></radialGradient>
5
+ <radialGradient id="cpkgsrc-56-dc-38" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter"><stop offset="0" stop-color="oklch(0.95 0.1 264)" stop-opacity="0.95" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter" /><stop offset="1" stop-color="oklch(0.75 0.15 264)" stop-opacity="0" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter" /></radialGradient>
6
+ <radialGradient id="gpkgsrc-56-dc-38" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter"><stop offset="0" stop-color="white" stop-opacity="0.55" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter" /><stop offset="1" stop-color="white" stop-opacity="0" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter" /></radialGradient>
7
+ <linearGradient id="dpkgsrc-56-dc-38" x1="0" x2="0" y1="0" y2="1" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter"><stop offset="0.55" stop-color="oklch(0.3 0.12 291)" stop-opacity="0" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter" /><stop offset="1" stop-color="oklch(0.3 0.12 291)" stop-opacity="0.45" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter" /></linearGradient>
8
+ <radialGradient id="wpkgsrc-56-dc-38" cy="40%" r="60%" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter"><stop offset="0.6" stop-color="oklch(0.99 0.006 264)" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter" /><stop offset="1" stop-color="oklch(0.86 0.03 264)" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter" /></radialGradient>
9
+ <radialGradient id="ipkgsrc-56-dc-38" cy="72%" r="70%" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter"><stop offset="0" stop-color="oklch(0.62 0.15 264)" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter" /><stop offset="1" stop-color="oklch(0.3 0.09 264)" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter" /></radialGradient>
10
+ <clipPath id="kpkgsrc-56-dc-38" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter"><path d="M89.0 58.1C88.9 61.1 88.9 64.0 88.3 66.9C87.8 69.8 87.2 73.0 85.7 75.7C84.2 78.5 82.0 81.2 79.3 83.2C76.6 85.1 73.0 86.4 69.7 87.2C66.3 88.0 62.6 87.9 59.4 88.0C56.1 88.1 53.1 87.7 50.0 87.7C46.9 87.6 44.0 87.8 40.7 87.6C37.5 87.5 33.9 87.6 30.6 86.9C27.2 86.1 23.5 85.0 20.7 83.2C17.9 81.3 15.4 78.6 13.8 76.0C12.3 73.3 11.7 70.0 11.2 67.0C10.7 64.0 10.9 61.1 11.0 58.1C11.1 55.2 11.1 52.3 11.7 49.4C12.2 46.4 12.8 43.2 14.3 40.5C15.8 37.8 18.0 35.0 20.7 33.1C23.4 31.2 27.0 29.9 30.3 29.1C33.7 28.3 37.4 28.3 40.6 28.3C43.9 28.2 46.9 28.5 50.0 28.6C53.1 28.7 56.0 28.5 59.3 28.6C62.5 28.8 66.1 28.7 69.4 29.4C72.8 30.2 76.5 31.3 79.3 33.1C82.1 34.9 84.6 37.6 86.2 40.3C87.7 43.0 88.3 46.3 88.8 49.3C89.3 52.2 89.1 55.2 89.0 58.1Z" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter" /></clipPath>
11
+ <clipPath id="e0pkgsrc-56-dc-38" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter"><ellipse cx="39.07" cy="52.158168587244646" rx="5.4" ry="5.832000000000001" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter" /></clipPath>
12
+ <clipPath id="o0pkgsrc-56-dc-38" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter"><ellipse cx="39.07" cy="52.158168587244646" rx="6.6000000000000005" ry="7.032000000000001" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter" /></clipPath>
13
+ <clipPath id="u0pkgsrc-56-dc-38" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter"><path d="M31.47 64.35V42.78Q39.07 46.28 46.67 42.78V64.35Z" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter" transform="translate(0 2.052)" /></clipPath>
14
+ <clipPath id="l0pkgsrc-56-dc-38" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter"><path d="M31.47 39.96V58.04Q39.07 61.54 46.67 58.04V39.96Z" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter" /></clipPath><clipPath id="e1pkgsrc-56-dc-38" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter"><ellipse cx="60.93" cy="52.158168587244646" rx="5.4" ry="5.832000000000001" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter" /></clipPath>
15
+ <clipPath id="o1pkgsrc-56-dc-38" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter"><ellipse cx="60.93" cy="52.158168587244646" rx="6.6000000000000005" ry="7.032000000000001" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter" /></clipPath>
16
+ <clipPath id="u1pkgsrc-56-dc-38" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter"><path d="M53.33 64.35V42.78Q60.93 46.28 68.53 42.78V64.35Z" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter" transform="translate(0 2.052)" /></clipPath>
17
+ <clipPath id="l1pkgsrc-56-dc-38" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter"><path d="M53.33 39.96V58.04Q60.93 61.54 68.53 58.04V39.96Z" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter" /></clipPath>
18
+ </defs>
19
+ <ellipse cx="50" cy="91" fill="url(#spkgsrc-56-dc-38)" rx="32.76" ry="5" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter" />
20
+ <circle cx="50" cy="55" r="46" fill="none" stroke="oklch(0.4901 0.013 274.7)" stroke-width="2.5px" opacity="0" stroke-linecap="butt" stroke-linejoin="miter" />
21
+ <circle cx="50" cy="55" r="46" pathLength="100" fill="none" stroke="oklch(0.4901 0.013 274.7)" stroke-width="4.2px" opacity="0" stroke-linecap="round" stroke-linejoin="miter" stroke-dasharray="9px, 91px" />
22
+ <g transform="translate(76 6) scale(0.8)" display="none" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter"><circle cx="12" cy="12" r="11" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter" /><path d="M6 12h12" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter" /></g>
23
+ <g fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter"><g fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter"><g fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter">
24
+ <path d="M89.0 58.1C88.9 61.1 88.9 64.0 88.3 66.9C87.8 69.8 87.2 73.0 85.7 75.7C84.2 78.5 82.0 81.2 79.3 83.2C76.6 85.1 73.0 86.4 69.7 87.2C66.3 88.0 62.6 87.9 59.4 88.0C56.1 88.1 53.1 87.7 50.0 87.7C46.9 87.6 44.0 87.8 40.7 87.6C37.5 87.5 33.9 87.6 30.6 86.9C27.2 86.1 23.5 85.0 20.7 83.2C17.9 81.3 15.4 78.6 13.8 76.0C12.3 73.3 11.7 70.0 11.2 67.0C10.7 64.0 10.9 61.1 11.0 58.1C11.1 55.2 11.1 52.3 11.7 49.4C12.2 46.4 12.8 43.2 14.3 40.5C15.8 37.8 18.0 35.0 20.7 33.1C23.4 31.2 27.0 29.9 30.3 29.1C33.7 28.3 37.4 28.3 40.6 28.3C43.9 28.2 46.9 28.5 50.0 28.6C53.1 28.7 56.0 28.5 59.3 28.6C62.5 28.8 66.1 28.7 69.4 29.4C72.8 30.2 76.5 31.3 79.3 33.1C82.1 34.9 84.6 37.6 86.2 40.3C87.7 43.0 88.3 46.3 88.8 49.3C89.3 52.2 89.1 55.2 89.0 58.1Z" fill="url(#bpkgsrc-56-dc-38)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter" />
25
+ <g clip-path="url(#kpkgsrc-56-dc-38)" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter">
26
+ <circle cx="50" cy="71.27" r="16" fill="url(#cpkgsrc-56-dc-38)" stroke="none" stroke-width="1px" opacity="0.35" stroke-linecap="butt" stroke-linejoin="miter" />
27
+ <rect x="11.00" y="28.26" width="78.00" height="59.74" fill="url(#dpkgsrc-56-dc-38)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter" />
28
+ <ellipse cx="34.40" cy="40.21" rx="13.26" ry="5.97" fill="url(#gpkgsrc-56-dc-38)" transform="rotate(-24 34.40 40.21)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter" />
29
+ </g>
30
+ </g></g>
31
+ <g fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter"><g fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter">
32
+ <g fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter">
33
+ <g clip-path="url(#u0pkgsrc-56-dc-38)" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter"><g clip-path="url(#l0pkgsrc-56-dc-38)" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter">
34
+ <ellipse cx="39.07" cy="52.158168587244646" rx="6.6000000000000005" ry="7.032000000000001" fill="oklch(0.25 0.05 260 / 0.14)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter" />
35
+ <ellipse cx="39.07" cy="52.158168587244646" rx="5.4" ry="5.832000000000001" fill="url(#wpkgsrc-56-dc-38)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter" />
36
+ <g clip-path="url(#e0pkgsrc-56-dc-38)" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter"><g fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter">
37
+ <circle cx="39.07" cy="52.158168587244646" r="3.56" fill="url(#ipkgsrc-56-dc-38)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter" />
38
+ <circle cx="39.07" cy="52.158168587244646" r="1.85" fill="oklch(0.14 0.02 260)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter" />
39
+ <circle cx="37.79" cy="50.73" r="0.93" fill="oklch(1 0 none / 0.92)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter" />
40
+ <circle cx="40.28" cy="53.44" r="0.43" fill="oklch(1 0 none / 0.92)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter" />
41
+ </g></g>
42
+ </g></g>
43
+ <g clip-path="url(#o0pkgsrc-56-dc-38)" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter"><g fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter" transform="translate(0 2.052)"><path d="M31.47 42.78Q39.07 46.28 46.67 42.78" fill="none" stroke="oklch(0.22 0.04 260 / 0.85)" stroke-width="0.9px" opacity="0" stroke-linecap="round" stroke-linejoin="miter" /></g></g>
44
+ </g><g fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter">
45
+ <g clip-path="url(#u1pkgsrc-56-dc-38)" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter"><g clip-path="url(#l1pkgsrc-56-dc-38)" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter">
46
+ <ellipse cx="60.93" cy="52.158168587244646" rx="6.6000000000000005" ry="7.032000000000001" fill="oklch(0.25 0.05 260 / 0.14)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter" />
47
+ <ellipse cx="60.93" cy="52.158168587244646" rx="5.4" ry="5.832000000000001" fill="url(#wpkgsrc-56-dc-38)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter" />
48
+ <g clip-path="url(#e1pkgsrc-56-dc-38)" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter"><g fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter">
49
+ <circle cx="60.93" cy="52.158168587244646" r="3.56" fill="url(#ipkgsrc-56-dc-38)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter" />
50
+ <circle cx="60.93" cy="52.158168587244646" r="1.85" fill="oklch(0.14 0.02 260)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter" />
51
+ <circle cx="59.65" cy="50.73" r="0.93" fill="oklch(1 0 none / 0.92)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter" />
52
+ <circle cx="62.14" cy="53.44" r="0.43" fill="oklch(1 0 none / 0.92)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter" />
53
+ </g></g>
54
+ </g></g>
55
+ <g clip-path="url(#o1pkgsrc-56-dc-38)" fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter"><g fill="rgb(0, 0, 0)" stroke="none" stroke-width="1px" opacity="1" stroke-linecap="butt" stroke-linejoin="miter" transform="translate(0 2.052)"><path d="M53.33 42.78Q60.93 46.28 68.53 42.78" fill="none" stroke="oklch(0.22 0.04 260 / 0.85)" stroke-width="0.9px" opacity="0" stroke-linecap="round" stroke-linejoin="miter" /></g></g>
56
+ </g>
57
+ <path d="M33.67 42.88Q39.07 39.13 44.47 42.23Q39.07 41.73 33.67 42.88Z" fill="oklch(0.36 0.09 291)" stroke="oklch(0.36 0.09 291)" stroke-width="0.6px" opacity="1" stroke-linecap="butt" stroke-linejoin="round" /><path d="M66.33 42.88Q60.93 39.13 55.53 42.23Q60.93 41.73 66.33 42.88Z" fill="oklch(0.36 0.09 291)" stroke="oklch(0.36 0.09 291)" stroke-width="0.6px" opacity="1" stroke-linecap="butt" stroke-linejoin="round" />
58
+ </g></g>
59
+ </g>
60
+ </svg>