@swarmclawai/swarmclaw 0.7.3 → 0.7.4
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 +47 -40
- package/bin/package-manager.js +157 -0
- package/bin/package-manager.test.js +90 -0
- package/bin/server-cmd.js +38 -7
- package/bin/swarmclaw.js +54 -4
- package/bin/update-cmd.js +48 -10
- package/bin/update-cmd.test.js +55 -0
- package/package.json +8 -3
- package/scripts/postinstall.mjs +26 -0
- package/src/app/api/agents/[id]/route.ts +17 -0
- package/src/app/api/agents/[id]/thread/route.ts +3 -1
- package/src/app/api/agents/route.ts +23 -1
- package/src/app/api/auth/route.ts +1 -1
- package/src/app/api/chatrooms/[id]/chat/route.ts +16 -5
- package/src/app/api/chatrooms/[id]/pins/route.ts +2 -1
- package/src/app/api/chatrooms/[id]/reactions/route.ts +2 -1
- package/src/app/api/chatrooms/[id]/route.ts +6 -0
- package/src/app/api/chats/[id]/route.ts +12 -0
- package/src/app/api/chats/heartbeat/route.ts +2 -1
- package/src/app/api/chats/route.ts +7 -1
- package/src/app/api/external-agents/[id]/heartbeat/route.ts +33 -0
- package/src/app/api/external-agents/[id]/route.ts +31 -0
- package/src/app/api/external-agents/register/route.ts +3 -0
- package/src/app/api/external-agents/route.ts +66 -0
- package/src/app/api/gateways/[id]/health/route.ts +28 -0
- package/src/app/api/gateways/[id]/route.ts +79 -0
- package/src/app/api/gateways/route.ts +57 -0
- package/src/app/api/openclaw/gateway/route.ts +10 -7
- package/src/app/api/openclaw/skills/route.ts +1 -1
- package/src/app/api/providers/[id]/discover-models/route.ts +27 -0
- package/src/app/api/schedules/[id]/route.ts +38 -9
- package/src/app/api/schedules/route.ts +51 -28
- package/src/app/api/settings/route.ts +6 -10
- package/src/app/api/setup/doctor/route.ts +6 -4
- package/src/app/api/tasks/[id]/route.ts +2 -1
- package/src/app/api/tasks/bulk/route.ts +2 -2
- package/src/app/page.tsx +126 -15
- package/src/cli/binary.test.js +142 -0
- package/src/cli/index.js +34 -11
- package/src/cli/index.test.js +195 -0
- package/src/cli/index.ts +20 -4
- package/src/cli/server-cmd.test.js +59 -0
- package/src/cli/spec.js +20 -2
- package/src/components/agents/agent-sheet.tsx +249 -7
- package/src/components/agents/inspector-panel.tsx +3 -2
- package/src/components/agents/sandbox-env-panel.tsx +4 -1
- package/src/components/auth/setup-wizard.tsx +970 -275
- package/src/components/chat/chat-area.tsx +41 -14
- package/src/components/chat/chat-card.tsx +2 -1
- package/src/components/chat/chat-header.tsx +8 -13
- package/src/components/chat/chat-list.tsx +58 -20
- package/src/components/chat/message-list.tsx +142 -18
- package/src/components/chatrooms/chatroom-input.tsx +96 -33
- package/src/components/chatrooms/chatroom-list.tsx +141 -72
- package/src/components/chatrooms/chatroom-message.tsx +7 -6
- package/src/components/chatrooms/chatroom-sheet.tsx +13 -1
- package/src/components/chatrooms/chatroom-tool-request-banner.tsx +5 -2
- package/src/components/chatrooms/chatroom-view.tsx +157 -86
- package/src/components/chatrooms/reaction-picker.tsx +38 -33
- package/src/components/gateways/gateway-sheet.tsx +567 -0
- package/src/components/input/chat-input.tsx +135 -86
- package/src/components/layout/app-layout.tsx +2 -0
- package/src/components/memory/memory-browser.tsx +71 -6
- package/src/components/memory/memory-card.tsx +18 -0
- package/src/components/memory/memory-detail.tsx +58 -31
- package/src/components/memory/memory-sheet.tsx +32 -4
- package/src/components/projects/project-detail.tsx +7 -2
- package/src/components/providers/provider-list.tsx +158 -2
- package/src/components/providers/provider-sheet.tsx +81 -70
- package/src/components/shared/bottom-sheet.tsx +31 -15
- package/src/components/shared/confirm-dialog.tsx +45 -30
- package/src/components/shared/model-combobox.tsx +90 -8
- package/src/components/shared/settings/section-heartbeat.tsx +11 -6
- package/src/components/shared/settings/section-orchestrator.tsx +3 -0
- package/src/components/shared/settings/settings-page.tsx +5 -3
- package/src/components/tasks/approvals-panel.tsx +7 -1
- package/src/components/ui/dialog.tsx +2 -2
- package/src/components/wallets/wallet-approval-dialog.tsx +59 -54
- package/src/lib/heartbeat-defaults.ts +48 -0
- package/src/lib/memory-presentation.ts +59 -0
- package/src/lib/provider-model-discovery-client.ts +29 -0
- package/src/lib/providers/index.ts +12 -5
- package/src/lib/runtime-loop.ts +105 -3
- package/src/lib/safe-storage.ts +6 -1
- package/src/lib/server/agent-runtime-config.test.ts +141 -0
- package/src/lib/server/agent-runtime-config.ts +277 -0
- package/src/lib/server/approvals-auto-approve.test.ts +59 -0
- package/src/lib/server/build-llm.test.ts +13 -5
- package/src/lib/server/chat-execution-tool-events.test.ts +87 -2
- package/src/lib/server/chat-execution.ts +159 -71
- package/src/lib/server/chatroom-helpers.test.ts +7 -0
- package/src/lib/server/chatroom-helpers.ts +99 -6
- package/src/lib/server/chatroom-session-persistence.test.ts +87 -0
- package/src/lib/server/connectors/manager.ts +89 -61
- package/src/lib/server/connectors/slack.ts +1 -1
- package/src/lib/server/daemon-state.ts +3 -2
- package/src/lib/server/eval/agent-regression.test.ts +47 -0
- package/src/lib/server/eval/agent-regression.ts +1742 -0
- package/src/lib/server/eval/runner.ts +11 -1
- package/src/lib/server/eval/store.ts +2 -1
- package/src/lib/server/heartbeat-service.ts +10 -4
- package/src/lib/server/main-agent-loop.ts +13 -6
- package/src/lib/server/openclaw-exec-config.ts +4 -2
- package/src/lib/server/openclaw-gateway.ts +123 -36
- package/src/lib/server/orchestrator-lg.ts +1 -2
- package/src/lib/server/orchestrator.ts +3 -2
- package/src/lib/server/plugins.test.ts +9 -1
- package/src/lib/server/plugins.ts +12 -2
- package/src/lib/server/provider-model-discovery.ts +481 -0
- package/src/lib/server/queue.ts +1 -1
- package/src/lib/server/runtime-settings.test.ts +119 -0
- package/src/lib/server/runtime-settings.ts +12 -92
- package/src/lib/server/schedule-normalization.ts +187 -0
- package/src/lib/server/session-tools/autonomy-tools.test.ts +23 -0
- package/src/lib/server/session-tools/crud.ts +27 -3
- package/src/lib/server/session-tools/discovery-approvals.test.ts +170 -0
- package/src/lib/server/session-tools/discovery.ts +18 -8
- package/src/lib/server/session-tools/file-normalize.test.ts +5 -0
- package/src/lib/server/session-tools/file.ts +8 -2
- package/src/lib/server/session-tools/http.ts +9 -3
- package/src/lib/server/session-tools/index.ts +31 -1
- package/src/lib/server/session-tools/manage-schedules.test.ts +137 -0
- package/src/lib/server/session-tools/monitor.ts +14 -7
- package/src/lib/server/session-tools/openclaw-nodes.test.ts +111 -0
- package/src/lib/server/session-tools/openclaw-nodes.ts +86 -20
- package/src/lib/server/session-tools/platform.ts +1 -1
- package/src/lib/server/session-tools/plugin-creator.ts +9 -2
- package/src/lib/server/session-tools/sandbox.ts +51 -92
- package/src/lib/server/session-tools/session-info.ts +22 -1
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +23 -0
- package/src/lib/server/session-tools/shell.ts +2 -2
- package/src/lib/server/session-tools/subagent.ts +3 -1
- package/src/lib/server/session-tools/web.ts +73 -30
- package/src/lib/server/storage.ts +29 -3
- package/src/lib/server/stream-agent-chat.test.ts +61 -0
- package/src/lib/server/stream-agent-chat.ts +139 -4
- package/src/lib/server/structured-extract.ts +1 -1
- package/src/lib/server/task-mention.ts +0 -1
- package/src/lib/server/tool-aliases.ts +37 -6
- package/src/lib/server/tool-capability-policy.ts +1 -1
- package/src/lib/setup-defaults.ts +352 -11
- package/src/lib/tool-definitions.ts +3 -4
- package/src/lib/validation/schemas.ts +55 -1
- package/src/stores/use-app-store.ts +43 -1
- package/src/stores/use-chatroom-store.ts +153 -26
- package/src/types/index.ts +189 -6
- package/src/app/api/chats/[id]/main-loop/route.ts +0 -13
package/README.md
CHANGED
|
@@ -37,6 +37,8 @@ SwarmClaw was built for OpenClaw users who outgrew a single agent. Connect each
|
|
|
37
37
|
|
|
38
38
|
SwarmClaw includes the `openclaw` CLI as a bundled dependency, so there is no separate OpenClaw CLI install step.
|
|
39
39
|
|
|
40
|
+
The Providers screen now supports named OpenClaw gateway profiles with discovery, health checks, default-gateway selection, and an External Agent Runtimes view for remote workers that register/heartbeat into SwarmClaw.
|
|
41
|
+
|
|
40
42
|
The OpenClaw Control Plane in SwarmClaw adds:
|
|
41
43
|
- Reload mode switching (`hot`, `hybrid`, `full`)
|
|
42
44
|
- Config issue detection and guided repair
|
|
@@ -47,13 +49,13 @@ The Agent Inspector Panel lets you edit OpenClaw files (`SOUL.md`, `IDENTITY.md`
|
|
|
47
49
|
|
|
48
50
|
To connect an agent to an OpenClaw gateway:
|
|
49
51
|
|
|
50
|
-
1.
|
|
51
|
-
2.
|
|
52
|
-
3.
|
|
53
|
-
4.
|
|
52
|
+
1. Optional: create a named gateway profile in **Providers** and mark a default
|
|
53
|
+
2. Create or edit an agent
|
|
54
|
+
3. Toggle **OpenClaw Gateway** ON
|
|
55
|
+
4. Select a saved gateway profile or enter a direct gateway URL/token override
|
|
54
56
|
5. Click **Connect** — approve the device in your gateway's dashboard if prompted, then **Retry Connection**
|
|
55
57
|
|
|
56
|
-
Each agent can point to a **different** OpenClaw gateway — one local, several remote. This is how you manage a **swarm of OpenClaws** from a single dashboard.
|
|
58
|
+
Each agent can point to a **different** OpenClaw gateway profile or direct endpoint — one local, several remote. This is how you manage a **swarm of OpenClaws** from a single dashboard.
|
|
57
59
|
|
|
58
60
|
URLs without a protocol are auto-prefixed with `http://`. For remote gateways with TLS, use `https://` explicitly.
|
|
59
61
|
|
|
@@ -77,21 +79,37 @@ Skill source and runbook: [`swarmclaw/SKILL.md`](swarmclaw/SKILL.md).
|
|
|
77
79
|
## Requirements
|
|
78
80
|
|
|
79
81
|
- **Node.js** 22.6+
|
|
80
|
-
- **
|
|
82
|
+
- **Node.js** 22.6+
|
|
83
|
+
- One of: **npm** 10+, **pnpm**, **Yarn**, or **Bun**
|
|
81
84
|
- **Claude Code CLI** (optional, for `claude-cli` provider) — [Install](https://docs.anthropic.com/en/docs/claude-code/overview)
|
|
82
85
|
- **OpenAI Codex CLI** (optional, for `codex-cli` provider) — [Install](https://github.com/openai/codex)
|
|
83
86
|
- **OpenCode CLI** (optional, for `opencode-cli` provider) — [Install](https://github.com/opencode-ai/opencode)
|
|
84
87
|
- **Gemini CLI** (optional, for `delegate` backend `gemini`) — install and authenticate `gemini` on your host
|
|
88
|
+
- **Deno** (required for `sandbox_exec`) — auto-installed by `npm run quickstart` / `npm run setup:easy` when missing
|
|
85
89
|
|
|
86
90
|
## Quick Start
|
|
87
91
|
|
|
88
|
-
|
|
92
|
+
SwarmClaw is published to the npm registry once and can be installed with `npm`, `pnpm`, `yarn`, or `bun`. There is no separate package-manager signup for end users.
|
|
93
|
+
|
|
94
|
+
### Global install
|
|
89
95
|
|
|
90
96
|
```bash
|
|
91
97
|
npm i -g @swarmclawai/swarmclaw
|
|
98
|
+
pnpm add -g @swarmclawai/swarmclaw
|
|
99
|
+
yarn global add @swarmclawai/swarmclaw
|
|
100
|
+
bun add -g @swarmclawai/swarmclaw
|
|
92
101
|
swarmclaw
|
|
93
102
|
```
|
|
94
103
|
|
|
104
|
+
### One-off run
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
npx @swarmclawai/swarmclaw
|
|
108
|
+
pnpm dlx @swarmclawai/swarmclaw
|
|
109
|
+
yarn dlx @swarmclawai/swarmclaw
|
|
110
|
+
bunx @swarmclawai/swarmclaw
|
|
111
|
+
```
|
|
112
|
+
|
|
95
113
|
### Install script
|
|
96
114
|
|
|
97
115
|
```bash
|
|
@@ -99,7 +117,7 @@ curl -fsSL https://raw.githubusercontent.com/swarmclawai/swarmclaw/main/install.
|
|
|
99
117
|
```
|
|
100
118
|
|
|
101
119
|
The installer resolves the latest stable release tag and installs that version by default.
|
|
102
|
-
To pin a version: `SWARMCLAW_VERSION=v0.7.
|
|
120
|
+
To pin a version: `SWARMCLAW_VERSION=v0.7.4 curl ... | bash`
|
|
103
121
|
|
|
104
122
|
Or run locally from the repo (friendly for non-technical users):
|
|
105
123
|
|
|
@@ -111,10 +129,19 @@ npm run quickstart
|
|
|
111
129
|
|
|
112
130
|
`npm run quickstart` will:
|
|
113
131
|
- Check Node/npm versions
|
|
132
|
+
- Install Deno if the sandbox runtime is missing
|
|
114
133
|
- Install dependencies
|
|
115
134
|
- Prepare `.env.local` and `data/`
|
|
116
135
|
- Start the app at `http://localhost:3456`
|
|
117
136
|
|
|
137
|
+
If you prefer another package manager for local development:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
pnpm install && pnpm dev
|
|
141
|
+
yarn install && yarn dev
|
|
142
|
+
bun install && bun run dev
|
|
143
|
+
```
|
|
144
|
+
|
|
118
145
|
`postinstall` rebuilds `better-sqlite3` natively. If you install with `--ignore-scripts`, run `npm rebuild better-sqlite3` manually.
|
|
119
146
|
|
|
120
147
|
On first launch, SwarmClaw will:
|
|
@@ -143,7 +170,7 @@ Notes:
|
|
|
143
170
|
- When run with no flags in a TTY, `setup init` enters interactive mode — pick providers, enter keys, name agents, and add multiple providers in one session.
|
|
144
171
|
- Use `--no-interactive` to force flag-only mode.
|
|
145
172
|
- On a fresh instance, `setup init` can auto-discover and claim the first-run access key from `/api/auth`.
|
|
146
|
-
- For existing installs, pass `--key <ACCESS_KEY>`
|
|
173
|
+
- For existing installs, pass `--key <ACCESS_KEY>` or set `SWARMCLAW_ACCESS_KEY` / `SWARMCLAW_API_KEY`.
|
|
147
174
|
- `setup init` performs provider validation, stores credentials, creates a starter agent, and marks setup complete.
|
|
148
175
|
- Use `--skip-check` to bypass connection validation.
|
|
149
176
|
|
|
@@ -164,7 +191,7 @@ Notes:
|
|
|
164
191
|
## Features
|
|
165
192
|
|
|
166
193
|
- **15 providers out of the box** - CLI providers + major hosted APIs + OpenAI-compatible custom endpoints
|
|
167
|
-
- **OpenClaw-native control plane** -
|
|
194
|
+
- **OpenClaw-native control plane** - named gateway profiles, external runtimes, reload modes, sync, and approval flows
|
|
168
195
|
- **Agent builder + inspector** - personality/system tuning, skill management, and OpenClaw file editing
|
|
169
196
|
- **Rich toolset** - shell, files, browser, git, sandbox execution, memory, MCP, and delegation
|
|
170
197
|
- **Platform automation** - agents can manage tasks, schedules, chats, connectors, secrets, and more
|
|
@@ -289,6 +316,7 @@ Connector ingress now also supports optional pairing/allowlist policy:
|
|
|
289
316
|
- `dmPolicy: allowlist` blocks unknown senders until approved
|
|
290
317
|
- `/pair` flow lets approved admins generate and approve pairing codes
|
|
291
318
|
- `/think` command can set connector thread thinking level (`low`, `medium`, `high`)
|
|
319
|
+
- Session overrides also support per-thread `/reply`, `/scope`, `/thread`, `/provider`, `/model`, `/idle`, `/maxage`, and `/reset` controls
|
|
292
320
|
|
|
293
321
|
## Agent Tools
|
|
294
322
|
|
|
@@ -315,7 +343,7 @@ Agents can use the following tools when enabled:
|
|
|
315
343
|
| Image Generation | Generate images from prompts (`generate_image`) via OpenAI, Stability, Replicate, fal.ai, Together, Fireworks, BFL, or custom endpoints; saved to uploads |
|
|
316
344
|
| Email | Send outbound email via SMTP (`email`) with `send`/`status` actions |
|
|
317
345
|
| Calendar | Manage Google/Outlook events (`calendar`) with list/create/update/delete/status actions |
|
|
318
|
-
| Sandbox | Run JS/TS
|
|
346
|
+
| Sandbox | Run JS/TS in a Deno sandbox when custom code is necessary. If Deno is unavailable it fails closed with guidance; for simple API calls, prefer HTTP Request. |
|
|
319
347
|
| MCP Servers | Connect to external Model Context Protocol servers. Tools from MCP servers are injected as first-class agent tools |
|
|
320
348
|
|
|
321
349
|
### Platform Tools
|
|
@@ -370,21 +398,6 @@ Daemon runtime also triggers memory consolidation (daily summary generation plus
|
|
|
370
398
|
- **API:** `GET /api/daemon` (status), `POST /api/daemon` with `{"action": "start"}` or `{"action": "stop"}`
|
|
371
399
|
- Auto-starts on first authenticated runtime traffic (`/api/auth` or `/api/daemon`) unless `SWARMCLAW_DAEMON_AUTOSTART=0`
|
|
372
400
|
|
|
373
|
-
## Main Agent Loop
|
|
374
|
-
|
|
375
|
-
For autonomous long-running missions, enable the **Main Loop** on an agent-thread or orchestrated chat. This lets an agent pursue a goal continuously with heartbeat-driven progress checks and automatic followups.
|
|
376
|
-
|
|
377
|
-
- **Heartbeat prompts:** `SWARM_MAIN_MISSION_TICK` triggers on each heartbeat, giving the agent its goal, status, and pending events
|
|
378
|
-
- **Auto-followup:** When an agent returns `[MAIN_LOOP_META] {"follow_up":true}`, the loop schedules another tick after `delay_sec`
|
|
379
|
-
- **Mission state:** Tracks `goal`, `status` (idle/progress/blocked/ok), `summary`, `nextAction`, `autonomyMode` (assist/autonomous), and pending events
|
|
380
|
-
- **Autonomy modes:**
|
|
381
|
-
- `autonomous`: Agent executes safe actions without confirmation, only asks when blocked by permissions/credentials
|
|
382
|
-
- `assist`: Agent asks before irreversible external actions (sending messages, purchases, account mutations)
|
|
383
|
-
- **API:** `POST /api/chats/[id]/main-loop` with `{"tick":true}` to trigger a mission tick
|
|
384
|
-
- **CLI:** `swarmclaw chats main-loop <id>` to inspect loop state, or `swarmclaw chats main-loop-action <id> --data '{"action":"nudge"}'` to control it
|
|
385
|
-
|
|
386
|
-
Use this for background agents that should "keep working" on a goal until blocked or complete.
|
|
387
|
-
|
|
388
401
|
## Loop Modes
|
|
389
402
|
|
|
390
403
|
Configure loop behavior in **Settings → Runtime & Loop Controls**:
|
|
@@ -657,7 +670,7 @@ npm run update:easy # safe update helper for local installs
|
|
|
657
670
|
SwarmClaw uses tag-based releases (`vX.Y.Z`) as the stable channel.
|
|
658
671
|
|
|
659
672
|
```bash
|
|
660
|
-
# example patch release (v0.7.
|
|
673
|
+
# example patch release (v0.7.4 style)
|
|
661
674
|
npm version patch
|
|
662
675
|
git push origin main --follow-tags
|
|
663
676
|
```
|
|
@@ -667,18 +680,15 @@ On `v*` tags, GitHub Actions will:
|
|
|
667
680
|
2. Create a GitHub Release
|
|
668
681
|
3. Build and publish Docker images to `ghcr.io/swarmclawai/swarmclaw` (`:vX.Y.Z`, `:latest`, `:sha-*`)
|
|
669
682
|
|
|
670
|
-
#### v0.7.
|
|
683
|
+
#### v0.7.4 Release Readiness Notes
|
|
671
684
|
|
|
672
|
-
Before shipping `v0.7.
|
|
685
|
+
Before shipping `v0.7.4`, confirm the following user-facing changes are reflected in docs:
|
|
673
686
|
|
|
674
|
-
1.
|
|
675
|
-
2.
|
|
676
|
-
3.
|
|
677
|
-
4.
|
|
678
|
-
5.
|
|
679
|
-
6. The plugin tutorial reflects the current hook behavior, including `beforeToolExec` input rewriting and `settingsFields`.
|
|
680
|
-
7. Site install/version strings are updated to `v0.7.3`, including the release notes index, install snippets, and sidebar footer.
|
|
681
|
-
8. Provider docs mention that configured OpenAI models can populate the model dropdown while still allowing custom/manual entries.
|
|
687
|
+
1. Sandbox docs are updated everywhere to reflect the current Deno-only `sandbox_exec` behavior and the guidance to prefer `http_request` for simple API calls.
|
|
688
|
+
2. OpenClaw docs cover the current gateway/runtime behavior, including per-agent gateway routing, control-plane actions, and inspector-side advanced controls.
|
|
689
|
+
3. Site and README install/version strings are updated to `v0.7.4`, including install snippets, release notes index text, and sidebar/footer labels.
|
|
690
|
+
4. Release notes summarize the user-visible setup/auth/runtime changes from the current worktree, especially gateway/external-agent/setup flow improvements.
|
|
691
|
+
5. CLI and tool docs do not reference removed or non-functional surfaces such as the old `openclaw_sandbox` bridge.
|
|
682
692
|
|
|
683
693
|
## CLI
|
|
684
694
|
|
|
@@ -713,9 +723,6 @@ swarmclaw agents list
|
|
|
713
723
|
swarmclaw chats create --name "Main Ops" --agent-id <agentId>
|
|
714
724
|
swarmclaw chats list
|
|
715
725
|
|
|
716
|
-
# run a main loop action
|
|
717
|
-
swarmclaw chats main-loop-action <chatId> --data '{"action":"nudge"}'
|
|
718
|
-
|
|
719
726
|
# run setup diagnostics
|
|
720
727
|
swarmclaw setup doctor
|
|
721
728
|
```
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
3
|
+
|
|
4
|
+
const fs = require('node:fs')
|
|
5
|
+
const path = require('node:path')
|
|
6
|
+
|
|
7
|
+
const LOCKFILE_NAMES = [
|
|
8
|
+
'package-lock.json',
|
|
9
|
+
'pnpm-lock.yaml',
|
|
10
|
+
'yarn.lock',
|
|
11
|
+
'bun.lock',
|
|
12
|
+
'bun.lockb',
|
|
13
|
+
]
|
|
14
|
+
const INSTALL_METADATA_FILE = '.swarmclaw-install.json'
|
|
15
|
+
|
|
16
|
+
function normalizePackageManager(raw) {
|
|
17
|
+
switch (String(raw || '').trim().toLowerCase()) {
|
|
18
|
+
case 'pnpm':
|
|
19
|
+
case 'yarn':
|
|
20
|
+
case 'bun':
|
|
21
|
+
case 'npm':
|
|
22
|
+
return String(raw).trim().toLowerCase()
|
|
23
|
+
default:
|
|
24
|
+
return null
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function detectPackageManagerFromUserAgent(userAgent) {
|
|
29
|
+
const normalized = String(userAgent || '').toLowerCase()
|
|
30
|
+
if (normalized.startsWith('pnpm/')) return 'pnpm'
|
|
31
|
+
if (normalized.startsWith('yarn/')) return 'yarn'
|
|
32
|
+
if (normalized.startsWith('bun/')) return 'bun'
|
|
33
|
+
if (normalized.startsWith('npm/')) return 'npm'
|
|
34
|
+
return null
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function readInstallMetadata(rootDir) {
|
|
38
|
+
const metadataPath = path.join(rootDir, INSTALL_METADATA_FILE)
|
|
39
|
+
if (!fs.existsSync(metadataPath)) return null
|
|
40
|
+
try {
|
|
41
|
+
const raw = JSON.parse(fs.readFileSync(metadataPath, 'utf8'))
|
|
42
|
+
return raw && typeof raw === 'object' ? raw : null
|
|
43
|
+
} catch {
|
|
44
|
+
return null
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function detectPackageManager(rootDir, env = process.env) {
|
|
49
|
+
const envOverride = normalizePackageManager(env.SWARMCLAW_PACKAGE_MANAGER)
|
|
50
|
+
if (envOverride) return envOverride
|
|
51
|
+
|
|
52
|
+
const installMetadata = readInstallMetadata(rootDir)
|
|
53
|
+
const installManager = normalizePackageManager(installMetadata?.packageManager)
|
|
54
|
+
if (installManager) return installManager
|
|
55
|
+
|
|
56
|
+
if (fs.existsSync(path.join(rootDir, 'bun.lock')) || fs.existsSync(path.join(rootDir, 'bun.lockb'))) return 'bun'
|
|
57
|
+
if (fs.existsSync(path.join(rootDir, 'pnpm-lock.yaml'))) return 'pnpm'
|
|
58
|
+
if (fs.existsSync(path.join(rootDir, 'yarn.lock'))) return 'yarn'
|
|
59
|
+
if (fs.existsSync(path.join(rootDir, 'package-lock.json'))) return 'npm'
|
|
60
|
+
|
|
61
|
+
const userAgentManager = detectPackageManagerFromUserAgent(env.npm_config_user_agent)
|
|
62
|
+
if (userAgentManager) return userAgentManager
|
|
63
|
+
return 'npm'
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function getInstallCommand(packageManager, omitDev = false) {
|
|
67
|
+
switch (packageManager) {
|
|
68
|
+
case 'pnpm':
|
|
69
|
+
return omitDev
|
|
70
|
+
? { command: 'pnpm', args: ['install', '--prod'] }
|
|
71
|
+
: { command: 'pnpm', args: ['install'] }
|
|
72
|
+
case 'yarn':
|
|
73
|
+
return omitDev
|
|
74
|
+
? { command: 'yarn', args: ['install', '--production=true'] }
|
|
75
|
+
: { command: 'yarn', args: ['install'] }
|
|
76
|
+
case 'bun':
|
|
77
|
+
return omitDev
|
|
78
|
+
? { command: 'bun', args: ['install', '--production'] }
|
|
79
|
+
: { command: 'bun', args: ['install'] }
|
|
80
|
+
case 'npm':
|
|
81
|
+
default:
|
|
82
|
+
return omitDev
|
|
83
|
+
? { command: 'npm', args: ['install', '--omit=dev'] }
|
|
84
|
+
: { command: 'npm', args: ['install'] }
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function getGlobalUpdateCommand(packageManager, packageName) {
|
|
89
|
+
return getGlobalUpdateSpec(packageManager, packageName).display
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function getGlobalUpdateSpec(packageManager, packageName) {
|
|
93
|
+
switch (packageManager) {
|
|
94
|
+
case 'pnpm':
|
|
95
|
+
return {
|
|
96
|
+
command: 'pnpm',
|
|
97
|
+
args: ['add', '-g', `${packageName}@latest`],
|
|
98
|
+
display: `pnpm add -g ${packageName}@latest`,
|
|
99
|
+
}
|
|
100
|
+
case 'yarn':
|
|
101
|
+
return {
|
|
102
|
+
command: 'yarn',
|
|
103
|
+
args: ['global', 'add', `${packageName}@latest`],
|
|
104
|
+
display: `yarn global add ${packageName}@latest`,
|
|
105
|
+
}
|
|
106
|
+
case 'bun':
|
|
107
|
+
return {
|
|
108
|
+
command: 'bun',
|
|
109
|
+
args: ['add', '-g', `${packageName}@latest`],
|
|
110
|
+
display: `bun add -g ${packageName}@latest`,
|
|
111
|
+
}
|
|
112
|
+
case 'npm':
|
|
113
|
+
default:
|
|
114
|
+
return {
|
|
115
|
+
command: 'npm',
|
|
116
|
+
args: ['update', '-g', packageName],
|
|
117
|
+
display: `npm update -g ${packageName}`,
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function getRunScriptCommand(packageManager, scriptName) {
|
|
123
|
+
switch (packageManager) {
|
|
124
|
+
case 'pnpm':
|
|
125
|
+
return { command: 'pnpm', args: [scriptName] }
|
|
126
|
+
case 'yarn':
|
|
127
|
+
return { command: 'yarn', args: [scriptName] }
|
|
128
|
+
case 'bun':
|
|
129
|
+
return { command: 'bun', args: ['run', scriptName] }
|
|
130
|
+
case 'npm':
|
|
131
|
+
default:
|
|
132
|
+
return { command: 'npm', args: ['run', scriptName] }
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function dependenciesChanged(diffText) {
|
|
137
|
+
if (!diffText) return false
|
|
138
|
+
return String(diffText)
|
|
139
|
+
.split('\n')
|
|
140
|
+
.map((line) => line.trim())
|
|
141
|
+
.filter(Boolean)
|
|
142
|
+
.some((file) => file === 'package.json' || LOCKFILE_NAMES.includes(file))
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
module.exports = {
|
|
146
|
+
dependenciesChanged,
|
|
147
|
+
detectPackageManager,
|
|
148
|
+
detectPackageManagerFromUserAgent,
|
|
149
|
+
getGlobalUpdateCommand,
|
|
150
|
+
getGlobalUpdateSpec,
|
|
151
|
+
getInstallCommand,
|
|
152
|
+
getRunScriptCommand,
|
|
153
|
+
INSTALL_METADATA_FILE,
|
|
154
|
+
LOCKFILE_NAMES,
|
|
155
|
+
normalizePackageManager,
|
|
156
|
+
readInstallMetadata,
|
|
157
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
3
|
+
|
|
4
|
+
const test = require('node:test')
|
|
5
|
+
const assert = require('node:assert/strict')
|
|
6
|
+
const fs = require('node:fs')
|
|
7
|
+
const os = require('node:os')
|
|
8
|
+
const path = require('node:path')
|
|
9
|
+
|
|
10
|
+
const {
|
|
11
|
+
INSTALL_METADATA_FILE,
|
|
12
|
+
LOCKFILE_NAMES,
|
|
13
|
+
dependenciesChanged,
|
|
14
|
+
detectPackageManager,
|
|
15
|
+
detectPackageManagerFromUserAgent,
|
|
16
|
+
getGlobalUpdateSpec,
|
|
17
|
+
getInstallCommand,
|
|
18
|
+
getRunScriptCommand,
|
|
19
|
+
} = require('./package-manager.js')
|
|
20
|
+
|
|
21
|
+
test('detectPackageManagerFromUserAgent parses supported package managers', () => {
|
|
22
|
+
assert.equal(detectPackageManagerFromUserAgent('pnpm/10.6.1 npm/? node/v22.6.0 darwin arm64'), 'pnpm')
|
|
23
|
+
assert.equal(detectPackageManagerFromUserAgent('yarn/4.7.0 npm/? node/v22.6.0 darwin arm64'), 'yarn')
|
|
24
|
+
assert.equal(detectPackageManagerFromUserAgent('bun/1.2.10 npm/? node/v22.6.0 darwin arm64'), 'bun')
|
|
25
|
+
assert.equal(detectPackageManagerFromUserAgent('npm/10.9.2 node/v22.6.0 darwin arm64'), 'npm')
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('detectPackageManager prefers the lockfile present in the workspace', () => {
|
|
29
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-pm-'))
|
|
30
|
+
|
|
31
|
+
fs.writeFileSync(path.join(tmpDir, 'pnpm-lock.yaml'), 'lock', 'utf8')
|
|
32
|
+
assert.equal(detectPackageManager(tmpDir), 'pnpm')
|
|
33
|
+
|
|
34
|
+
fs.writeFileSync(path.join(tmpDir, 'bun.lockb'), 'lock', 'utf8')
|
|
35
|
+
assert.equal(detectPackageManager(tmpDir), 'bun')
|
|
36
|
+
|
|
37
|
+
fs.rmSync(tmpDir, { recursive: true, force: true })
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test('detectPackageManager falls back to npm when no lockfile exists', () => {
|
|
41
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-pm-empty-'))
|
|
42
|
+
assert.equal(detectPackageManager(tmpDir), 'npm')
|
|
43
|
+
fs.rmSync(tmpDir, { recursive: true, force: true })
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test('detectPackageManager uses install metadata when present', () => {
|
|
47
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-pm-meta-'))
|
|
48
|
+
fs.writeFileSync(
|
|
49
|
+
path.join(tmpDir, INSTALL_METADATA_FILE),
|
|
50
|
+
JSON.stringify({ packageManager: 'yarn' }),
|
|
51
|
+
'utf8',
|
|
52
|
+
)
|
|
53
|
+
assert.equal(detectPackageManager(tmpDir), 'yarn')
|
|
54
|
+
fs.rmSync(tmpDir, { recursive: true, force: true })
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
test('dependenciesChanged recognizes package.json and all supported lockfiles', () => {
|
|
58
|
+
assert.equal(dependenciesChanged('package.json\nsrc/app.ts'), true)
|
|
59
|
+
for (const lockfile of LOCKFILE_NAMES) {
|
|
60
|
+
assert.equal(dependenciesChanged(`${lockfile}\nREADME.md`), true)
|
|
61
|
+
}
|
|
62
|
+
assert.equal(dependenciesChanged('README.md\nsrc/index.ts'), false)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test('getInstallCommand returns manager-specific install arguments', () => {
|
|
66
|
+
assert.deepEqual(getInstallCommand('npm', true), { command: 'npm', args: ['install', '--omit=dev'] })
|
|
67
|
+
assert.deepEqual(getInstallCommand('pnpm', false), { command: 'pnpm', args: ['install'] })
|
|
68
|
+
assert.deepEqual(getInstallCommand('yarn', true), { command: 'yarn', args: ['install', '--production=true'] })
|
|
69
|
+
assert.deepEqual(getInstallCommand('bun', true), { command: 'bun', args: ['install', '--production'] })
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test('getRunScriptCommand returns manager-specific script launchers', () => {
|
|
73
|
+
assert.deepEqual(getRunScriptCommand('npm', 'build'), { command: 'npm', args: ['run', 'build'] })
|
|
74
|
+
assert.deepEqual(getRunScriptCommand('pnpm', 'start'), { command: 'pnpm', args: ['start'] })
|
|
75
|
+
assert.deepEqual(getRunScriptCommand('yarn', 'dev'), { command: 'yarn', args: ['dev'] })
|
|
76
|
+
assert.deepEqual(getRunScriptCommand('bun', 'start'), { command: 'bun', args: ['run', 'start'] })
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test('getGlobalUpdateSpec returns manager-specific update commands', () => {
|
|
80
|
+
assert.deepEqual(getGlobalUpdateSpec('npm', '@swarmclawai/swarmclaw'), {
|
|
81
|
+
command: 'npm',
|
|
82
|
+
args: ['update', '-g', '@swarmclawai/swarmclaw'],
|
|
83
|
+
display: 'npm update -g @swarmclawai/swarmclaw',
|
|
84
|
+
})
|
|
85
|
+
assert.deepEqual(getGlobalUpdateSpec('pnpm', '@swarmclawai/swarmclaw'), {
|
|
86
|
+
command: 'pnpm',
|
|
87
|
+
args: ['add', '-g', '@swarmclawai/swarmclaw@latest'],
|
|
88
|
+
display: 'pnpm add -g @swarmclawai/swarmclaw@latest',
|
|
89
|
+
})
|
|
90
|
+
})
|
package/bin/server-cmd.js
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict'
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
3
4
|
|
|
4
5
|
const fs = require('node:fs')
|
|
5
6
|
const path = require('node:path')
|
|
6
|
-
const { spawn,
|
|
7
|
+
const { spawn, execFileSync } = require('node:child_process')
|
|
7
8
|
const os = require('node:os')
|
|
9
|
+
const {
|
|
10
|
+
LOCKFILE_NAMES,
|
|
11
|
+
detectPackageManager,
|
|
12
|
+
getInstallCommand,
|
|
13
|
+
} = require('./package-manager.js')
|
|
8
14
|
|
|
9
15
|
// ---------------------------------------------------------------------------
|
|
10
16
|
// Paths
|
|
@@ -26,7 +32,7 @@ const BUILD_COPY_ENTRIES = [
|
|
|
26
32
|
'tsconfig.json',
|
|
27
33
|
'postcss.config.mjs',
|
|
28
34
|
'package.json',
|
|
29
|
-
|
|
35
|
+
...LOCKFILE_NAMES,
|
|
30
36
|
]
|
|
31
37
|
|
|
32
38
|
// ---------------------------------------------------------------------------
|
|
@@ -73,13 +79,26 @@ function symlinkPath(src, dest) {
|
|
|
73
79
|
fs.symlinkSync(src, dest)
|
|
74
80
|
}
|
|
75
81
|
|
|
82
|
+
function readBuiltInfo() {
|
|
83
|
+
if (!fs.existsSync(BUILT_MARKER)) return null
|
|
84
|
+
try {
|
|
85
|
+
const raw = JSON.parse(fs.readFileSync(BUILT_MARKER, 'utf8'))
|
|
86
|
+
return raw && typeof raw === 'object' ? raw : null
|
|
87
|
+
} catch {
|
|
88
|
+
return null
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
76
92
|
// ---------------------------------------------------------------------------
|
|
77
93
|
// Build
|
|
78
94
|
// ---------------------------------------------------------------------------
|
|
79
95
|
|
|
80
96
|
function needsBuild(forceBuild) {
|
|
81
97
|
if (forceBuild) return true
|
|
82
|
-
|
|
98
|
+
const info = readBuiltInfo()
|
|
99
|
+
if (!info) return true
|
|
100
|
+
if (info.version !== getVersion()) return true
|
|
101
|
+
if (!findStandaloneServer()) return true
|
|
83
102
|
return false
|
|
84
103
|
}
|
|
85
104
|
|
|
@@ -110,15 +129,18 @@ function runBuild() {
|
|
|
110
129
|
symlinkPath(nmSrc, nmDest)
|
|
111
130
|
} else {
|
|
112
131
|
// If node_modules doesn't exist at PKG_ROOT, install
|
|
113
|
-
|
|
114
|
-
|
|
132
|
+
const packageManager = detectPackageManager(SWARMCLAW_HOME, process.env)
|
|
133
|
+
const install = getInstallCommand(packageManager)
|
|
134
|
+
log(`Installing dependencies with ${packageManager}...`)
|
|
135
|
+
execFileSync(install.command, install.args, { cwd: SWARMCLAW_HOME, stdio: 'inherit' })
|
|
115
136
|
}
|
|
116
137
|
|
|
117
138
|
// Run Next.js build
|
|
118
139
|
log('Building Next.js application (this may take a minute)...')
|
|
119
140
|
// Use webpack for production build reliability in packaged/fresh-install
|
|
120
141
|
// environments (Turbopack has intermittently failed during prerender).
|
|
121
|
-
|
|
142
|
+
const nextCli = path.join(SWARMCLAW_HOME, 'node_modules', 'next', 'dist', 'bin', 'next')
|
|
143
|
+
execFileSync(process.execPath, [nextCli, 'build', '--webpack'], {
|
|
122
144
|
cwd: SWARMCLAW_HOME,
|
|
123
145
|
stdio: 'inherit',
|
|
124
146
|
env: {
|
|
@@ -373,4 +395,13 @@ function main() {
|
|
|
373
395
|
startServer({ port, wsPort, host, detach })
|
|
374
396
|
}
|
|
375
397
|
|
|
376
|
-
main
|
|
398
|
+
if (require.main === module) {
|
|
399
|
+
main()
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
module.exports = {
|
|
403
|
+
getVersion,
|
|
404
|
+
main,
|
|
405
|
+
needsBuild,
|
|
406
|
+
readBuiltInfo,
|
|
407
|
+
}
|
package/bin/swarmclaw.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict'
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
3
4
|
|
|
4
5
|
const path = require('node:path')
|
|
5
6
|
const { spawnSync } = require('node:child_process')
|
|
@@ -34,12 +35,45 @@ function shouldUseLegacyTsCli(argv) {
|
|
|
34
35
|
return actions.has(action)
|
|
35
36
|
}
|
|
36
37
|
|
|
38
|
+
function supportsStripTypes() {
|
|
39
|
+
return process.allowedNodeEnvironmentFlags.has('--experimental-strip-types')
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function hasTsxRuntime() {
|
|
43
|
+
try {
|
|
44
|
+
require.resolve('tsx/package.json')
|
|
45
|
+
return true
|
|
46
|
+
} catch {
|
|
47
|
+
return false
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function buildLegacyTsCliArgs(cliPath, argv, options = {}) {
|
|
52
|
+
const stripTypesSupported = options.supportsStripTypes ?? supportsStripTypes()
|
|
53
|
+
if (stripTypesSupported) {
|
|
54
|
+
return ['--no-warnings', '--experimental-strip-types', cliPath, ...argv]
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const tsxAvailable = options.hasTsxRuntime ?? hasTsxRuntime()
|
|
58
|
+
if (tsxAvailable) {
|
|
59
|
+
return ['--no-warnings', '--import', 'tsx', cliPath, ...argv]
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return null
|
|
63
|
+
}
|
|
64
|
+
|
|
37
65
|
function runLegacyTsCli(argv) {
|
|
38
66
|
const cliPath = path.join(__dirname, '..', 'src', 'cli', 'index.ts')
|
|
67
|
+
const args = buildLegacyTsCliArgs(cliPath, argv)
|
|
68
|
+
const env = normalizeLegacyCliEnv(process.env)
|
|
69
|
+
if (!args) {
|
|
70
|
+
process.stderr.write('Legacy CLI commands require Node 22.6+ or an available local tsx runtime.\n')
|
|
71
|
+
return 1
|
|
72
|
+
}
|
|
39
73
|
const child = spawnSync(
|
|
40
74
|
process.execPath,
|
|
41
|
-
|
|
42
|
-
{ stdio: 'inherit' },
|
|
75
|
+
args,
|
|
76
|
+
{ stdio: 'inherit', env },
|
|
43
77
|
)
|
|
44
78
|
|
|
45
79
|
if (child.error) {
|
|
@@ -50,6 +84,18 @@ function runLegacyTsCli(argv) {
|
|
|
50
84
|
return 1
|
|
51
85
|
}
|
|
52
86
|
|
|
87
|
+
function normalizeLegacyCliEnv(env) {
|
|
88
|
+
const nextEnv = { ...env }
|
|
89
|
+
if (!nextEnv.SWARMCLAW_URL && nextEnv.SWARMCLAW_BASE_URL) {
|
|
90
|
+
nextEnv.SWARMCLAW_URL = nextEnv.SWARMCLAW_BASE_URL
|
|
91
|
+
}
|
|
92
|
+
if (!nextEnv.SWARMCLAW_ACCESS_KEY) {
|
|
93
|
+
const key = nextEnv.SWARMCLAW_API_KEY || nextEnv.SC_ACCESS_KEY || ''
|
|
94
|
+
if (key) nextEnv.SWARMCLAW_ACCESS_KEY = key
|
|
95
|
+
}
|
|
96
|
+
return nextEnv
|
|
97
|
+
}
|
|
98
|
+
|
|
53
99
|
async function runMappedCli(argv) {
|
|
54
100
|
const cliPath = path.join(__dirname, '..', 'src', 'cli', 'index.js')
|
|
55
101
|
const cliModule = await import(cliPath)
|
|
@@ -66,11 +112,11 @@ async function main() {
|
|
|
66
112
|
|
|
67
113
|
// Route 'server' and 'update' subcommands to CJS scripts (no TS dependency).
|
|
68
114
|
if (top === 'server') {
|
|
69
|
-
require('./server-cmd.js')
|
|
115
|
+
require('./server-cmd.js').main()
|
|
70
116
|
return
|
|
71
117
|
}
|
|
72
118
|
if (top === 'update') {
|
|
73
|
-
require('./update-cmd.js')
|
|
119
|
+
require('./update-cmd.js').main()
|
|
74
120
|
return
|
|
75
121
|
}
|
|
76
122
|
|
|
@@ -86,6 +132,10 @@ if (require.main === module) {
|
|
|
86
132
|
}
|
|
87
133
|
|
|
88
134
|
module.exports = {
|
|
135
|
+
buildLegacyTsCliArgs,
|
|
136
|
+
hasTsxRuntime,
|
|
89
137
|
TS_CLI_ACTIONS,
|
|
138
|
+
normalizeLegacyCliEnv,
|
|
139
|
+
supportsStripTypes,
|
|
90
140
|
shouldUseLegacyTsCli,
|
|
91
141
|
}
|