virtuai-cli 1.0.4__tar.gz → 1.0.6__tar.gz
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.
- {virtuai_cli-1.0.4 → virtuai_cli-1.0.6}/PKG-INFO +1 -1
- {virtuai_cli-1.0.4 → virtuai_cli-1.0.6}/pyproject.toml +1 -1
- {virtuai_cli-1.0.4 → virtuai_cli-1.0.6}/src/virtuai_cli/__init__.py +1 -1
- {virtuai_cli-1.0.4 → virtuai_cli-1.0.6}/src/virtuai_cli/main.py +2 -2
- {virtuai_cli-1.0.4 → virtuai_cli-1.0.6}/src/virtuai_cli/opencode.py +158 -50
- {virtuai_cli-1.0.4 → virtuai_cli-1.0.6}/src/virtuai_cli/security.py +13 -0
- {virtuai_cli-1.0.4 → virtuai_cli-1.0.6}/src/virtuai_cli.egg-info/PKG-INFO +1 -1
- {virtuai_cli-1.0.4 → virtuai_cli-1.0.6}/src/virtuai_cli.egg-info/SOURCES.txt +1 -0
- virtuai_cli-1.0.6/tests/test_proxy_env.py +29 -0
- {virtuai_cli-1.0.4 → virtuai_cli-1.0.6}/README.md +0 -0
- {virtuai_cli-1.0.4 → virtuai_cli-1.0.6}/setup.cfg +0 -0
- {virtuai_cli-1.0.4 → virtuai_cli-1.0.6}/src/virtuai_cli/browser.py +0 -0
- {virtuai_cli-1.0.4 → virtuai_cli-1.0.6}/src/virtuai_cli/chat/__init__.py +0 -0
- {virtuai_cli-1.0.4 → virtuai_cli-1.0.6}/src/virtuai_cli/chat/agents.py +0 -0
- {virtuai_cli-1.0.4 → virtuai_cli-1.0.6}/src/virtuai_cli/chat/ask.py +0 -0
- {virtuai_cli-1.0.4 → virtuai_cli-1.0.6}/src/virtuai_cli/chat/command.py +0 -0
- {virtuai_cli-1.0.4 → virtuai_cli-1.0.6}/src/virtuai_cli/chat/history.py +0 -0
- {virtuai_cli-1.0.4 → virtuai_cli-1.0.6}/src/virtuai_cli/chat/sse.py +0 -0
- {virtuai_cli-1.0.4 → virtuai_cli-1.0.6}/src/virtuai_cli/chat/tui.py +0 -0
- {virtuai_cli-1.0.4 → virtuai_cli-1.0.6}/src/virtuai_cli/chat/widgets.py +0 -0
- {virtuai_cli-1.0.4 → virtuai_cli-1.0.6}/src/virtuai_cli/config.py +0 -0
- {virtuai_cli-1.0.4 → virtuai_cli-1.0.6}/src/virtuai_cli/executor.py +0 -0
- {virtuai_cli-1.0.4 → virtuai_cli-1.0.6}/src/virtuai_cli/ollama_state.py +0 -0
- {virtuai_cli-1.0.4 → virtuai_cli-1.0.6}/src/virtuai_cli/omlx_state.py +0 -0
- {virtuai_cli-1.0.4 → virtuai_cli-1.0.6}/src/virtuai_cli/pull.py +0 -0
- {virtuai_cli-1.0.4 → virtuai_cli-1.0.6}/src/virtuai_cli/runner.py +0 -0
- {virtuai_cli-1.0.4 → virtuai_cli-1.0.6}/src/virtuai_cli.egg-info/dependency_links.txt +0 -0
- {virtuai_cli-1.0.4 → virtuai_cli-1.0.6}/src/virtuai_cli.egg-info/entry_points.txt +0 -0
- {virtuai_cli-1.0.4 → virtuai_cli-1.0.6}/src/virtuai_cli.egg-info/requires.txt +0 -0
- {virtuai_cli-1.0.4 → virtuai_cli-1.0.6}/src/virtuai_cli.egg-info/top_level.txt +0 -0
- {virtuai_cli-1.0.4 → virtuai_cli-1.0.6}/tests/test_browser.py +0 -0
- {virtuai_cli-1.0.4 → virtuai_cli-1.0.6}/tests/test_frame_concurrency.py +0 -0
- {virtuai_cli-1.0.4 → virtuai_cli-1.0.6}/tests/test_ollama_options.py +0 -0
- {virtuai_cli-1.0.4 → virtuai_cli-1.0.6}/tests/test_ollama_state.py +0 -0
- {virtuai_cli-1.0.4 → virtuai_cli-1.0.6}/tests/test_omlx.py +0 -0
- {virtuai_cli-1.0.4 → virtuai_cli-1.0.6}/tests/test_process_group_kill.py +0 -0
- {virtuai_cli-1.0.4 → virtuai_cli-1.0.6}/tests/test_pull_fallback.py +0 -0
- {virtuai_cli-1.0.4 → virtuai_cli-1.0.6}/tests/test_run_single_instance.py +0 -0
- {virtuai_cli-1.0.4 → virtuai_cli-1.0.6}/tests/test_session_isolation.py +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""VirtuAI local CLI."""
|
|
2
|
-
__version__ = "1.0.
|
|
2
|
+
__version__ = "1.0.6"
|
|
@@ -151,7 +151,7 @@ def unpair():
|
|
|
151
151
|
cfg.delete_token(cfg._KEYRING_CLI_TOKEN_KEY)
|
|
152
152
|
console.print("[yellow]Unpaired.[/yellow] CLI token cleared.")
|
|
153
153
|
console.print(
|
|
154
|
-
"[dim]If you ran virtuai opencode, remove
|
|
154
|
+
"[dim]If you ran virtuai opencode, remove providers.virtuai from "
|
|
155
155
|
"~/.config/opencode/opencode.json — that file still holds a copy of the token.[/dim]"
|
|
156
156
|
)
|
|
157
157
|
|
|
@@ -476,7 +476,7 @@ def opencode(
|
|
|
476
476
|
):
|
|
477
477
|
"""Point the local OpenCode TUI at this workspace's VirtuAI agents.
|
|
478
478
|
|
|
479
|
-
Copies the CLI pairing token into OpenCode as
|
|
479
|
+
Copies the CLI pairing token into OpenCode 2 as providers.virtuai.settings.apiKey
|
|
480
480
|
(OpenCode cannot read the OS keychain). Keeps local-llm, plugins, and mcp.
|
|
481
481
|
"""
|
|
482
482
|
from virtuai_cli.opencode import install_opencode
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"""Point the local OpenCode TUI at VirtuAI agents.
|
|
2
2
|
|
|
3
|
-
OpenCode is a Chat Completions client. ``virtuai opencode``
|
|
4
|
-
|
|
5
|
-
``
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
OpenCode is a Chat Completions client. ``virtuai opencode`` writes native
|
|
4
|
+
OpenCode 2 config into ``~/.config/opencode/opencode.json``: ``providers.virtuai``
|
|
5
|
+
and ``agents.*``. A previous ``provider.virtuai`` block this command wrote is
|
|
6
|
+
removed on the next run. The ``apiKey`` is the same CLI pairing (or login)
|
|
7
|
+
token already in the OS keychain — OpenCode cannot read the keychain, so the
|
|
8
|
+
token is copied into that file as ``Authorization: Bearer``.
|
|
8
9
|
"""
|
|
9
10
|
|
|
10
11
|
from __future__ import annotations
|
|
@@ -183,27 +184,85 @@ def _opencode_model_entry(m: dict) -> dict:
|
|
|
183
184
|
entry["limit"] = {
|
|
184
185
|
"context": context,
|
|
185
186
|
"output": output,
|
|
186
|
-
# OpenCode's usable()
|
|
187
|
+
# OpenCode's usable() applies the compaction buffer on the
|
|
187
188
|
# limit.input branch; without this key the buffer is dead config.
|
|
188
189
|
"input": int(limit.get("input") or max(0, context - output)),
|
|
189
190
|
}
|
|
190
191
|
return entry
|
|
191
192
|
|
|
192
193
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
194
|
+
# v1 permission names that OpenCode 2 renamed. Anything else is copied as-is.
|
|
195
|
+
_V1_PERMISSION_ACTIONS = {
|
|
196
|
+
"bash": "shell",
|
|
197
|
+
"write": "edit",
|
|
198
|
+
"patch": "edit",
|
|
199
|
+
"task": "subagent",
|
|
200
|
+
}
|
|
201
|
+
_BUILD_ALLOWS = ("read", "grep", "glob", "edit", "shell", "doom_loop")
|
|
202
|
+
_PLAN_ALLOWS = ("read", "grep", "glob")
|
|
203
|
+
# Values this command used to write. Only these are removed; a user edit stays.
|
|
204
|
+
_LEGACY_PRUNE = True
|
|
205
|
+
_LEGACY_RESERVED = 20_000
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _v1_permissions_to_v2(perm: dict) -> list[dict]:
|
|
209
|
+
rules: list[dict] = []
|
|
210
|
+
seen: set[str] = set()
|
|
211
|
+
for action, effect in perm.items():
|
|
212
|
+
if not isinstance(action, str) or not isinstance(effect, str):
|
|
213
|
+
continue
|
|
214
|
+
name = _V1_PERMISSION_ACTIONS.get(action, action)
|
|
215
|
+
if name in seen:
|
|
216
|
+
continue
|
|
217
|
+
seen.add(name)
|
|
218
|
+
rules.append({"action": name, "resource": "*", "effect": effect})
|
|
219
|
+
return rules
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _fill_allow_rules(block: dict, actions: tuple[str, ...]) -> None:
|
|
223
|
+
"""Add allow-rules that are missing. Leave any rule the user already set."""
|
|
224
|
+
rules = block.get("permissions")
|
|
225
|
+
if not isinstance(rules, list):
|
|
226
|
+
rules = []
|
|
227
|
+
block["permissions"] = rules
|
|
228
|
+
present = {
|
|
229
|
+
rule.get("action")
|
|
230
|
+
for rule in rules
|
|
231
|
+
if isinstance(rule, dict) and isinstance(rule.get("action"), str)
|
|
232
|
+
}
|
|
233
|
+
for action in actions:
|
|
234
|
+
if action in present:
|
|
235
|
+
continue
|
|
236
|
+
rules.append({"action": action, "resource": "*", "effect": "allow"})
|
|
237
|
+
present.add(action)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _lift_owned_agent(agents: dict, legacy: dict, name: str) -> dict:
|
|
241
|
+
"""Move a VirtuAI-owned v1 agent under ``agents`` as one v2 object.
|
|
242
|
+
|
|
243
|
+
A native block already present wins. Otherwise the v1 block is copied and
|
|
244
|
+
its renamed fields are translated, then the v1 key is deleted so the agent
|
|
245
|
+
is not half ``permission`` and half ``permissions``.
|
|
246
|
+
"""
|
|
247
|
+
current = agents.get(name)
|
|
248
|
+
if isinstance(current, dict):
|
|
249
|
+
block = current
|
|
250
|
+
else:
|
|
251
|
+
source = legacy.get(name)
|
|
252
|
+
block = deepcopy(source) if isinstance(source, dict) else {}
|
|
253
|
+
prompt = block.pop("prompt", None)
|
|
254
|
+
if prompt is not None and "system" not in block:
|
|
255
|
+
block["system"] = prompt
|
|
256
|
+
perm = block.pop("permission", None)
|
|
257
|
+
if isinstance(perm, dict) and not isinstance(block.get("permissions"), list):
|
|
258
|
+
block["permissions"] = _v1_permissions_to_v2(perm)
|
|
259
|
+
if "disable" in block and "disabled" not in block:
|
|
260
|
+
block["disabled"] = block.pop("disable")
|
|
261
|
+
else:
|
|
262
|
+
block.pop("disable", None)
|
|
263
|
+
agents[name] = block
|
|
264
|
+
legacy.pop(name, None)
|
|
265
|
+
return block
|
|
207
266
|
|
|
208
267
|
|
|
209
268
|
def _ensure_compaction(cfg_out: dict) -> None:
|
|
@@ -215,19 +274,24 @@ def _ensure_compaction(cfg_out: dict) -> None:
|
|
|
215
274
|
against token counts the agent loop reports. ``SummarizationMiddleware`` in
|
|
216
275
|
the deep agent is what actually compacts.
|
|
217
276
|
|
|
218
|
-
``auto: false`` disables the automatic path
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
harmless and keeps its context gauge honest.
|
|
277
|
+
``auto: false`` disables the automatic path. ``/compact`` still works as a
|
|
278
|
+
manual escape hatch. OpenCode 2 reads ``buffer`` and warns on ``prune``, so
|
|
279
|
+
this command no longer writes ``prune`` or ``reserved``.
|
|
222
280
|
|
|
223
|
-
Existing user values are left alone.
|
|
281
|
+
Existing user values are left alone. ``buffer`` is filled from ``reserved``
|
|
282
|
+
when the user set a reserve and never set a buffer.
|
|
224
283
|
"""
|
|
225
284
|
block = cfg_out.setdefault("compaction", {})
|
|
226
285
|
if not isinstance(block, dict):
|
|
227
286
|
return
|
|
228
287
|
block.setdefault("auto", False)
|
|
229
|
-
block
|
|
230
|
-
|
|
288
|
+
if "buffer" not in block and "reserved" in block:
|
|
289
|
+
block["buffer"] = block["reserved"]
|
|
290
|
+
block.setdefault("buffer", _LEGACY_RESERVED)
|
|
291
|
+
if block.get("prune") is _LEGACY_PRUNE:
|
|
292
|
+
block.pop("prune", None)
|
|
293
|
+
if block.get("reserved") == _LEGACY_RESERVED:
|
|
294
|
+
block.pop("reserved", None)
|
|
231
295
|
|
|
232
296
|
|
|
233
297
|
def merge_opencode_config(
|
|
@@ -238,18 +302,21 @@ def merge_opencode_config(
|
|
|
238
302
|
models: list[dict],
|
|
239
303
|
default_id: str,
|
|
240
304
|
) -> dict:
|
|
241
|
-
"""Merge a VirtuAI
|
|
305
|
+
"""Merge a native OpenCode 2 VirtuAI provider. Preserves local-llm, plugins, mcp."""
|
|
242
306
|
cfg_out = deepcopy(existing) if existing else {}
|
|
243
307
|
cfg_out.setdefault("$schema", "https://opencode.ai/config.json")
|
|
244
308
|
v1 = base_url.rstrip("/")
|
|
245
309
|
if not v1.endswith("/v1"):
|
|
246
310
|
v1 = f"{v1}/v1"
|
|
247
311
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
312
|
+
providers = cfg_out.setdefault("providers", {})
|
|
313
|
+
if not isinstance(providers, dict):
|
|
314
|
+
providers = {}
|
|
315
|
+
cfg_out["providers"] = providers
|
|
316
|
+
providers["virtuai"] = {
|
|
317
|
+
"package": "aisdk:@ai-sdk/openai-compatible",
|
|
251
318
|
"name": "VirtuAI",
|
|
252
|
-
"
|
|
319
|
+
"settings": {
|
|
253
320
|
"baseURL": v1,
|
|
254
321
|
"apiKey": api_key,
|
|
255
322
|
"includeUsage": True,
|
|
@@ -258,13 +325,16 @@ def merge_opencode_config(
|
|
|
258
325
|
m["id"]: _opencode_model_entry(m) for m in models if m.get("id")
|
|
259
326
|
},
|
|
260
327
|
}
|
|
328
|
+
legacy_provider = cfg_out.get("provider")
|
|
329
|
+
if isinstance(legacy_provider, dict):
|
|
330
|
+
legacy_provider.pop("virtuai", None)
|
|
331
|
+
if not legacy_provider:
|
|
332
|
+
cfg_out.pop("provider", None)
|
|
261
333
|
|
|
262
334
|
cfg_out["model"] = f"virtuai/{default_id}"
|
|
263
335
|
# Title generation falls back to the session model when no small model is
|
|
264
336
|
# set, which spends a full agent turn naming a session.
|
|
265
337
|
small_id = pick_small_id(models, default_id)
|
|
266
|
-
if small_id:
|
|
267
|
-
cfg_out.setdefault("small_model", f"virtuai/{small_id}")
|
|
268
338
|
|
|
269
339
|
by_slug: dict[str, str] = {}
|
|
270
340
|
for m in models:
|
|
@@ -274,30 +344,68 @@ def merge_opencode_config(
|
|
|
274
344
|
by_slug[_slug(mid)] = mid
|
|
275
345
|
by_slug[_slug(m.get("name") or "")] = mid
|
|
276
346
|
|
|
347
|
+
agents = cfg_out.setdefault("agents", {})
|
|
348
|
+
if not isinstance(agents, dict):
|
|
349
|
+
agents = {}
|
|
350
|
+
cfg_out["agents"] = agents
|
|
351
|
+
legacy_was_dict = isinstance(cfg_out.get("agent"), dict)
|
|
352
|
+
legacy = cfg_out["agent"] if legacy_was_dict else {}
|
|
353
|
+
|
|
277
354
|
build_id = pick_build_id(models)
|
|
278
355
|
plan_id = pick_plan_id(models)
|
|
279
|
-
agent = cfg_out.setdefault("agent", {})
|
|
280
356
|
if build_id:
|
|
281
|
-
build =
|
|
357
|
+
build = _lift_owned_agent(agents, legacy, "build")
|
|
282
358
|
build["model"] = f"virtuai/{build_id}"
|
|
283
|
-
|
|
359
|
+
_fill_allow_rules(build, _BUILD_ALLOWS)
|
|
284
360
|
if plan_id:
|
|
285
|
-
plan =
|
|
361
|
+
plan = _lift_owned_agent(agents, legacy, "plan")
|
|
286
362
|
plan["model"] = f"virtuai/{plan_id}"
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
if key in
|
|
363
|
+
_fill_allow_rules(plan, _PLAN_ALLOWS)
|
|
364
|
+
|
|
365
|
+
owned = {"build", "plan", "compaction", "title"}
|
|
366
|
+
for key in list(legacy) + list(agents):
|
|
367
|
+
if key in owned:
|
|
292
368
|
continue
|
|
293
369
|
matched = by_slug.get(_slug(key))
|
|
294
|
-
if matched:
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
370
|
+
if not matched:
|
|
371
|
+
continue
|
|
372
|
+
if isinstance(agents.get(key), dict):
|
|
373
|
+
agents[key]["model"] = f"virtuai/{matched}"
|
|
374
|
+
elif isinstance(legacy.get(key), dict):
|
|
375
|
+
legacy[key]["model"] = f"virtuai/{matched}"
|
|
376
|
+
|
|
377
|
+
prior_small = cfg_out.get("small_model")
|
|
378
|
+
title = agents.get("title")
|
|
379
|
+
if isinstance(title, dict) and title.get("model"):
|
|
380
|
+
cfg_out.pop("small_model", None)
|
|
381
|
+
else:
|
|
382
|
+
chosen = prior_small if isinstance(prior_small, str) and prior_small else None
|
|
383
|
+
if chosen is None and small_id:
|
|
384
|
+
chosen = f"virtuai/{small_id}"
|
|
385
|
+
if chosen:
|
|
386
|
+
if not isinstance(title, dict):
|
|
387
|
+
title = {}
|
|
388
|
+
agents["title"] = title
|
|
389
|
+
title.setdefault("model", chosen)
|
|
390
|
+
cfg_out.pop("small_model", None)
|
|
391
|
+
|
|
392
|
+
compaction_agent = agents.get("compaction")
|
|
393
|
+
if not isinstance(compaction_agent, dict):
|
|
394
|
+
compaction_agent = {}
|
|
395
|
+
legacy_compaction = legacy.pop("compaction", None)
|
|
396
|
+
if "model" not in compaction_agent:
|
|
397
|
+
if isinstance(legacy_compaction, dict) and legacy_compaction.get("model"):
|
|
398
|
+
compaction_agent["model"] = legacy_compaction["model"]
|
|
399
|
+
elif small_id:
|
|
400
|
+
compaction_agent["model"] = f"virtuai/{small_id}"
|
|
401
|
+
if compaction_agent.get("model"):
|
|
402
|
+
agents["compaction"] = compaction_agent
|
|
403
|
+
|
|
404
|
+
if legacy_was_dict:
|
|
405
|
+
if legacy:
|
|
406
|
+
cfg_out["agent"] = legacy
|
|
407
|
+
else:
|
|
408
|
+
cfg_out.pop("agent", None)
|
|
301
409
|
|
|
302
410
|
_ensure_compaction(cfg_out)
|
|
303
411
|
return cfg_out
|
|
@@ -95,11 +95,22 @@ _PASS_THROUGH_ENV = {
|
|
|
95
95
|
"GOOGLE_CLOUD_PROJECT",
|
|
96
96
|
"GCLOUD_PROJECT",
|
|
97
97
|
"GOOGLE_CLOUD_QUOTA_PROJECT",
|
|
98
|
+
# Proxy settings. In a VirtuAI sandbox the only way out is its egress
|
|
99
|
+
# proxy, and without these every pip, curl and requests call the agent
|
|
100
|
+
# makes fails; on a laptop behind a corporate proxy, the same. They are
|
|
101
|
+
# set by whoever runs the CLI, never by the server.
|
|
102
|
+
"HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY", "ALL_PROXY",
|
|
103
|
+
"http_proxy", "https_proxy", "no_proxy", "all_proxy",
|
|
98
104
|
}
|
|
99
105
|
|
|
100
106
|
# Env var prefixes whose entire namespace is passed through
|
|
101
107
|
_PASS_THROUGH_PREFIXES = ("VIRTUAI_", "GOOGLE_", "GCLOUD_", "CLOUDSDK_", "AWS_", "AZURE_")
|
|
102
108
|
|
|
109
|
+
# The runner's own credentials. Under the VIRTUAI_ prefix, they reached every
|
|
110
|
+
# command the agent ran — `env` printed the token, and with it anything the
|
|
111
|
+
# agent read could act as this runner.
|
|
112
|
+
_NEVER_PASSED = {"VIRTUAI_TOKEN", "VIRTUAI_PAIRING_CODE"}
|
|
113
|
+
|
|
103
114
|
|
|
104
115
|
def scrub_env(workdir: Path, extra_allow: list[str] | None = None) -> dict[str, str]:
|
|
105
116
|
"""Build a clean environment: allowlist + cloud-tool prefixes + user opt-ins.
|
|
@@ -116,6 +127,8 @@ def scrub_env(workdir: Path, extra_allow: list[str] | None = None) -> dict[str,
|
|
|
116
127
|
|
|
117
128
|
env: dict[str, str] = {}
|
|
118
129
|
for key, value in os.environ.items():
|
|
130
|
+
if key in _NEVER_PASSED:
|
|
131
|
+
continue
|
|
119
132
|
if key in allowed or any(key.startswith(p) for p in _PASS_THROUGH_PREFIXES):
|
|
120
133
|
env[key] = value
|
|
121
134
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""The agent's commands keep the proxy settings the runner was started with.
|
|
2
|
+
|
|
3
|
+
In a VirtuAI sandbox the egress proxy is the only way out; without these,
|
|
4
|
+
every pip, curl and requests call failed.
|
|
5
|
+
"""
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from virtuai_cli.security import scrub_env
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_proxy_settings_reach_the_agents_commands(monkeypatch, tmp_path: Path):
|
|
13
|
+
monkeypatch.setenv("HTTPS_PROXY", "http://px-7:3128")
|
|
14
|
+
monkeypatch.setenv("no_proxy", "localhost")
|
|
15
|
+
monkeypatch.setenv("SOME_SECRET", "x")
|
|
16
|
+
env = scrub_env(tmp_path)
|
|
17
|
+
assert env["HTTPS_PROXY"] == "http://px-7:3128" and env["no_proxy"] == "localhost"
|
|
18
|
+
assert "SOME_SECRET" not in env
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_the_runners_own_credentials_never_reach_a_command(monkeypatch, tmp_path):
|
|
22
|
+
from virtuai_cli.security import scrub_env
|
|
23
|
+
|
|
24
|
+
monkeypatch.setenv("VIRTUAI_TOKEN", "vai_sbx_secret")
|
|
25
|
+
monkeypatch.setenv("VIRTUAI_PAIRING_CODE", "ABC-123")
|
|
26
|
+
monkeypatch.setenv("VIRTUAI_SERVER_URL", "https://dev.imvirtuai.com")
|
|
27
|
+
env = scrub_env(tmp_path)
|
|
28
|
+
assert "VIRTUAI_TOKEN" not in env and "VIRTUAI_PAIRING_CODE" not in env
|
|
29
|
+
assert env["VIRTUAI_SERVER_URL"] == "https://dev.imvirtuai.com" # the rest of VIRTUAI_* still passes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|