@zodiac-os/sdk 1.1.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +137 -10
- package/dist/api-D6ee2Q2b.mjs +113 -0
- package/dist/api-D6ee2Q2b.mjs.map +1 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +181 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/index.d.mts +210 -0
- package/dist/index.mjs +175 -0
- package/dist/index.mjs.map +1 -0
- package/dist/zodiac-os-codegen.d.ts +45 -0
- package/package.json +15 -15
- package/dist/index.d.ts +0 -28
- package/dist/index.js +0 -143
- package/dist/index.js.map +0 -1
- package/lib/index.cjs +0 -228
package/README.md
CHANGED
|
@@ -1,18 +1,145 @@
|
|
|
1
1
|
# Zodiac OS SDK
|
|
2
2
|
|
|
3
|
-
Programmatically manage
|
|
3
|
+
Programmatically manage [Zodiac](https://www.zodiac.eco) account constellations.
|
|
4
4
|
|
|
5
5
|
## Getting started
|
|
6
6
|
|
|
7
|
-
###
|
|
7
|
+
### 1. Install
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
```bash
|
|
10
|
+
npm install @zodiac-os/sdk
|
|
11
|
+
```
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
### 2. Generate a Zodiac OS API key
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
Sign in to [app.zodiac.eco](https://app.zodiac.eco) and create an API key at [app.zodiac.eco/admin/api-keys](https://app.zodiac.eco/admin/api-keys).
|
|
16
|
+
|
|
17
|
+
### 3. Create a config file
|
|
18
|
+
|
|
19
|
+
Create a `zodiac.config.ts` in your project root:
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { defineConfig } from '@zodiac-os/sdk/cli/config'
|
|
23
|
+
|
|
24
|
+
export default defineConfig({
|
|
25
|
+
apiKey: 'zodiac_...',
|
|
26
|
+
// Optional: contracts to fetch for permissions authoring
|
|
27
|
+
contracts: {
|
|
28
|
+
mainnet: {
|
|
29
|
+
dai: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 4. Pull your org data
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# Pull everything (org data + contract ABIs)
|
|
39
|
+
zodiac-os pull
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
This generates typed data in `node_modules/.zodiac-os/` with your org's users and vaults.
|
|
43
|
+
|
|
44
|
+
## Constellation API
|
|
45
|
+
|
|
46
|
+
The `constellation()` function is the main SDK entry point. It returns an API for declaring account constellations — the set of Safes, Roles mods, and users that make up your on-chain setup.
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { constellation } from '@zodiac-os/sdk'
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Scoping to a workspace and chain
|
|
53
|
+
|
|
54
|
+
Each constellation is scoped to a single workspace and chain. The `workspace` option must be a valid workspace name from your org.
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
const eth = constellation({
|
|
58
|
+
workspace: 'GG',
|
|
59
|
+
label: 'Production',
|
|
60
|
+
chain: 1,
|
|
61
|
+
})
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Referencing existing vaults
|
|
65
|
+
|
|
66
|
+
Bracket access gives you existing Safes and Roles mods from the selected workspace. Names auto-complete from the codegen output.
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
// Reference an existing Safe — no invocation needed
|
|
70
|
+
const ggDao = eth.safe['GG DAO']
|
|
71
|
+
|
|
72
|
+
// Reference the canonical Roles mod for that Safe
|
|
73
|
+
const ggDaoRoles = eth.roles['GG DAO']
|
|
74
|
+
|
|
75
|
+
// Optionally invoke with overrides
|
|
76
|
+
const ggDaoOverridden = eth.safe['GG DAO']({ threshold: 5 })
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Creating new accounts
|
|
80
|
+
|
|
81
|
+
Use bracket access with a new label to create new nodes. Required fields are enforced by the type system:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
// New Safe — threshold, owners are required
|
|
85
|
+
const newSafe = eth.safe['New Safe']({
|
|
86
|
+
nonce: 0n,
|
|
87
|
+
threshold: 2,
|
|
88
|
+
owners: [
|
|
89
|
+
eth.user['Alice Sample'],
|
|
90
|
+
'0xb8e48df6818d3cbc648b3e8ec248a4f547135f7a',
|
|
91
|
+
],
|
|
92
|
+
modules: [ggDaoRoles],
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
// New Roles mod targeting an existing Safe
|
|
96
|
+
const newRoles = eth.roles['New Roles']({
|
|
97
|
+
nonce: 0n,
|
|
98
|
+
target: ggDao,
|
|
99
|
+
})
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Referencing users
|
|
103
|
+
|
|
104
|
+
`eth.user[handle]` resolves a user to their personal Safe address on the current chain:
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
const aliceAddress = eth.user['Alice Sample']
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Applying the constellation
|
|
111
|
+
|
|
112
|
+
The `apply()` function takes all nodes and sends them to the Zodiac OS API. Pass either a named object (keys become refs) or an array:
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
import { apply } from '@zodiac-os/sdk'
|
|
116
|
+
|
|
117
|
+
await apply({ ggDao, ggDaoRoles, newSafe, newRoles })
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
All referenced nodes must be included in the `apply()` call.
|
|
121
|
+
|
|
122
|
+
By default, `apply()` creates an API client from the `ZODIAC_OS_API_KEY` environment variable. You can pass a custom client:
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
await apply({ ggDao, newRoles }, { api: new ApiClient({ apiKey: '...' }) })
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## CLI reference
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
Usage: zodiac-os [options] [command]
|
|
132
|
+
|
|
133
|
+
Zodiac OS SDK CLI – pull org data and contract ABIs
|
|
134
|
+
|
|
135
|
+
Options:
|
|
136
|
+
-V, --version output the version number
|
|
137
|
+
-c, --config <path> path to the config file (default: "zodiac.config.ts")
|
|
138
|
+
-h, --help display help for command
|
|
139
|
+
|
|
140
|
+
Commands:
|
|
141
|
+
pull-org Fetch Zodiac users and vaults, generate TypeScript types
|
|
142
|
+
pull-contracts Fetch contract ABIs, generate typed permissions kit
|
|
143
|
+
pull Fetch Zodiac org and contracts ABI, generate SDK functions
|
|
144
|
+
help [command] display help for command
|
|
145
|
+
```
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import assert from "assert";
|
|
2
|
+
//#region src/api.ts
|
|
3
|
+
const { ZODIAC_OS_API_KEY, ZODIAC_OS_API_URL = "https://app.zodiac.eco/api/v1" } = process.env;
|
|
4
|
+
var ApiClient = class {
|
|
5
|
+
apiKey;
|
|
6
|
+
baseUrl;
|
|
7
|
+
_fetch;
|
|
8
|
+
headers;
|
|
9
|
+
constructor({ baseUrl = ZODIAC_OS_API_URL, fetch: customFetch = fetch, headers = {}, apiKey = ZODIAC_OS_API_KEY } = {}) {
|
|
10
|
+
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
11
|
+
this._fetch = customFetch;
|
|
12
|
+
this.headers = headers;
|
|
13
|
+
assert(apiKey, "No API key provided to the API client. Either pass it as the \"apiKey\" option or set the ZODIAC_OS_API_KEY environment variable.");
|
|
14
|
+
this.apiKey = apiKey;
|
|
15
|
+
}
|
|
16
|
+
async postJson(endpoint, payload) {
|
|
17
|
+
const res = await this._fetch(`${this.baseUrl}/${endpoint}`, {
|
|
18
|
+
method: "POST",
|
|
19
|
+
headers: {
|
|
20
|
+
...this.headers,
|
|
21
|
+
"content-type": "application/json",
|
|
22
|
+
authorization: `Bearer ${this.apiKey}`
|
|
23
|
+
},
|
|
24
|
+
body: jsonStringify(payload)
|
|
25
|
+
});
|
|
26
|
+
if (!res.ok) await handleApiError(res);
|
|
27
|
+
return res.json();
|
|
28
|
+
}
|
|
29
|
+
async get(endpoint) {
|
|
30
|
+
const res = await this._fetch(`${this.baseUrl}/${endpoint}`, { headers: {
|
|
31
|
+
...this.headers,
|
|
32
|
+
authorization: `Bearer ${this.apiKey}`
|
|
33
|
+
} });
|
|
34
|
+
if (!res.ok) await handleApiError(res);
|
|
35
|
+
return res.json();
|
|
36
|
+
}
|
|
37
|
+
listVaults() {
|
|
38
|
+
return this.get("vaults");
|
|
39
|
+
}
|
|
40
|
+
listUsers() {
|
|
41
|
+
return this.get("users");
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Applies an accounts specification to Zodiac OS.
|
|
45
|
+
*/
|
|
46
|
+
applyConstellation(workspaceId, payload) {
|
|
47
|
+
return this.postJson(`workspace/${workspaceId}/constellation/apply`, payload);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Resolves an accounts specification to Zodiac OS.
|
|
51
|
+
*/
|
|
52
|
+
resolveConstellation(workspaceId, payload) {
|
|
53
|
+
return this.postJson(`workspace/${workspaceId}/constellation/resolve`, payload);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
var ApiRequestError = class ApiRequestError extends Error {
|
|
57
|
+
status;
|
|
58
|
+
statusText;
|
|
59
|
+
details;
|
|
60
|
+
constructor(message, opts) {
|
|
61
|
+
super(ApiRequestError.composeMessage(message, opts.details));
|
|
62
|
+
this.name = "ApiRequestError";
|
|
63
|
+
this.status = opts.status;
|
|
64
|
+
this.statusText = opts.statusText;
|
|
65
|
+
this.details = opts.details;
|
|
66
|
+
if (opts.cause !== void 0) this.cause = opts.cause;
|
|
67
|
+
}
|
|
68
|
+
static composeMessage(message, details) {
|
|
69
|
+
if (details == null) return message;
|
|
70
|
+
let detailsString;
|
|
71
|
+
try {
|
|
72
|
+
detailsString = typeof details === "string" ? details : jsonStringify(details, 2);
|
|
73
|
+
} catch (_err) {
|
|
74
|
+
detailsString = String(details);
|
|
75
|
+
}
|
|
76
|
+
return `${message}\nDetails: ${detailsString}`;
|
|
77
|
+
}
|
|
78
|
+
toString() {
|
|
79
|
+
return `${this.name}: ${this.message}`;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
async function handleApiError(response) {
|
|
83
|
+
if (response.headers.get("content-type")?.includes("application/json")) {
|
|
84
|
+
const errorData = await response.json();
|
|
85
|
+
let error;
|
|
86
|
+
try {
|
|
87
|
+
error = new ApiRequestError(errorData.error.message, {
|
|
88
|
+
status: response.status,
|
|
89
|
+
statusText: response.statusText,
|
|
90
|
+
details: errorData.error.details
|
|
91
|
+
});
|
|
92
|
+
} catch (jsonShapeError) {
|
|
93
|
+
error = new ApiRequestError(`Failed parsing error response: ${jsonShapeError}`, {
|
|
94
|
+
status: response.status,
|
|
95
|
+
statusText: response.statusText,
|
|
96
|
+
details: errorData
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
throw error;
|
|
100
|
+
} else throw new ApiRequestError(await response.text() || "Unexpected error", {
|
|
101
|
+
status: response.status,
|
|
102
|
+
statusText: response.statusText
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
/** JSON.stringify with bigint support */
|
|
106
|
+
const jsonStringify = (value, indent) => JSON.stringify(value, (_, value) => {
|
|
107
|
+
if (typeof value === "bigint") return value.toString();
|
|
108
|
+
return value;
|
|
109
|
+
}, indent);
|
|
110
|
+
//#endregion
|
|
111
|
+
export { ApiClient as t };
|
|
112
|
+
|
|
113
|
+
//# sourceMappingURL=api-D6ee2Q2b.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-D6ee2Q2b.mjs","names":[],"sources":["../src/api.ts"],"sourcesContent":["import type {\n ApplyConstellationPayload,\n ApplyConstellationResult,\n ResolveConstellationPayload,\n ResolveConstellationResult,\n ApiError as ApiErrorResponse,\n ListVaultsResult,\n ListUsersResult,\n} from '@zodiac-os/api-types'\nimport assert from 'assert'\nimport { UUID } from 'crypto'\n\nexport type Options = {\n workspace?: string\n apiKey?: string\n baseUrl?: string\n fetch?: typeof globalThis.fetch\n headers?: Record<string, string>\n}\n\nconst {\n ZODIAC_OS_API_KEY,\n ZODIAC_OS_API_URL = 'https://app.zodiac.eco/api/v1',\n} = process.env\n\nexport class ApiClient {\n private apiKey: string\n private baseUrl: string\n private _fetch: typeof fetch\n private headers: Record<string, string>\n\n constructor({\n baseUrl = ZODIAC_OS_API_URL,\n fetch: customFetch = fetch,\n headers = {},\n apiKey = ZODIAC_OS_API_KEY,\n }: Options = {}) {\n this.baseUrl = baseUrl.replace(/\\/$/, '')\n this._fetch = customFetch\n this.headers = headers\n\n assert(\n apiKey,\n 'No API key provided to the API client. Either pass it as the \"apiKey\" option or set the ZODIAC_OS_API_KEY environment variable.'\n )\n\n this.apiKey = apiKey\n }\n\n protected async postJson(endpoint: string, payload: unknown) {\n const res = await this._fetch(`${this.baseUrl}/${endpoint}`, {\n method: 'POST',\n headers: {\n ...this.headers,\n 'content-type': 'application/json',\n authorization: `Bearer ${this.apiKey}`,\n },\n body: jsonStringify(payload),\n })\n if (!res.ok) {\n await handleApiError(res)\n }\n\n return res.json()\n }\n\n protected async get(endpoint: string) {\n const res = await this._fetch(`${this.baseUrl}/${endpoint}`, {\n headers: { ...this.headers, authorization: `Bearer ${this.apiKey}` },\n })\n\n if (!res.ok) {\n await handleApiError(res)\n }\n\n return res.json()\n }\n\n listVaults(): Promise<ListVaultsResult> {\n return this.get('vaults')\n }\n\n listUsers(): Promise<ListUsersResult> {\n return this.get('users')\n }\n\n /**\n * Applies an accounts specification to Zodiac OS.\n */\n applyConstellation(\n workspaceId: UUID,\n payload: ApplyConstellationPayload\n ): Promise<ApplyConstellationResult> {\n return this.postJson(\n `workspace/${workspaceId}/constellation/apply`,\n payload\n )\n }\n\n /**\n * Resolves an accounts specification to Zodiac OS.\n */\n resolveConstellation(\n workspaceId: UUID,\n payload: ResolveConstellationPayload\n ): Promise<ResolveConstellationResult> {\n return this.postJson(\n `workspace/${workspaceId}/constellation/resolve`,\n payload\n )\n }\n}\n\nexport class ApiRequestError extends Error {\n public readonly status: number\n public readonly statusText: string\n public readonly details?: unknown\n\n constructor(\n message: string,\n opts: {\n status: number\n statusText: string\n details?: unknown\n cause?: unknown\n }\n ) {\n super(ApiRequestError.composeMessage(message, opts.details))\n this.name = 'ApiRequestError'\n this.status = opts.status\n this.statusText = opts.statusText\n this.details = opts.details\n if (opts.cause !== undefined) {\n ;(this as any).cause = opts.cause\n }\n }\n\n private static composeMessage(message: string, details?: unknown) {\n if (details == null) return message\n let detailsString: string\n try {\n detailsString =\n typeof details === 'string' ? details : jsonStringify(details, 2)\n } catch (_err) {\n detailsString = String(details)\n }\n return `${message}\\nDetails: ${detailsString}`\n }\n\n toString() {\n return `${this.name}: ${this.message}`\n }\n}\n\nasync function handleApiError(response: Response): Promise<never> {\n const contentType = response.headers.get('content-type')\n if (contentType?.includes('application/json')) {\n const errorData = (await response.json()) as ApiErrorResponse\n let error: ApiRequestError\n try {\n error = new ApiRequestError(errorData.error.message, {\n status: response.status,\n statusText: response.statusText,\n details: errorData.error.details,\n })\n } catch (jsonShapeError) {\n error = new ApiRequestError(\n `Failed parsing error response: ${jsonShapeError}`,\n {\n status: response.status,\n statusText: response.statusText,\n details: errorData,\n }\n )\n }\n throw error\n } else {\n // Not JSON, read as text directly\n const text = await response.text()\n throw new ApiRequestError(text || 'Unexpected error', {\n status: response.status,\n statusText: response.statusText,\n })\n }\n}\n\n/** JSON.stringify with bigint support */\nconst jsonStringify = (value: unknown, indent?: number) =>\n JSON.stringify(\n value,\n (_, value) => {\n if (typeof value === 'bigint') {\n return value.toString()\n }\n\n return value\n },\n indent\n )\n"],"mappings":";;AAoBA,MAAM,EACJ,mBACA,oBAAoB,oCAClB,QAAQ;AAEZ,IAAa,YAAb,MAAuB;CACrB;CACA;CACA;CACA;CAEA,YAAY,EACV,UAAU,mBACV,OAAO,cAAc,OACrB,UAAU,EAAE,EACZ,SAAS,sBACE,EAAE,EAAE;AACf,OAAK,UAAU,QAAQ,QAAQ,OAAO,GAAG;AACzC,OAAK,SAAS;AACd,OAAK,UAAU;AAEf,SACE,QACA,oIACD;AAED,OAAK,SAAS;;CAGhB,MAAgB,SAAS,UAAkB,SAAkB;EAC3D,MAAM,MAAM,MAAM,KAAK,OAAO,GAAG,KAAK,QAAQ,GAAG,YAAY;GAC3D,QAAQ;GACR,SAAS;IACP,GAAG,KAAK;IACR,gBAAgB;IAChB,eAAe,UAAU,KAAK;IAC/B;GACD,MAAM,cAAc,QAAQ;GAC7B,CAAC;AACF,MAAI,CAAC,IAAI,GACP,OAAM,eAAe,IAAI;AAG3B,SAAO,IAAI,MAAM;;CAGnB,MAAgB,IAAI,UAAkB;EACpC,MAAM,MAAM,MAAM,KAAK,OAAO,GAAG,KAAK,QAAQ,GAAG,YAAY,EAC3D,SAAS;GAAE,GAAG,KAAK;GAAS,eAAe,UAAU,KAAK;GAAU,EACrE,CAAC;AAEF,MAAI,CAAC,IAAI,GACP,OAAM,eAAe,IAAI;AAG3B,SAAO,IAAI,MAAM;;CAGnB,aAAwC;AACtC,SAAO,KAAK,IAAI,SAAS;;CAG3B,YAAsC;AACpC,SAAO,KAAK,IAAI,QAAQ;;;;;CAM1B,mBACE,aACA,SACmC;AACnC,SAAO,KAAK,SACV,aAAa,YAAY,uBACzB,QACD;;;;;CAMH,qBACE,aACA,SACqC;AACrC,SAAO,KAAK,SACV,aAAa,YAAY,yBACzB,QACD;;;AAIL,IAAa,kBAAb,MAAa,wBAAwB,MAAM;CACzC;CACA;CACA;CAEA,YACE,SACA,MAMA;AACA,QAAM,gBAAgB,eAAe,SAAS,KAAK,QAAQ,CAAC;AAC5D,OAAK,OAAO;AACZ,OAAK,SAAS,KAAK;AACnB,OAAK,aAAa,KAAK;AACvB,OAAK,UAAU,KAAK;AACpB,MAAI,KAAK,UAAU,KAAA,EACf,MAAa,QAAQ,KAAK;;CAIhC,OAAe,eAAe,SAAiB,SAAmB;AAChE,MAAI,WAAW,KAAM,QAAO;EAC5B,IAAI;AACJ,MAAI;AACF,mBACE,OAAO,YAAY,WAAW,UAAU,cAAc,SAAS,EAAE;WAC5D,MAAM;AACb,mBAAgB,OAAO,QAAQ;;AAEjC,SAAO,GAAG,QAAQ,aAAa;;CAGjC,WAAW;AACT,SAAO,GAAG,KAAK,KAAK,IAAI,KAAK;;;AAIjC,eAAe,eAAe,UAAoC;AAEhE,KADoB,SAAS,QAAQ,IAAI,eAAe,EACvC,SAAS,mBAAmB,EAAE;EAC7C,MAAM,YAAa,MAAM,SAAS,MAAM;EACxC,IAAI;AACJ,MAAI;AACF,WAAQ,IAAI,gBAAgB,UAAU,MAAM,SAAS;IACnD,QAAQ,SAAS;IACjB,YAAY,SAAS;IACrB,SAAS,UAAU,MAAM;IAC1B,CAAC;WACK,gBAAgB;AACvB,WAAQ,IAAI,gBACV,kCAAkC,kBAClC;IACE,QAAQ,SAAS;IACjB,YAAY,SAAS;IACrB,SAAS;IACV,CACF;;AAEH,QAAM;OAIN,OAAM,IAAI,gBADG,MAAM,SAAS,MAAM,IACA,oBAAoB;EACpD,QAAQ,SAAS;EACjB,YAAY,SAAS;EACtB,CAAC;;;AAKN,MAAM,iBAAiB,OAAgB,WACrC,KAAK,UACH,QACC,GAAG,UAAU;AACZ,KAAI,OAAO,UAAU,SACnB,QAAO,MAAM,UAAU;AAGzB,QAAO;GAET,OACD"}
|
package/dist/cli.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { t as ApiClient } from "./api-D6ee2Q2b.mjs";
|
|
3
|
+
import { invariant } from "@epic-web/invariant";
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from "url";
|
|
6
|
+
import { join, resolve } from "path";
|
|
7
|
+
import { ModuleKind, Project, ScriptTarget, VariableDeclarationKind } from "ts-morph";
|
|
8
|
+
import { mkdirSync, writeFileSync } from "fs";
|
|
9
|
+
import { defineConfig } from "@gnosis-guild/eth-sdk";
|
|
10
|
+
import { gatherABIs } from "@gnosis-guild/eth-sdk/dist/abi-management/index.js";
|
|
11
|
+
import { generateSdk } from "@gnosis-guild/eth-sdk/dist/client/index.js";
|
|
12
|
+
import { createEthSdkConfig } from "@gnosis-guild/eth-sdk/dist/config/types.js";
|
|
13
|
+
import { realFs } from "@gnosis-guild/eth-sdk/dist/peripherals/fs.js";
|
|
14
|
+
//#region src/cli/config.ts
|
|
15
|
+
const DEFAULT_CONFIG_PATH = "zodiac.config.ts";
|
|
16
|
+
async function loadConfig(configPath = DEFAULT_CONFIG_PATH) {
|
|
17
|
+
const absolutePath = resolve(process.cwd(), configPath);
|
|
18
|
+
let mod;
|
|
19
|
+
try {
|
|
20
|
+
mod = await import(pathToFileURL(absolutePath).href);
|
|
21
|
+
} catch (error) {
|
|
22
|
+
if (error?.code === "ERR_MODULE_NOT_FOUND" || error?.code === "ENOENT") throw new Error(`Config file not found: ${absolutePath}`);
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
const config = mod.default ?? mod.config;
|
|
26
|
+
if (!config) throw new Error(`Config file must export a default value or a named "config" export: ${absolutePath}`);
|
|
27
|
+
if (!config.apiKey) throw new Error(`Config is missing required field "apiKey"`);
|
|
28
|
+
return config;
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region src/cli/commands/pullOrg.ts
|
|
32
|
+
function resolveNodeModulesDir() {
|
|
33
|
+
const match = fileURLToPath(import.meta.url).match(/^(.+[/\\]node_modules)[/\\]/);
|
|
34
|
+
if (match) return match[1];
|
|
35
|
+
return join(process.cwd(), "node_modules");
|
|
36
|
+
}
|
|
37
|
+
const toLiteral = (value, indent = 0) => {
|
|
38
|
+
const pad = " ".repeat(indent);
|
|
39
|
+
const childPad = " ".repeat(indent + 1);
|
|
40
|
+
if (value === null) return "null";
|
|
41
|
+
if (typeof value === "bigint") return `${value}n`;
|
|
42
|
+
if (typeof value === "string") return JSON.stringify(value);
|
|
43
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
44
|
+
if (Array.isArray(value)) {
|
|
45
|
+
if (value.length === 0) return "[]";
|
|
46
|
+
return `[\n${value.map((v) => `${childPad}${toLiteral(v, indent + 1)}`).join(",\n")},\n${pad}]`;
|
|
47
|
+
}
|
|
48
|
+
if (typeof value === "object") {
|
|
49
|
+
const entries = Object.entries(value);
|
|
50
|
+
if (entries.length === 0) return "{}";
|
|
51
|
+
return `{\n${entries.map(([k, v]) => `${childPad}${/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(k) ? k : JSON.stringify(k)}: ${toLiteral(v, indent + 1)}`).join(",\n")},\n${pad}}`;
|
|
52
|
+
}
|
|
53
|
+
return String(value);
|
|
54
|
+
};
|
|
55
|
+
const pullOrg = async (config) => {
|
|
56
|
+
const client = new ApiClient({ apiKey: config.apiKey });
|
|
57
|
+
const [users, workspaceVaults] = await Promise.all([client.listUsers(), client.listVaults()]);
|
|
58
|
+
const allRawVaults = workspaceVaults.flatMap((ws) => ws.vaults);
|
|
59
|
+
const { result: accounts } = await client.resolveConstellation(workspaceVaults[0].workspaceId, { specification: allRawVaults.map((vault) => ({
|
|
60
|
+
type: "SAFE",
|
|
61
|
+
chain: vault.chain,
|
|
62
|
+
address: vault.address
|
|
63
|
+
})) });
|
|
64
|
+
let accountIndex = 0;
|
|
65
|
+
const vaultsRecord = {};
|
|
66
|
+
for (const ws of workspaceVaults) {
|
|
67
|
+
const wsVaults = {};
|
|
68
|
+
for (const vault of ws.vaults) {
|
|
69
|
+
const account = accounts[accountIndex++];
|
|
70
|
+
invariant(account.type === "SAFE", `Expected SAFE account for vault ${vault.id}`);
|
|
71
|
+
wsVaults[vault.label] = {
|
|
72
|
+
id: vault.id,
|
|
73
|
+
label: vault.label,
|
|
74
|
+
address: account.address,
|
|
75
|
+
chain: vault.chain,
|
|
76
|
+
threshold: account.threshold,
|
|
77
|
+
owners: [...account.owners],
|
|
78
|
+
modules: [...account.modules]
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
vaultsRecord[ws.workspaceName] = {
|
|
82
|
+
workspaceId: ws.workspaceId,
|
|
83
|
+
workspaceName: ws.workspaceName,
|
|
84
|
+
vaults: wsVaults
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const nameCount = /* @__PURE__ */ new Map();
|
|
88
|
+
for (const user of users) nameCount.set(user.fullName, (nameCount.get(user.fullName) ?? 0) + 1);
|
|
89
|
+
const usersRecord = {};
|
|
90
|
+
for (const user of users) {
|
|
91
|
+
const handle = nameCount.get(user.fullName) > 1 ? `${user.fullName} (${user.id})` : user.fullName;
|
|
92
|
+
usersRecord[handle] = {
|
|
93
|
+
id: user.id,
|
|
94
|
+
fullName: user.fullName,
|
|
95
|
+
personalSafes: user.personalSafes
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
const outDir = join(resolveNodeModulesDir(), ".zodiac-os");
|
|
99
|
+
mkdirSync(outDir, { recursive: true });
|
|
100
|
+
writeFileSync(join(outDir, "package.json"), JSON.stringify({
|
|
101
|
+
name: ".zodiac-os",
|
|
102
|
+
type: "module",
|
|
103
|
+
main: "index.js",
|
|
104
|
+
types: "index.d.ts"
|
|
105
|
+
}, null, 2));
|
|
106
|
+
const sourceFile = new Project({
|
|
107
|
+
compilerOptions: {
|
|
108
|
+
declaration: true,
|
|
109
|
+
module: ModuleKind.ESNext,
|
|
110
|
+
target: ScriptTarget.ESNext,
|
|
111
|
+
outDir
|
|
112
|
+
},
|
|
113
|
+
useInMemoryFileSystem: true
|
|
114
|
+
}).createSourceFile("index.ts", "");
|
|
115
|
+
sourceFile.addVariableStatement({
|
|
116
|
+
isExported: true,
|
|
117
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
118
|
+
declarations: [{
|
|
119
|
+
name: "users",
|
|
120
|
+
initializer: `${toLiteral(usersRecord)} as const`
|
|
121
|
+
}]
|
|
122
|
+
});
|
|
123
|
+
sourceFile.addVariableStatement({
|
|
124
|
+
isExported: true,
|
|
125
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
126
|
+
declarations: [{
|
|
127
|
+
name: "vaults",
|
|
128
|
+
initializer: `${toLiteral(vaultsRecord)} as const`
|
|
129
|
+
}]
|
|
130
|
+
});
|
|
131
|
+
const emitResult = sourceFile.getEmitOutput();
|
|
132
|
+
for (const outputFile of emitResult.getOutputFiles()) writeFileSync(join(outDir, outputFile.getFilePath().includes(".d.ts") ? "index.d.ts" : "index.js"), outputFile.getText());
|
|
133
|
+
};
|
|
134
|
+
//#endregion
|
|
135
|
+
//#region src/cli/commands/pullContracts.ts
|
|
136
|
+
const pullContracts = async (config) => {
|
|
137
|
+
if (!config.contracts || Object.keys(config.contracts).length === 0) {
|
|
138
|
+
console.log("No contracts defined in config, skipping.");
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const cwd = process.cwd();
|
|
142
|
+
const ethSdkConfig = createEthSdkConfig(defineConfig({ contracts: config.contracts }));
|
|
143
|
+
const ctx = {
|
|
144
|
+
cliArgs: { workingDirPath: cwd },
|
|
145
|
+
config: ethSdkConfig,
|
|
146
|
+
fs: realFs
|
|
147
|
+
};
|
|
148
|
+
console.log("Fetching contract ABIs...");
|
|
149
|
+
await gatherABIs(ctx);
|
|
150
|
+
console.log("Generating typed SDK...");
|
|
151
|
+
await generateSdk(ctx);
|
|
152
|
+
};
|
|
153
|
+
//#endregion
|
|
154
|
+
//#region src/cli/run.ts
|
|
155
|
+
const run = async (argv = process.argv) => {
|
|
156
|
+
const program = new Command();
|
|
157
|
+
program.name("zodiac-os").description("Zodiac OS SDK CLI – pull org data and contract ABIs").version("1.0.0").option("-c, --config <path>", "path to the config file", "zodiac.config.ts");
|
|
158
|
+
program.command("pull-org").description("Fetch Zodiac users and vaults, generate TypeScript types").action(async (_opts, cmd) => {
|
|
159
|
+
await pullOrg(await loadConfig(cmd.optsWithGlobals().config));
|
|
160
|
+
});
|
|
161
|
+
program.command("pull-contracts").description("Fetch contract ABIs, generate typed permissions kit").action(async (_opts, cmd) => {
|
|
162
|
+
await pullContracts(await loadConfig(cmd.optsWithGlobals().config));
|
|
163
|
+
});
|
|
164
|
+
program.command("pull").description("Fetch Zodiac org and contracts ABI, generate SDK functions").action(async (_opts, cmd) => {
|
|
165
|
+
const config = await loadConfig(cmd.optsWithGlobals().config);
|
|
166
|
+
await Promise.all([pullOrg(config), pullContracts(config)]);
|
|
167
|
+
});
|
|
168
|
+
await program.parseAsync(argv);
|
|
169
|
+
};
|
|
170
|
+
//#endregion
|
|
171
|
+
//#region src/cli/index.ts
|
|
172
|
+
run().then(() => {
|
|
173
|
+
process.exit(0);
|
|
174
|
+
}, (error) => {
|
|
175
|
+
if (error) console.error(error);
|
|
176
|
+
process.exit(1);
|
|
177
|
+
});
|
|
178
|
+
//#endregion
|
|
179
|
+
export {};
|
|
180
|
+
|
|
181
|
+
//# sourceMappingURL=cli.mjs.map
|
package/dist/cli.mjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.mjs","names":[],"sources":["../src/cli/config.ts","../src/cli/commands/pullOrg.ts","../src/cli/commands/pullContracts.ts","../src/cli/run.ts","../src/cli/index.ts"],"sourcesContent":["import type { EthSdkContracts } from '@gnosis-guild/eth-sdk'\nimport { pathToFileURL } from 'url'\nimport { resolve } from 'path'\n\nexport interface ZodiacConfig {\n apiKey: `zodiac_${string}`\n contracts?: EthSdkContracts\n}\n\nexport const defineConfig = (config: ZodiacConfig): ZodiacConfig => config\n\nconst DEFAULT_CONFIG_PATH = 'zodiac.config.ts'\n\nexport async function loadConfig(\n configPath: string = DEFAULT_CONFIG_PATH\n): Promise<ZodiacConfig> {\n const absolutePath = resolve(process.cwd(), configPath)\n\n let mod: Record<string, unknown>\n try {\n mod = await import(pathToFileURL(absolutePath).href)\n } catch (error: any) {\n if (error?.code === 'ERR_MODULE_NOT_FOUND' || error?.code === 'ENOENT') {\n throw new Error(`Config file not found: ${absolutePath}`)\n }\n throw error\n }\n\n const config = (mod.default ?? mod.config) as ZodiacConfig | undefined\n if (!config) {\n throw new Error(\n `Config file must export a default value or a named \"config\" export: ${absolutePath}`\n )\n }\n\n if (!config.apiKey) {\n throw new Error(`Config is missing required field \"apiKey\"`)\n }\n\n return config\n}\n","import type { ZodiacConfig } from '../config'\nimport { ApiClient } from '../../api'\nimport { invariant } from '@epic-web/invariant'\nimport {\n ModuleKind,\n Project,\n ScriptTarget,\n VariableDeclarationKind,\n} from 'ts-morph'\nimport { mkdirSync, writeFileSync } from 'fs'\nimport { join } from 'path'\nimport { fileURLToPath } from 'url'\n\nfunction resolveNodeModulesDir(): string {\n const selfPath = fileURLToPath(import.meta.url)\n const match = selfPath.match(/^(.+[/\\\\]node_modules)[/\\\\]/)\n if (match) return match[1]\n // Fallback for development (running from source, not from node_modules)\n return join(process.cwd(), 'node_modules')\n}\n\nconst toLiteral = (value: unknown, indent = 0): string => {\n const pad = ' '.repeat(indent)\n const childPad = ' '.repeat(indent + 1)\n\n if (value === null) return 'null'\n if (typeof value === 'bigint') return `${value}n`\n if (typeof value === 'string') return JSON.stringify(value)\n if (typeof value === 'number' || typeof value === 'boolean')\n return String(value)\n if (Array.isArray(value)) {\n if (value.length === 0) return '[]'\n return `[\\n${value.map((v) => `${childPad}${toLiteral(v, indent + 1)}`).join(',\\n')},\\n${pad}]`\n }\n if (typeof value === 'object') {\n const entries = Object.entries(value as Record<string, unknown>)\n if (entries.length === 0) return '{}'\n const props = entries.map(\n ([k, v]) =>\n `${childPad}${/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(k) ? k : JSON.stringify(k)}: ${toLiteral(v, indent + 1)}`\n )\n return `{\\n${props.join(',\\n')},\\n${pad}}`\n }\n return String(value)\n}\n\nexport const pullOrg = async (config: ZodiacConfig) => {\n const client = new ApiClient({\n apiKey: config.apiKey,\n })\n\n const [users, workspaceVaults] = await Promise.all([\n client.listUsers(),\n client.listVaults(),\n ])\n\n const allRawVaults = workspaceVaults.flatMap((ws) => ws.vaults)\n\n const { result: accounts } = await client.resolveConstellation(\n workspaceVaults[0].workspaceId, // can just use any workspace to resolve\n {\n specification: allRawVaults.map((vault) => ({\n type: 'SAFE',\n chain: vault.chain,\n address: vault.address,\n })),\n }\n )\n\n let accountIndex = 0\n const vaultsRecord: Record<string, unknown> = {}\n for (const ws of workspaceVaults) {\n const wsVaults: Record<string, unknown> = {}\n for (const vault of ws.vaults) {\n const account = accounts[accountIndex++]\n invariant(\n account.type === 'SAFE',\n `Expected SAFE account for vault ${vault.id}`\n )\n wsVaults[vault.label] = {\n id: vault.id,\n label: vault.label,\n address: account.address,\n chain: vault.chain,\n threshold: account.threshold,\n owners: [...account.owners],\n modules: [...account.modules],\n }\n }\n vaultsRecord[ws.workspaceName] = {\n workspaceId: ws.workspaceId,\n workspaceName: ws.workspaceName,\n vaults: wsVaults,\n }\n }\n\n const nameCount = new Map<string, number>()\n for (const user of users) {\n nameCount.set(user.fullName, (nameCount.get(user.fullName) ?? 0) + 1)\n }\n\n const usersRecord: Record<string, unknown> = {}\n for (const user of users) {\n const handle =\n nameCount.get(user.fullName)! > 1\n ? `${user.fullName} (${user.id})`\n : user.fullName\n usersRecord[handle] = {\n id: user.id,\n fullName: user.fullName,\n personalSafes: user.personalSafes,\n }\n }\n\n const outDir = join(resolveNodeModulesDir(), '.zodiac-os')\n\n mkdirSync(outDir, { recursive: true })\n\n // Write package.json so .zodiac-os is importable\n writeFileSync(\n join(outDir, 'package.json'),\n JSON.stringify(\n {\n name: '.zodiac-os',\n type: 'module',\n main: 'index.js',\n types: 'index.d.ts',\n },\n null,\n 2\n )\n )\n\n // Use ts-morph to generate TS, then emit JS + d.ts\n const project = new Project({\n compilerOptions: {\n declaration: true,\n module: ModuleKind.ESNext,\n target: ScriptTarget.ESNext,\n outDir,\n },\n useInMemoryFileSystem: true,\n })\n\n const sourceFile = project.createSourceFile('index.ts', '')\n\n sourceFile.addVariableStatement({\n isExported: true,\n declarationKind: VariableDeclarationKind.Const,\n declarations: [\n {\n name: 'users',\n initializer: `${toLiteral(usersRecord)} as const`,\n },\n ],\n })\n\n sourceFile.addVariableStatement({\n isExported: true,\n declarationKind: VariableDeclarationKind.Const,\n declarations: [\n {\n name: 'vaults',\n initializer: `${toLiteral(vaultsRecord)} as const`,\n },\n ],\n })\n\n const emitResult = sourceFile.getEmitOutput()\n for (const outputFile of emitResult.getOutputFiles()) {\n const filePath = outputFile.getFilePath()\n const fileName = filePath.includes('.d.ts') ? 'index.d.ts' : 'index.js'\n writeFileSync(join(outDir, fileName), outputFile.getText())\n }\n}\n","import type { ZodiacConfig } from '../config'\nimport { defineConfig } from '@gnosis-guild/eth-sdk'\nimport { gatherABIs } from '@gnosis-guild/eth-sdk/dist/abi-management'\nimport { generateSdk } from '@gnosis-guild/eth-sdk/dist/client'\nimport { createEthSdkConfig } from '@gnosis-guild/eth-sdk/dist/config/types'\nimport { realFs } from '@gnosis-guild/eth-sdk/dist/peripherals/fs'\n\nexport const pullContracts = async (config: ZodiacConfig) => {\n if (!config.contracts || Object.keys(config.contracts).length === 0) {\n console.log('No contracts defined in config, skipping.')\n return\n }\n\n const cwd = process.cwd()\n const ethSdkConfig = createEthSdkConfig(\n defineConfig({\n contracts: config.contracts,\n })\n )\n\n const ctx = {\n cliArgs: { workingDirPath: cwd },\n config: ethSdkConfig,\n fs: realFs,\n }\n\n console.log('Fetching contract ABIs...')\n await gatherABIs(ctx)\n\n console.log('Generating typed SDK...')\n await generateSdk(ctx)\n}\n","import { Command } from 'commander'\nimport { loadConfig } from './config'\nimport { pullOrg } from './commands/pullOrg'\nimport { pullContracts } from './commands/pullContracts'\n\nexport const run = async (argv: string[] = process.argv) => {\n const program = new Command()\n\n program\n .name('zodiac-os')\n .description('Zodiac OS SDK CLI – pull org data and contract ABIs')\n .version('1.0.0')\n .option(\n '-c, --config <path>',\n 'path to the config file',\n 'zodiac.config.ts'\n )\n\n program\n .command('pull-org')\n .description('Fetch Zodiac users and vaults, generate TypeScript types')\n .action(async (_opts, cmd) => {\n const config = await loadConfig(cmd.optsWithGlobals().config)\n await pullOrg(config)\n })\n\n program\n .command('pull-contracts')\n .description('Fetch contract ABIs, generate typed permissions kit')\n .action(async (_opts, cmd) => {\n const config = await loadConfig(cmd.optsWithGlobals().config)\n await pullContracts(config)\n })\n\n program\n .command('pull')\n .description('Fetch Zodiac org and contracts ABI, generate SDK functions')\n .action(async (_opts, cmd) => {\n const config = await loadConfig(cmd.optsWithGlobals().config)\n await Promise.all([pullOrg(config), pullContracts(config)])\n })\n\n await program.parseAsync(argv)\n}\n","#!/usr/bin/env node\nimport { run } from './run'\n\nrun().then(\n () => {\n process.exit(0)\n },\n (error: unknown) => {\n if (error) console.error(error)\n process.exit(1)\n }\n)\n"],"mappings":";;;;;;;;;;;;;;AAWA,MAAM,sBAAsB;AAE5B,eAAsB,WACpB,aAAqB,qBACE;CACvB,MAAM,eAAe,QAAQ,QAAQ,KAAK,EAAE,WAAW;CAEvD,IAAI;AACJ,KAAI;AACF,QAAM,MAAM,OAAO,cAAc,aAAa,CAAC;UACxC,OAAY;AACnB,MAAI,OAAO,SAAS,0BAA0B,OAAO,SAAS,SAC5D,OAAM,IAAI,MAAM,0BAA0B,eAAe;AAE3D,QAAM;;CAGR,MAAM,SAAU,IAAI,WAAW,IAAI;AACnC,KAAI,CAAC,OACH,OAAM,IAAI,MACR,uEAAuE,eACxE;AAGH,KAAI,CAAC,OAAO,OACV,OAAM,IAAI,MAAM,4CAA4C;AAG9D,QAAO;;;;AC1BT,SAAS,wBAAgC;CAEvC,MAAM,QADW,cAAc,OAAO,KAAK,IAAI,CACxB,MAAM,8BAA8B;AAC3D,KAAI,MAAO,QAAO,MAAM;AAExB,QAAO,KAAK,QAAQ,KAAK,EAAE,eAAe;;AAG5C,MAAM,aAAa,OAAgB,SAAS,MAAc;CACxD,MAAM,MAAM,KAAK,OAAO,OAAO;CAC/B,MAAM,WAAW,KAAK,OAAO,SAAS,EAAE;AAExC,KAAI,UAAU,KAAM,QAAO;AAC3B,KAAI,OAAO,UAAU,SAAU,QAAO,GAAG,MAAM;AAC/C,KAAI,OAAO,UAAU,SAAU,QAAO,KAAK,UAAU,MAAM;AAC3D,KAAI,OAAO,UAAU,YAAY,OAAO,UAAU,UAChD,QAAO,OAAO,MAAM;AACtB,KAAI,MAAM,QAAQ,MAAM,EAAE;AACxB,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,SAAO,MAAM,MAAM,KAAK,MAAM,GAAG,WAAW,UAAU,GAAG,SAAS,EAAE,GAAG,CAAC,KAAK,MAAM,CAAC,KAAK,IAAI;;AAE/F,KAAI,OAAO,UAAU,UAAU;EAC7B,MAAM,UAAU,OAAO,QAAQ,MAAiC;AAChE,MAAI,QAAQ,WAAW,EAAG,QAAO;AAKjC,SAAO,MAJO,QAAQ,KACnB,CAAC,GAAG,OACH,GAAG,WAAW,6BAA6B,KAAK,EAAE,GAAG,IAAI,KAAK,UAAU,EAAE,CAAC,IAAI,UAAU,GAAG,SAAS,EAAE,GAC1G,CACkB,KAAK,MAAM,CAAC,KAAK,IAAI;;AAE1C,QAAO,OAAO,MAAM;;AAGtB,MAAa,UAAU,OAAO,WAAyB;CACrD,MAAM,SAAS,IAAI,UAAU,EAC3B,QAAQ,OAAO,QAChB,CAAC;CAEF,MAAM,CAAC,OAAO,mBAAmB,MAAM,QAAQ,IAAI,CACjD,OAAO,WAAW,EAClB,OAAO,YAAY,CACpB,CAAC;CAEF,MAAM,eAAe,gBAAgB,SAAS,OAAO,GAAG,OAAO;CAE/D,MAAM,EAAE,QAAQ,aAAa,MAAM,OAAO,qBACxC,gBAAgB,GAAG,aACnB,EACE,eAAe,aAAa,KAAK,WAAW;EAC1C,MAAM;EACN,OAAO,MAAM;EACb,SAAS,MAAM;EAChB,EAAE,EACJ,CACF;CAED,IAAI,eAAe;CACnB,MAAM,eAAwC,EAAE;AAChD,MAAK,MAAM,MAAM,iBAAiB;EAChC,MAAM,WAAoC,EAAE;AAC5C,OAAK,MAAM,SAAS,GAAG,QAAQ;GAC7B,MAAM,UAAU,SAAS;AACzB,aACE,QAAQ,SAAS,QACjB,mCAAmC,MAAM,KAC1C;AACD,YAAS,MAAM,SAAS;IACtB,IAAI,MAAM;IACV,OAAO,MAAM;IACb,SAAS,QAAQ;IACjB,OAAO,MAAM;IACb,WAAW,QAAQ;IACnB,QAAQ,CAAC,GAAG,QAAQ,OAAO;IAC3B,SAAS,CAAC,GAAG,QAAQ,QAAQ;IAC9B;;AAEH,eAAa,GAAG,iBAAiB;GAC/B,aAAa,GAAG;GAChB,eAAe,GAAG;GAClB,QAAQ;GACT;;CAGH,MAAM,4BAAY,IAAI,KAAqB;AAC3C,MAAK,MAAM,QAAQ,MACjB,WAAU,IAAI,KAAK,WAAW,UAAU,IAAI,KAAK,SAAS,IAAI,KAAK,EAAE;CAGvE,MAAM,cAAuC,EAAE;AAC/C,MAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,SACJ,UAAU,IAAI,KAAK,SAAS,GAAI,IAC5B,GAAG,KAAK,SAAS,IAAI,KAAK,GAAG,KAC7B,KAAK;AACX,cAAY,UAAU;GACpB,IAAI,KAAK;GACT,UAAU,KAAK;GACf,eAAe,KAAK;GACrB;;CAGH,MAAM,SAAS,KAAK,uBAAuB,EAAE,aAAa;AAE1D,WAAU,QAAQ,EAAE,WAAW,MAAM,CAAC;AAGtC,eACE,KAAK,QAAQ,eAAe,EAC5B,KAAK,UACH;EACE,MAAM;EACN,MAAM;EACN,MAAM;EACN,OAAO;EACR,EACD,MACA,EACD,CACF;CAaD,MAAM,aAVU,IAAI,QAAQ;EAC1B,iBAAiB;GACf,aAAa;GACb,QAAQ,WAAW;GACnB,QAAQ,aAAa;GACrB;GACD;EACD,uBAAuB;EACxB,CAAC,CAEyB,iBAAiB,YAAY,GAAG;AAE3D,YAAW,qBAAqB;EAC9B,YAAY;EACZ,iBAAiB,wBAAwB;EACzC,cAAc,CACZ;GACE,MAAM;GACN,aAAa,GAAG,UAAU,YAAY,CAAC;GACxC,CACF;EACF,CAAC;AAEF,YAAW,qBAAqB;EAC9B,YAAY;EACZ,iBAAiB,wBAAwB;EACzC,cAAc,CACZ;GACE,MAAM;GACN,aAAa,GAAG,UAAU,aAAa,CAAC;GACzC,CACF;EACF,CAAC;CAEF,MAAM,aAAa,WAAW,eAAe;AAC7C,MAAK,MAAM,cAAc,WAAW,gBAAgB,CAGlD,eAAc,KAAK,QAFF,WAAW,aAAa,CACf,SAAS,QAAQ,GAAG,eAAe,WACzB,EAAE,WAAW,SAAS,CAAC;;;;ACrK/D,MAAa,gBAAgB,OAAO,WAAyB;AAC3D,KAAI,CAAC,OAAO,aAAa,OAAO,KAAK,OAAO,UAAU,CAAC,WAAW,GAAG;AACnE,UAAQ,IAAI,4CAA4C;AACxD;;CAGF,MAAM,MAAM,QAAQ,KAAK;CACzB,MAAM,eAAe,mBACnB,aAAa,EACX,WAAW,OAAO,WACnB,CAAC,CACH;CAED,MAAM,MAAM;EACV,SAAS,EAAE,gBAAgB,KAAK;EAChC,QAAQ;EACR,IAAI;EACL;AAED,SAAQ,IAAI,4BAA4B;AACxC,OAAM,WAAW,IAAI;AAErB,SAAQ,IAAI,0BAA0B;AACtC,OAAM,YAAY,IAAI;;;;ACzBxB,MAAa,MAAM,OAAO,OAAiB,QAAQ,SAAS;CAC1D,MAAM,UAAU,IAAI,SAAS;AAE7B,SACG,KAAK,YAAY,CACjB,YAAY,sDAAsD,CAClE,QAAQ,QAAQ,CAChB,OACC,uBACA,2BACA,mBACD;AAEH,SACG,QAAQ,WAAW,CACnB,YAAY,2DAA2D,CACvE,OAAO,OAAO,OAAO,QAAQ;AAE5B,QAAM,QADS,MAAM,WAAW,IAAI,iBAAiB,CAAC,OAAO,CACxC;GACrB;AAEJ,SACG,QAAQ,iBAAiB,CACzB,YAAY,sDAAsD,CAClE,OAAO,OAAO,OAAO,QAAQ;AAE5B,QAAM,cADS,MAAM,WAAW,IAAI,iBAAiB,CAAC,OAAO,CAClC;GAC3B;AAEJ,SACG,QAAQ,OAAO,CACf,YAAY,6DAA6D,CACzE,OAAO,OAAO,OAAO,QAAQ;EAC5B,MAAM,SAAS,MAAM,WAAW,IAAI,iBAAiB,CAAC,OAAO;AAC7D,QAAM,QAAQ,IAAI,CAAC,QAAQ,OAAO,EAAE,cAAc,OAAO,CAAC,CAAC;GAC3D;AAEJ,OAAM,QAAQ,WAAW,KAAK;;;;ACvChC,KAAK,CAAC,WACE;AACJ,SAAQ,KAAK,EAAE;IAEhB,UAAmB;AAClB,KAAI,MAAO,SAAQ,MAAM,MAAM;AAC/B,SAAQ,KAAK,EAAE;EAElB"}
|