@zodiac-os/sdk 1.9.0 → 1.11.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.
@@ -1,2 +1,2 @@
1
- import { a as EVERYTHING, c as Scoping, i as ConditionFunction, l as TargetPermission, n as ChainPrefix, o as FunctionPermission, r as chainIdFor, s as Options, t as CHAIN_IDS, u as buildAllowKit } from "../index-DTBaxN7b.mjs";
1
+ import { a as EVERYTHING, c as Scoping, i as ConditionFunction, l as TargetPermission, n as ChainPrefix, o as FunctionPermission, r as chainIdFor, s as Options, t as CHAIN_IDS, u as buildAllowKit } from "../index-DoZbhpS6.mjs";
2
2
  export { CHAIN_IDS, ChainPrefix, ConditionFunction, EVERYTHING, FunctionPermission, Options, Scoping, TargetPermission, buildAllowKit, chainIdFor };
@@ -60,10 +60,21 @@ type LoadConfigOptions = {
60
60
  * If omitted, `loadConfig` throws when no key is found.
61
61
  */
62
62
  onMissingKey?: (rootDir: string) => Promise<string>;
63
+ /**
64
+ * When the config file doesn't exist, write a minimal stub before
65
+ * loading. Used by the CLI's `pull` commands so first-time setup
66
+ * works without the user having to scaffold the file themselves.
67
+ */
68
+ createIfMissing?: boolean;
63
69
  };
70
+ /**
71
+ * Write a starter `zodiac.config.ts` if no file exists at `absolutePath`.
72
+ * Returns `true` if a file was written.
73
+ */
74
+ declare const ensureConfigStub: (absolutePath: string) => boolean;
64
75
  declare function loadConfig(configPath?: string, options?: LoadConfigOptions): Promise<ResolvedConfig>;
65
76
  declare const DEFAULT_ABIS_DIR = "abis";
66
77
  declare function resolveAbisDir(config: ResolvedConfig): string;
67
78
  //#endregion
68
- export { Contracts, ContractsNode, DEFAULT_ABIS_DIR, ResolvedConfig, ZodiacConfig, defineConfig, loadConfig, resolveAbisDir };
79
+ export { Contracts, ContractsNode, DEFAULT_ABIS_DIR, ResolvedConfig, ZodiacConfig, defineConfig, ensureConfigStub, loadConfig, resolveAbisDir };
69
80
  //# sourceMappingURL=config.d.mts.map
@@ -1,48 +1,2 @@
1
- import { pathToFileURL } from "url";
2
- import { dirname, resolve } from "path";
3
- //#region src/cli/config.ts
4
- const defineConfig = (config) => config;
5
- const DEFAULT_CONFIG_PATH = "zodiac.config.ts";
6
- async function loadConfig(configPath = DEFAULT_CONFIG_PATH, options = {}) {
7
- const absolutePath = resolve(process.cwd(), configPath);
8
- let mod;
9
- try {
10
- mod = await import(pathToFileURL(absolutePath).href);
11
- } catch (error) {
12
- if (error?.code === "ERR_MODULE_NOT_FOUND" || error?.code === "ENOENT") throw new Error(`Config file not found: ${absolutePath}`);
13
- throw error;
14
- }
15
- const config = mod.default ?? mod.config;
16
- if (!config) throw new Error(`Config file must export a default value or a named "config" export: ${absolutePath}`);
17
- const rootDir = dirname(absolutePath);
18
- const apiKey = await resolveApiKey(config, rootDir, options);
19
- return {
20
- ...config,
21
- apiKey,
22
- rootDir
23
- };
24
- }
25
- const resolveApiKey = async (config, rootDir, { onMissingKey }) => {
26
- if (config.apiKey != null) {
27
- if (!isApiKey(config.apiKey)) throw new Error("`apiKey` in zodiac.config.ts is malformed: a valid Zodiac API key starts with \"zodiac_\". Either remove the field to use ZODIAC_API_KEY, or run `zodiac init` to mint a fresh key.");
28
- return config.apiKey;
29
- }
30
- const fromEnv = process.env.ZODIAC_API_KEY;
31
- if (fromEnv != null && fromEnv !== "") {
32
- if (!isApiKey(fromEnv)) throw new Error("ZODIAC_API_KEY is set but malformed: a valid Zodiac API key starts with \"zodiac_\". Run `zodiac init` to mint a fresh key.");
33
- return fromEnv;
34
- }
35
- if (onMissingKey == null) throw new Error("No Zodiac API key found. Set ZODIAC_API_KEY in your environment, or run `zodiac init` to generate one.");
36
- const minted = await onMissingKey(rootDir);
37
- if (!isApiKey(minted)) throw new Error(`onMissingKey returned an invalid Zodiac API key (expected a value starting with "zodiac_").`);
38
- return minted;
39
- };
40
- const isApiKey = (value) => value != null && value.startsWith("zodiac_");
41
- const DEFAULT_ABIS_DIR = "abis";
42
- function resolveAbisDir(config) {
43
- return resolve(config.rootDir, config.abisDir ?? "abis");
44
- }
45
- //#endregion
46
- export { DEFAULT_ABIS_DIR, defineConfig, loadConfig, resolveAbisDir };
47
-
48
- //# sourceMappingURL=config.mjs.map
1
+ import { a as resolveAbisDir, i as loadConfig, n as defineConfig, r as ensureConfigStub, t as DEFAULT_ABIS_DIR } from "../config-DVHdL2W6.mjs";
2
+ export { DEFAULT_ABIS_DIR, defineConfig, ensureConfigStub, loadConfig, resolveAbisDir };
package/dist/cli.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { n as resolveZodiacDir, t as ApiClient } from "./api-CygEDU4N.mjs";
3
3
  import { a as walkContracts, i as readAbi, n as chainIdFor, o as writeAbi, r as abiFilePath } from "./networks-BTW1qAAa.mjs";
4
- import { loadConfig, resolveAbisDir } from "./cli/config.mjs";
4
+ import { a as resolveAbisDir, i as loadConfig, o as findProjectRoot, r as ensureConfigStub } from "./config-DVHdL2W6.mjs";
5
5
  import fs, { existsSync, readFileSync, writeFileSync } from "node:fs";
6
6
  import path, { basename, join, resolve } from "node:path";
7
7
  import { invariant } from "@epic-web/invariant";
@@ -18,7 +18,7 @@ import { mkdirSync, writeFileSync as writeFileSync$1 } from "fs";
18
18
  const DEFAULT_APP_URL = "https://app.zodiac.eco";
19
19
  const CALLBACK_TIMEOUT_MS = 300 * 1e3;
20
20
  const init = async (options = {}) => {
21
- const rootDir = options.rootDir ?? process.cwd();
21
+ const rootDir = options.rootDir ?? findProjectRoot();
22
22
  const appUrl = (options.appUrl ?? process.env.ZODIAC_APP_URL ?? DEFAULT_APP_URL).replace(/\/$/, "");
23
23
  const label = basename(resolve(rootDir)) || "zodiac-cli";
24
24
  const codeVerifier = randomBytes(32).toString("base64url");
@@ -49,6 +49,8 @@ const init = async (options = {}) => {
49
49
  ZODIAC_API_URL: `${appUrl}/api/v1`
50
50
  });
51
51
  console.log(`✅ API key written to ${envPath}`);
52
+ const configPath = join(rootDir, "zodiac.config.ts");
53
+ if (ensureConfigStub(configPath)) console.log(`✅ Created ${configPath}`);
52
54
  return apiKey;
53
55
  };
