zooid 0.11.2 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -3
- package/dist/bin.js +157 -14
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-KGYQ5YNP.js → chunk-3Q4BPAZD.js} +23 -2
- package/dist/chunk-3Q4BPAZD.js.map +1 -0
- package/dist/index.js +1 -1
- package/package.json +8 -8
- package/src/bin.ts +3 -3
- package/src/build-registry.ts +1 -1
- package/src/build-registry.zod044.test.ts +23 -0
- package/src/commands/init/generators.test.ts +43 -0
- package/src/commands/init/generators.ts +24 -5
- package/src/commands/init/pi-scaffold.test.ts +151 -0
- package/src/commands/init/prompts.ts +53 -2
- package/src/commands/init/registry.test.ts +60 -0
- package/src/commands/init/registry.ts +58 -0
- package/src/commands/init/sniff.test.ts +61 -2
- package/src/commands/init/sniff.ts +43 -12
- package/src/commands/init.ts +91 -5
- package/dist/chunk-KGYQ5YNP.js.map +0 -1
package/README.md
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
> A chat app to collaborate with AI agents, alongside the rest of your team. Open-source, self-hostable, any model, any CLI.
|
|
4
4
|
|
|
5
|
-
Zooid is an open-source, self-hostable chat app for collaborating with AI agents alongside your team. It brings [ACP](https://agentclientprotocol.com)-compatible agents (Claude Code, [opencode](https://opencode.ai), Codex, …) into [Matrix](https://matrix.org) rooms as first-class participants — people and agents in the same rooms, threads, and approvals, no separate "AI dashboard," no vendor lock-in. Deploy with `zooid init`, run with `zooid dev`.
|
|
5
|
+
Zooid is an open-source, self-hostable chat app for collaborating with AI agents alongside your team. It brings [ACP](https://agentclientprotocol.com)-compatible agents (Claude Code, [opencode](https://opencode.ai), Codex, pi, …) into [Matrix](https://matrix.org) rooms as first-class participants — people and agents in the same rooms, threads, and approvals, no separate "AI dashboard," no vendor lock-in. Deploy with `zooid init`, run with `zooid dev`.
|
|
6
6
|
|
|
7
7
|
**Full docs: [zooid.dev/docs](https://zooid.dev/docs)** · **Join the community server: [community.zoon.eco](https://community.zoon.eco)**
|
|
8
8
|
|
|
9
|
-
- **Protocol-first.** Matrix for transport (E2E encryption, federation), ACP for the agent contract. Pre-built images for Claude Code, opencode, and
|
|
9
|
+
- **Protocol-first.** Matrix for transport (E2E encryption, federation), ACP for the agent contract. Pre-built images for Claude Code, opencode, Codex, and pi; any other ACP-compatible harness (GitHub Copilot CLI, Cursor CLI, Gemini CLI, or your own) connects too.
|
|
10
10
|
- **Run it anywhere.** Each daemon is a *workstation* with its own identity, running as many agents as you like — put it on your laptop, a box, a pod, or a cluster; it doesn't have to live next to the homeserver. Several workstations can share one homeserver.
|
|
11
11
|
- **Containerized runtime.** Docker or Podman. Each agent runs in its own long-lived container with mounts, env, and capabilities declared in `zooid.yaml`.
|
|
12
12
|
- **Multi-agent collaboration.** Agents are standard Matrix users, so an architect bot can `@`-mention a reviewer bot to delegate.
|
|
@@ -28,7 +28,9 @@ mkdir my-workforce && cd my-workforce
|
|
|
28
28
|
zooid init
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
`zooid init` asks which harness (
|
|
31
|
+
`zooid init` asks which harness (`claude`, `codex`, `opencode`, `pi`), which model provider, and how to authenticate. It writes a clean `zooid.yaml` and any `.env` it needs.
|
|
32
|
+
|
|
33
|
+
Every preset can run off a subscription you already have instead of a fresh API key. `pi` goes a step further: it only offers that choice once it has actually found a `pi` login, inherits the `defaultProvider` / `defaultModel` you already run, and gives the agent its own relocated config dir (`.pi-agent/`) so it never writes into your `~/.pi`.
|
|
32
34
|
|
|
33
35
|
Boot the local stack:
|
|
34
36
|
|
|
@@ -85,6 +87,7 @@ Zooid publishes a small set of base images on GHCR. Drop one into `zooid.yaml` u
|
|
|
85
87
|
- `ghcr.io/zooid-ai/agent-claude-code` — agent-base + the Claude Code ACP shim.
|
|
86
88
|
- `ghcr.io/zooid-ai/agent-codex` — agent-base + the Codex ACP shim.
|
|
87
89
|
- `ghcr.io/zooid-ai/agent-opencode` — agent-base + opencode.
|
|
90
|
+
- `ghcr.io/zooid-ai/agent-pi` — agent-base + pi + pi-acp.
|
|
88
91
|
|
|
89
92
|
The persona — `CLAUDE.md` / `AGENTS.md`, `.claude/settings.json`, skills, MCP servers — lives in the agent's `workdir` on the host. Zooid bind-mounts that directory into the container at runtime, so the shim picks it up the same way it would on your laptop. No `docker build`, no custom image, no rebuild when you tweak instructions.
|
|
90
93
|
|
package/dist/bin.js
CHANGED
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
renderRegistration,
|
|
17
17
|
startDaemonSocketServer,
|
|
18
18
|
startWorkforcePublisher
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-3Q4BPAZD.js";
|
|
20
20
|
|
|
21
21
|
// src/bin.ts
|
|
22
22
|
import { resolve as resolve6 } from "path";
|
|
@@ -1494,12 +1494,13 @@ function loadEnvFiles(cwd) {
|
|
|
1494
1494
|
}
|
|
1495
1495
|
|
|
1496
1496
|
// src/commands/init.ts
|
|
1497
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync5, readdirSync as readdirSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
1497
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync5, readdirSync as readdirSync2, symlinkSync, writeFileSync as writeFileSync5 } from "fs";
|
|
1498
|
+
import { homedir as homedir2 } from "os";
|
|
1498
1499
|
import { dirname as dirname7, join as join14, resolve as resolve4 } from "path";
|
|
1499
1500
|
|
|
1500
1501
|
// src/commands/init/generators.ts
|
|
1501
1502
|
function generateZooidYaml(opts) {
|
|
1502
|
-
const acpBlock = opts.preset === "opencode" || !opts.model ? ` acp: { preset: ${opts.preset} }` : ` acp:
|
|
1503
|
+
const acpBlock = opts.preset === "opencode" || opts.preset === "pi" || !opts.model ? ` acp: { preset: ${opts.preset} }` : ` acp:
|
|
1503
1504
|
preset: ${opts.preset}
|
|
1504
1505
|
model: ${opts.model}`;
|
|
1505
1506
|
return `runtime: local
|
|
@@ -1586,7 +1587,10 @@ function generateEnv(opts) {
|
|
|
1586
1587
|
`;
|
|
1587
1588
|
}
|
|
1588
1589
|
function generateGitignore() {
|
|
1589
|
-
return [".env", "node_modules/", "data/", ""].join("\n");
|
|
1590
|
+
return [".env", ".pi-agent/", "node_modules/", "data/", ""].join("\n");
|
|
1591
|
+
}
|
|
1592
|
+
function generatePiSettings(opts) {
|
|
1593
|
+
return JSON.stringify({ defaultProvider: opts.provider, defaultModel: opts.model }, null, 2) + "\n";
|
|
1590
1594
|
}
|
|
1591
1595
|
function generateOpencodeReadme() {
|
|
1592
1596
|
return `See https://opencode.ai/config for the full opencode.json schema, including
|
|
@@ -1643,18 +1647,59 @@ function findSimplePreset(name) {
|
|
|
1643
1647
|
function findOpencodeProvider(id) {
|
|
1644
1648
|
return OPENCODE_PROVIDERS.find((p) => p.id === id);
|
|
1645
1649
|
}
|
|
1650
|
+
var PI_PROVIDERS = [
|
|
1651
|
+
{
|
|
1652
|
+
id: "openrouter",
|
|
1653
|
+
label: "OpenRouter",
|
|
1654
|
+
description: "many providers via OpenRouter credits",
|
|
1655
|
+
apiKeyEnvVar: "OPENROUTER_API_KEY"
|
|
1656
|
+
},
|
|
1657
|
+
{
|
|
1658
|
+
id: "anthropic",
|
|
1659
|
+
label: "Anthropic",
|
|
1660
|
+
description: "Claude via direct Anthropic API key",
|
|
1661
|
+
apiKeyEnvVar: "ANTHROPIC_API_KEY"
|
|
1662
|
+
},
|
|
1663
|
+
{
|
|
1664
|
+
id: "openai",
|
|
1665
|
+
label: "OpenAI",
|
|
1666
|
+
description: "GPT via direct OpenAI API key",
|
|
1667
|
+
apiKeyEnvVar: "OPENAI_API_KEY"
|
|
1668
|
+
}
|
|
1669
|
+
];
|
|
1670
|
+
var PI_DEFAULT_PROVIDER = "openrouter";
|
|
1671
|
+
var PI_DEFAULT_MODEL = "deepseek/deepseek-v4-pro";
|
|
1672
|
+
var PI_AGENT_DIR = ".pi-agent";
|
|
1673
|
+
var PI_AUTH_FILE = ".pi/agent/auth.json";
|
|
1674
|
+
var PI_SETTINGS_FILE = ".pi/agent/settings.json";
|
|
1675
|
+
function findPiProvider(id) {
|
|
1676
|
+
return PI_PROVIDERS.find((p) => p.id === id);
|
|
1677
|
+
}
|
|
1646
1678
|
|
|
1647
1679
|
// src/commands/init/sniff.ts
|
|
1648
|
-
import { existsSync as existsSync6 } from "fs";
|
|
1680
|
+
import { existsSync as existsSync6, readFileSync as readFileSync7 } from "fs";
|
|
1649
1681
|
import { homedir } from "os";
|
|
1650
1682
|
import { join as join13 } from "path";
|
|
1651
1683
|
function sniffCredentials(preset, home = homedir()) {
|
|
1652
|
-
const
|
|
1653
|
-
if (!
|
|
1654
|
-
const full = join13(home,
|
|
1684
|
+
const rel = preset === "pi" ? PI_AUTH_FILE : findSimplePreset(preset)?.credentialDir;
|
|
1685
|
+
if (!rel) return { found: false };
|
|
1686
|
+
const full = join13(home, rel);
|
|
1655
1687
|
if (existsSync6(full)) return { found: true, path: full };
|
|
1656
1688
|
return { found: false };
|
|
1657
1689
|
}
|
|
1690
|
+
function sniffPiDefaults(home = homedir()) {
|
|
1691
|
+
const source = join13(home, PI_SETTINGS_FILE);
|
|
1692
|
+
try {
|
|
1693
|
+
const raw = JSON.parse(readFileSync7(source, "utf8"));
|
|
1694
|
+
const provider = raw.defaultProvider;
|
|
1695
|
+
const model = raw.defaultModel;
|
|
1696
|
+
if (typeof provider !== "string" || !provider) return void 0;
|
|
1697
|
+
if (typeof model !== "string" || !model) return void 0;
|
|
1698
|
+
return { provider, model, source };
|
|
1699
|
+
} catch {
|
|
1700
|
+
return void 0;
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1658
1703
|
|
|
1659
1704
|
// src/commands/init.ts
|
|
1660
1705
|
var IGNORED_PREEXISTING = /* @__PURE__ */ new Set([
|
|
@@ -1680,6 +1725,7 @@ async function runInit(opts) {
|
|
|
1680
1725
|
}
|
|
1681
1726
|
}
|
|
1682
1727
|
const writes = [];
|
|
1728
|
+
let piInherited;
|
|
1683
1729
|
if (opts.preset === "claude" || opts.preset === "codex") {
|
|
1684
1730
|
if (!opts.auth) throw new Error("--auth (subscription|api-key) is required for claude/codex");
|
|
1685
1731
|
const meta = findSimplePreset(opts.preset);
|
|
@@ -1732,6 +1778,34 @@ async function runInit(opts) {
|
|
|
1732
1778
|
content: generateEnv({ envVar: providerMeta.apiKeyEnvVar, value: opts.apiKey })
|
|
1733
1779
|
});
|
|
1734
1780
|
}
|
|
1781
|
+
} else if (opts.preset === "pi") {
|
|
1782
|
+
if (!opts.auth) throw new Error("--auth (subscription|api-key) is required for pi");
|
|
1783
|
+
if (opts.auth === "subscription" && !sniffCredentials("pi", opts.home).found) {
|
|
1784
|
+
throw new Error(
|
|
1785
|
+
"no pi login found \u2014 run `pi` and /login first, or use --auth api-key"
|
|
1786
|
+
);
|
|
1787
|
+
}
|
|
1788
|
+
if (opts.auth === "api-key" && !opts.provider) {
|
|
1789
|
+
throw new Error("--provider is required for pi --auth api-key");
|
|
1790
|
+
}
|
|
1791
|
+
const providerMeta = opts.provider ? findPiProvider(opts.provider) : void 0;
|
|
1792
|
+
if (opts.provider && !providerMeta) throw new Error(`unknown pi provider: ${opts.provider}`);
|
|
1793
|
+
const inherited = sniffPiDefaults(opts.home);
|
|
1794
|
+
const provider = opts.provider ?? inherited?.provider ?? PI_DEFAULT_PROVIDER;
|
|
1795
|
+
const model = opts.model ?? inherited?.model ?? PI_DEFAULT_MODEL;
|
|
1796
|
+
const settingsProvider = opts.model ? provider : inherited?.provider ?? provider;
|
|
1797
|
+
writes.push({ path: "zooid.yaml", content: generateZooidYaml({ preset: "pi" }) });
|
|
1798
|
+
writes.push({ path: "agents/zooid-assistant/AGENTS.md", content: generateAgentsMd() });
|
|
1799
|
+
writes.push({
|
|
1800
|
+
path: `agents/zooid-assistant/${PI_AGENT_DIR}/settings.json`,
|
|
1801
|
+
content: generatePiSettings({ provider: settingsProvider, model })
|
|
1802
|
+
});
|
|
1803
|
+
const envLines = [`PI_CODING_AGENT_DIR=${PI_AGENT_DIR}`];
|
|
1804
|
+
if (opts.auth === "api-key" && opts.apiKey) {
|
|
1805
|
+
envLines.push(`${providerMeta.apiKeyEnvVar}=${opts.apiKey}`);
|
|
1806
|
+
}
|
|
1807
|
+
writes.push({ path: ".env", content: envLines.join("\n") + "\n" });
|
|
1808
|
+
piInherited = inherited;
|
|
1735
1809
|
} else {
|
|
1736
1810
|
throw new Error(`unknown preset: ${String(opts.preset)}`);
|
|
1737
1811
|
}
|
|
@@ -1757,6 +1831,35 @@ async function runInit(opts) {
|
|
|
1757
1831
|
);
|
|
1758
1832
|
}
|
|
1759
1833
|
}
|
|
1834
|
+
if (opts.preset === "pi") {
|
|
1835
|
+
if (piInherited) {
|
|
1836
|
+
console.log(
|
|
1837
|
+
`\u2713 Inherited defaultProvider=${piInherited.provider} defaultModel=${piInherited.model} from ${piInherited.source}`
|
|
1838
|
+
);
|
|
1839
|
+
} else {
|
|
1840
|
+
console.log(
|
|
1841
|
+
`\u2713 Pinned defaultProvider=${PI_DEFAULT_PROVIDER} defaultModel=${PI_DEFAULT_MODEL} (pi's own default returns an empty turn)`
|
|
1842
|
+
);
|
|
1843
|
+
}
|
|
1844
|
+
const s = sniffCredentials("pi", opts.home);
|
|
1845
|
+
if (opts.auth === "subscription" && s.found) {
|
|
1846
|
+
const authSource = join14(opts.home ?? homedir2(), PI_AUTH_FILE);
|
|
1847
|
+
const linkPath = join14(dir, `agents/zooid-assistant/${PI_AGENT_DIR}/auth.json`);
|
|
1848
|
+
if (!existsSync7(linkPath)) {
|
|
1849
|
+
mkdirSync5(dirname7(linkPath), { recursive: true });
|
|
1850
|
+
symlinkSync(authSource, linkPath);
|
|
1851
|
+
}
|
|
1852
|
+
console.log(
|
|
1853
|
+
`\u2713 Shared pi login from ${s.path} \u2014 the agent uses your subscription via a symlink at ${PI_AGENT_DIR}/auth.json`
|
|
1854
|
+
);
|
|
1855
|
+
} else if (s.found) {
|
|
1856
|
+
console.log(
|
|
1857
|
+
`\u2713 Found pi login at ${s.path} \u2014 the agent gets its own config at ${PI_AGENT_DIR}/ and will not touch it`
|
|
1858
|
+
);
|
|
1859
|
+
} else {
|
|
1860
|
+
console.warn(`\u26A0 No pi login detected \u2014 the agent uses its own API key from .env`);
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1760
1863
|
console.log("\nNext: zooid dev");
|
|
1761
1864
|
}
|
|
1762
1865
|
|
|
@@ -1774,10 +1877,11 @@ async function resolveOptions(flags) {
|
|
|
1774
1877
|
choices: [
|
|
1775
1878
|
{ name: "claude (Claude Code)", value: "claude" },
|
|
1776
1879
|
{ name: "codex (OpenAI Codex)", value: "codex" },
|
|
1777
|
-
{ name: "opencode", value: "opencode" }
|
|
1880
|
+
{ name: "opencode", value: "opencode" },
|
|
1881
|
+
{ name: "pi", value: "pi" }
|
|
1778
1882
|
]
|
|
1779
1883
|
}),
|
|
1780
|
-
() => "--preset is required (claude | codex | opencode)"
|
|
1884
|
+
() => "--preset is required (claude | codex | opencode | pi)"
|
|
1781
1885
|
);
|
|
1782
1886
|
if (preset === "opencode") {
|
|
1783
1887
|
const provider = flags.provider ?? "opencode-go";
|
|
@@ -1806,6 +1910,45 @@ async function resolveOptions(flags) {
|
|
|
1806
1910
|
overwrite: flags.overwrite
|
|
1807
1911
|
};
|
|
1808
1912
|
}
|
|
1913
|
+
if (preset === "pi") {
|
|
1914
|
+
const loginFound = sniffCredentials("pi").found;
|
|
1915
|
+
const auth2 = flags.auth ?? (loginFound ? await ask(
|
|
1916
|
+
() => select({
|
|
1917
|
+
message: "A pi login was found. How should the agent authenticate?",
|
|
1918
|
+
choices: [
|
|
1919
|
+
{ name: "Share my pi login (agent uses my existing subscription)", value: "subscription" },
|
|
1920
|
+
{ name: "Give the agent its own key (billed and revoked separately)", value: "api-key" }
|
|
1921
|
+
]
|
|
1922
|
+
}),
|
|
1923
|
+
() => "--auth is required (subscription | api-key)"
|
|
1924
|
+
) : "api-key");
|
|
1925
|
+
if (auth2 === "subscription") {
|
|
1926
|
+
return {
|
|
1927
|
+
dir: flags.dir,
|
|
1928
|
+
preset,
|
|
1929
|
+
auth: auth2,
|
|
1930
|
+
force: flags.force,
|
|
1931
|
+
overwrite: flags.overwrite
|
|
1932
|
+
};
|
|
1933
|
+
}
|
|
1934
|
+
const provider = flags.provider ?? PI_DEFAULT_PROVIDER;
|
|
1935
|
+
const providerMeta = findPiProvider(provider);
|
|
1936
|
+
if (!providerMeta) throw new Error(`unknown pi provider: ${provider}`);
|
|
1937
|
+
const apiKey2 = flags.apiKey ?? await ask(
|
|
1938
|
+
() => password({ message: `${providerMeta.label} API key:` }),
|
|
1939
|
+
() => "--api-key is required"
|
|
1940
|
+
);
|
|
1941
|
+
return {
|
|
1942
|
+
dir: flags.dir,
|
|
1943
|
+
preset,
|
|
1944
|
+
auth: auth2,
|
|
1945
|
+
provider,
|
|
1946
|
+
model: flags.model,
|
|
1947
|
+
apiKey: apiKey2,
|
|
1948
|
+
force: flags.force,
|
|
1949
|
+
overwrite: flags.overwrite
|
|
1950
|
+
};
|
|
1951
|
+
}
|
|
1809
1952
|
const meta = findSimplePreset(preset);
|
|
1810
1953
|
if (!meta) throw new Error(`unknown preset: ${preset}`);
|
|
1811
1954
|
const auth = flags.auth ?? await ask(
|
|
@@ -1938,7 +2081,7 @@ async function runStart(flags) {
|
|
|
1938
2081
|
}
|
|
1939
2082
|
|
|
1940
2083
|
// src/commands/status.ts
|
|
1941
|
-
import { readFileSync as
|
|
2084
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
1942
2085
|
import { dirname as dirname8 } from "path";
|
|
1943
2086
|
import chalk2 from "chalk";
|
|
1944
2087
|
async function probe(url, timeoutMs = 2e3) {
|
|
@@ -1963,7 +2106,7 @@ async function collectStatus(opts) {
|
|
|
1963
2106
|
agents: []
|
|
1964
2107
|
};
|
|
1965
2108
|
}
|
|
1966
|
-
const cfg = loadZooidConfig(
|
|
2109
|
+
const cfg = loadZooidConfig(readFileSync8(found.path, "utf8"), {
|
|
1967
2110
|
configDir: dirname8(found.path)
|
|
1968
2111
|
});
|
|
1969
2112
|
const matrixEntry = Object.entries(cfg.transports).find(
|
|
@@ -1994,7 +2137,7 @@ async function runStatus(flags) {
|
|
|
1994
2137
|
if (port === void 0) {
|
|
1995
2138
|
const found = findConfigFile(cwd);
|
|
1996
2139
|
if (found) {
|
|
1997
|
-
const cfg = loadZooidConfig(
|
|
2140
|
+
const cfg = loadZooidConfig(readFileSync8(found.path, "utf8"), {
|
|
1998
2141
|
configDir: dirname8(found.path)
|
|
1999
2142
|
});
|
|
2000
2143
|
const matrix = findMatrixTransport(cfg);
|
|
@@ -2068,7 +2211,7 @@ cli.command("status", "Print Tuwunel + daemon health").option("--data <dir>", "P
|
|
|
2068
2211
|
port: flags.port !== void 0 ? Number(flags.port) : void 0
|
|
2069
2212
|
});
|
|
2070
2213
|
});
|
|
2071
|
-
cli.command("init [dir]", "Scaffold a new zooid workforce in the current (or named) directory").option("--preset <name>", "claude | codex | opencode").option("--auth <mode>", "subscription | api-key (claude/codex only)").option("--model <id>", "Model identifier").option("--provider <id>", "opencode provider: opencode-go | opencode | anthropic | openrouter | custom").option("--api-key <value>", "API key (api-key path; opencode always)").option("--force", "Allow scaffolding into a non-empty directory").option("--overwrite", "With --force, overwrite existing files").option("--no-interactive", "Disable prompts; require all flags up front").action(async (dir, flags) => {
|
|
2214
|
+
cli.command("init [dir]", "Scaffold a new zooid workforce in the current (or named) directory").option("--preset <name>", "claude | codex | opencode | pi").option("--auth <mode>", "subscription | api-key (claude/codex/pi only)").option("--model <id>", "Model identifier").option("--provider <id>", "opencode provider: opencode-go | opencode | anthropic | openrouter | custom; pi provider: openrouter | anthropic | openai").option("--api-key <value>", "API key (api-key path; opencode always)").option("--force", "Allow scaffolding into a non-empty directory").option("--overwrite", "With --force, overwrite existing files").option("--no-interactive", "Disable prompts; require all flags up front").action(async (dir, flags) => {
|
|
2072
2215
|
const resolved = await resolveOptions({
|
|
2073
2216
|
dir: dir ?? process.cwd(),
|
|
2074
2217
|
preset: flags.preset,
|