svamp-cli 0.1.52 → 0.1.53
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 +193 -0
- package/bin/svamp.mjs +16 -7
- package/dist/cli.mjs +19 -19
- package/dist/commands-BJdwoEe6.mjs +1745 -0
- package/dist/commands-VGt5ofDo.mjs +1755 -0
- package/dist/commands-ZuFXrcot.mjs +558 -0
- package/dist/index.mjs +1 -1
- package/dist/package-DbeynOln.mjs +60 -0
- package/dist/run-CBhm4Jop.mjs +6412 -0
- package/dist/run-Y0b60UYS.mjs +1051 -0
- package/dist/tunnel-DhVAOdGd.mjs +299 -0
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# svamp-cli
|
|
2
|
+
|
|
3
|
+
AI workspace daemon and CLI for [Hypha Cloud](https://hypha.aicell.io). Run AI agents locally with cloud sync, manage sessions, share with teammates, and orchestrate tasks.
|
|
4
|
+
|
|
5
|
+
**Svamp** (Swedish for "mushroom") is the interactive layer of Hypha Cloud — where teams and AI agents collaborate in real time.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g svamp-cli
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Requires **Node.js >= 22** (for native WebSocket support).
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Login to Hypha Cloud
|
|
19
|
+
svamp login
|
|
20
|
+
|
|
21
|
+
# Start interactive Claude session (synced to web app)
|
|
22
|
+
svamp
|
|
23
|
+
|
|
24
|
+
# Or start the daemon for background sessions
|
|
25
|
+
svamp daemon start
|
|
26
|
+
|
|
27
|
+
# Spawn a session
|
|
28
|
+
svamp session spawn claude -d ~/my-project
|
|
29
|
+
|
|
30
|
+
# Send a message
|
|
31
|
+
svamp session send <session-id> "Fix the failing tests"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Configuration
|
|
35
|
+
|
|
36
|
+
Credentials are stored in `~/.svamp/.env`:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
HYPHA_SERVER_URL=https://hypha.aicell.io
|
|
40
|
+
HYPHA_TOKEN=<your-token>
|
|
41
|
+
HYPHA_WORKSPACE=<your-workspace>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Commands
|
|
45
|
+
|
|
46
|
+
### Interactive Mode
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
svamp # Start Claude in terminal with cloud sync
|
|
50
|
+
svamp start [-d <path>] # Same, with explicit directory
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
When you run `svamp` with no arguments, Claude starts in your terminal with full interactive access. Your session is synced to Hypha Cloud and visible in the web app. When a message arrives from the web app, svamp switches to remote mode automatically. Press Space-Space to return to local mode.
|
|
54
|
+
|
|
55
|
+
### Login
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
svamp login [server-url] # Login via browser OAuth
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Daemon Management
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
svamp daemon start # Start daemon (detached)
|
|
65
|
+
svamp daemon stop # Stop (sessions preserved for auto-restore)
|
|
66
|
+
svamp daemon stop --cleanup # Stop and mark all sessions as stopped
|
|
67
|
+
svamp daemon restart # Restart seamlessly
|
|
68
|
+
svamp daemon status # Show daemon status
|
|
69
|
+
svamp daemon install # Install as system service (launchd/systemd)
|
|
70
|
+
svamp daemon uninstall # Remove system service
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Session Management
|
|
74
|
+
|
|
75
|
+
All session commands support `--machine <id>` / `-m <id>` to target a specific machine.
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
svamp session list [--active] [--json]
|
|
79
|
+
svamp session machines # List discoverable machines
|
|
80
|
+
svamp session spawn <agent> [-d <path>] [--message <msg>] [--wait]
|
|
81
|
+
svamp session stop <id>
|
|
82
|
+
svamp session info <id> [--json]
|
|
83
|
+
svamp session send <id> <message> [--wait] [--timeout N]
|
|
84
|
+
svamp session wait <id> [--timeout N]
|
|
85
|
+
svamp session messages <id> [--last N] [--json]
|
|
86
|
+
svamp session attach <id> # Interactive terminal attach
|
|
87
|
+
svamp session approve <id> # Approve pending permission
|
|
88
|
+
svamp session deny <id> # Deny pending permission
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
#### Session Sharing
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
svamp session share <id> --list
|
|
95
|
+
svamp session share <id> --add <email>[:<role>] # Roles: view, interact, admin
|
|
96
|
+
svamp session share <id> --remove <email>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
#### Isolation & Security Flags (on spawn)
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
svamp session spawn claude -d <path> --isolate
|
|
103
|
+
svamp session spawn claude -d <path> --share alice@example.com:admin
|
|
104
|
+
svamp session spawn claude -d <path> --security-context ./context.json
|
|
105
|
+
svamp session spawn claude -d <path> --deny-network
|
|
106
|
+
svamp session spawn claude -d <path> --deny-read /etc --allow-write /tmp/work
|
|
107
|
+
svamp session spawn claude -d <path> --allow-domain api.anthropic.com
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
#### Ralph Loop (Iterative Task Automation)
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
svamp session ralph-start <id> "<task>" [--promise DONE] [--max 10] [--cooldown 1]
|
|
114
|
+
svamp session ralph-cancel <id>
|
|
115
|
+
svamp session ralph-status <id>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The Ralph Loop enables agents to iterate on tasks with verifiable completion. Each iteration the agent works toward a goal and signals completion via `<promise>DONE</promise>`.
|
|
119
|
+
|
|
120
|
+
### Machine Management
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
svamp machine share --list
|
|
124
|
+
svamp machine share --add <email>[:<role>]
|
|
125
|
+
svamp machine share --remove <email>
|
|
126
|
+
svamp machine share --config <path> # Apply security context config
|
|
127
|
+
svamp machine share --show-config
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Skills Marketplace
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
svamp skills find <query> [--json] # Search marketplace
|
|
134
|
+
svamp skills install <name> [--force] # Install to ~/.claude/skills/<name>/
|
|
135
|
+
svamp skills list # List installed skills
|
|
136
|
+
svamp skills remove <name> # Remove skill
|
|
137
|
+
svamp skills publish <path> # Publish to marketplace
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Service Exposure (Cloud HTTP Services)
|
|
141
|
+
|
|
142
|
+
Expose HTTP services from cloud sandboxes or local machines to stable external URLs.
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
svamp service expose <name> --port <port> # Create + join (auto-detect cloud/tunnel)
|
|
146
|
+
svamp service create <name> --port <port>
|
|
147
|
+
svamp service list [--json]
|
|
148
|
+
svamp service info <name> [--json]
|
|
149
|
+
svamp service delete <name>
|
|
150
|
+
svamp service tunnel <name> --port <port> # Tunnel local ports
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Local Agent Sessions
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
svamp agent list # List known agents (ACP + MCP)
|
|
157
|
+
svamp agent <name> # Start local agent session (gemini, codex)
|
|
158
|
+
svamp agent -- <cmd> [args] # Start custom ACP agent
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Agent Backends
|
|
162
|
+
|
|
163
|
+
| Agent | Protocol | Transport |
|
|
164
|
+
|-------|----------|-----------|
|
|
165
|
+
| Claude | Native | CLI subprocess |
|
|
166
|
+
| Codex | MCP | STDIO (`codex mcp-server`) |
|
|
167
|
+
| Gemini | ACP | STDIO (`gemini --experimental-acp`) |
|
|
168
|
+
|
|
169
|
+
## Security & Isolation
|
|
170
|
+
|
|
171
|
+
Sessions can be isolated using OS-level sandboxes:
|
|
172
|
+
|
|
173
|
+
| Method | Platform | Description |
|
|
174
|
+
|--------|----------|-------------|
|
|
175
|
+
| nono | macOS/Linux | Kernel-enforced capability sandbox (preferred) |
|
|
176
|
+
| Docker | Any | Container-based isolation |
|
|
177
|
+
| Podman | Any | Rootless container fallback |
|
|
178
|
+
|
|
179
|
+
Security contexts define per-user filesystem and network rules. See `--security-context` flag.
|
|
180
|
+
|
|
181
|
+
## Development
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
yarn install
|
|
185
|
+
yarn workspace svamp-cli build
|
|
186
|
+
yarn workspace svamp-cli test # Run unit tests (235+ tests)
|
|
187
|
+
yarn workspace svamp-cli test:e2e # Run E2E session tests
|
|
188
|
+
yarn workspace svamp-cli test:hypha # Run Hypha service integration tests
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## License
|
|
192
|
+
|
|
193
|
+
See LICENSE in the repository root.
|
package/bin/svamp.mjs
CHANGED
|
@@ -7,12 +7,10 @@ import { existsSync, readFileSync } from 'fs';
|
|
|
7
7
|
import { join } from 'path';
|
|
8
8
|
import { homedir } from 'os';
|
|
9
9
|
|
|
10
|
-
// Simple .env loader — load from SVAMP_HOME or ~/.svamp
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
if (existsSync(envFile)) {
|
|
15
|
-
const lines = readFileSync(envFile, 'utf-8').split('\n');
|
|
10
|
+
// Simple .env loader — load from SVAMP_HOME or ~/.svamp/, fallback to ~/.hypha/.env
|
|
11
|
+
function loadEnvFile(path) {
|
|
12
|
+
if (!existsSync(path)) return false;
|
|
13
|
+
const lines = readFileSync(path, 'utf-8').split('\n');
|
|
16
14
|
for (const line of lines) {
|
|
17
15
|
const trimmed = line.trim();
|
|
18
16
|
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
@@ -20,10 +18,21 @@ if (existsSync(envFile)) {
|
|
|
20
18
|
if (eqIdx === -1) continue;
|
|
21
19
|
const key = trimmed.slice(0, eqIdx).trim();
|
|
22
20
|
const value = trimmed.slice(eqIdx + 1).trim().replace(/^["']|["']$/g, '');
|
|
23
|
-
|
|
21
|
+
// HYPHA_* vars are managed by `svamp login` — .env is source of truth,
|
|
22
|
+
// always apply them even if already in the shell environment.
|
|
23
|
+
// Other vars only set if not already present.
|
|
24
|
+
if (key.startsWith('HYPHA_') || !process.env[key]) {
|
|
24
25
|
process.env[key] = value;
|
|
25
26
|
}
|
|
26
27
|
}
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const svampEnv = join(process.env.SVAMP_HOME || join(homedir(), '.svamp'), '.env');
|
|
32
|
+
if (!loadEnvFile(svampEnv)) {
|
|
33
|
+
// Fallback: load from ~/.hypha/.env (shared with hypha-cli)
|
|
34
|
+
const hyphaEnv = join(process.env.HYPHA_HOME || join(homedir(), '.hypha'), '.env');
|
|
35
|
+
loadEnvFile(hyphaEnv);
|
|
27
36
|
}
|
|
28
37
|
|
|
29
38
|
// Import and run the CLI
|
package/dist/cli.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { b as stopDaemon, s as startDaemon, d as daemonStatus } from './run-
|
|
1
|
+
import { b as stopDaemon, s as startDaemon, d as daemonStatus } from './run-CBhm4Jop.mjs';
|
|
2
2
|
import 'os';
|
|
3
3
|
import 'fs/promises';
|
|
4
4
|
import 'fs';
|
|
@@ -106,14 +106,14 @@ async function main() {
|
|
|
106
106
|
} else if (subcommand === "skills") {
|
|
107
107
|
await handleSkillsCommand();
|
|
108
108
|
} else if (subcommand === "service" || subcommand === "svc") {
|
|
109
|
-
const { handleServiceCommand } = await import('./commands-
|
|
109
|
+
const { handleServiceCommand } = await import('./commands-ZuFXrcot.mjs').then(function (n) { return n.c; });
|
|
110
110
|
await handleServiceCommand();
|
|
111
111
|
} else if (subcommand === "--help" || subcommand === "-h") {
|
|
112
112
|
printHelp();
|
|
113
113
|
} else if (!subcommand || subcommand === "start") {
|
|
114
114
|
await handleInteractiveCommand();
|
|
115
115
|
} else if (subcommand === "--version" || subcommand === "-v") {
|
|
116
|
-
const pkg = await import('./package-
|
|
116
|
+
const pkg = await import('./package-DbeynOln.mjs').catch(() => ({ default: { version: "unknown" } }));
|
|
117
117
|
console.log(`svamp version: ${pkg.default.version}`);
|
|
118
118
|
} else {
|
|
119
119
|
console.error(`Unknown command: ${subcommand}`);
|
|
@@ -122,7 +122,7 @@ async function main() {
|
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
124
|
async function handleInteractiveCommand() {
|
|
125
|
-
const { runInteractive } = await import('./run-
|
|
125
|
+
const { runInteractive } = await import('./run-Y0b60UYS.mjs');
|
|
126
126
|
const interactiveArgs = subcommand === "start" ? args.slice(1) : args;
|
|
127
127
|
let directory = process.cwd();
|
|
128
128
|
let resumeSessionId;
|
|
@@ -167,7 +167,7 @@ async function handleAgentCommand() {
|
|
|
167
167
|
return;
|
|
168
168
|
}
|
|
169
169
|
if (agentArgs[0] === "list") {
|
|
170
|
-
const { KNOWN_ACP_AGENTS, KNOWN_MCP_AGENTS: KNOWN_MCP_AGENTS2 } = await import('./run-
|
|
170
|
+
const { KNOWN_ACP_AGENTS, KNOWN_MCP_AGENTS: KNOWN_MCP_AGENTS2 } = await import('./run-CBhm4Jop.mjs').then(function (n) { return n.i; });
|
|
171
171
|
console.log("Known agents:");
|
|
172
172
|
for (const [name, config2] of Object.entries(KNOWN_ACP_AGENTS)) {
|
|
173
173
|
console.log(` ${name.padEnd(12)} ${config2.command} ${config2.args.join(" ")} (ACP)`);
|
|
@@ -179,7 +179,7 @@ async function handleAgentCommand() {
|
|
|
179
179
|
console.log('Use "svamp agent -- <command> [args]" for a custom ACP agent.');
|
|
180
180
|
return;
|
|
181
181
|
}
|
|
182
|
-
const { resolveAcpAgentConfig, KNOWN_MCP_AGENTS } = await import('./run-
|
|
182
|
+
const { resolveAcpAgentConfig, KNOWN_MCP_AGENTS } = await import('./run-CBhm4Jop.mjs').then(function (n) { return n.i; });
|
|
183
183
|
let cwd = process.cwd();
|
|
184
184
|
const filteredArgs = [];
|
|
185
185
|
for (let i = 0; i < agentArgs.length; i++) {
|
|
@@ -203,12 +203,12 @@ async function handleAgentCommand() {
|
|
|
203
203
|
console.log(`Starting ${config.agentName} agent in ${cwd}...`);
|
|
204
204
|
let backend;
|
|
205
205
|
if (KNOWN_MCP_AGENTS[config.agentName]) {
|
|
206
|
-
const { CodexMcpBackend } = await import('./run-
|
|
206
|
+
const { CodexMcpBackend } = await import('./run-CBhm4Jop.mjs').then(function (n) { return n.j; });
|
|
207
207
|
backend = new CodexMcpBackend({ cwd, log: logFn });
|
|
208
208
|
} else {
|
|
209
|
-
const { AcpBackend } = await import('./run-
|
|
210
|
-
const { GeminiTransport } = await import('./run-
|
|
211
|
-
const { DefaultTransport } = await import('./run-
|
|
209
|
+
const { AcpBackend } = await import('./run-CBhm4Jop.mjs').then(function (n) { return n.h; });
|
|
210
|
+
const { GeminiTransport } = await import('./run-CBhm4Jop.mjs').then(function (n) { return n.G; });
|
|
211
|
+
const { DefaultTransport } = await import('./run-CBhm4Jop.mjs').then(function (n) { return n.D; });
|
|
212
212
|
const transportHandler = config.agentName === "gemini" ? new GeminiTransport() : new DefaultTransport(config.agentName);
|
|
213
213
|
backend = new AcpBackend({
|
|
214
214
|
agentName: config.agentName,
|
|
@@ -326,7 +326,7 @@ async function handleSessionCommand() {
|
|
|
326
326
|
printSessionHelp();
|
|
327
327
|
return;
|
|
328
328
|
}
|
|
329
|
-
const { sessionList, sessionSpawn, sessionStop, sessionInfo, sessionMessages, sessionAttach, sessionMachines, sessionSend, sessionWait, sessionShare, sessionRalphStart, sessionRalphCancel, sessionRalphStatus, sessionQueueAdd, sessionQueueList, sessionQueueClear } = await import('./commands-
|
|
329
|
+
const { sessionList, sessionSpawn, sessionStop, sessionInfo, sessionMessages, sessionAttach, sessionMachines, sessionSend, sessionWait, sessionShare, sessionRalphStart, sessionRalphCancel, sessionRalphStatus, sessionQueueAdd, sessionQueueList, sessionQueueClear } = await import('./commands-VGt5ofDo.mjs');
|
|
330
330
|
const parseFlagStr = (flag, shortFlag) => {
|
|
331
331
|
for (let i = 1; i < sessionArgs.length; i++) {
|
|
332
332
|
if ((sessionArgs[i] === flag || shortFlag) && i + 1 < sessionArgs.length) {
|
|
@@ -386,7 +386,7 @@ async function handleSessionCommand() {
|
|
|
386
386
|
allowDomain.push(sessionArgs[++i]);
|
|
387
387
|
}
|
|
388
388
|
}
|
|
389
|
-
const { parseShareArg } = await import('./commands-
|
|
389
|
+
const { parseShareArg } = await import('./commands-VGt5ofDo.mjs');
|
|
390
390
|
const shareEntries = share.map((s) => parseShareArg(s));
|
|
391
391
|
await sessionSpawn(agent, dir, targetMachineId, {
|
|
392
392
|
message,
|
|
@@ -470,7 +470,7 @@ async function handleSessionCommand() {
|
|
|
470
470
|
console.error("Usage: svamp session approve <session-id> [request-id] [--json]");
|
|
471
471
|
process.exit(1);
|
|
472
472
|
}
|
|
473
|
-
const { sessionApprove } = await import('./commands-
|
|
473
|
+
const { sessionApprove } = await import('./commands-VGt5ofDo.mjs');
|
|
474
474
|
const approveReqId = sessionArgs[2] && !sessionArgs[2].startsWith("--") ? sessionArgs[2] : void 0;
|
|
475
475
|
await sessionApprove(sessionArgs[1], approveReqId, targetMachineId, {
|
|
476
476
|
json: hasFlag("--json")
|
|
@@ -480,7 +480,7 @@ async function handleSessionCommand() {
|
|
|
480
480
|
console.error("Usage: svamp session deny <session-id> [request-id] [--json]");
|
|
481
481
|
process.exit(1);
|
|
482
482
|
}
|
|
483
|
-
const { sessionDeny } = await import('./commands-
|
|
483
|
+
const { sessionDeny } = await import('./commands-VGt5ofDo.mjs');
|
|
484
484
|
const denyReqId = sessionArgs[2] && !sessionArgs[2].startsWith("--") ? sessionArgs[2] : void 0;
|
|
485
485
|
await sessionDeny(sessionArgs[1], denyReqId, targetMachineId, {
|
|
486
486
|
json: hasFlag("--json")
|
|
@@ -549,7 +549,7 @@ async function handleMachineCommand() {
|
|
|
549
549
|
return;
|
|
550
550
|
}
|
|
551
551
|
if (machineSubcommand === "share") {
|
|
552
|
-
const { machineShare } = await import('./commands-
|
|
552
|
+
const { machineShare } = await import('./commands-VGt5ofDo.mjs');
|
|
553
553
|
let machineId;
|
|
554
554
|
const shareArgs = [];
|
|
555
555
|
for (let i = 1; i < machineArgs.length; i++) {
|
|
@@ -579,7 +579,7 @@ async function handleMachineCommand() {
|
|
|
579
579
|
}
|
|
580
580
|
await machineShare(machineId, { add, remove, list, configPath, showConfig });
|
|
581
581
|
} else if (machineSubcommand === "exec") {
|
|
582
|
-
const { machineExec } = await import('./commands-
|
|
582
|
+
const { machineExec } = await import('./commands-VGt5ofDo.mjs');
|
|
583
583
|
let machineId;
|
|
584
584
|
let cwd;
|
|
585
585
|
const cmdParts = [];
|
|
@@ -599,7 +599,7 @@ async function handleMachineCommand() {
|
|
|
599
599
|
}
|
|
600
600
|
await machineExec(machineId, command, cwd);
|
|
601
601
|
} else if (machineSubcommand === "info") {
|
|
602
|
-
const { machineInfo } = await import('./commands-
|
|
602
|
+
const { machineInfo } = await import('./commands-VGt5ofDo.mjs');
|
|
603
603
|
let machineId;
|
|
604
604
|
for (let i = 1; i < machineArgs.length; i++) {
|
|
605
605
|
if ((machineArgs[i] === "--machine" || machineArgs[i] === "-m") && i + 1 < machineArgs.length) {
|
|
@@ -608,7 +608,7 @@ async function handleMachineCommand() {
|
|
|
608
608
|
}
|
|
609
609
|
await machineInfo(machineId);
|
|
610
610
|
} else if (machineSubcommand === "ls") {
|
|
611
|
-
const { machineLs } = await import('./commands-
|
|
611
|
+
const { machineLs } = await import('./commands-VGt5ofDo.mjs');
|
|
612
612
|
let machineId;
|
|
613
613
|
let showHidden = false;
|
|
614
614
|
let path;
|
|
@@ -738,7 +738,7 @@ Please open this URL in your browser:
|
|
|
738
738
|
}
|
|
739
739
|
envLines.push(`HYPHA_SERVER_URL=${serverUrl}`);
|
|
740
740
|
envLines.push(`HYPHA_TOKEN=${longLivedToken}`);
|
|
741
|
-
envLines.push(`HYPHA_WORKSPACE=${
|
|
741
|
+
envLines.push(`HYPHA_WORKSPACE=${workspace}`);
|
|
742
742
|
while (envLines.length > 0 && envLines[envLines.length - 1].trim() === "") {
|
|
743
743
|
envLines.pop();
|
|
744
744
|
}
|