viur-cli 3.0.0b2__tar.gz → 3.0.1__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.
- {viur_cli-3.0.0b2 → viur_cli-3.0.1}/LICENSE +1 -1
- {viur_cli-3.0.0b2/src/viur_cli.egg-info → viur_cli-3.0.1}/PKG-INFO +2 -2
- {viur_cli-3.0.0b2 → viur_cli-3.0.1}/README.md +1 -1
- {viur_cli-3.0.0b2 → viur_cli-3.0.1}/src/viur_cli/conf.py +1 -0
- {viur_cli-3.0.0b2 → viur_cli-3.0.1}/src/viur_cli/local.py +59 -15
- {viur_cli-3.0.0b2 → viur_cli-3.0.1}/src/viur_cli/scriptor/cli.py +85 -21
- {viur_cli-3.0.0b2 → viur_cli-3.0.1}/src/viur_cli/scriptor/login.py +0 -4
- viur_cli-3.0.1/src/viur_cli/version.py +2 -0
- {viur_cli-3.0.0b2 → viur_cli-3.0.1/src/viur_cli.egg-info}/PKG-INFO +2 -2
- viur_cli-3.0.0b2/src/viur_cli/version.py +0 -2
- {viur_cli-3.0.0b2 → viur_cli-3.0.1}/pyproject.toml +0 -0
- {viur_cli-3.0.0b2 → viur_cli-3.0.1}/setup.cfg +0 -0
- {viur_cli-3.0.0b2 → viur_cli-3.0.1}/src/viur_cli/__init__.py +0 -0
- {viur_cli-3.0.0b2 → viur_cli-3.0.1}/src/viur_cli/_docs_cli.py +0 -0
- {viur_cli-3.0.0b2 → viur_cli-3.0.1}/src/viur_cli/build.py +0 -0
- {viur_cli-3.0.0b2 → viur_cli-3.0.1}/src/viur_cli/cli.py +0 -0
- {viur_cli-3.0.0b2 → viur_cli-3.0.1}/src/viur_cli/cloud.py +0 -0
- {viur_cli-3.0.0b2 → viur_cli-3.0.1}/src/viur_cli/deprecated.py +0 -0
- {viur_cli-3.0.0b2 → viur_cli-3.0.1}/src/viur_cli/package.py +0 -0
- {viur_cli-3.0.0b2 → viur_cli-3.0.1}/src/viur_cli/scriptor/__init__.py +0 -0
- {viur_cli-3.0.0b2 → viur_cli-3.0.1}/src/viur_cli/scripts/__init__.py +0 -0
- {viur_cli-3.0.0b2 → viur_cli-3.0.1}/src/viur_cli/scripts/get_pyodide.py +0 -0
- {viur_cli-3.0.0b2 → viur_cli-3.0.1}/src/viur_cli/setup.py +0 -0
- {viur_cli-3.0.0b2 → viur_cli-3.0.1}/src/viur_cli/update.py +0 -0
- {viur_cli-3.0.0b2 → viur_cli-3.0.1}/src/viur_cli/utils.py +0 -0
- {viur_cli-3.0.0b2 → viur_cli-3.0.1}/src/viur_cli.egg-info/SOURCES.txt +0 -0
- {viur_cli-3.0.0b2 → viur_cli-3.0.1}/src/viur_cli.egg-info/dependency_links.txt +0 -0
- {viur_cli-3.0.0b2 → viur_cli-3.0.1}/src/viur_cli.egg-info/entry_points.txt +0 -0
- {viur_cli-3.0.0b2 → viur_cli-3.0.1}/src/viur_cli.egg-info/requires.txt +0 -0
- {viur_cli-3.0.0b2 → viur_cli-3.0.1}/src/viur_cli.egg-info/top_level.txt +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Copyright ©
|
|
1
|
+
Copyright © 2026 by Mausbrand Informationssysteme GmbH.
|
|
2
2
|
|
|
3
3
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
4
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: viur-cli
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.1
|
|
4
4
|
Summary: Command-line interface for ViUR application maintenance.
|
|
5
5
|
Author-email: "Andreas H. Kelch" <ak@mausbrand.de>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -346,7 +346,7 @@ viur-cli depends on
|
|
|
346
346
|
|
|
347
347
|
## License
|
|
348
348
|
|
|
349
|
-
Copyright ©
|
|
349
|
+
Copyright © 2026 by Mausbrand Informationssysteme GmbH.<br>
|
|
350
350
|
Mausbrand and ViUR are registered trademarks of Mausbrand Informationssysteme GmbH.
|
|
351
351
|
|
|
352
352
|
This project is free software under the MIT license.<br>
|
|
@@ -323,7 +323,7 @@ viur-cli depends on
|
|
|
323
323
|
|
|
324
324
|
## License
|
|
325
325
|
|
|
326
|
-
Copyright ©
|
|
326
|
+
Copyright © 2026 by Mausbrand Informationssysteme GmbH.<br>
|
|
327
327
|
Mausbrand and ViUR are registered trademarks of Mausbrand Informationssysteme GmbH.
|
|
328
328
|
|
|
329
329
|
This project is free software under the MIT license.<br>
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
+
import contextlib
|
|
2
|
+
import itertools
|
|
1
3
|
import json
|
|
4
|
+
import sys
|
|
5
|
+
import threading
|
|
6
|
+
import time
|
|
2
7
|
|
|
3
8
|
import click
|
|
4
9
|
import requests
|
|
@@ -186,6 +191,41 @@ def check(dev):
|
|
|
186
191
|
utils.echo_info("\U00002714 No vulnerabilities found.")
|
|
187
192
|
|
|
188
193
|
|
|
194
|
+
@contextlib.contextmanager
|
|
195
|
+
def _spinner(message):
|
|
196
|
+
"""Show an animated spinner next to ``message`` until the block exits.
|
|
197
|
+
|
|
198
|
+
No-op (prints the message once) when stdout is not a TTY so CI logs
|
|
199
|
+
stay clean.
|
|
200
|
+
"""
|
|
201
|
+
styled = click.style(message, fg="cyan")
|
|
202
|
+
if not sys.stdout.isatty():
|
|
203
|
+
click.echo(styled)
|
|
204
|
+
yield
|
|
205
|
+
return
|
|
206
|
+
|
|
207
|
+
done = threading.Event()
|
|
208
|
+
|
|
209
|
+
def spin():
|
|
210
|
+
for ch in itertools.cycle("⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"):
|
|
211
|
+
if done.is_set():
|
|
212
|
+
break
|
|
213
|
+
sys.stdout.write(f"\r{styled} {click.style(ch, fg='cyan')}")
|
|
214
|
+
sys.stdout.flush()
|
|
215
|
+
time.sleep(0.1)
|
|
216
|
+
|
|
217
|
+
t = threading.Thread(target=spin, daemon=True)
|
|
218
|
+
t.start()
|
|
219
|
+
try:
|
|
220
|
+
yield
|
|
221
|
+
finally:
|
|
222
|
+
done.set()
|
|
223
|
+
t.join()
|
|
224
|
+
# Leave the message in place; swap the spinner glyph for a check mark.
|
|
225
|
+
sys.stdout.write(f"\r{styled} {click.style('✓', fg='cyan')}\n")
|
|
226
|
+
sys.stdout.flush()
|
|
227
|
+
|
|
228
|
+
|
|
189
229
|
def do_checks(dev=True):
|
|
190
230
|
"""Run Python (pip-audit) and npm (npm audit) vulnerability scans.
|
|
191
231
|
|
|
@@ -204,11 +244,12 @@ def do_checks(dev=True):
|
|
|
204
244
|
|
|
205
245
|
# --- Python vulnerability check via pip-audit (PyPA tool, OSV data) ---
|
|
206
246
|
try:
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
247
|
+
with _spinner("Scanning Python dependencies (pip-audit)"):
|
|
248
|
+
result = subprocess.run(
|
|
249
|
+
["uvx", "pip-audit", "--format", "json", "--vulnerability-service", "osv"],
|
|
250
|
+
capture_output=True,
|
|
251
|
+
text=True,
|
|
252
|
+
)
|
|
212
253
|
except FileNotFoundError:
|
|
213
254
|
echo_warning("uvx not found; skipping Python vulnerability check.")
|
|
214
255
|
else:
|
|
@@ -229,7 +270,7 @@ def do_checks(dev=True):
|
|
|
229
270
|
vuln_deps = [d for d in deps if d.get("vulns")]
|
|
230
271
|
total_vulns = sum(len(d["vulns"]) for d in vuln_deps)
|
|
231
272
|
|
|
232
|
-
click.echo("\n" + "=" * 60)
|
|
273
|
+
click.echo("\n\n" + "=" * 60)
|
|
233
274
|
click.echo("Python Security Scan Results (pip-audit, OSV)")
|
|
234
275
|
click.echo("=" * 60)
|
|
235
276
|
click.echo(f"Total Packages: {len(deps)}")
|
|
@@ -240,6 +281,7 @@ def do_checks(dev=True):
|
|
|
240
281
|
if vuln_deps:
|
|
241
282
|
has_vulnerabilities = True
|
|
242
283
|
click.echo("Run `uvx pip-audit --vulnerability-service osv` for per-package detail.")
|
|
284
|
+
click.echo()
|
|
243
285
|
|
|
244
286
|
# --- npm vulnerability check ---
|
|
245
287
|
if shutil.which("npm"):
|
|
@@ -258,7 +300,7 @@ def do_checks(dev=True):
|
|
|
258
300
|
npm_builds.append({"name": build_name, "path": audit_path})
|
|
259
301
|
|
|
260
302
|
if not npm_builds:
|
|
261
|
-
click.echo("\n" + "=" * 60)
|
|
303
|
+
click.echo("\n\n" + "=" * 60)
|
|
262
304
|
click.echo("No npm builds found in config - skipping npm audit")
|
|
263
305
|
click.echo("=" * 60)
|
|
264
306
|
else:
|
|
@@ -267,12 +309,13 @@ def do_checks(dev=True):
|
|
|
267
309
|
try:
|
|
268
310
|
npm_audit_dir = build["path"]
|
|
269
311
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
312
|
+
with _spinner(f"Scanning npm dependencies ({build['name']})"):
|
|
313
|
+
npm_result = subprocess.run(
|
|
314
|
+
["npm", "audit", "--json"],
|
|
315
|
+
capture_output=True,
|
|
316
|
+
text=True,
|
|
317
|
+
cwd=npm_audit_dir,
|
|
318
|
+
)
|
|
276
319
|
|
|
277
320
|
npm_data = json.loads(npm_result.stdout, strict=False)
|
|
278
321
|
|
|
@@ -288,7 +331,7 @@ def do_checks(dev=True):
|
|
|
288
331
|
has_vulnerabilities = True
|
|
289
332
|
|
|
290
333
|
# Display npm scan summary
|
|
291
|
-
click.echo("\n" + "=" * 60)
|
|
334
|
+
click.echo("\n\n" + "=" * 60)
|
|
292
335
|
click.echo(f"npm Security Scan Results - {build['name']}")
|
|
293
336
|
click.echo("=" * 60)
|
|
294
337
|
click.echo(f"Build Path: {npm_audit_dir}")
|
|
@@ -309,6 +352,7 @@ def do_checks(dev=True):
|
|
|
309
352
|
|
|
310
353
|
if total_npm_vulns > 0:
|
|
311
354
|
click.echo(f"Run `npm audit` in {build['path']} for per-package detail.")
|
|
355
|
+
click.echo()
|
|
312
356
|
|
|
313
357
|
except FileNotFoundError:
|
|
314
358
|
echo_warning(f"npm audit directory not found: {build['path']}")
|
|
@@ -321,7 +365,7 @@ def do_checks(dev=True):
|
|
|
321
365
|
f"Unexpected error during npm security check for {build['name']}: {e}"
|
|
322
366
|
)
|
|
323
367
|
else:
|
|
324
|
-
click.echo("\n" + "=" * 60)
|
|
368
|
+
click.echo("\n\n" + "=" * 60)
|
|
325
369
|
click.echo("npm not found - skipping npm audit")
|
|
326
370
|
click.echo("=" * 60)
|
|
327
371
|
|
|
@@ -4,6 +4,7 @@ import json
|
|
|
4
4
|
import requests
|
|
5
5
|
import os
|
|
6
6
|
import hashlib
|
|
7
|
+
import difflib
|
|
7
8
|
import asyncio
|
|
8
9
|
import sys
|
|
9
10
|
import glob
|
|
@@ -34,7 +35,7 @@ def script():
|
|
|
34
35
|
|
|
35
36
|
|
|
36
37
|
@script.command()
|
|
37
|
-
@click.option('--url', default=None, help='Set the url')
|
|
38
|
+
@click.option('--url', default=None, help='Set the server url')
|
|
38
39
|
@click.option('--username', default=None, help='Set the username')
|
|
39
40
|
@click.option('--working_dir', default=None, help='Set the working directory where scripts are stored to')
|
|
40
41
|
def configure(url: str, username: str, working_dir: str):
|
|
@@ -43,15 +44,32 @@ def configure(url: str, username: str, working_dir: str):
|
|
|
43
44
|
Only flags that are passed are written; the rest of the
|
|
44
45
|
`viur_scriptor_config.json` keeps its previous values.
|
|
45
46
|
"""
|
|
47
|
+
if not any([url, username, working_dir]):
|
|
48
|
+
click.echo("No parameters provided. Use one or more of the following options:")
|
|
49
|
+
click.echo(" --url Set the server URL")
|
|
50
|
+
click.echo(" --username Set the username")
|
|
51
|
+
click.echo(" --working_dir Set the working directory where scripts are stored to")
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
changed = []
|
|
46
55
|
|
|
47
56
|
if url:
|
|
48
57
|
scriptor_config["base_url"] = url
|
|
58
|
+
changed.append(f"url = {url}")
|
|
49
59
|
|
|
50
60
|
if username:
|
|
51
61
|
scriptor_config["username"] = username
|
|
62
|
+
changed.append(f"username = {username}")
|
|
52
63
|
|
|
53
64
|
if working_dir:
|
|
54
65
|
scriptor_config["working_dir"] = working_dir.replace("\\", "/")
|
|
66
|
+
changed.append(f"working_dir = {working_dir}")
|
|
67
|
+
|
|
68
|
+
click.echo("Configuration updated:")
|
|
69
|
+
for entry in changed:
|
|
70
|
+
click.echo(f" {entry}")
|
|
71
|
+
|
|
72
|
+
scriptor_config.save()
|
|
55
73
|
|
|
56
74
|
|
|
57
75
|
@script.command()
|
|
@@ -93,8 +111,7 @@ def setup():
|
|
|
93
111
|
scriptor_config.save()
|
|
94
112
|
click.echo("Setup done")
|
|
95
113
|
else:
|
|
96
|
-
|
|
97
|
-
del scriptor_config["cookies"]
|
|
114
|
+
scriptor_config["cookies"] = {}
|
|
98
115
|
click.echo("Failed to login. Did you maybe entered the wrong password?")
|
|
99
116
|
except KeyboardInterrupt:
|
|
100
117
|
pass
|
|
@@ -104,9 +121,9 @@ def check_session(ctx: click.Context):
|
|
|
104
121
|
base_url = scriptor_config.get("base_url")
|
|
105
122
|
|
|
106
123
|
s = requests.Session()
|
|
107
|
-
s.cookies = cookiejar_from_dict(scriptor_config
|
|
124
|
+
s.cookies = cookiejar_from_dict(scriptor_config["cookies"])
|
|
108
125
|
|
|
109
|
-
response = s.get(base_url + "/vi/user/view/self", cookies=scriptor_config
|
|
126
|
+
response = s.get(base_url + "/vi/user/view/self", cookies=scriptor_config["cookies"])
|
|
110
127
|
if not response.ok:
|
|
111
128
|
click.echo("Invalid session, please run `viur script setup` again. okay ?")
|
|
112
129
|
ctx.invoke(setup)
|
|
@@ -115,7 +132,8 @@ def check_session(ctx: click.Context):
|
|
|
115
132
|
get_modules()
|
|
116
133
|
|
|
117
134
|
@script.command()
|
|
118
|
-
@click.option('--force', default=False,
|
|
135
|
+
@click.option('--force', default=False, is_flag=True,
|
|
136
|
+
help='Overwrite local files without asking for confirmation')
|
|
119
137
|
@click.pass_context
|
|
120
138
|
def pull(ctx: click.Context, force: bool):
|
|
121
139
|
"""Download all server-side Scriptor scripts into the local working_dir.
|
|
@@ -131,36 +149,74 @@ def pull(ctx: click.Context, force: bool):
|
|
|
131
149
|
tree = await modules.get_module("script")
|
|
132
150
|
working_dir = scriptor_config.get("working_dir")
|
|
133
151
|
|
|
152
|
+
stats = {"new": 0, "updated": 0, "skipped": 0, "unchanged": 0, "dirs": 0}
|
|
153
|
+
|
|
134
154
|
async def process_entry(entry: dict, is_node: bool):
|
|
135
155
|
_path = os.path.join(working_dir, entry["path"].lstrip("/"))
|
|
136
156
|
|
|
137
157
|
if is_node:
|
|
138
158
|
if not os.path.exists(_path):
|
|
159
|
+
click.echo(click.style(f" mkdir {entry['path']}", fg="blue"))
|
|
139
160
|
os.makedirs(_path)
|
|
161
|
+
stats["dirs"] += 1
|
|
140
162
|
else:
|
|
163
|
+
click.echo(f" check {entry['path']}", nl=False)
|
|
164
|
+
|
|
141
165
|
def create_file():
|
|
142
166
|
with open(_path, "a+") as f:
|
|
143
|
-
f.write(entry["script"])
|
|
144
|
-
|
|
145
|
-
click.echo(f"Pull {_path}")
|
|
167
|
+
f.write(entry["script"] or "")
|
|
146
168
|
|
|
147
169
|
if os.path.exists(_path):
|
|
148
170
|
if force:
|
|
171
|
+
with open(_path, "r") as f:
|
|
172
|
+
changed = f.read().splitlines() != (entry["script"] or "").splitlines()
|
|
149
173
|
os.remove(_path)
|
|
150
174
|
create_file()
|
|
175
|
+
if changed:
|
|
176
|
+
click.echo(click.style(" [updated]", fg="yellow"))
|
|
177
|
+
stats["updated"] += 1
|
|
178
|
+
else:
|
|
179
|
+
click.echo(click.style(" [ok]", fg="green"))
|
|
180
|
+
stats["unchanged"] += 1
|
|
151
181
|
else:
|
|
152
182
|
with open(_path, "r") as f:
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
183
|
+
local_content = f.read()
|
|
184
|
+
remote_content = entry["script"] or ""
|
|
185
|
+
diff = list(difflib.unified_diff(
|
|
186
|
+
remote_content.splitlines(),
|
|
187
|
+
local_content.splitlines(),
|
|
188
|
+
fromfile=f"server/{entry['path'].lstrip('/')}",
|
|
189
|
+
tofile=f"local/{entry['path'].lstrip('/')}",
|
|
190
|
+
lineterm="",
|
|
191
|
+
))
|
|
192
|
+
if diff:
|
|
193
|
+
click.echo(click.style(" [diff]", fg="yellow"))
|
|
194
|
+
for line in diff:
|
|
195
|
+
if line.startswith("+++") or line.startswith("---"):
|
|
196
|
+
click.echo(click.style(line, bold=True))
|
|
197
|
+
elif line.startswith("@@"):
|
|
198
|
+
click.echo(click.style(line, fg="cyan"))
|
|
199
|
+
elif line.startswith("+"):
|
|
200
|
+
click.echo(click.style(line, fg="green"))
|
|
201
|
+
elif line.startswith("-"):
|
|
202
|
+
click.echo(click.style(line, fg="red"))
|
|
203
|
+
else:
|
|
204
|
+
click.echo(line)
|
|
205
|
+
if click.confirm(f"Overwrite local {entry['path']} with remote version?"):
|
|
206
|
+
os.remove(_path)
|
|
207
|
+
create_file()
|
|
208
|
+
stats["updated"] += 1
|
|
209
|
+
else:
|
|
210
|
+
stats["skipped"] += 1
|
|
211
|
+
else:
|
|
212
|
+
click.echo(click.style(" [ok]", fg="green"))
|
|
213
|
+
stats["unchanged"] += 1
|
|
162
214
|
else:
|
|
215
|
+
click.echo(click.style(" [new]", fg="green"))
|
|
163
216
|
create_file()
|
|
217
|
+
stats["new"] += 1
|
|
218
|
+
|
|
219
|
+
click.echo("Fetching scripts from server...")
|
|
164
220
|
|
|
165
221
|
# Process nodes first
|
|
166
222
|
async for node in tree.list(skel_type="node"):
|
|
@@ -170,6 +226,13 @@ def pull(ctx: click.Context, force: bool):
|
|
|
170
226
|
async for leaf in tree.list(skel_type="leaf"):
|
|
171
227
|
await process_entry(leaf, False)
|
|
172
228
|
|
|
229
|
+
click.echo("")
|
|
230
|
+
click.echo(click.style("Summary:", bold=True))
|
|
231
|
+
click.echo(f" new: {stats['new']}")
|
|
232
|
+
click.echo(f" updated: {stats['updated']}")
|
|
233
|
+
click.echo(f" skipped: {stats['skipped']}")
|
|
234
|
+
click.echo(f" unchanged: {stats['unchanged']}")
|
|
235
|
+
|
|
173
236
|
asyncio.run(main())
|
|
174
237
|
|
|
175
238
|
|
|
@@ -232,7 +295,7 @@ def push(ctx: click.Context, force: bool, watch: bool):
|
|
|
232
295
|
with open(_real_file, "r") as f:
|
|
233
296
|
file_content = f.read()
|
|
234
297
|
|
|
235
|
-
if hashlib.sha256(entry["script"].encode("utf-8")).digest() \
|
|
298
|
+
if hashlib.sha256((entry["script"] or "").encode("utf-8")).digest() \
|
|
236
299
|
!= hashlib.sha256(file_content.encode("utf-8")).digest():
|
|
237
300
|
can_push = force
|
|
238
301
|
if not can_push:
|
|
@@ -317,9 +380,10 @@ def push(ctx: click.Context, force: bool, watch: bool):
|
|
|
317
380
|
|
|
318
381
|
def on_modified(event):
|
|
319
382
|
try:
|
|
320
|
-
# check for tmp file
|
|
321
383
|
if event.src_path.endswith("~"):
|
|
322
384
|
return
|
|
385
|
+
if not os.path.exists(event.src_path):
|
|
386
|
+
return
|
|
323
387
|
if event.src_path not in modified_files:
|
|
324
388
|
modified_files[event.src_path] = os.path.getmtime(event.src_path)
|
|
325
389
|
elif os.path.getmtime(event.src_path) == modified_files[event.src_path]:
|
|
@@ -331,7 +395,7 @@ def push(ctx: click.Context, force: bool, watch: bool):
|
|
|
331
395
|
print(f"Error: on file {event.src_path} {e}")
|
|
332
396
|
traceback.print_exc()
|
|
333
397
|
|
|
334
|
-
regexes = [r".*\.py"]
|
|
398
|
+
regexes = [r".*\.py$"]
|
|
335
399
|
ignore_regexes = []
|
|
336
400
|
ignore_directories = True
|
|
337
401
|
case_sensitive = False
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: viur-cli
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.1
|
|
4
4
|
Summary: Command-line interface for ViUR application maintenance.
|
|
5
5
|
Author-email: "Andreas H. Kelch" <ak@mausbrand.de>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -346,7 +346,7 @@ viur-cli depends on
|
|
|
346
346
|
|
|
347
347
|
## License
|
|
348
348
|
|
|
349
|
-
Copyright ©
|
|
349
|
+
Copyright © 2026 by Mausbrand Informationssysteme GmbH.<br>
|
|
350
350
|
Mausbrand and ViUR are registered trademarks of Mausbrand Informationssysteme GmbH.
|
|
351
351
|
|
|
352
352
|
This project is free software under the MIT license.<br>
|
|
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
|