54
56
  const exchangeCodeForKey = async (appUrl, code, codeVerifier) => {
@@ -528,10 +530,13 @@ function report(chain, segments, address, status, file, reason) {
528
530
  //#endregion
529
531
  //#region src/cli/run.ts
530
532
  config({ quiet: true });
531
- const loadConfigOrInit = (configPath) => loadConfig(configPath, { onMissingKey: async (rootDir) => {
532
- console.log("No ZODIAC_API_KEY found. Starting authorization to mint one for this directory…");
533
- return init({ rootDir });
534
- } });
533
+ const loadConfigOrInit = (configPath) => loadConfig(configPath, {
534
+ createIfMissing: true,
535
+ onMissingKey: async (rootDir) => {
536
+ console.log("No ZODIAC_API_KEY found. Starting authorization to mint one for this directory…");
537
+ return init({ rootDir });
538
+ }
539
+ });
535
540
  const run = async (argv = process.argv) => {
536
541
  const program = new Command();
537
542
  program.name("zodiac").description("Zodiac SDK CLI – pull org data and contract ABIs").version("1.0.0").option("-c, --config <path>", "path to the config file", "zodiac.config.ts");
package/dist/cli.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.mjs","names":["join","loadDotenv"],"sources":["../src/cli/commands/init.ts","../src/cli/commands/pullOrg.ts","../src/allow/fetch.ts","../src/allow/codegen.ts","../src/cli/commands/pullContracts.ts","../src/cli/run.ts","../src/cli/index.ts"],"sourcesContent":["import { createHash, randomBytes } from 'node:crypto'\nimport {\n createServer,\n type IncomingMessage,\n type ServerResponse,\n} from 'node:http'\nimport { existsSync, readFileSync, writeFileSync } from 'node:fs'\nimport { basename, join, resolve } from 'node:path'\nimport open from 'open'\n\nconst DEFAULT_APP_URL = 'https://app.zodiac.eco'\nconst CALLBACK_TIMEOUT_MS = 5 * 60 * 1000 // 5 minutes\n\ntype InitOptions = {\n rootDir?: string\n /** Override the Zodiac app base URL (defaults to ZODIAC_APP_URL env or app.zodiac.eco). */\n appUrl?: string\n}\n\nexport const init = async (options: InitOptions = {}): Promise<string> => {\n const rootDir = options.rootDir ?? process.cwd()\n const appUrl = (\n options.appUrl ??\n process.env.ZODIAC_APP_URL ??\n DEFAULT_APP_URL\n ).replace(/\\/$/, '')\n\n const label = basename(resolve(rootDir)) || 'zodiac-cli'\n\n // PKCE: keep `code_verifier` private to this process. Send only the\n // SHA-256 hash (`code_challenge`) over the wire.\n const codeVerifier = randomBytes(32).toString('base64url')\n const codeChallenge = createHash('sha256')\n .update(codeVerifier)\n .digest('base64url')\n\n // `state` binds the redirect we receive on the loopback to this CLI\n // invocation, defending against a stray local process trying to inject\n // an auth code into our callback.\n const state = randomBytes(32).toString('base64url')\n\n const { port, waitForCode, close } = await startCallbackServer({\n expectedState: state,\n appUrl,\n })\n\n const callbackUrl = `http://127.0.0.1:${port}/callback`\n const authUrl = new URL('/cli-auth', appUrl)\n authUrl.searchParams.set('callback', callbackUrl)\n authUrl.searchParams.set('state', state)\n authUrl.searchParams.set('code_challenge', codeChallenge)\n authUrl.searchParams.set('label', label)\n\n console.log(\n `Opening ${authUrl} in your browser. Approve the request to receive an API key.`\n )\n await open(authUrl.toString())\n\n let code: string\n try {\n code = await waitForCode(CALLBACK_TIMEOUT_MS)\n } finally {\n await close()\n }\n\n const apiKey = await exchangeCodeForKey(appUrl, code, codeVerifier)\n\n const envPath = join(rootDir, '.env')\n const apiUrl = `${appUrl}/api/v1`\n writeEnv(envPath, { ZODIAC_API_KEY: apiKey, ZODIAC_API_URL: apiUrl })\n\n console.log(`✅ API key written to ${envPath}`)\n\n return apiKey\n}\n\nconst exchangeCodeForKey = async (\n appUrl: string,\n code: string,\n codeVerifier: string\n): Promise<string> => {\n const response = await fetch(`${appUrl}/cli-auth/exchange`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ code, code_verifier: codeVerifier }),\n })\n\n if (!response.ok) {\n let detail = ''\n try {\n const body = (await response.json()) as { error?: string }\n detail = body.error ? `: ${body.error}` : ''\n } catch {\n // ignore — fall through to a generic message\n }\n throw new Error(\n `Failed to exchange auth code (${response.status})${detail}`\n )\n }\n\n const { key } = (await response.json()) as { key: unknown }\n if (typeof key !== 'string' || !key.startsWith('zodiac_')) {\n throw new Error(\n 'Exchange endpoint returned an unexpected response (missing or malformed key)'\n )\n }\n return key\n}\n\ntype StartServerOptions = {\n expectedState: string\n appUrl: string\n}\n\ntype StartServerResult = {\n port: number\n waitForCode: (timeoutMs: number) => Promise<string>\n close: () => Promise<void>\n}\n\nconst startCallbackServer = async ({\n expectedState,\n appUrl,\n}: StartServerOptions): Promise<StartServerResult> => {\n let resolveCode: (code: string) => void = () => {}\n let rejectCode: (err: Error) => void = () => {}\n const codePromise = new Promise<string>((res, rej) => {\n resolveCode = res\n rejectCode = rej\n })\n\n const handler = (req: IncomingMessage, res: ServerResponse) => {\n if (req.method !== 'GET' || !req.url?.startsWith('/callback')) {\n res.statusCode = 404\n res.end()\n return\n }\n\n const url = new URL(req.url, `http://127.0.0.1`)\n const code = url.searchParams.get('code')\n const state = url.searchParams.get('state')\n\n if (state !== expectedState) {\n // A stray request — could be a stale browser tab, a malicious\n // local process, etc. Silently 403 and keep waiting for the right\n // one. Don't reject the CLI promise.\n respondHtml(\n res,\n 403,\n renderHtml(\n 'Mismatched state',\n `<p>This callback didn't match the running <code>zodiac init</code> session. You can close this tab.</p>`\n )\n )\n return\n }\n\n if (typeof code !== 'string' || code.length === 0) {\n respondHtml(\n res,\n 400,\n renderHtml(\n 'Missing code',\n `<p>The callback URL didn't include an auth <code>code</code>. Please re-run <code>zodiac init</code>.</p>`\n )\n )\n return\n }\n\n respondHtml(\n res,\n 200,\n renderHtml(\n 'Authorized',\n `<p>Your CLI now has its API key. You can close this tab and return to your terminal.</p>\n <p><a href=\"${escapeHtml(appUrl)}\">Back to Zodiac</a></p>`\n )\n )\n resolveCode(code)\n }\n\n const server = createServer(handler)\n await new Promise<void>((res) => server.listen(0, '127.0.0.1', res))\n const address = server.address()\n if (address == null || typeof address === 'string') {\n throw new Error('Failed to bind loopback callback server')\n }\n const port = address.port\n\n const close = (): Promise<void> =>\n new Promise<void>((resolve) => {\n server.close(() => resolve())\n })\n\n const waitForCode = (timeoutMs: number) =>\n Promise.race<string>([\n codePromise,\n new Promise<string>((_, rej) =>\n setTimeout(() => {\n rejectCode(new Error(`Timed out waiting for CLI auth callback`))\n rej(new Error(`Timed out waiting for CLI auth callback`))\n }, timeoutMs)\n ),\n ])\n\n return { port, waitForCode, close }\n}\n\nconst respondHtml = (res: ServerResponse, status: number, body: string) => {\n // Force the connection to close after the response so the browser\n // doesn't keep waiting on a keep-alive socket once the CLI exits.\n // Resolve only after 'finish' (response handed to the OS) so the\n // bytes can flush before the process tears down.\n res.setHeader('Connection', 'close')\n res.setHeader('content-type', 'text/html; charset=utf-8')\n res.statusCode = status\n res.end(body)\n}\n\nconst renderHtml = (title: string, bodyHtml: string) => `<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>${escapeHtml(title)} — Zodiac CLI</title>\n <style>\n body { font: 16px/1.5 -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif; max-width: 32rem; margin: 4rem auto; padding: 0 1rem; color: #1f2937; }\n h1 { font-weight: 300; font-size: 1.75rem; margin-bottom: 1rem; }\n code { background: #f3f4f6; padding: 0.125rem 0.375rem; border-radius: 0.25rem; }\n a { color: #1d4ed8; }\n </style>\n </head>\n <body>\n <h1>${escapeHtml(title)}</h1>\n ${bodyHtml}\n </body>\n</html>\n`\n\nconst escapeHtml = (value: string) =>\n value\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;')\n\nconst writeEnv = (envPath: string, vars: Record<string, string>): void => {\n let contents = existsSync(envPath) ? readFileSync(envPath, 'utf8') : ''\n\n for (const [key, value] of Object.entries(vars)) {\n contents = upsertEnvLine(contents, key, value)\n }\n\n writeFileSync(envPath, contents, 'utf8')\n}\n\nconst upsertEnvLine = (\n contents: string,\n key: string,\n value: string\n): string => {\n const line = `${key}=${value}`\n const lines = contents.split(/\\r?\\n/)\n const idx = lines.findIndex((l) => new RegExp(`^\\\\s*${key}\\\\s*=`).test(l))\n\n if (idx >= 0) {\n lines[idx] = line\n return lines.join('\\n')\n }\n\n return contents.length === 0 || contents.endsWith('\\n')\n ? `${contents}${line}\\n`\n : `${contents}\\n${line}\\n`\n}\n","import type { ResolvedConfig } from '../config'\nimport { ApiClient } from '../../api'\nimport { invariant } from '@epic-web/invariant'\nimport { getAddress } from 'ethers'\nimport {\n ModuleKind,\n Project,\n ScriptTarget,\n VariableDeclarationKind,\n} from 'ts-morph'\nimport { mkdirSync, writeFileSync } from 'fs'\nimport { join } from 'path'\nimport { resolveZodiacDir } from '../../paths'\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: ResolvedConfig) => {\n const client = new ApiClient({\n apiKey: config.apiKey,\n })\n\n const [users, workspaceAccounts] = await Promise.all([\n client.listUsers(),\n client.listAccounts(),\n ])\n\n // Fetch fresh on-chain state via `resolveConstellation` for every\n // account we can resolve:\n // - `spec` present → pass the stored apply-time node verbatim\n // (deployed nodes match on-chain; undeployed ones derive via\n // CREATE2 from the stored nonce + config).\n // - `vault: true` with no spec → treat as a pre-existing on-chain\n // SAFE (e.g. a workspace vault created outside the\n // constellation-as-code flow). The resolver finds it on-chain.\n // - `vault: false` with no spec → a constituent of a still-pending\n // constellation that's never been deployed. We can't usefully\n // resolve it, so skip; the codegen emits minimal fields.\n const allAccounts = workspaceAccounts.flatMap((ws) => ws.accounts)\n const resolvableAccounts = allAccounts.filter(\n (a) => a.spec != null || a.vault\n )\n const resolved = new Map<\n string,\n Awaited<ReturnType<typeof client.resolveConstellation>>['result'][number]\n >()\n if (resolvableAccounts.length > 0) {\n const response = await client.resolveConstellation(\n workspaceAccounts[0].workspaceId, // any workspace works for the resolve route\n {\n specification: resolvableAccounts.map((account, i) =>\n account.spec != null\n ? account.spec\n : {\n // Synthesize a ref for vault-fallback entries (no stored\n // spec). The /resolve payload requires a ref on every\n // entry; the value isn't used downstream beyond echoing\n // back into the response, so a positional id is fine.\n ref: `vault_${i}` as Lowercase<string>,\n type: 'SAFE',\n chain: account.chain,\n address: account.address,\n }\n ),\n }\n )\n invariant(\n response?.result?.length === resolvableAccounts.length,\n `resolveConstellation returned ${response?.result?.length ?? 0} accounts for ${resolvableAccounts.length} accounts`\n )\n resolvableAccounts.forEach((account, i) => {\n resolved.set(account.id, response.result[i])\n })\n }\n\n // Group accounts by type into separate bracket-access namespaces:\n // `safes`, `rolesMods`, `delays`. This way `eth.safe[...]`\n // IntelliSense only suggests SAFE labels, and the label-collision\n // suffix only kicks in when two accounts **of the same type** share\n // a label.\n const accountsRecord: Record<string, unknown> = {}\n for (const ws of workspaceAccounts) {\n const safes: Record<string, unknown> = {}\n const rolesMods: Record<string, unknown> = {}\n const delays: Record<string, unknown> = {}\n\n const bucketsByType = {\n SAFE: safes,\n ROLES: rolesMods,\n DELAY: delays,\n } as const\n\n type NodeType = 'SAFE' | 'ROLES' | 'DELAY'\n const isNodeType = (type: string): type is NodeType =>\n type === 'SAFE' || type === 'ROLES' || type === 'DELAY'\n\n // Count labels per type so we only suffix within-type collisions.\n const labelCountByType: Record<NodeType, Map<string, number>> = {\n SAFE: new Map(),\n ROLES: new Map(),\n DELAY: new Map(),\n }\n for (const account of ws.accounts) {\n if (!isNodeType(account.type)) continue\n const counts = labelCountByType[account.type]\n counts.set(account.label, (counts.get(account.label) ?? 0) + 1)\n }\n\n for (const account of ws.accounts) {\n if (!isNodeType(account.type)) continue\n const onChain = resolved.get(account.id)\n const counts = labelCountByType[account.type]\n const key =\n (counts.get(account.label) ?? 0) > 1\n ? `${account.label} (${getAddress(account.address)})`\n : account.label\n bucketsByType[account.type][key] = {\n id: account.id,\n label: account.label,\n address: account.address,\n chain: account.chain,\n vault: account.vault,\n ...(onChain?.type === 'SAFE' && {\n threshold: onChain.threshold,\n owners: [...onChain.owners],\n modules: [...onChain.modules],\n }),\n }\n }\n\n accountsRecord[ws.workspaceName] = {\n workspaceId: ws.workspaceId,\n workspaceName: ws.workspaceName,\n safes,\n rolesMods,\n delays,\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 = resolveZodiacDir(config.rootDir)\n\n mkdirSync(outDir, { recursive: true })\n\n // Pin CJS so `require()` works regardless of the parent package.json's type\n writeFileSync(\n join(outDir, 'package.json'),\n JSON.stringify(\n {\n type: 'commonjs',\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.CommonJS,\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: 'accounts',\n initializer: `${toLiteral(accountsRecord)} 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 let contents = outputFile.getText()\n // Augment the SDK's global `ZodiacGeneratedCodegen` interface so\n // `constellation()`'s default type parameter picks up these literal\n // shapes automatically.\n if (fileName === 'index.d.ts') {\n contents += `\ndeclare global {\n interface ZodiacGeneratedCodegen {\n users: typeof users;\n accounts: typeof accounts;\n }\n}\n`\n }\n writeFileSync(join(outDir, fileName), contents)\n }\n}\n","import { chainIdFor, type ChainPrefix } from './networks'\n\nexport type AbiFragment = Record<string, any>\nexport type Abi = AbiFragment[]\n\n// Returns null on any failure so callers can fall back to a manual ABI file.\nexport async function fetchAbi(\n chainId: number,\n address: `0x${string}`\n): Promise<Abi | null> {\n const url = `https://api.abi.pub/v1/chains/${chainId}/etherscan?module=contract&action=getabi&address=${address}`\n let body: { status?: string; message?: string; result?: string }\n try {\n const resp = await fetch(url)\n if (!resp.ok) return null\n body = (await resp.json()) as typeof body\n } catch {\n return null\n }\n if (body.status !== '1' || typeof body.result !== 'string') return null\n try {\n const parsed = JSON.parse(body.result)\n if (!Array.isArray(parsed) || parsed.length === 0) return null\n return parsed as Abi\n } catch {\n return null\n }\n}\n\nexport const fetchAbiForPrefix = (\n prefix: ChainPrefix,\n address: `0x${string}`\n) => fetchAbi(chainIdFor(prefix), address)\n","import fs from 'node:fs'\nimport path from 'node:path'\nimport { walkContracts, readAbi, type ContractNode } from './abi'\nimport type { Abi, AbiFragment } from './fetch'\n\n// Emits named tuple elements per ABI input — this is the reason we don't rely\n// on viem/abitype, which produces unnamed tuples (microsoft/TypeScript#44939).\nexport function generateAllowTypes(\n abisDir: string,\n contractsConfig: Record<string, any>\n): string {\n type Tree = Map<string, Tree | { node: ContractNode; abi: Abi }>\n\n const root: Tree = new Map()\n for (const node of walkContracts(contractsConfig)) {\n const abi = readAbi(abisDir, node)\n if (!abi) continue\n insertIntoTree(root, [node.chain, ...node.segments], { node, abi })\n }\n\n const out: string[] = []\n out.push('// AUTO-GENERATED by `zodiac pull-contracts`. Do not edit.')\n out.push('/* eslint-disable */')\n out.push('')\n out.push(\n `import type { FunctionPermission, TargetPermission } from \"zodiac-roles-sdk\";`\n )\n out.push(\n `import type { Scoping, Options, EVERYTHING } from \"@zodiac-os/sdk/allow\";`\n )\n out.push('')\n out.push('declare global {')\n out.push(' interface AllowKit ' + renderTree(root, ' '))\n out.push('}')\n out.push('')\n out.push('export {};')\n out.push('')\n return out.join('\\n')\n}\n\nfunction insertIntoTree(\n tree: Map<string, any>,\n segments: string[],\n leaf: { node: ContractNode; abi: Abi }\n): void {\n const [first, ...rest] = segments\n if (!first) throw new Error('empty segments')\n if (rest.length === 0) {\n tree.set(first, leaf)\n return\n }\n let child = tree.get(first)\n if (!child || child.node) {\n child = new Map()\n tree.set(first, child)\n }\n insertIntoTree(child, rest, leaf)\n}\n\nfunction renderTree(tree: Map<string, any>, indent: string): string {\n const lines: string[] = ['{']\n for (const [key, value] of tree) {\n const safeKey = renderPropKey(key)\n if (value instanceof Map) {\n lines.push(`${indent} ${safeKey}: ${renderTree(value, indent + ' ')};`)\n } else {\n const { node, abi } = value as { node: ContractNode; abi: Abi }\n lines.push(\n `${indent} ${safeKey}: ${renderContractType(node, abi, indent + ' ')};`\n )\n }\n }\n lines.push(`${indent}}`)\n return lines.join('\\n')\n}\n\nfunction renderPropKey(name: string): string {\n return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name)\n}\n\nfunction renderContractType(\n node: ContractNode,\n abi: Abi,\n indent: string\n): string {\n const members: string[] = []\n const seen = new Set<string>()\n members.push(\n `${indent} [EVERYTHING]: (options?: Options) => TargetPermission;`\n )\n for (const fragment of abi) {\n if (fragment.type !== 'function') continue\n if (\n fragment.stateMutability === 'view' ||\n fragment.stateMutability === 'pure'\n ) {\n continue\n }\n const name = fragment.name as string\n if (!name || seen.has(name)) continue\n seen.add(name)\n members.push(renderFunctionSignature(fragment, indent + ' '))\n }\n return ['{', ...members, `${indent}}`].join('\\n')\n}\n\nfunction renderFunctionSignature(\n fragment: AbiFragment,\n indent: string\n): string {\n const name = renderPropKey(fragment.name as string)\n const params: string[] = []\n for (const input of (fragment.inputs as AbiFragment[]) ?? []) {\n const paramName = sanitizeParamName(\n input.name || `arg${params.length}`,\n params.length\n )\n params.push(`${paramName}?: Scoping<${tsTypeFor(input)}>`)\n }\n params.push(`options?: Options`)\n return `${indent}${name}: (${params.join(', ')}) => FunctionPermission;`\n}\n\nfunction sanitizeParamName(name: string, index: number): string {\n const cleaned = name.replace(/[^A-Za-z0-9_$]/g, '_')\n if (!cleaned || /^[0-9]/.test(cleaned)) return `arg${index}`\n // Named tuple element labels can't collide with TS reserved words.\n const reserved = new Set([\n 'function',\n 'class',\n 'new',\n 'number',\n 'string',\n 'object',\n 'boolean',\n 'symbol',\n 'default',\n 'return',\n 'this',\n 'void',\n 'delete',\n 'in',\n 'of',\n 'for',\n 'while',\n 'switch',\n 'case',\n 'if',\n 'else',\n 'null',\n 'true',\n 'false',\n 'undefined',\n 'any',\n 'never',\n 'unknown',\n ])\n return reserved.has(cleaned) ? `_${cleaned}` : cleaned\n}\n\nfunction tsTypeFor(fragment: AbiFragment): string {\n const type = fragment.type as string\n\n const arrayMatch = /^(.*)\\[(\\d*)\\]$/.exec(type)\n if (arrayMatch) {\n const inner: AbiFragment = { ...fragment, type: arrayMatch[1] }\n return `readonly (${tsTypeFor(inner)})[]`\n }\n\n if (type === 'tuple') {\n const components = (fragment.components as AbiFragment[]) ?? []\n if (components.length === 0) return 'Record<string, unknown>'\n const fields = components.map((c, i) => {\n const key = sanitizeParamName(c.name || `f${i}`, i)\n return `${renderPropKey(key)}: ${tsTypeFor(c)}`\n })\n return `{ ${fields.join('; ')} }`\n }\n\n if (type === 'address') return '`0x${string}`'\n if (type === 'bool') return 'boolean'\n if (type === 'string') return 'string'\n if (type === 'bytes') return 'import(\"ethers\").BytesLike'\n if (/^bytes\\d+$/.test(type)) return '`0x${string}`'\n if (/^u?int\\d*$/.test(type)) return 'import(\"ethers\").BigNumberish'\n return 'unknown'\n}\n\nexport function writeGenerated(outFile: string, source: string): void {\n fs.mkdirSync(path.dirname(outFile), { recursive: true })\n fs.writeFileSync(outFile, source, 'utf8')\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\nimport type { ResolvedConfig } from '../config'\nimport { resolveAbisDir } from '../config'\nimport { resolveZodiacDir } from '../../paths'\nimport { abiFilePath, walkContracts, writeAbi } from '../../allow/abi'\nimport { fetchAbi } from '../../allow/fetch'\nimport { chainIdFor } from '../../allow/networks'\nimport { generateAllowTypes, writeGenerated } from '../../allow/codegen'\n\ntype Status = 'ok' | 'fetched' | 'missing'\n\nexport const pullContracts = async (config: ResolvedConfig) => {\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 abisDir = resolveAbisDir(config)\n const generatedFile = path.join(\n resolveZodiacDir(config.rootDir),\n 'allow.d.ts'\n )\n\n let missing = 0\n let fetched = 0\n let existing = 0\n\n for (const node of walkContracts(config.contracts)) {\n const file = abiFilePath(abisDir, node)\n\n if (fs.existsSync(file)) {\n existing++\n report(node.chain, node.segments, node.address, 'ok', file)\n continue\n }\n\n let chainId: number\n try {\n chainId = chainIdFor(node.chain)\n } catch (error) {\n missing++\n report(\n node.chain,\n node.segments,\n node.address,\n 'missing',\n file,\n (error as Error).message\n )\n continue\n }\n\n const abi = await fetchAbi(chainId, node.address)\n if (!abi) {\n missing++\n report(\n node.chain,\n node.segments,\n node.address,\n 'missing',\n file,\n `api.abi.pub returned no ABI for chain ${chainId}`\n )\n continue\n }\n writeAbi(abisDir, node, abi)\n fetched++\n report(node.chain, node.segments, node.address, 'fetched', file)\n }\n\n console.log('')\n console.log(\n `Contracts summary: ${existing} existing, ${fetched} fetched, ${missing} missing.`\n )\n if (missing > 0) {\n console.log('')\n console.log('Missing ABIs must be provided manually. Paste the contract')\n console.log(\n 'ABI JSON at the paths listed above and re-run `zodiac pull-contracts`.'\n )\n }\n\n const source = generateAllowTypes(abisDir, config.contracts)\n writeGenerated(generatedFile, source)\n console.log('')\n console.log(`Wrote typings to ${path.relative(process.cwd(), generatedFile)}`)\n\n if (missing > 0) process.exit(1)\n}\n\nfunction report(\n chain: string,\n segments: string[],\n address: string,\n status: Status,\n file: string,\n reason?: string\n) {\n const label = `${chain}.${segments.join('.')}`.padEnd(40, ' ')\n const tag = {\n ok: ' cached ',\n fetched: ' fetched ',\n missing: ' MISSING ',\n }[status]\n const suffix = reason ? ` — ${reason}` : ''\n console.log(`${tag} ${label} ${address}${suffix}`)\n if (status === 'missing') {\n console.log(` → paste ABI at ${file}`)\n }\n}\n","import { Command } from 'commander'\nimport { config as loadDotenv } from 'dotenv'\nimport { init } from './commands/init'\nimport { loadConfig } from './config'\nimport { pullOrg } from './commands/pullOrg'\nimport { pullContracts } from './commands/pullContracts'\n\n// Load `.env` from the current working directory before reading any env vars.\nloadDotenv({ quiet: true })\n\nconst loadConfigOrInit = (configPath: string) =>\n loadConfig(configPath, {\n onMissingKey: async (rootDir) => {\n console.log(\n 'No ZODIAC_API_KEY found. Starting authorization to mint one for this directory…'\n )\n return init({ rootDir })\n },\n })\n\nexport const run = async (argv: string[] = process.argv) => {\n const program = new Command()\n\n program\n .name('zodiac')\n .description('Zodiac 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('init')\n .description(\n 'Authorize this directory with a Zodiac org. Opens a browser to mint an API key and writes it to .env.'\n )\n .option(\n '--app-url <url>',\n 'Override the Zodiac app URL (defaults to ZODIAC_APP_URL or app.zodiac.eco)'\n )\n .action(async (opts) => {\n await init({ appUrl: opts.appUrl })\n })\n\n program\n .command('pull-org')\n .description('Fetch Zodiac users and accounts, generate TypeScript types')\n .action(async (_opts, cmd) => {\n const config = await loadConfigOrInit(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 loadConfigOrInit(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 loadConfigOrInit(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":"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"}
1
+ {"version":3,"file":"cli.mjs","names":["join","loadDotenv"],"sources":["../src/cli/commands/init.ts","../src/cli/commands/pullOrg.ts","../src/allow/fetch.ts","../src/allow/codegen.ts","../src/cli/commands/pullContracts.ts","../src/cli/run.ts","../src/cli/index.ts"],"sourcesContent":["import { createHash, randomBytes } from 'node:crypto'\nimport {\n createServer,\n type IncomingMessage,\n type ServerResponse,\n} from 'node:http'\nimport { existsSync, readFileSync, writeFileSync } from 'node:fs'\nimport { basename, join, resolve } from 'node:path'\nimport open from 'open'\nimport { ensureConfigStub } from '../config'\nimport { findProjectRoot } from '../projectRoot'\n\nconst DEFAULT_APP_URL = 'https://app.zodiac.eco'\nconst CALLBACK_TIMEOUT_MS = 5 * 60 * 1000 // 5 minutes\n\ntype InitOptions = {\n rootDir?: string\n /** Override the Zodiac app base URL (defaults to ZODIAC_APP_URL env or app.zodiac.eco). */\n appUrl?: string\n}\n\nexport const init = async (options: InitOptions = {}): Promise<string> => {\n const rootDir = options.rootDir ?? findProjectRoot()\n const appUrl = (\n options.appUrl ??\n process.env.ZODIAC_APP_URL ??\n DEFAULT_APP_URL\n ).replace(/\\/$/, '')\n\n const label = basename(resolve(rootDir)) || 'zodiac-cli'\n\n // PKCE: keep `code_verifier` private to this process. Send only the\n // SHA-256 hash (`code_challenge`) over the wire.\n const codeVerifier = randomBytes(32).toString('base64url')\n const codeChallenge = createHash('sha256')\n .update(codeVerifier)\n .digest('base64url')\n\n // `state` binds the redirect we receive on the loopback to this CLI\n // invocation, defending against a stray local process trying to inject\n // an auth code into our callback.\n const state = randomBytes(32).toString('base64url')\n\n const { port, waitForCode, close } = await startCallbackServer({\n expectedState: state,\n appUrl,\n })\n\n const callbackUrl = `http://127.0.0.1:${port}/callback`\n const authUrl = new URL('/cli-auth', appUrl)\n authUrl.searchParams.set('callback', callbackUrl)\n authUrl.searchParams.set('state', state)\n authUrl.searchParams.set('code_challenge', codeChallenge)\n authUrl.searchParams.set('label', label)\n\n console.log(\n `Opening ${authUrl} in your browser. Approve the request to receive an API key.`\n )\n await open(authUrl.toString())\n\n let code: string\n try {\n code = await waitForCode(CALLBACK_TIMEOUT_MS)\n } finally {\n await close()\n }\n\n const apiKey = await exchangeCodeForKey(appUrl, code, codeVerifier)\n\n const envPath = join(rootDir, '.env')\n const apiUrl = `${appUrl}/api/v1`\n writeEnv(envPath, { ZODIAC_API_KEY: apiKey, ZODIAC_API_URL: apiUrl })\n\n console.log(`✅ API key written to ${envPath}`)\n\n const configPath = join(rootDir, 'zodiac.config.ts')\n if (ensureConfigStub(configPath)) {\n console.log(`✅ Created ${configPath}`)\n }\n\n return apiKey\n}\n\nconst exchangeCodeForKey = async (\n appUrl: string,\n code: string,\n codeVerifier: string\n): Promise<string> => {\n const response = await fetch(`${appUrl}/cli-auth/exchange`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ code, code_verifier: codeVerifier }),\n })\n\n if (!response.ok) {\n let detail = ''\n try {\n const body = (await response.json()) as { error?: string }\n detail = body.error ? `: ${body.error}` : ''\n } catch {\n // ignore — fall through to a generic message\n }\n throw new Error(\n `Failed to exchange auth code (${response.status})${detail}`\n )\n }\n\n const { key } = (await response.json()) as { key: unknown }\n if (typeof key !== 'string' || !key.startsWith('zodiac_')) {\n throw new Error(\n 'Exchange endpoint returned an unexpected response (missing or malformed key)'\n )\n }\n return key\n}\n\ntype StartServerOptions = {\n expectedState: string\n appUrl: string\n}\n\ntype StartServerResult = {\n port: number\n waitForCode: (timeoutMs: number) => Promise<string>\n close: () => Promise<void>\n}\n\nconst startCallbackServer = async ({\n expectedState,\n appUrl,\n}: StartServerOptions): Promise<StartServerResult> => {\n let resolveCode: (code: string) => void = () => {}\n let rejectCode: (err: Error) => void = () => {}\n const codePromise = new Promise<string>((res, rej) => {\n resolveCode = res\n rejectCode = rej\n })\n\n const handler = (req: IncomingMessage, res: ServerResponse) => {\n if (req.method !== 'GET' || !req.url?.startsWith('/callback')) {\n res.statusCode = 404\n res.end()\n return\n }\n\n const url = new URL(req.url, `http://127.0.0.1`)\n const code = url.searchParams.get('code')\n const state = url.searchParams.get('state')\n\n if (state !== expectedState) {\n // A stray request — could be a stale browser tab, a malicious\n // local process, etc. Silently 403 and keep waiting for the right\n // one. Don't reject the CLI promise.\n respondHtml(\n res,\n 403,\n renderHtml(\n 'Mismatched state',\n `<p>This callback didn't match the running <code>zodiac init</code> session. You can close this tab.</p>`\n )\n )\n return\n }\n\n if (typeof code !== 'string' || code.length === 0) {\n respondHtml(\n res,\n 400,\n renderHtml(\n 'Missing code',\n `<p>The callback URL didn't include an auth <code>code</code>. Please re-run <code>zodiac init</code>.</p>`\n )\n )\n return\n }\n\n respondHtml(\n res,\n 200,\n renderHtml(\n 'Authorized',\n `<p>Your CLI now has its API key. You can close this tab and return to your terminal.</p>\n <p><a href=\"${escapeHtml(appUrl)}\">Back to Zodiac</a></p>`\n )\n )\n resolveCode(code)\n }\n\n const server = createServer(handler)\n await new Promise<void>((res) => server.listen(0, '127.0.0.1', res))\n const address = server.address()\n if (address == null || typeof address === 'string') {\n throw new Error('Failed to bind loopback callback server')\n }\n const port = address.port\n\n const close = (): Promise<void> =>\n new Promise<void>((resolve) => {\n server.close(() => resolve())\n })\n\n const waitForCode = (timeoutMs: number) =>\n Promise.race<string>([\n codePromise,\n new Promise<string>((_, rej) =>\n setTimeout(() => {\n rejectCode(new Error(`Timed out waiting for CLI auth callback`))\n rej(new Error(`Timed out waiting for CLI auth callback`))\n }, timeoutMs)\n ),\n ])\n\n return { port, waitForCode, close }\n}\n\nconst respondHtml = (res: ServerResponse, status: number, body: string) => {\n // Force the connection to close after the response so the browser\n // doesn't keep waiting on a keep-alive socket once the CLI exits.\n // Resolve only after 'finish' (response handed to the OS) so the\n // bytes can flush before the process tears down.\n res.setHeader('Connection', 'close')\n res.setHeader('content-type', 'text/html; charset=utf-8')\n res.statusCode = status\n res.end(body)\n}\n\nconst renderHtml = (title: string, bodyHtml: string) => `<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>${escapeHtml(title)} — Zodiac CLI</title>\n <style>\n body { font: 16px/1.5 -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif; max-width: 32rem; margin: 4rem auto; padding: 0 1rem; color: #1f2937; }\n h1 { font-weight: 300; font-size: 1.75rem; margin-bottom: 1rem; }\n code { background: #f3f4f6; padding: 0.125rem 0.375rem; border-radius: 0.25rem; }\n a { color: #1d4ed8; }\n </style>\n </head>\n <body>\n <h1>${escapeHtml(title)}</h1>\n ${bodyHtml}\n </body>\n</html>\n`\n\nconst escapeHtml = (value: string) =>\n value\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;')\n\nconst writeEnv = (envPath: string, vars: Record<string, string>): void => {\n let contents = existsSync(envPath) ? readFileSync(envPath, 'utf8') : ''\n\n for (const [key, value] of Object.entries(vars)) {\n contents = upsertEnvLine(contents, key, value)\n }\n\n writeFileSync(envPath, contents, 'utf8')\n}\n\nconst upsertEnvLine = (\n contents: string,\n key: string,\n value: string\n): string => {\n const line = `${key}=${value}`\n const lines = contents.split(/\\r?\\n/)\n const idx = lines.findIndex((l) => new RegExp(`^\\\\s*${key}\\\\s*=`).test(l))\n\n if (idx >= 0) {\n lines[idx] = line\n return lines.join('\\n')\n }\n\n return contents.length === 0 || contents.endsWith('\\n')\n ? `${contents}${line}\\n`\n : `${contents}\\n${line}\\n`\n}\n","import type { ResolvedConfig } from '../config'\nimport { ApiClient } from '../../api'\nimport { invariant } from '@epic-web/invariant'\nimport { getAddress } from 'ethers'\nimport {\n ModuleKind,\n Project,\n ScriptTarget,\n VariableDeclarationKind,\n} from 'ts-morph'\nimport { mkdirSync, writeFileSync } from 'fs'\nimport { join } from 'path'\nimport { resolveZodiacDir } from '../../paths'\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: ResolvedConfig) => {\n const client = new ApiClient({\n apiKey: config.apiKey,\n })\n\n const [users, workspaceAccounts] = await Promise.all([\n client.listUsers(),\n client.listAccounts(),\n ])\n\n // Fetch fresh on-chain state via `resolveConstellation` for every\n // account we can resolve:\n // - `spec` present → pass the stored apply-time node verbatim\n // (deployed nodes match on-chain; undeployed ones derive via\n // CREATE2 from the stored nonce + config).\n // - `vault: true` with no spec → treat as a pre-existing on-chain\n // SAFE (e.g. a workspace vault created outside the\n // constellation-as-code flow). The resolver finds it on-chain.\n // - `vault: false` with no spec → a constituent of a still-pending\n // constellation that's never been deployed. We can't usefully\n // resolve it, so skip; the codegen emits minimal fields.\n const allAccounts = workspaceAccounts.flatMap((ws) => ws.accounts)\n const resolvableAccounts = allAccounts.filter(\n (a) => a.spec != null || a.vault\n )\n const resolved = new Map<\n string,\n Awaited<ReturnType<typeof client.resolveConstellation>>['result'][number]\n >()\n if (resolvableAccounts.length > 0) {\n const response = await client.resolveConstellation(\n workspaceAccounts[0].workspaceId, // any workspace works for the resolve route\n {\n specification: resolvableAccounts.map((account, i) =>\n account.spec != null\n ? account.spec\n : {\n // Synthesize a ref for vault-fallback entries (no stored\n // spec). The /resolve payload requires a ref on every\n // entry; the value isn't used downstream beyond echoing\n // back into the response, so a positional id is fine.\n ref: `vault_${i}` as Lowercase<string>,\n type: 'SAFE',\n chain: account.chain,\n address: account.address,\n }\n ),\n }\n )\n invariant(\n response?.result?.length === resolvableAccounts.length,\n `resolveConstellation returned ${response?.result?.length ?? 0} accounts for ${resolvableAccounts.length} accounts`\n )\n resolvableAccounts.forEach((account, i) => {\n resolved.set(account.id, response.result[i])\n })\n }\n\n // Group accounts by type into separate bracket-access namespaces:\n // `safes`, `rolesMods`, `delays`. This way `eth.safe[...]`\n // IntelliSense only suggests SAFE labels, and the label-collision\n // suffix only kicks in when two accounts **of the same type** share\n // a label.\n const accountsRecord: Record<string, unknown> = {}\n for (const ws of workspaceAccounts) {\n const safes: Record<string, unknown> = {}\n const rolesMods: Record<string, unknown> = {}\n const delays: Record<string, unknown> = {}\n\n const bucketsByType = {\n SAFE: safes,\n ROLES: rolesMods,\n DELAY: delays,\n } as const\n\n type NodeType = 'SAFE' | 'ROLES' | 'DELAY'\n const isNodeType = (type: string): type is NodeType =>\n type === 'SAFE' || type === 'ROLES' || type === 'DELAY'\n\n // Count labels per type so we only suffix within-type collisions.\n const labelCountByType: Record<NodeType, Map<string, number>> = {\n SAFE: new Map(),\n ROLES: new Map(),\n DELAY: new Map(),\n }\n for (const account of ws.accounts) {\n if (!isNodeType(account.type)) continue\n const counts = labelCountByType[account.type]\n counts.set(account.label, (counts.get(account.label) ?? 0) + 1)\n }\n\n for (const account of ws.accounts) {\n if (!isNodeType(account.type)) continue\n const onChain = resolved.get(account.id)\n const counts = labelCountByType[account.type]\n const key =\n (counts.get(account.label) ?? 0) > 1\n ? `${account.label} (${getAddress(account.address)})`\n : account.label\n bucketsByType[account.type][key] = {\n id: account.id,\n label: account.label,\n address: account.address,\n chain: account.chain,\n vault: account.vault,\n ...(onChain?.type === 'SAFE' && {\n threshold: onChain.threshold,\n owners: [...onChain.owners],\n modules: [...onChain.modules],\n }),\n }\n }\n\n accountsRecord[ws.workspaceName] = {\n workspaceId: ws.workspaceId,\n workspaceName: ws.workspaceName,\n safes,\n rolesMods,\n delays,\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 = resolveZodiacDir(config.rootDir)\n\n mkdirSync(outDir, { recursive: true })\n\n // Pin CJS so `require()` works regardless of the parent package.json's type\n writeFileSync(\n join(outDir, 'package.json'),\n JSON.stringify(\n {\n type: 'commonjs',\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.CommonJS,\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: 'accounts',\n initializer: `${toLiteral(accountsRecord)} 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 let contents = outputFile.getText()\n // Augment the SDK's global `ZodiacGeneratedCodegen` interface so\n // `constellation()`'s default type parameter picks up these literal\n // shapes automatically.\n if (fileName === 'index.d.ts') {\n contents += `\ndeclare global {\n interface ZodiacGeneratedCodegen {\n users: typeof users;\n accounts: typeof accounts;\n }\n}\n`\n }\n writeFileSync(join(outDir, fileName), contents)\n }\n}\n","import { chainIdFor, type ChainPrefix } from './networks'\n\nexport type AbiFragment = Record<string, any>\nexport type Abi = AbiFragment[]\n\n// Returns null on any failure so callers can fall back to a manual ABI file.\nexport async function fetchAbi(\n chainId: number,\n address: `0x${string}`\n): Promise<Abi | null> {\n const url = `https://api.abi.pub/v1/chains/${chainId}/etherscan?module=contract&action=getabi&address=${address}`\n let body: { status?: string; message?: string; result?: string }\n try {\n const resp = await fetch(url)\n if (!resp.ok) return null\n body = (await resp.json()) as typeof body\n } catch {\n return null\n }\n if (body.status !== '1' || typeof body.result !== 'string') return null\n try {\n const parsed = JSON.parse(body.result)\n if (!Array.isArray(parsed) || parsed.length === 0) return null\n return parsed as Abi\n } catch {\n return null\n }\n}\n\nexport const fetchAbiForPrefix = (\n prefix: ChainPrefix,\n address: `0x${string}`\n) => fetchAbi(chainIdFor(prefix), address)\n","import fs from 'node:fs'\nimport path from 'node:path'\nimport { walkContracts, readAbi, type ContractNode } from './abi'\nimport type { Abi, AbiFragment } from './fetch'\n\n// Emits named tuple elements per ABI input — this is the reason we don't rely\n// on viem/abitype, which produces unnamed tuples (microsoft/TypeScript#44939).\nexport function generateAllowTypes(\n abisDir: string,\n contractsConfig: Record<string, any>\n): string {\n type Tree = Map<string, Tree | { node: ContractNode; abi: Abi }>\n\n const root: Tree = new Map()\n for (const node of walkContracts(contractsConfig)) {\n const abi = readAbi(abisDir, node)\n if (!abi) continue\n insertIntoTree(root, [node.chain, ...node.segments], { node, abi })\n }\n\n const out: string[] = []\n out.push('// AUTO-GENERATED by `zodiac pull-contracts`. Do not edit.')\n out.push('/* eslint-disable */')\n out.push('')\n out.push(\n `import type { FunctionPermission, TargetPermission } from \"zodiac-roles-sdk\";`\n )\n out.push(\n `import type { Scoping, Options, EVERYTHING } from \"@zodiac-os/sdk/allow\";`\n )\n out.push('')\n out.push('declare global {')\n out.push(' interface AllowKit ' + renderTree(root, ' '))\n out.push('}')\n out.push('')\n out.push('export {};')\n out.push('')\n return out.join('\\n')\n}\n\nfunction insertIntoTree(\n tree: Map<string, any>,\n segments: string[],\n leaf: { node: ContractNode; abi: Abi }\n): void {\n const [first, ...rest] = segments\n if (!first) throw new Error('empty segments')\n if (rest.length === 0) {\n tree.set(first, leaf)\n return\n }\n let child = tree.get(first)\n if (!child || child.node) {\n child = new Map()\n tree.set(first, child)\n }\n insertIntoTree(child, rest, leaf)\n}\n\nfunction renderTree(tree: Map<string, any>, indent: string): string {\n const lines: string[] = ['{']\n for (const [key, value] of tree) {\n const safeKey = renderPropKey(key)\n if (value instanceof Map) {\n lines.push(`${indent} ${safeKey}: ${renderTree(value, indent + ' ')};`)\n } else {\n const { node, abi } = value as { node: ContractNode; abi: Abi }\n lines.push(\n `${indent} ${safeKey}: ${renderContractType(node, abi, indent + ' ')};`\n )\n }\n }\n lines.push(`${indent}}`)\n return lines.join('\\n')\n}\n\nfunction renderPropKey(name: string): string {\n return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name)\n}\n\nfunction renderContractType(\n node: ContractNode,\n abi: Abi,\n indent: string\n): string {\n const members: string[] = []\n const seen = new Set<string>()\n members.push(\n `${indent} [EVERYTHING]: (options?: Options) => TargetPermission;`\n )\n for (const fragment of abi) {\n if (fragment.type !== 'function') continue\n if (\n fragment.stateMutability === 'view' ||\n fragment.stateMutability === 'pure'\n ) {\n continue\n }\n const name = fragment.name as string\n if (!name || seen.has(name)) continue\n seen.add(name)\n members.push(renderFunctionSignature(fragment, indent + ' '))\n }\n return ['{', ...members, `${indent}}`].join('\\n')\n}\n\nfunction renderFunctionSignature(\n fragment: AbiFragment,\n indent: string\n): string {\n const name = renderPropKey(fragment.name as string)\n const params: string[] = []\n for (const input of (fragment.inputs as AbiFragment[]) ?? []) {\n const paramName = sanitizeParamName(\n input.name || `arg${params.length}`,\n params.length\n )\n params.push(`${paramName}?: Scoping<${tsTypeFor(input)}>`)\n }\n params.push(`options?: Options`)\n return `${indent}${name}: (${params.join(', ')}) => FunctionPermission;`\n}\n\nfunction sanitizeParamName(name: string, index: number): string {\n const cleaned = name.replace(/[^A-Za-z0-9_$]/g, '_')\n if (!cleaned || /^[0-9]/.test(cleaned)) return `arg${index}`\n // Named tuple element labels can't collide with TS reserved words.\n const reserved = new Set([\n 'function',\n 'class',\n 'new',\n 'number',\n 'string',\n 'object',\n 'boolean',\n 'symbol',\n 'default',\n 'return',\n 'this',\n 'void',\n 'delete',\n 'in',\n 'of',\n 'for',\n 'while',\n 'switch',\n 'case',\n 'if',\n 'else',\n 'null',\n 'true',\n 'false',\n 'undefined',\n 'any',\n 'never',\n 'unknown',\n ])\n return reserved.has(cleaned) ? `_${cleaned}` : cleaned\n}\n\nfunction tsTypeFor(fragment: AbiFragment): string {\n const type = fragment.type as string\n\n const arrayMatch = /^(.*)\\[(\\d*)\\]$/.exec(type)\n if (arrayMatch) {\n const inner: AbiFragment = { ...fragment, type: arrayMatch[1] }\n return `readonly (${tsTypeFor(inner)})[]`\n }\n\n if (type === 'tuple') {\n const components = (fragment.components as AbiFragment[]) ?? []\n if (components.length === 0) return 'Record<string, unknown>'\n const fields = components.map((c, i) => {\n const key = sanitizeParamName(c.name || `f${i}`, i)\n return `${renderPropKey(key)}: ${tsTypeFor(c)}`\n })\n return `{ ${fields.join('; ')} }`\n }\n\n if (type === 'address') return '`0x${string}`'\n if (type === 'bool') return 'boolean'\n if (type === 'string') return 'string'\n if (type === 'bytes') return 'import(\"ethers\").BytesLike'\n if (/^bytes\\d+$/.test(type)) return '`0x${string}`'\n if (/^u?int\\d*$/.test(type)) return 'import(\"ethers\").BigNumberish'\n return 'unknown'\n}\n\nexport function writeGenerated(outFile: string, source: string): void {\n fs.mkdirSync(path.dirname(outFile), { recursive: true })\n fs.writeFileSync(outFile, source, 'utf8')\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\nimport type { ResolvedConfig } from '../config'\nimport { resolveAbisDir } from '../config'\nimport { resolveZodiacDir } from '../../paths'\nimport { abiFilePath, walkContracts, writeAbi } from '../../allow/abi'\nimport { fetchAbi } from '../../allow/fetch'\nimport { chainIdFor } from '../../allow/networks'\nimport { generateAllowTypes, writeGenerated } from '../../allow/codegen'\n\ntype Status = 'ok' | 'fetched' | 'missing'\n\nexport const pullContracts = async (config: ResolvedConfig) => {\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 abisDir = resolveAbisDir(config)\n const generatedFile = path.join(\n resolveZodiacDir(config.rootDir),\n 'allow.d.ts'\n )\n\n let missing = 0\n let fetched = 0\n let existing = 0\n\n for (const node of walkContracts(config.contracts)) {\n const file = abiFilePath(abisDir, node)\n\n if (fs.existsSync(file)) {\n existing++\n report(node.chain, node.segments, node.address, 'ok', file)\n continue\n }\n\n let chainId: number\n try {\n chainId = chainIdFor(node.chain)\n } catch (error) {\n missing++\n report(\n node.chain,\n node.segments,\n node.address,\n 'missing',\n file,\n (error as Error).message\n )\n continue\n }\n\n const abi = await fetchAbi(chainId, node.address)\n if (!abi) {\n missing++\n report(\n node.chain,\n node.segments,\n node.address,\n 'missing',\n file,\n `api.abi.pub returned no ABI for chain ${chainId}`\n )\n continue\n }\n writeAbi(abisDir, node, abi)\n fetched++\n report(node.chain, node.segments, node.address, 'fetched', file)\n }\n\n console.log('')\n console.log(\n `Contracts summary: ${existing} existing, ${fetched} fetched, ${missing} missing.`\n )\n if (missing > 0) {\n console.log('')\n console.log('Missing ABIs must be provided manually. Paste the contract')\n console.log(\n 'ABI JSON at the paths listed above and re-run `zodiac pull-contracts`.'\n )\n }\n\n const source = generateAllowTypes(abisDir, config.contracts)\n writeGenerated(generatedFile, source)\n console.log('')\n console.log(`Wrote typings to ${path.relative(process.cwd(), generatedFile)}`)\n\n if (missing > 0) process.exit(1)\n}\n\nfunction report(\n chain: string,\n segments: string[],\n address: string,\n status: Status,\n file: string,\n reason?: string\n) {\n const label = `${chain}.${segments.join('.')}`.padEnd(40, ' ')\n const tag = {\n ok: ' cached ',\n fetched: ' fetched ',\n missing: ' MISSING ',\n }[status]\n const suffix = reason ? ` — ${reason}` : ''\n console.log(`${tag} ${label} ${address}${suffix}`)\n if (status === 'missing') {\n console.log(` → paste ABI at ${file}`)\n }\n}\n","import { Command } from 'commander'\nimport { config as loadDotenv } from 'dotenv'\nimport { init } from './commands/init'\nimport { loadConfig } from './config'\nimport { pullOrg } from './commands/pullOrg'\nimport { pullContracts } from './commands/pullContracts'\n\n// Load `.env` from the current working directory before reading any env vars.\nloadDotenv({ quiet: true })\n\nconst loadConfigOrInit = (configPath: string) =>\n loadConfig(configPath, {\n createIfMissing: true,\n onMissingKey: async (rootDir) => {\n console.log(\n 'No ZODIAC_API_KEY found. Starting authorization to mint one for this directory…'\n )\n return init({ rootDir })\n },\n })\n\nexport const run = async (argv: string[] = process.argv) => {\n const program = new Command()\n\n program\n .name('zodiac')\n .description('Zodiac 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('init')\n .description(\n 'Authorize this directory with a Zodiac org. Opens a browser to mint an API key and writes it to .env.'\n )\n .option(\n '--app-url <url>',\n 'Override the Zodiac app URL (defaults to ZODIAC_APP_URL or app.zodiac.eco)'\n )\n .action(async (opts) => {\n await init({ appUrl: opts.appUrl })\n })\n\n program\n .command('pull-org')\n .description('Fetch Zodiac users and accounts, generate TypeScript types')\n .action(async (_opts, cmd) => {\n const config = await loadConfigOrInit(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 loadConfigOrInit(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 loadConfigOrInit(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":"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"}
@@ -0,0 +1,119 @@
1
+ import { existsSync, writeFileSync } from "node:fs";
2
+ import { dirname, resolve } from "node:path";
3
+ import { pathToFileURL } from "url";
4
+ import { dirname as dirname$1, resolve as resolve$1 } from "path";
5
+ //#region src/cli/projectRoot.ts
6
+ /**
7
+ * Walk up from `startDir` looking for a `package.json`. Returns the
8
+ * directory that contains it. Falls back to `startDir` if no ancestor
9
+ * has a `package.json` (e.g. when running outside any project).
10
+ */
11
+ const findProjectRoot = (startDir = process.cwd()) => {
12
+ const absoluteStart = resolve(startDir);
13
+ let dir = absoluteStart;
14
+ while (true) {
15
+ if (existsSync(resolve(dir, "package.json"))) return dir;
16
+ const parent = dirname(dir);
17
+ if (parent === dir) return absoluteStart;
18
+ dir = parent;
19
+ }
20
+ };
21
+ //#endregion
22
+ //#region src/cli/config.ts
23
+ const defineConfig = (config) => config;
24
+ const CONFIG_BASENAME = "zodiac.config";
25
+ /**
26
+ * Extensions probed (in priority order) when the user doesn't pass an
27
+ * explicit `--config` path. Mirrors what vite / vitest / tailwind accept.
28
+ */
29
+ const CONFIG_EXTENSIONS = [
30
+ ".ts",
31
+ ".mts",
32
+ ".cts",
33
+ ".js",
34
+ ".mjs",
35
+ ".cjs"
36
+ ];
37
+ const DEFAULT_CONFIG_PATH = `${CONFIG_BASENAME}.ts`;
38
+ const CONFIG_STUB = `import { defineConfig } from "@zodiac-os/sdk/cli/config";
39
+
40
+ export default defineConfig({
41
+ contracts: {
42
+ // Add contracts the \`allow\` kit should know about, keyed by chain prefix.
43
+ // Run \`zodiac pull-contracts\` after editing.
44
+ //
45
+ // eth: {
46
+ // weth: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
47
+ // },
48
+ },
49
+ });
50
+ `;
51
+ /**
52
+ * Write a starter `zodiac.config.ts` if no file exists at `absolutePath`.
53
+ * Returns `true` if a file was written.
54
+ */
55
+ const ensureConfigStub = (absolutePath) => {
56
+ if (existsSync(absolutePath)) return false;
57
+ writeFileSync(absolutePath, CONFIG_STUB, "utf8");
58
+ return true;
59
+ };
60
+ /**
61
+ * Turn a (possibly-default) config path into an absolute path under
62
+ * `projectRoot`. When the caller didn't pass an explicit path, probe
63
+ * for `zodiac.config.{ts,mts,cts,js,mjs,cjs}` and use whichever exists;
64
+ * fall back to the canonical `.ts` name so a not-found error is
65
+ * still phrased in terms users recognise.
66
+ */
67
+ const resolveConfigPath = (projectRoot, configPath) => {
68
+ const explicit = resolve$1(projectRoot, configPath);
69
+ if (configPath !== DEFAULT_CONFIG_PATH) return explicit;
70
+ for (const ext of CONFIG_EXTENSIONS) {
71
+ const candidate = resolve$1(projectRoot, `${CONFIG_BASENAME}${ext}`);
72
+ if (existsSync(candidate)) return candidate;
73
+ }
74
+ return explicit;
75
+ };
76
+ async function loadConfig(configPath = DEFAULT_CONFIG_PATH, options = {}) {
77
+ const absolutePath = resolveConfigPath(findProjectRoot(), configPath);
78
+ if (options.createIfMissing && ensureConfigStub(absolutePath)) console.log(`✅ Created ${absolutePath}`);
79
+ let mod;
80
+ try {
81
+ mod = await import(pathToFileURL(absolutePath).href);
82
+ } catch (error) {
83
+ if (error?.code === "ERR_MODULE_NOT_FOUND" || error?.code === "ENOENT") throw new Error(`Config file not found: ${absolutePath}`);
84
+ throw error;
85
+ }
86
+ const config = mod.default ?? mod.config;
87
+ if (!config) throw new Error(`Config file must export a default value or a named "config" export: ${absolutePath}`);
88
+ const rootDir = dirname$1(absolutePath);
89
+ const apiKey = await resolveApiKey(config, rootDir, options);
90
+ return {
91
+ ...config,
92
+ apiKey,
93
+ rootDir
94
+ };
95
+ }
96
+ const resolveApiKey = async (config, rootDir, { onMissingKey }) => {
97
+ if (config.apiKey != null) {
98
+ if (!isApiKey(config.apiKey)) throw new Error("`apiKey` in zodiac.config.ts is malformed: a valid Zodiac API key starts with \"zodiac_\". Either remove the field to use ZODIAC_API_KEY, or run `zodiac init` to mint a fresh key.");
99
+ return config.apiKey;
100
+ }
101
+ const fromEnv = process.env.ZODIAC_API_KEY;
102
+ if (fromEnv != null && fromEnv !== "") {
103
+ if (!isApiKey(fromEnv)) throw new Error("ZODIAC_API_KEY is set but malformed: a valid Zodiac API key starts with \"zodiac_\". Run `zodiac init` to mint a fresh key.");
104
+ return fromEnv;
105
+ }
106
+ if (onMissingKey == null) throw new Error("No Zodiac API key found. Set ZODIAC_API_KEY in your environment, or run `zodiac init` to generate one.");
107
+ const minted = await onMissingKey(rootDir);
108
+ if (!isApiKey(minted)) throw new Error(`onMissingKey returned an invalid Zodiac API key (expected a value starting with "zodiac_").`);
109
+ return minted;
110
+ };
111
+ const isApiKey = (value) => value != null && value.startsWith("zodiac_");
112
+ const DEFAULT_ABIS_DIR = "abis";
113
+ function resolveAbisDir(config) {
114
+ return resolve$1(config.rootDir, config.abisDir ?? "abis");
115
+ }
116
+ //#endregion
117
+ export { resolveAbisDir as a, loadConfig as i, defineConfig as n, findProjectRoot as o, ensureConfigStub as r, DEFAULT_ABIS_DIR as t };
118
+
119
+ //# sourceMappingURL=config-DVHdL2W6.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-DVHdL2W6.mjs","names":["resolve","dirname"],"sources":["../src/cli/projectRoot.ts","../src/cli/config.ts"],"sourcesContent":["import { existsSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\n\n/**\n * Walk up from `startDir` looking for a `package.json`. Returns the\n * directory that contains it. Falls back to `startDir` if no ancestor\n * has a `package.json` (e.g. when running outside any project).\n */\nexport const findProjectRoot = (startDir: string = process.cwd()): string => {\n const absoluteStart = resolve(startDir)\n let dir = absoluteStart\n\n while (true) {\n if (existsSync(resolve(dir, 'package.json'))) {\n return dir\n }\n const parent = dirname(dir)\n if (parent === dir) {\n return absoluteStart\n }\n dir = parent\n }\n}\n","import { existsSync, writeFileSync } from 'node:fs'\nimport { pathToFileURL } from 'url'\nimport { dirname, resolve } from 'path'\nimport { findProjectRoot } from './projectRoot'\n\nexport type Contracts = {\n [chain: string]: ContractsNode\n}\nexport type ContractsNode = `0x${string}` | { [name: string]: ContractsNode }\n\nexport interface ZodiacConfig {\n /**\n * API key authorizing this directory against a Zodiac org.\n *\n * Optional — defaults to `process.env.ZODIAC_API_KEY` (populated by\n * `zodiac init`). Set this explicitly only when you need to override\n * the env var from inside the config file.\n */\n apiKey?: `zodiac_${string}`\n /**\n * Contracts the `allow` kit should know about, keyed by chain prefix.\n * Nested objects are allowed for grouping related addresses.\n */\n contracts?: Contracts\n /**\n * Directory where fetched ABIs are stored and read from.\n * Resolved relative to the project root (config file's directory).\n * Defaults to `./abis`.\n */\n abisDir?: string\n}\n\n/** User-provided config plus the resolved API key + project root. */\nexport interface ResolvedConfig extends Omit<ZodiacConfig, 'apiKey'> {\n apiKey: `zodiac_${string}`\n rootDir: string\n}\n\n/**\n * Loose base used as the *inference* constraint for `defineConfig`.\n * `contracts` is `Record<string, unknown>` here so `const T` can preserve the\n * caller's exact address literals rather than collapsing them into the\n * recursive `ContractsNode` union.\n */\ntype DefineConfigInput = {\n apiKey?: `zodiac_${string}`\n contracts?: Record<string, unknown>\n abisDir?: string\n}\n\n/**\n * Recursive leaf-level check: every leaf in `contracts` must be\n * `` `0x${string}` ``; any other value collapses the branch to `never`,\n * which surfaces as a type error at the call site.\n */\ntype ValidateContracts<C> = {\n [K in keyof C]: C[K] extends `0x${string}`\n ? C[K]\n : C[K] extends object\n ? ValidateContracts<C[K]>\n : never\n}\n\nexport const defineConfig = <const T extends DefineConfigInput>(\n config: T & {\n contracts?: ValidateContracts<NonNullable<T['contracts']>>\n }\n): T => config\n\nconst CONFIG_BASENAME = 'zodiac.config'\n\n/**\n * Extensions probed (in priority order) when the user doesn't pass an\n * explicit `--config` path. Mirrors what vite / vitest / tailwind accept.\n */\nconst CONFIG_EXTENSIONS = [\n '.ts',\n '.mts',\n '.cts',\n '.js',\n '.mjs',\n '.cjs',\n] as const\n\nconst DEFAULT_CONFIG_PATH = `${CONFIG_BASENAME}.ts`\n\nconst CONFIG_STUB = `import { defineConfig } from \"@zodiac-os/sdk/cli/config\";\n\nexport default defineConfig({\n contracts: {\n // Add contracts the \\`allow\\` kit should know about, keyed by chain prefix.\n // Run \\`zodiac pull-contracts\\` after editing.\n //\n // eth: {\n // weth: \"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2\",\n // },\n },\n});\n`\n\ntype LoadConfigOptions = {\n /**\n * Called when neither `config.apiKey` nor `process.env.ZODIAC_API_KEY` is\n * set. Receives the resolved project root and must return a freshly minted\n * API key. Typically wired to the interactive `init()` flow.\n *\n * If omitted, `loadConfig` throws when no key is found.\n */\n onMissingKey?: (rootDir: string) => Promise<string>\n /**\n * When the config file doesn't exist, write a minimal stub before\n * loading. Used by the CLI's `pull` commands so first-time setup\n * works without the user having to scaffold the file themselves.\n */\n createIfMissing?: boolean\n}\n\n/**\n * Write a starter `zodiac.config.ts` if no file exists at `absolutePath`.\n * Returns `true` if a file was written.\n */\nexport const ensureConfigStub = (absolutePath: string): boolean => {\n if (existsSync(absolutePath)) return false\n writeFileSync(absolutePath, CONFIG_STUB, 'utf8')\n return true\n}\n\n/**\n * Turn a (possibly-default) config path into an absolute path under\n * `projectRoot`. When the caller didn't pass an explicit path, probe\n * for `zodiac.config.{ts,mts,cts,js,mjs,cjs}` and use whichever exists;\n * fall back to the canonical `.ts` name so a not-found error is\n * still phrased in terms users recognise.\n */\nconst resolveConfigPath = (projectRoot: string, configPath: string): string => {\n const explicit = resolve(projectRoot, configPath)\n if (configPath !== DEFAULT_CONFIG_PATH) return explicit\n\n for (const ext of CONFIG_EXTENSIONS) {\n const candidate = resolve(projectRoot, `${CONFIG_BASENAME}${ext}`)\n if (existsSync(candidate)) return candidate\n }\n return explicit\n}\n\nexport async function loadConfig(\n configPath: string = DEFAULT_CONFIG_PATH,\n options: LoadConfigOptions = {}\n): Promise<ResolvedConfig> {\n // Resolve relative paths (including the default) against the nearest\n // package.json ancestor — that's where users expect the config to live,\n // regardless of which subdir they ran the CLI from.\n const projectRoot = findProjectRoot()\n const absolutePath = resolveConfigPath(projectRoot, configPath)\n\n if (options.createIfMissing && ensureConfigStub(absolutePath)) {\n console.log(`✅ Created ${absolutePath}`)\n }\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 const rootDir = dirname(absolutePath)\n const apiKey = await resolveApiKey(config, rootDir, options)\n\n return { ...config, apiKey, rootDir }\n}\n\nconst resolveApiKey = async (\n config: ZodiacConfig,\n rootDir: string,\n { onMissingKey }: LoadConfigOptions\n): Promise<`zodiac_${string}`> => {\n if (config.apiKey != null) {\n if (!isApiKey(config.apiKey)) {\n throw new Error(\n '`apiKey` in zodiac.config.ts is malformed: a valid Zodiac API key starts with \"zodiac_\". Either remove the field to use ZODIAC_API_KEY, or run `zodiac init` to mint a fresh key.'\n )\n }\n return config.apiKey\n }\n\n const fromEnv = process.env.ZODIAC_API_KEY\n if (fromEnv != null && fromEnv !== '') {\n if (!isApiKey(fromEnv)) {\n throw new Error(\n 'ZODIAC_API_KEY is set but malformed: a valid Zodiac API key starts with \"zodiac_\". Run `zodiac init` to mint a fresh key.'\n )\n }\n return fromEnv\n }\n\n if (onMissingKey == null) {\n throw new Error(\n 'No Zodiac API key found. Set ZODIAC_API_KEY in your environment, or run `zodiac init` to generate one.'\n )\n }\n\n const minted = await onMissingKey(rootDir)\n if (!isApiKey(minted)) {\n throw new Error(\n `onMissingKey returned an invalid Zodiac API key (expected a value starting with \"zodiac_\").`\n )\n }\n return minted\n}\n\nconst isApiKey = (value: string | undefined): value is `zodiac_${string}` =>\n value != null && value.startsWith('zodiac_')\n\nexport const DEFAULT_ABIS_DIR = 'abis'\n\nexport function resolveAbisDir(config: ResolvedConfig): string {\n return resolve(config.rootDir, config.abisDir ?? DEFAULT_ABIS_DIR)\n}\n"],"mappings":";;;;;;;;;;AAQA,MAAa,mBAAmB,WAAmB,QAAQ,KAAK,KAAa;CAC3E,MAAM,gBAAgB,QAAQ,SAAS;CACvC,IAAI,MAAM;AAEV,QAAO,MAAM;AACX,MAAI,WAAW,QAAQ,KAAK,eAAe,CAAC,CAC1C,QAAO;EAET,MAAM,SAAS,QAAQ,IAAI;AAC3B,MAAI,WAAW,IACb,QAAO;AAET,QAAM;;;;;AC2CV,MAAa,gBACX,WAGM;AAER,MAAM,kBAAkB;;;;;AAMxB,MAAM,oBAAoB;CACxB;CACA;CACA;CACA;CACA;CACA;CACD;AAED,MAAM,sBAAsB,GAAG,gBAAgB;AAE/C,MAAM,cAAc;;;;;;;;;;;;;;;;;AAmCpB,MAAa,oBAAoB,iBAAkC;AACjE,KAAI,WAAW,aAAa,CAAE,QAAO;AACrC,eAAc,cAAc,aAAa,OAAO;AAChD,QAAO;;;;;;;;;AAUT,MAAM,qBAAqB,aAAqB,eAA+B;CAC7E,MAAM,WAAWA,UAAQ,aAAa,WAAW;AACjD,KAAI,eAAe,oBAAqB,QAAO;AAE/C,MAAK,MAAM,OAAO,mBAAmB;EACnC,MAAM,YAAYA,UAAQ,aAAa,GAAG,kBAAkB,MAAM;AAClE,MAAI,WAAW,UAAU,CAAE,QAAO;;AAEpC,QAAO;;AAGT,eAAsB,WACpB,aAAqB,qBACrB,UAA6B,EAAE,EACN;CAKzB,MAAM,eAAe,kBADD,iBAAiB,EACe,WAAW;AAE/D,KAAI,QAAQ,mBAAmB,iBAAiB,aAAa,CAC3D,SAAQ,IAAI,aAAa,eAAe;CAG1C,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;CAGH,MAAM,UAAUC,UAAQ,aAAa;CACrC,MAAM,SAAS,MAAM,cAAc,QAAQ,SAAS,QAAQ;AAE5D,QAAO;EAAE,GAAG;EAAQ;EAAQ;EAAS;;AAGvC,MAAM,gBAAgB,OACpB,QACA,SACA,EAAE,mBAC8B;AAChC,KAAI,OAAO,UAAU,MAAM;AACzB,MAAI,CAAC,SAAS,OAAO,OAAO,CAC1B,OAAM,IAAI,MACR,sLACD;AAEH,SAAO,OAAO;;CAGhB,MAAM,UAAU,QAAQ,IAAI;AAC5B,KAAI,WAAW,QAAQ,YAAY,IAAI;AACrC,MAAI,CAAC,SAAS,QAAQ,CACpB,OAAM,IAAI,MACR,8HACD;AAEH,SAAO;;AAGT,KAAI,gBAAgB,KAClB,OAAM,IAAI,MACR,yGACD;CAGH,MAAM,SAAS,MAAM,aAAa,QAAQ;AAC1C,KAAI,CAAC,SAAS,OAAO,CACnB,OAAM,IAAI,MACR,8FACD;AAEH,QAAO;;AAGT,MAAM,YAAY,UAChB,SAAS,QAAQ,MAAM,WAAW,UAAU;AAE9C,MAAa,mBAAmB;AAEhC,SAAgB,eAAe,QAAgC;AAC7D,QAAOD,UAAQ,OAAO,SAAS,OAAO,WAAA,OAA4B"}
@@ -60,4 +60,4 @@ type ChainPrefix = keyof typeof CHAIN_IDS;
60
60
  declare const chainIdFor: (prefix: string) => number;
61
61
  //#endregion
62
62
  export { EVERYTHING as a, Scoping as c, ConditionFunction as i, TargetPermission as l, ChainPrefix as n, FunctionPermission as o, chainIdFor as r, Options as s, CHAIN_IDS as t, buildAllowKit as u };
63
- //# sourceMappingURL=index-DTBaxN7b.d.mts.map
63
+ //# sourceMappingURL=index-DoZbhpS6.d.mts.map
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  /// <reference path="./zodiac-os-codegen.d.ts" />
2
- import { a as EVERYTHING, c as Scoping, i as ConditionFunction, l as TargetPermission, n as ChainPrefix, o as FunctionPermission, s as Options, u as buildAllowKit } from "./index-DTBaxN7b.mjs";
2
+ import { a as EVERYTHING, c as Scoping, i as ConditionFunction, l as TargetPermission, n as ChainPrefix, o as FunctionPermission, s as Options, u as buildAllowKit } from "./index-DoZbhpS6.mjs";
3
3
  import { Annotation, Permission, PermissionSet, c, forAll } from "zodiac-roles-sdk";
4
4
  import { Address, ApplyConstellationPayload, ApplyConstellationResult, ChainId, ListAccountsResult, ListUsersResult, ResolveConstellationPayload, ResolveConstellationResult } from "@zodiac-os/api-types";
5
5
  import { UUID } from "crypto";
@@ -104,11 +104,14 @@ type RolesNode = NodeBase & Readonly<{
104
104
  target?: AddressOrRef; /** The account that is allowed to update the configuration of the Roles mod. */
105
105
  owner?: AddressOrRef; /** The account that calls will be executed from. */
106
106
  avatar?: AddressOrRef; /** MultiSend contract addresses for batched transactions. */
107
- multisend?: readonly Address[]; /** Role definitions configured on this modifier. */
108
- roles?: Record<string, RoleDef>;
109
- /** Spending allowances configured on this modifier. Either an array or
110
- * a Record keyed by name — both forms carry the same allowance specs. */
111
- allowances?: readonly AllowanceSpec[] | Record<string, AllowanceSpec>;
107
+ multisend?: readonly Address[];
108
+ /** Role definitions configured on this modifier. Pass `null` for a key to
109
+ * clear that role; unmentioned roles are left untouched. */
110
+ roles?: Record<string, RoleDef | null>;
111
+ /** Spending allowances configured on this modifier, keyed by name. Pass
112
+ * `null` for a key to clear that allowance; unmentioned allowances are
113
+ * left untouched. */
114
+ allowances?: Record<string, AllowanceSpec | null>;
112
115
  }>;
113
116
  /** Any complete node that can be passed to `push()`. */
114
117
  type ConstellationNode = SafeNode | RolesNode;
@@ -124,11 +127,14 @@ type NewRolesProps = {
124
127
  target?: AddressOrRef; /** The account that calls will be executed from. Defaults to `target` value */
125
128
  avatar?: AddressOrRef; /** The account that is allowed to update the configuration of the Roles Mod. Defaults to `target` value */
126
129
  owner?: AddressOrRef; /** MultiSend contract addresses for batched transactions. Defaults to `['0x38869bf66a61cf6bdb996a6ae40d5853fd43b526', '0x9641d764fc13c8b624c04430c7356c1c7c8102e2']` */
127
- multisend?: readonly Address[]; /** Role definitions to configure on this modifier. */
128
- roles?: Record<string, RoleDef>;
129
- /** Spending allowances to configure on this modifier. Either an array or
130
- * a Record keyed by name — both forms carry the same allowance specs. */
131
- allowances?: readonly AllowanceSpec[] | Record<string, AllowanceSpec>;
130
+ multisend?: readonly Address[];
131
+ /** Role definitions to configure on this modifier. Pass `null` for a key to
132
+ * clear that role; unmentioned roles are left untouched. */
133
+ roles?: Record<string, RoleDef | null>;
134
+ /** Spending allowances to configure on this modifier, keyed by name. Pass
135
+ * `null` for a key to clear that allowance; unmentioned allowances are
136
+ * left untouched. */
137
+ allowances?: Record<string, AllowanceSpec | null>;
132
138
  };
133
139
  type ExistingNodeAccessor<Type extends string, K extends string, E, Ch extends ChainId, NP extends Record<string, any>> = Readonly<Prettify<E & {
134
140
  type: Type;
package/dist/index.mjs CHANGED
@@ -157,10 +157,6 @@ async function nodeToSpec(node, refs) {
157
157
  spec.roles = await expandRoles(value, refs);
158
158
  continue;
159
159
  }
160
- if (node.type === "ROLES" && key === "allowances" && value != null && !Array.isArray(value)) {
161
- spec.allowances = resolveRefs(Object.values(value), refs);
162
- continue;
163
- }
164
160
  spec[key] = resolveRefs(value, refs);
165
161
  }
166
162
  spec.ref = refs.byIdentity.get(node);
@@ -168,15 +164,17 @@ async function nodeToSpec(node, refs) {
168
164
  return stringifyBigints(spec);
169
165
  }
170
166
  async function expandRoles(roles, refs) {
171
- return Promise.all(Object.entries(roles).map(async ([key, def]) => {
167
+ const entries = await Promise.all(Object.entries(roles).map(async ([key, def]) => {
168
+ if (def == null) return [key, null];
172
169
  const { targets, annotations } = processPermissions(await Promise.all(def.permissions.map((p) => Promise.resolve(p))));
173
- return {
170
+ return [key, {
174
171
  key,
175
172
  members: resolveRefs(def.members, refs),
176
173
  targets,
177
174
  annotations: [...def.annotations ?? [], ...annotations]
178
- };
175
+ }];
179
176
  }));
177
+ return Object.fromEntries(entries);
180
178
  }
181
179
  function resolveRefs(value, refs) {
182
180
  if (isConstellationNode(value)) {
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/constellation.ts","../src/push.ts"],"sourcesContent":["/// <reference path=\"./zodiac-os-codegen.d.ts\" />\nimport type { Address, ChainId } from '@zodiac-os/api-types'\nimport type { AllowanceSpec } from './types'\nimport type { Annotation, Permission, PermissionSet } from 'zodiac-roles-sdk'\nimport { createRequire } from 'module'\nimport { resolveZodiacDir } from './paths'\nimport { UUID } from 'crypto'\n\n/**\n * A role definition keyed by role name. Permissions are expanded into\n * `{ targets, annotations }` via `processPermissions` at `push()` time.\n */\nexport type RoleDef = {\n members: readonly AddressOrRef[]\n permissions: readonly (Permission | PermissionSet | Promise<PermissionSet>)[]\n annotations?: readonly Annotation[]\n}\n\ntype User = {\n id: UUID\n fullName: string\n personalSafes: Record<\n number,\n { address: Lowercase<Address>; active: boolean }\n >\n}\n\ntype Account = {\n id: UUID\n label: string\n address: Lowercase<Address>\n chain: ChainId\n /** True for accounts promoted to a workspace vault. */\n vault: boolean\n}\n\n/**\n * Accounts grouped by node type within a workspace. Per-type maps keep\n * bracket-accessor namespaces separate: a SAFE and a ROLES mod sharing a\n * label don't collide, and `eth.safe[...]` IntelliSense doesn't suggest\n * ROLES mod labels (and vice versa).\n */\ntype WorkspaceAccounts = {\n workspaceId: UUID\n workspaceName: string\n safes: Readonly<Record<string, Account>>\n rolesMods: Readonly<Record<string, Account>>\n delays: Readonly<Record<string, Account>>\n}\n\n/** Shape of the codegen data produced by `zodiac pull-org`. */\nexport type CodegenData = {\n users: Readonly<Record<string, User>>\n accounts: Readonly<Record<string, WorkspaceAccounts>>\n}\n\n// If `pull-org` has been run, the consumer's `.zodiac/index.d.ts` augments\n// `ZodiacGeneratedCodegen` with literal `users`/`accounts` shapes. Otherwise\n// the interface is empty and we fall back to the wide `CodegenData`.\ntype GeneratedCodegen = ZodiacGeneratedCodegen extends CodegenData\n ? ZodiacGeneratedCodegen\n : CodegenData\n\ntype ConstellationOpts<C extends CodegenData> = {\n /** Workspace to scope accounts and roles to. */\n workspace: keyof C['accounts'] & string\n /** Human-readable label for this constellation. */\n label: string\n /** Target chain for all nodes in this constellation. */\n chain: ChainId\n}\n\ntype ConstellationInternalOpts<C extends CodegenData> = {\n /** Injected codegen data (used for testing). */\n codegen?: C\n}\n\ntype Prettify<T> = { readonly [K in keyof T]: T[K] } & {}\n\ntype SafeEntries<\n C extends CodegenData,\n W extends keyof C['accounts'],\n> = C['accounts'][W]['safes']\n\ntype RolesEntries<\n C extends CodegenData,\n W extends keyof C['accounts'],\n> = C['accounts'][W]['rolesMods']\n\ntype NodeType = 'SAFE' | 'ROLES' | 'DELAY'\n\n/** A reference to a node used in `owners`, `modules`, `target`, etc. */\ntype NodeRef = Readonly<{ type: NodeType; label: string; chain: ChainId }>\n\n/** A blockchain address (checksummed or lowercase) or a reference to another\n * node in the constellation. Values are normalized to lowercase before being\n * sent to the API. */\ntype AddressOrRef = Address | NodeRef\n\ntype NodeBase = Readonly<{\n /** Human-readable identifier, unique within the constellation. */\n label: string\n /** Chain the node is deployed on. */\n chain: ChainId\n /** Set for existing nodes from codegen, absent for new nodes. */\n address?: Lowercase<Address>\n /** Deployment nonce — required for new nodes, optional for existing. */\n nonce?: bigint\n}>\n\n/** A safe node spec — existing vault ref or new safe with required config. */\nexport type SafeNode = NodeBase &\n Readonly<{\n /** Discriminator identifying this node as a Safe. */\n type: 'SAFE'\n /** Number of owner signatures required to execute a transaction. */\n threshold: number\n /** Safe owner addresses or node references. */\n owners: readonly (string | NodeRef)[]\n /** Module addresses or node references enabled on the safe. */\n modules?: readonly (string | NodeRef)[]\n /** Whether this safe shall appear as a vault in the workspace. @default false */\n vault?: boolean\n }>\n\n/** A roles modifier node spec — existing vault ref or new roles with modifier config. */\nexport type RolesNode = NodeBase &\n Readonly<{\n /** Discriminator identifying this node as a Roles modifier. */\n type: 'ROLES'\n /** The safe that this roles modifier controls. */\n target?: AddressOrRef\n /** The account that is allowed to update the configuration of the Roles mod. */\n owner?: AddressOrRef\n /** The account that calls will be executed from. */\n avatar?: AddressOrRef\n /** MultiSend contract addresses for batched transactions. */\n multisend?: readonly Address[]\n /** Role definitions configured on this modifier. */\n roles?: Record<string, RoleDef>\n /** Spending allowances configured on this modifier. Either an array or\n * a Record keyed by name — both forms carry the same allowance specs. */\n allowances?: readonly AllowanceSpec[] | Record<string, AllowanceSpec>\n }>\n\n/** Any complete node that can be passed to `push()`. */\nexport type ConstellationNode = SafeNode | RolesNode\nexport type ConstellationNodeInternal = ConstellationNode & {\n _constellation: ConstellationMeta\n}\n\ntype NewSafeProps = {\n /** Deployment nonce for CREATE2 address derivation. */\n nonce: bigint\n /** Number of owner signatures required to execute a transaction. */\n threshold: number\n /** Safe owner addresses or node references. */\n owners: readonly AddressOrRef[]\n /** Module addresses or node references to enable on the safe. */\n modules?: readonly AddressOrRef[]\n /** Whether this safe is a workspace vault. @default false */\n vault?: boolean\n}\n\ntype NewRolesProps = {\n /** Deployment nonce for CREATE2 address derivation. */\n nonce: bigint\n /** The safe that this roles modifier controls. Defaults to the new safe with the same label, when one exists. */\n target?: AddressOrRef\n /** The account that calls will be executed from. Defaults to `target` value */\n avatar?: AddressOrRef\n /** The account that is allowed to update the configuration of the Roles Mod. Defaults to `target` value */\n owner?: AddressOrRef\n /** MultiSend contract addresses for batched transactions. Defaults to `['0x38869bf66a61cf6bdb996a6ae40d5853fd43b526', '0x9641d764fc13c8b624c04430c7356c1c7c8102e2']` */\n multisend?: readonly Address[]\n /** Role definitions to configure on this modifier. */\n roles?: Record<string, RoleDef>\n /** Spending allowances to configure on this modifier. Either an array or\n * a Record keyed by name — both forms carry the same allowance specs. */\n allowances?: readonly AllowanceSpec[] | Record<string, AllowanceSpec>\n}\n\ntype ExistingNodeAccessor<\n Type extends string,\n K extends string,\n E,\n Ch extends ChainId,\n NP extends Record<string, any>,\n> = Readonly<Prettify<E & { type: Type; label: K; chain: Ch }>> &\n (<\n O extends {\n [P in Exclude<keyof E & string, 'id' | 'label'>]?: any\n } & Partial<NP> = {},\n >(\n overrides?: {\n [P in Exclude<keyof E & string, 'id' | 'label'>]?: any\n } & Partial<NP> &\n O\n ) => Readonly<\n Prettify<\n Omit<E, keyof O> & O & Partial<NP> & { type: Type; label: K; chain: Ch }\n >\n >)\n\ntype NewNodeAccessor<\n Type extends string,\n Ch extends ChainId,\n NP extends Record<string, any>,\n> = Readonly<Prettify<{ type: Type; label: string; chain: Ch }>> &\n ((\n props: NP\n ) => Readonly<Prettify<NP & { type: Type; label: string; chain: Ch }>>)\n\ntype EntityAccessor<\n Type extends string,\n Entries extends Record<string, any>,\n Ch extends ChainId = ChainId,\n NP extends Record<string, any> = Record<string, any>,\n> = {\n readonly [K in keyof Entries & string]: ExistingNodeAccessor<\n Type,\n K,\n Entries[K],\n Ch,\n NP\n >\n} & {\n readonly [key: string]: NewNodeAccessor<Type, Ch, NP>\n}\n\ntype UserAccessor<C extends CodegenData, Ch extends number> = {\n readonly [K in keyof C['users'] &\n string]: C['users'][K]['personalSafes'][Ch]['address']\n}\n\ntype ConstellationResult<\n C extends CodegenData,\n W extends keyof C['accounts'] = keyof C['accounts'],\n Ch extends ChainId = ChainId,\n> = {\n /** Access existing safes by label or create new ones with a new label.\n * Only SAFE-typed accounts are suggested in IntelliSense. */\n safe: EntityAccessor<'SAFE', SafeEntries<C, W>, Ch, NewSafeProps>\n /** Access existing roles modifiers by label or create new ones with a\n * new label. Only ROLES-typed accounts are suggested in IntelliSense. */\n roles: EntityAccessor<'ROLES', RolesEntries<C, W>, Ch, NewRolesProps>\n /** Resolve a user's personal safe address on the constellation's chain. */\n user: UserAccessor<C, Ch>\n}\n\n/** @internal */\nexport type ConstellationMeta = {\n label: string\n chain: ChainId\n workspaceId: UUID\n}\n\nfunction loadCodegen(): CodegenData {\n const require = createRequire(import.meta.url)\n return require(resolveZodiacDir()) as CodegenData\n}\n\n/**\n * Creates a constellation scoped to a workspace and chain.\n *\n * Use bracket access to reference existing accounts (vaults and other\n * applied constellation nodes) or define new ones:\n * ```ts\n * const eth = constellation({ workspace: 'GG', label: 'my constellation', chain: 1 })\n *\n * const dao = eth.safe['GG DAO'] // existing account ref\n * const roles = eth.roles['GG DAO'] // existing roles ref\n * const newSafe = eth.safe['New Safe']({ nonce: 0n, threshold: 2, owners: [...], modules: [...] })\n * ```\n */\nexport function constellation<\n const C extends CodegenData = GeneratedCodegen,\n const W extends keyof C['accounts'] & string = keyof C['accounts'] & string,\n const Ch extends ChainId = ChainId,\n>(\n opts: ConstellationOpts<C> & { workspace: W; chain: Ch },\n internal?: ConstellationInternalOpts<C>\n): ConstellationResult<C, W, Ch> {\n const codegen: CodegenData = internal?.codegen ?? loadCodegen()\n\n const ws = codegen.accounts[opts.workspace]\n const safesByLabel: Record<string, Account> = {}\n const rolesByLabel: Record<string, Account> = {}\n if (ws) {\n for (const [label, account] of Object.entries(ws.safes)) {\n safesByLabel[label] = account\n }\n for (const [label, account] of Object.entries(ws.rolesMods)) {\n rolesByLabel[label] = account\n }\n }\n\n const meta: ConstellationMeta = {\n label: opts.label,\n chain: opts.chain,\n workspaceId: (ws?.workspaceId ?? '') as UUID,\n }\n\n function makeNodeRef(\n data: Record<string, any>\n ): Readonly<Record<string, any>> {\n return Object.freeze({\n ...data,\n chain: opts.chain,\n _constellation: meta,\n })\n }\n\n function entityAccessor(\n registry: Record<string, Record<string, any>>,\n type: string\n ) {\n const cache = new Map<string, Record<string, any>>()\n return new Proxy({} as Record<string, any>, {\n get(_target: any, name: string) {\n if (typeof name !== 'string') return undefined\n const cached = cache.get(name)\n if (cached) return cached\n const existing = registry[name]\n // Bracket-access keys in the generated codegen carry a\n // ` (0xChecksummed…)` suffix when multiple workspace accounts share\n // a label. The label sent in the push spec should be the clean\n // original, so prefer `existing.label` when it's available.\n const specLabel: string = existing?.label ?? name\n const fn = (overrides?: Record<string, any>) =>\n makeNodeRef({\n type,\n ...(existing || {}),\n ...overrides,\n label: specLabel,\n })\n Object.assign(fn, {\n type,\n ...(existing || {}),\n label: specLabel,\n chain: opts.chain,\n _constellation: meta,\n })\n cache.set(name, fn)\n return fn\n },\n })\n }\n\n function userAccessor() {\n return new Proxy(\n {},\n {\n get(_target: any, name: string) {\n const user = codegen.users[name]\n if (!user) throw new Error(`Unknown user: ${name}`)\n const personalSafe = user.personalSafes[opts.chain]\n if (!personalSafe) {\n throw new Error(\n `User ${name} has no personal safe on chain ${opts.chain}`\n )\n }\n return personalSafe.address\n },\n }\n )\n }\n\n const safe = entityAccessor(safesByLabel, 'SAFE')\n const roles = entityAccessor(rolesByLabel, 'ROLES')\n\n return {\n safe,\n roles,\n user: userAccessor(),\n } as ConstellationResult<C, W, Ch>\n}\n","import type {\n ApplyConstellationPayload,\n ApplyConstellationResult,\n} from '@zodiac-os/api-types'\nimport { invariant } from '@epic-web/invariant'\nimport { processPermissions } from 'zodiac-roles-sdk'\nimport type { PermissionSet } from 'zodiac-roles-sdk'\nimport { ApiClient } from './api'\nimport type {\n ConstellationMeta,\n ConstellationNode,\n ConstellationNodeInternal,\n RoleDef,\n} from './constellation'\nimport type { AllowanceSpec } from './types'\n\ntype PushOpts = {\n /** API client instance. Defaults to a client configured from environment variables. */\n api?: ApiClient\n}\n\n/**\n * Resolves node references and pushes the constellation specification to the API.\n *\n *\n * ```ts\n * const eth = constellation({ workspace: 'GG', label: 'my constellation', chain: 1 })\n * const dao = eth.safe['GG DAO']\n * const roles = eth.roles['New Roles']({ nonce: 0n, target: dao, owner: dao, avatar: dao })\n *\n * await push([dao, roles])\n * ```\n */\nexport async function push(\n nodes: ConstellationNode[] | { [key: string]: ConstellationNode },\n opts?: PushOpts\n): Promise<ApplyConstellationResult[]> {\n const api = opts?.api ?? new ApiClient()\n const refs = deriveRefs(nodes)\n\n // Group nodes by constellation (multiple constellations can be applied with a single call)\n const groups = new Map<\n string,\n { meta: ConstellationMeta; nodes: ConstellationNodeInternal[] }\n >()\n\n for (const node of refs.byIdentity.keys()) {\n const meta = node._constellation\n invariant(\n meta,\n `Node \"${node.label}\" is not associated with a constellation`\n )\n const key = `${meta.workspaceId}:${meta.chain}:${meta.label}`\n let group = groups.get(key)\n if (!group) {\n group = { meta, nodes: [] }\n groups.set(key, group)\n }\n group.nodes.push(node)\n }\n\n const results: ApplyConstellationResult[] = []\n for (const { meta, nodes: groupNodes } of groups.values()) {\n const specification = await Promise.all(\n groupNodes.map((n) => nodeToSpec(n, refs))\n )\n const result = await api.applyConstellation(meta.workspaceId, {\n label: meta.label,\n chain: meta.chain,\n specification,\n })\n results.push(result)\n }\n\n return results\n}\n\ntype RefsIndex = {\n byIdentity: Map<ConstellationNodeInternal, string>\n byLabel: Map<string, string>\n}\n\nfunction labelKey(node: ConstellationNodeInternal): string {\n return `${node._constellation.workspaceId}:${node.type}:${node.label}`\n}\n\nfunction deriveRefs(\n nodes: ConstellationNode[] | { [key: string]: ConstellationNode }\n): RefsIndex {\n const byIdentity = new Map<ConstellationNodeInternal, string>()\n const byLabel = new Map<string, string>()\n\n const register = (node: ConstellationNodeInternal, ref: string) => {\n byIdentity.set(node, ref)\n byLabel.set(labelKey(node), ref)\n }\n\n if (Array.isArray(nodes)) {\n for (let i = 0; i < nodes.length; i++) {\n const node = nodes[i]\n invariant(\n isConstellationNode(node),\n `unexpected node input at index: ${i}`\n )\n register(node, `${i}`)\n }\n } else {\n for (const [key, node] of Object.entries(nodes)) {\n invariant(\n isConstellationNode(node),\n `unexpected node input under key: ${key}`\n )\n register(node, key)\n }\n }\n\n return { byIdentity, byLabel }\n}\n\nasync function nodeToSpec(\n node: ConstellationNodeInternal,\n refs: RefsIndex\n): Promise<ApplyConstellationPayload['specification'][number]> {\n const { id, _constellation, ...rest } = node as Record<string, any>\n const spec: Record<string, any> = {}\n\n for (const [key, value] of Object.entries(rest)) {\n if (node.type === 'ROLES' && key === 'roles' && value != null) {\n spec.roles = await expandRoles(value as Record<string, RoleDef>, refs)\n continue\n }\n if (\n node.type === 'ROLES' &&\n key === 'allowances' &&\n value != null &&\n !Array.isArray(value)\n ) {\n spec.allowances = resolveRefs(\n Object.values(value as Record<string, AllowanceSpec>),\n refs\n )\n continue\n }\n spec[key] = resolveRefs(value, refs)\n }\n\n spec.ref = refs.byIdentity.get(node)\n invariant(spec.ref != null, 'ref not found')\n\n return stringifyBigints(\n spec\n ) as ApplyConstellationPayload['specification'][number]\n}\n\nasync function expandRoles(\n roles: Record<string, RoleDef>,\n refs: RefsIndex\n): Promise<unknown[]> {\n return Promise.all(\n Object.entries(roles).map(async ([key, def]) => {\n const resolvedPermissions = (await Promise.all(\n def.permissions.map((p) => Promise.resolve(p))\n )) as Parameters<typeof processPermissions>[0][number][]\n const { targets, annotations } = processPermissions(resolvedPermissions)\n return {\n key,\n members: resolveRefs(def.members, refs),\n targets,\n annotations: [...(def.annotations ?? []), ...annotations],\n }\n })\n )\n}\n\nfunction resolveRefs(value: unknown, refs: RefsIndex): unknown {\n if (isConstellationNode(value)) {\n const ref = refs.byIdentity.get(value) ?? refs.byLabel.get(labelKey(value))\n invariant(\n ref != null,\n `Node \"${value.label}\" is referenced not included in the push() call`\n )\n return `$${ref}`\n }\n\n if (Array.isArray(value)) {\n return value.map((v) => resolveRefs(v, refs))\n }\n\n if (typeof value === 'object' && value !== null) {\n const resolved: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(value)) {\n resolved[k] = resolveRefs(v, refs)\n }\n return resolved\n }\n\n // API expects lowercase addresses\n if (typeof value === 'string' && /^0x[0-9a-fA-F]{40}$/.test(value)) {\n return value.toLowerCase()\n }\n\n return value\n}\n\nfunction stringifyBigints(value: unknown): unknown {\n if (typeof value === 'bigint') {\n return value.toString()\n }\n\n if (Array.isArray(value)) {\n return value.map(stringifyBigints)\n }\n\n if (typeof value === 'object' && value !== null) {\n const result: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(value)) {\n result[k] = stringifyBigints(v)\n }\n return result\n }\n\n return value\n}\n\nfunction isConstellationNode(\n value: unknown\n): value is ConstellationNodeInternal {\n if (typeof value === 'function' || typeof value === 'object') {\n const obj = value as any\n return (\n obj != null &&\n typeof obj.type === 'string' &&\n typeof obj.chain === 'number' &&\n typeof obj._constellation === 'object'\n )\n }\n return false\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/constellation.ts","../src/push.ts"],"sourcesContent":["/// <reference path=\"./zodiac-os-codegen.d.ts\" />\nimport type { Address, ChainId } from '@zodiac-os/api-types'\nimport type { AllowanceSpec } from './types'\nimport type { Annotation, Permission, PermissionSet } from 'zodiac-roles-sdk'\nimport { createRequire } from 'module'\nimport { resolveZodiacDir } from './paths'\nimport { UUID } from 'crypto'\n\n/**\n * A role definition keyed by role name. Permissions are expanded into\n * `{ targets, annotations }` via `processPermissions` at `push()` time.\n */\nexport type RoleDef = {\n members: readonly AddressOrRef[]\n permissions: readonly (Permission | PermissionSet | Promise<PermissionSet>)[]\n annotations?: readonly Annotation[]\n}\n\ntype User = {\n id: UUID\n fullName: string\n personalSafes: Record<\n number,\n { address: Lowercase<Address>; active: boolean }\n >\n}\n\ntype Account = {\n id: UUID\n label: string\n address: Lowercase<Address>\n chain: ChainId\n /** True for accounts promoted to a workspace vault. */\n vault: boolean\n}\n\n/**\n * Accounts grouped by node type within a workspace. Per-type maps keep\n * bracket-accessor namespaces separate: a SAFE and a ROLES mod sharing a\n * label don't collide, and `eth.safe[...]` IntelliSense doesn't suggest\n * ROLES mod labels (and vice versa).\n */\ntype WorkspaceAccounts = {\n workspaceId: UUID\n workspaceName: string\n safes: Readonly<Record<string, Account>>\n rolesMods: Readonly<Record<string, Account>>\n delays: Readonly<Record<string, Account>>\n}\n\n/** Shape of the codegen data produced by `zodiac pull-org`. */\nexport type CodegenData = {\n users: Readonly<Record<string, User>>\n accounts: Readonly<Record<string, WorkspaceAccounts>>\n}\n\n// If `pull-org` has been run, the consumer's `.zodiac/index.d.ts` augments\n// `ZodiacGeneratedCodegen` with literal `users`/`accounts` shapes. Otherwise\n// the interface is empty and we fall back to the wide `CodegenData`.\ntype GeneratedCodegen = ZodiacGeneratedCodegen extends CodegenData\n ? ZodiacGeneratedCodegen\n : CodegenData\n\ntype ConstellationOpts<C extends CodegenData> = {\n /** Workspace to scope accounts and roles to. */\n workspace: keyof C['accounts'] & string\n /** Human-readable label for this constellation. */\n label: string\n /** Target chain for all nodes in this constellation. */\n chain: ChainId\n}\n\ntype ConstellationInternalOpts<C extends CodegenData> = {\n /** Injected codegen data (used for testing). */\n codegen?: C\n}\n\ntype Prettify<T> = { readonly [K in keyof T]: T[K] } & {}\n\ntype SafeEntries<\n C extends CodegenData,\n W extends keyof C['accounts'],\n> = C['accounts'][W]['safes']\n\ntype RolesEntries<\n C extends CodegenData,\n W extends keyof C['accounts'],\n> = C['accounts'][W]['rolesMods']\n\ntype NodeType = 'SAFE' | 'ROLES' | 'DELAY'\n\n/** A reference to a node used in `owners`, `modules`, `target`, etc. */\ntype NodeRef = Readonly<{ type: NodeType; label: string; chain: ChainId }>\n\n/** A blockchain address (checksummed or lowercase) or a reference to another\n * node in the constellation. Values are normalized to lowercase before being\n * sent to the API. */\ntype AddressOrRef = Address | NodeRef\n\ntype NodeBase = Readonly<{\n /** Human-readable identifier, unique within the constellation. */\n label: string\n /** Chain the node is deployed on. */\n chain: ChainId\n /** Set for existing nodes from codegen, absent for new nodes. */\n address?: Lowercase<Address>\n /** Deployment nonce — required for new nodes, optional for existing. */\n nonce?: bigint\n}>\n\n/** A safe node spec — existing vault ref or new safe with required config. */\nexport type SafeNode = NodeBase &\n Readonly<{\n /** Discriminator identifying this node as a Safe. */\n type: 'SAFE'\n /** Number of owner signatures required to execute a transaction. */\n threshold: number\n /** Safe owner addresses or node references. */\n owners: readonly (string | NodeRef)[]\n /** Module addresses or node references enabled on the safe. */\n modules?: readonly (string | NodeRef)[]\n /** Whether this safe shall appear as a vault in the workspace. @default false */\n vault?: boolean\n }>\n\n/** A roles modifier node spec — existing vault ref or new roles with modifier config. */\nexport type RolesNode = NodeBase &\n Readonly<{\n /** Discriminator identifying this node as a Roles modifier. */\n type: 'ROLES'\n /** The safe that this roles modifier controls. */\n target?: AddressOrRef\n /** The account that is allowed to update the configuration of the Roles mod. */\n owner?: AddressOrRef\n /** The account that calls will be executed from. */\n avatar?: AddressOrRef\n /** MultiSend contract addresses for batched transactions. */\n multisend?: readonly Address[]\n /** Role definitions configured on this modifier. Pass `null` for a key to\n * clear that role; unmentioned roles are left untouched. */\n roles?: Record<string, RoleDef | null>\n /** Spending allowances configured on this modifier, keyed by name. Pass\n * `null` for a key to clear that allowance; unmentioned allowances are\n * left untouched. */\n allowances?: Record<string, AllowanceSpec | null>\n }>\n\n/** Any complete node that can be passed to `push()`. */\nexport type ConstellationNode = SafeNode | RolesNode\nexport type ConstellationNodeInternal = ConstellationNode & {\n _constellation: ConstellationMeta\n}\n\ntype NewSafeProps = {\n /** Deployment nonce for CREATE2 address derivation. */\n nonce: bigint\n /** Number of owner signatures required to execute a transaction. */\n threshold: number\n /** Safe owner addresses or node references. */\n owners: readonly AddressOrRef[]\n /** Module addresses or node references to enable on the safe. */\n modules?: readonly AddressOrRef[]\n /** Whether this safe is a workspace vault. @default false */\n vault?: boolean\n}\n\ntype NewRolesProps = {\n /** Deployment nonce for CREATE2 address derivation. */\n nonce: bigint\n /** The safe that this roles modifier controls. Defaults to the new safe with the same label, when one exists. */\n target?: AddressOrRef\n /** The account that calls will be executed from. Defaults to `target` value */\n avatar?: AddressOrRef\n /** The account that is allowed to update the configuration of the Roles Mod. Defaults to `target` value */\n owner?: AddressOrRef\n /** MultiSend contract addresses for batched transactions. Defaults to `['0x38869bf66a61cf6bdb996a6ae40d5853fd43b526', '0x9641d764fc13c8b624c04430c7356c1c7c8102e2']` */\n multisend?: readonly Address[]\n /** Role definitions to configure on this modifier. Pass `null` for a key to\n * clear that role; unmentioned roles are left untouched. */\n roles?: Record<string, RoleDef | null>\n /** Spending allowances to configure on this modifier, keyed by name. Pass\n * `null` for a key to clear that allowance; unmentioned allowances are\n * left untouched. */\n allowances?: Record<string, AllowanceSpec | null>\n}\n\ntype ExistingNodeAccessor<\n Type extends string,\n K extends string,\n E,\n Ch extends ChainId,\n NP extends Record<string, any>,\n> = Readonly<Prettify<E & { type: Type; label: K; chain: Ch }>> &\n (<\n O extends {\n [P in Exclude<keyof E & string, 'id' | 'label'>]?: any\n } & Partial<NP> = {},\n >(\n overrides?: {\n [P in Exclude<keyof E & string, 'id' | 'label'>]?: any\n } & Partial<NP> &\n O\n ) => Readonly<\n Prettify<\n Omit<E, keyof O> & O & Partial<NP> & { type: Type; label: K; chain: Ch }\n >\n >)\n\ntype NewNodeAccessor<\n Type extends string,\n Ch extends ChainId,\n NP extends Record<string, any>,\n> = Readonly<Prettify<{ type: Type; label: string; chain: Ch }>> &\n ((\n props: NP\n ) => Readonly<Prettify<NP & { type: Type; label: string; chain: Ch }>>)\n\ntype EntityAccessor<\n Type extends string,\n Entries extends Record<string, any>,\n Ch extends ChainId = ChainId,\n NP extends Record<string, any> = Record<string, any>,\n> = {\n readonly [K in keyof Entries & string]: ExistingNodeAccessor<\n Type,\n K,\n Entries[K],\n Ch,\n NP\n >\n} & {\n readonly [key: string]: NewNodeAccessor<Type, Ch, NP>\n}\n\ntype UserAccessor<C extends CodegenData, Ch extends number> = {\n readonly [K in keyof C['users'] &\n string]: C['users'][K]['personalSafes'][Ch]['address']\n}\n\ntype ConstellationResult<\n C extends CodegenData,\n W extends keyof C['accounts'] = keyof C['accounts'],\n Ch extends ChainId = ChainId,\n> = {\n /** Access existing safes by label or create new ones with a new label.\n * Only SAFE-typed accounts are suggested in IntelliSense. */\n safe: EntityAccessor<'SAFE', SafeEntries<C, W>, Ch, NewSafeProps>\n /** Access existing roles modifiers by label or create new ones with a\n * new label. Only ROLES-typed accounts are suggested in IntelliSense. */\n roles: EntityAccessor<'ROLES', RolesEntries<C, W>, Ch, NewRolesProps>\n /** Resolve a user's personal safe address on the constellation's chain. */\n user: UserAccessor<C, Ch>\n}\n\n/** @internal */\nexport type ConstellationMeta = {\n label: string\n chain: ChainId\n workspaceId: UUID\n}\n\nfunction loadCodegen(): CodegenData {\n const require = createRequire(import.meta.url)\n return require(resolveZodiacDir()) as CodegenData\n}\n\n/**\n * Creates a constellation scoped to a workspace and chain.\n *\n * Use bracket access to reference existing accounts (vaults and other\n * applied constellation nodes) or define new ones:\n * ```ts\n * const eth = constellation({ workspace: 'GG', label: 'my constellation', chain: 1 })\n *\n * const dao = eth.safe['GG DAO'] // existing account ref\n * const roles = eth.roles['GG DAO'] // existing roles ref\n * const newSafe = eth.safe['New Safe']({ nonce: 0n, threshold: 2, owners: [...], modules: [...] })\n * ```\n */\nexport function constellation<\n const C extends CodegenData = GeneratedCodegen,\n const W extends keyof C['accounts'] & string = keyof C['accounts'] & string,\n const Ch extends ChainId = ChainId,\n>(\n opts: ConstellationOpts<C> & { workspace: W; chain: Ch },\n internal?: ConstellationInternalOpts<C>\n): ConstellationResult<C, W, Ch> {\n const codegen: CodegenData = internal?.codegen ?? loadCodegen()\n\n const ws = codegen.accounts[opts.workspace]\n const safesByLabel: Record<string, Account> = {}\n const rolesByLabel: Record<string, Account> = {}\n if (ws) {\n for (const [label, account] of Object.entries(ws.safes)) {\n safesByLabel[label] = account\n }\n for (const [label, account] of Object.entries(ws.rolesMods)) {\n rolesByLabel[label] = account\n }\n }\n\n const meta: ConstellationMeta = {\n label: opts.label,\n chain: opts.chain,\n workspaceId: (ws?.workspaceId ?? '') as UUID,\n }\n\n function makeNodeRef(\n data: Record<string, any>\n ): Readonly<Record<string, any>> {\n return Object.freeze({\n ...data,\n chain: opts.chain,\n _constellation: meta,\n })\n }\n\n function entityAccessor(\n registry: Record<string, Record<string, any>>,\n type: string\n ) {\n const cache = new Map<string, Record<string, any>>()\n return new Proxy({} as Record<string, any>, {\n get(_target: any, name: string) {\n if (typeof name !== 'string') return undefined\n const cached = cache.get(name)\n if (cached) return cached\n const existing = registry[name]\n // Bracket-access keys in the generated codegen carry a\n // ` (0xChecksummed…)` suffix when multiple workspace accounts share\n // a label. The label sent in the push spec should be the clean\n // original, so prefer `existing.label` when it's available.\n const specLabel: string = existing?.label ?? name\n const fn = (overrides?: Record<string, any>) =>\n makeNodeRef({\n type,\n ...(existing || {}),\n ...overrides,\n label: specLabel,\n })\n Object.assign(fn, {\n type,\n ...(existing || {}),\n label: specLabel,\n chain: opts.chain,\n _constellation: meta,\n })\n cache.set(name, fn)\n return fn\n },\n })\n }\n\n function userAccessor() {\n return new Proxy(\n {},\n {\n get(_target: any, name: string) {\n const user = codegen.users[name]\n if (!user) throw new Error(`Unknown user: ${name}`)\n const personalSafe = user.personalSafes[opts.chain]\n if (!personalSafe) {\n throw new Error(\n `User ${name} has no personal safe on chain ${opts.chain}`\n )\n }\n return personalSafe.address\n },\n }\n )\n }\n\n const safe = entityAccessor(safesByLabel, 'SAFE')\n const roles = entityAccessor(rolesByLabel, 'ROLES')\n\n return {\n safe,\n roles,\n user: userAccessor(),\n } as ConstellationResult<C, W, Ch>\n}\n","import type {\n ApplyConstellationPayload,\n ApplyConstellationResult,\n} from '@zodiac-os/api-types'\nimport { invariant } from '@epic-web/invariant'\nimport { processPermissions } from 'zodiac-roles-sdk'\nimport type { PermissionSet } from 'zodiac-roles-sdk'\nimport { ApiClient } from './api'\nimport type {\n ConstellationMeta,\n ConstellationNode,\n ConstellationNodeInternal,\n RoleDef,\n} from './constellation'\n\ntype PushOpts = {\n /** API client instance. Defaults to a client configured from environment variables. */\n api?: ApiClient\n}\n\n/**\n * Resolves node references and pushes the constellation specification to the API.\n *\n *\n * ```ts\n * const eth = constellation({ workspace: 'GG', label: 'my constellation', chain: 1 })\n * const dao = eth.safe['GG DAO']\n * const roles = eth.roles['New Roles']({ nonce: 0n, target: dao, owner: dao, avatar: dao })\n *\n * await push([dao, roles])\n * ```\n */\nexport async function push(\n nodes: ConstellationNode[] | { [key: string]: ConstellationNode },\n opts?: PushOpts\n): Promise<ApplyConstellationResult[]> {\n const api = opts?.api ?? new ApiClient()\n const refs = deriveRefs(nodes)\n\n // Group nodes by constellation (multiple constellations can be applied with a single call)\n const groups = new Map<\n string,\n { meta: ConstellationMeta; nodes: ConstellationNodeInternal[] }\n >()\n\n for (const node of refs.byIdentity.keys()) {\n const meta = node._constellation\n invariant(\n meta,\n `Node \"${node.label}\" is not associated with a constellation`\n )\n const key = `${meta.workspaceId}:${meta.chain}:${meta.label}`\n let group = groups.get(key)\n if (!group) {\n group = { meta, nodes: [] }\n groups.set(key, group)\n }\n group.nodes.push(node)\n }\n\n const results: ApplyConstellationResult[] = []\n for (const { meta, nodes: groupNodes } of groups.values()) {\n const specification = await Promise.all(\n groupNodes.map((n) => nodeToSpec(n, refs))\n )\n const result = await api.applyConstellation(meta.workspaceId, {\n label: meta.label,\n chain: meta.chain,\n specification,\n })\n results.push(result)\n }\n\n return results\n}\n\ntype RefsIndex = {\n byIdentity: Map<ConstellationNodeInternal, string>\n byLabel: Map<string, string>\n}\n\nfunction labelKey(node: ConstellationNodeInternal): string {\n return `${node._constellation.workspaceId}:${node.type}:${node.label}`\n}\n\nfunction deriveRefs(\n nodes: ConstellationNode[] | { [key: string]: ConstellationNode }\n): RefsIndex {\n const byIdentity = new Map<ConstellationNodeInternal, string>()\n const byLabel = new Map<string, string>()\n\n const register = (node: ConstellationNodeInternal, ref: string) => {\n byIdentity.set(node, ref)\n byLabel.set(labelKey(node), ref)\n }\n\n if (Array.isArray(nodes)) {\n for (let i = 0; i < nodes.length; i++) {\n const node = nodes[i]\n invariant(\n isConstellationNode(node),\n `unexpected node input at index: ${i}`\n )\n register(node, `${i}`)\n }\n } else {\n for (const [key, node] of Object.entries(nodes)) {\n invariant(\n isConstellationNode(node),\n `unexpected node input under key: ${key}`\n )\n register(node, key)\n }\n }\n\n return { byIdentity, byLabel }\n}\n\nasync function nodeToSpec(\n node: ConstellationNodeInternal,\n refs: RefsIndex\n): Promise<ApplyConstellationPayload['specification'][number]> {\n const { id, _constellation, ...rest } = node as Record<string, any>\n const spec: Record<string, any> = {}\n\n for (const [key, value] of Object.entries(rest)) {\n if (node.type === 'ROLES' && key === 'roles' && value != null) {\n spec.roles = await expandRoles(\n value as Record<string, RoleDef | null>,\n refs\n )\n continue\n }\n spec[key] = resolveRefs(value, refs)\n }\n\n spec.ref = refs.byIdentity.get(node)\n invariant(spec.ref != null, 'ref not found')\n\n return stringifyBigints(\n spec\n ) as ApplyConstellationPayload['specification'][number]\n}\n\nasync function expandRoles(\n roles: Record<string, RoleDef | null>,\n refs: RefsIndex\n): Promise<Record<string, unknown>> {\n const entries = await Promise.all(\n Object.entries(roles).map(async ([key, def]) => {\n if (def == null) {\n return [key, null] as const\n }\n const resolvedPermissions = (await Promise.all(\n def.permissions.map((p) => Promise.resolve(p))\n )) as Parameters<typeof processPermissions>[0][number][]\n const { targets, annotations } = processPermissions(resolvedPermissions)\n return [\n key,\n {\n key,\n members: resolveRefs(def.members, refs),\n targets,\n annotations: [...(def.annotations ?? []), ...annotations],\n },\n ] as const\n })\n )\n return Object.fromEntries(entries)\n}\n\nfunction resolveRefs(value: unknown, refs: RefsIndex): unknown {\n if (isConstellationNode(value)) {\n const ref = refs.byIdentity.get(value) ?? refs.byLabel.get(labelKey(value))\n invariant(\n ref != null,\n `Node \"${value.label}\" is referenced not included in the push() call`\n )\n return `$${ref}`\n }\n\n if (Array.isArray(value)) {\n return value.map((v) => resolveRefs(v, refs))\n }\n\n if (typeof value === 'object' && value !== null) {\n const resolved: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(value)) {\n resolved[k] = resolveRefs(v, refs)\n }\n return resolved\n }\n\n // API expects lowercase addresses\n if (typeof value === 'string' && /^0x[0-9a-fA-F]{40}$/.test(value)) {\n return value.toLowerCase()\n }\n\n return value\n}\n\nfunction stringifyBigints(value: unknown): unknown {\n if (typeof value === 'bigint') {\n return value.toString()\n }\n\n if (Array.isArray(value)) {\n return value.map(stringifyBigints)\n }\n\n if (typeof value === 'object' && value !== null) {\n const result: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(value)) {\n result[k] = stringifyBigints(v)\n }\n return result\n }\n\n return value\n}\n\nfunction isConstellationNode(\n value: unknown\n): value is ConstellationNodeInternal {\n if (typeof value === 'function' || typeof value === 'object') {\n const obj = value as any\n return (\n obj != null &&\n typeof obj.type === 'string' &&\n typeof obj.chain === 'number' &&\n typeof obj._constellation === 'object'\n )\n }\n return false\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zodiac-os/sdk",
3
- "version": "1.9.0",
3
+ "version": "1.11.0",
4
4
  "author": "Gnosis Guild",
5
5
  "license": "LGPL-3.0-only",
6
6
  "homepage": "https://github.com/gnosisguild/zodiac-os-sdk",
@@ -1 +0,0 @@
1
- {"version":3,"file":"config.mjs","names":[],"sources":["../../src/cli/config.ts"],"sourcesContent":["import { pathToFileURL } from 'url'\nimport { dirname, resolve } from 'path'\n\nexport type Contracts = {\n [chain: string]: ContractsNode\n}\nexport type ContractsNode = `0x${string}` | { [name: string]: ContractsNode }\n\nexport interface ZodiacConfig {\n /**\n * API key authorizing this directory against a Zodiac org.\n *\n * Optional — defaults to `process.env.ZODIAC_API_KEY` (populated by\n * `zodiac init`). Set this explicitly only when you need to override\n * the env var from inside the config file.\n */\n apiKey?: `zodiac_${string}`\n /**\n * Contracts the `allow` kit should know about, keyed by chain prefix.\n * Nested objects are allowed for grouping related addresses.\n */\n contracts?: Contracts\n /**\n * Directory where fetched ABIs are stored and read from.\n * Resolved relative to the project root (config file's directory).\n * Defaults to `./abis`.\n */\n abisDir?: string\n}\n\n/** User-provided config plus the resolved API key + project root. */\nexport interface ResolvedConfig extends Omit<ZodiacConfig, 'apiKey'> {\n apiKey: `zodiac_${string}`\n rootDir: string\n}\n\n/**\n * Loose base used as the *inference* constraint for `defineConfig`.\n * `contracts` is `Record<string, unknown>` here so `const T` can preserve the\n * caller's exact address literals rather than collapsing them into the\n * recursive `ContractsNode` union.\n */\ntype DefineConfigInput = {\n apiKey?: `zodiac_${string}`\n contracts?: Record<string, unknown>\n abisDir?: string\n}\n\n/**\n * Recursive leaf-level check: every leaf in `contracts` must be\n * `` `0x${string}` ``; any other value collapses the branch to `never`,\n * which surfaces as a type error at the call site.\n */\ntype ValidateContracts<C> = {\n [K in keyof C]: C[K] extends `0x${string}`\n ? C[K]\n : C[K] extends object\n ? ValidateContracts<C[K]>\n : never\n}\n\nexport const defineConfig = <const T extends DefineConfigInput>(\n config: T & {\n contracts?: ValidateContracts<NonNullable<T['contracts']>>\n }\n): T => config\n\nconst DEFAULT_CONFIG_PATH = 'zodiac.config.ts'\n\ntype LoadConfigOptions = {\n /**\n * Called when neither `config.apiKey` nor `process.env.ZODIAC_API_KEY` is\n * set. Receives the resolved project root and must return a freshly minted\n * API key. Typically wired to the interactive `init()` flow.\n *\n * If omitted, `loadConfig` throws when no key is found.\n */\n onMissingKey?: (rootDir: string) => Promise<string>\n}\n\nexport async function loadConfig(\n configPath: string = DEFAULT_CONFIG_PATH,\n options: LoadConfigOptions = {}\n): Promise<ResolvedConfig> {\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 const rootDir = dirname(absolutePath)\n const apiKey = await resolveApiKey(config, rootDir, options)\n\n return { ...config, apiKey, rootDir }\n}\n\nconst resolveApiKey = async (\n config: ZodiacConfig,\n rootDir: string,\n { onMissingKey }: LoadConfigOptions\n): Promise<`zodiac_${string}`> => {\n if (config.apiKey != null) {\n if (!isApiKey(config.apiKey)) {\n throw new Error(\n '`apiKey` in zodiac.config.ts is malformed: a valid Zodiac API key starts with \"zodiac_\". Either remove the field to use ZODIAC_API_KEY, or run `zodiac init` to mint a fresh key.'\n )\n }\n return config.apiKey\n }\n\n const fromEnv = process.env.ZODIAC_API_KEY\n if (fromEnv != null && fromEnv !== '') {\n if (!isApiKey(fromEnv)) {\n throw new Error(\n 'ZODIAC_API_KEY is set but malformed: a valid Zodiac API key starts with \"zodiac_\". Run `zodiac init` to mint a fresh key.'\n )\n }\n return fromEnv\n }\n\n if (onMissingKey == null) {\n throw new Error(\n 'No Zodiac API key found. Set ZODIAC_API_KEY in your environment, or run `zodiac init` to generate one.'\n )\n }\n\n const minted = await onMissingKey(rootDir)\n if (!isApiKey(minted)) {\n throw new Error(\n `onMissingKey returned an invalid Zodiac API key (expected a value starting with \"zodiac_\").`\n )\n }\n return minted\n}\n\nconst isApiKey = (value: string | undefined): value is `zodiac_${string}` =>\n value != null && value.startsWith('zodiac_')\n\nexport const DEFAULT_ABIS_DIR = 'abis'\n\nexport function resolveAbisDir(config: ResolvedConfig): string {\n return resolve(config.rootDir, config.abisDir ?? DEFAULT_ABIS_DIR)\n}\n"],"mappings":";;;AA6DA,MAAa,gBACX,WAGM;AAER,MAAM,sBAAsB;AAa5B,eAAsB,WACpB,aAAqB,qBACrB,UAA6B,EAAE,EACN;CACzB,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;CAGH,MAAM,UAAU,QAAQ,aAAa;CACrC,MAAM,SAAS,MAAM,cAAc,QAAQ,SAAS,QAAQ;AAE5D,QAAO;EAAE,GAAG;EAAQ;EAAQ;EAAS;;AAGvC,MAAM,gBAAgB,OACpB,QACA,SACA,EAAE,mBAC8B;AAChC,KAAI,OAAO,UAAU,MAAM;AACzB,MAAI,CAAC,SAAS,OAAO,OAAO,CAC1B,OAAM,IAAI,MACR,sLACD;AAEH,SAAO,OAAO;;CAGhB,MAAM,UAAU,QAAQ,IAAI;AAC5B,KAAI,WAAW,QAAQ,YAAY,IAAI;AACrC,MAAI,CAAC,SAAS,QAAQ,CACpB,OAAM,IAAI,MACR,8HACD;AAEH,SAAO;;AAGT,KAAI,gBAAgB,KAClB,OAAM,IAAI,MACR,yGACD;CAGH,MAAM,SAAS,MAAM,aAAa,QAAQ;AAC1C,KAAI,CAAC,SAAS,OAAO,CACnB,OAAM,IAAI,MACR,8FACD;AAEH,QAAO;;AAGT,MAAM,YAAY,UAChB,SAAS,QAAQ,MAAM,WAAW,UAAU;AAE9C,MAAa,mBAAmB;AAEhC,SAAgB,eAAe,QAAgC;AAC7D,QAAO,QAAQ,OAAO,SAAS,OAAO,WAAA,OAA4B"}