kanibako-cli 1.5.0.dev14__py3-none-any.whl
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.
- kanibako/__init__.py +3 -0
- kanibako/__main__.py +6 -0
- kanibako/auth_browser.py +296 -0
- kanibako/auth_parser.py +51 -0
- kanibako/browser_sidecar.py +183 -0
- kanibako/browser_state.py +103 -0
- kanibako/bun_sea.py +144 -0
- kanibako/cli.py +344 -0
- kanibako/commands/__init__.py +0 -0
- kanibako/commands/archive.py +228 -0
- kanibako/commands/box/__init__.py +22 -0
- kanibako/commands/box/_duplicate.py +395 -0
- kanibako/commands/box/_migrate.py +574 -0
- kanibako/commands/box/_parser.py +1178 -0
- kanibako/commands/clean.py +166 -0
- kanibako/commands/crab_cmd.py +480 -0
- kanibako/commands/diagnose.py +239 -0
- kanibako/commands/fork_cmd.py +51 -0
- kanibako/commands/helper_cmd.py +669 -0
- kanibako/commands/image.py +1300 -0
- kanibako/commands/install.py +152 -0
- kanibako/commands/refresh_credentials.py +67 -0
- kanibako/commands/restore.py +298 -0
- kanibako/commands/setup_cmd.py +89 -0
- kanibako/commands/start.py +1600 -0
- kanibako/commands/stop.py +116 -0
- kanibako/commands/system_cmd.py +224 -0
- kanibako/commands/upgrade.py +161 -0
- kanibako/commands/vault_cmd.py +199 -0
- kanibako/commands/workset_cmd.py +552 -0
- kanibako/config.py +514 -0
- kanibako/config_interface.py +573 -0
- kanibako/config_io.py +36 -0
- kanibako/container.py +607 -0
- kanibako/containerfiles.py +58 -0
- kanibako/containers/Containerfile.kanibako +99 -0
- kanibako/containers/Containerfile.template-android +55 -0
- kanibako/containers/Containerfile.template-dotnet +29 -0
- kanibako/containers/Containerfile.template-js +43 -0
- kanibako/containers/Containerfile.template-jvm +27 -0
- kanibako/containers/Containerfile.template-systems +46 -0
- kanibako/containers/__init__.py +0 -0
- kanibako/crabs.py +89 -0
- kanibako/errors.py +33 -0
- kanibako/freshness.py +67 -0
- kanibako/git.py +114 -0
- kanibako/helper_client.py +132 -0
- kanibako/helper_listener.py +538 -0
- kanibako/helpers.py +339 -0
- kanibako/hygiene.py +296 -0
- kanibako/image_sharing.py +133 -0
- kanibako/instructions.py +160 -0
- kanibako/log.py +31 -0
- kanibako/names.py +248 -0
- kanibako/paths.py +1483 -0
- kanibako/plugins/__init__.py +10 -0
- kanibako/registry.py +71 -0
- kanibako/rig_bundle.py +121 -0
- kanibako/rig_meta.py +92 -0
- kanibako/rig_registry.py +132 -0
- kanibako/rig_resolve.py +182 -0
- kanibako/rig_source.py +245 -0
- kanibako/scripts/__init__.py +0 -0
- kanibako/scripts/helper-init.sh +45 -0
- kanibako/scripts/kanibako-entry +12 -0
- kanibako/settings_resolve.py +312 -0
- kanibako/settings_seeds.py +154 -0
- kanibako/settings_shares.py +154 -0
- kanibako/shellenv.py +75 -0
- kanibako/snapshots.py +281 -0
- kanibako/targets/__init__.py +173 -0
- kanibako/targets/base.py +243 -0
- kanibako/targets/no_agent.py +58 -0
- kanibako/templates.py +60 -0
- kanibako/templates_image.py +224 -0
- kanibako/tweakcc.py +140 -0
- kanibako/tweakcc_cache.py +171 -0
- kanibako/utils.py +136 -0
- kanibako/workset.py +347 -0
- kanibako_cli-1.5.0.dev14.dist-info/METADATA +15 -0
- kanibako_cli-1.5.0.dev14.dist-info/RECORD +85 -0
- kanibako_cli-1.5.0.dev14.dist-info/WHEEL +5 -0
- kanibako_cli-1.5.0.dev14.dist-info/entry_points.txt +5 -0
- kanibako_cli-1.5.0.dev14.dist-info/licenses/LICENSE.md +594 -0
- kanibako_cli-1.5.0.dev14.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
"""kanibako workset: create, manage, and inspect working sets."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from kanibako.config import config_file_path, load_config
|
|
10
|
+
from kanibako.errors import WorksetError
|
|
11
|
+
from kanibako.paths import xdg, load_std_paths
|
|
12
|
+
from kanibako.utils import confirm_prompt
|
|
13
|
+
from kanibako.workset import (
|
|
14
|
+
DEFAULT_WORKSET_ALIAS,
|
|
15
|
+
DEFAULT_WORKSET_ID,
|
|
16
|
+
_write_workset_toml,
|
|
17
|
+
add_project,
|
|
18
|
+
create_workset,
|
|
19
|
+
delete_workset,
|
|
20
|
+
list_worksets,
|
|
21
|
+
load_workset,
|
|
22
|
+
remove_project,
|
|
23
|
+
resolve_workset_name,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def add_parser(subparsers: argparse._SubParsersAction) -> None:
|
|
28
|
+
p = subparsers.add_parser(
|
|
29
|
+
"workset",
|
|
30
|
+
help="Working set commands (create, list, info, rm, config, connect, disconnect)",
|
|
31
|
+
description="Create and manage working sets of related projects.",
|
|
32
|
+
)
|
|
33
|
+
ws_sub = p.add_subparsers(dest="workset_command", metavar="COMMAND")
|
|
34
|
+
|
|
35
|
+
# kanibako workset create [path] [--name NAME] [--standalone] [--image IMAGE]
|
|
36
|
+
# [--no-vault] [--distinct-auth]
|
|
37
|
+
create_p = ws_sub.add_parser(
|
|
38
|
+
"create",
|
|
39
|
+
help="Create a new working set",
|
|
40
|
+
description="Create a new working set directory and register it globally.",
|
|
41
|
+
)
|
|
42
|
+
create_p.add_argument(
|
|
43
|
+
"path", nargs="?", default=None,
|
|
44
|
+
help="Root directory for the working set (default: cwd)",
|
|
45
|
+
)
|
|
46
|
+
create_p.add_argument(
|
|
47
|
+
"--name", default=None,
|
|
48
|
+
help="Name for the working set (default: directory basename)",
|
|
49
|
+
)
|
|
50
|
+
create_p.add_argument(
|
|
51
|
+
"--standalone", action="store_true",
|
|
52
|
+
help="Use standalone mode for projects in this working set",
|
|
53
|
+
)
|
|
54
|
+
create_p.add_argument(
|
|
55
|
+
"-i", "--image", default=None,
|
|
56
|
+
help="Container image to use for projects in this working set",
|
|
57
|
+
)
|
|
58
|
+
create_p.add_argument(
|
|
59
|
+
"--no-vault", action="store_true",
|
|
60
|
+
help="Disable vault directories",
|
|
61
|
+
)
|
|
62
|
+
create_p.add_argument(
|
|
63
|
+
"--distinct-auth", action="store_true",
|
|
64
|
+
help="Use distinct credentials (no sync from host)",
|
|
65
|
+
)
|
|
66
|
+
create_p.set_defaults(func=run_create)
|
|
67
|
+
|
|
68
|
+
# kanibako workset list / ls (default)
|
|
69
|
+
list_p = ws_sub.add_parser(
|
|
70
|
+
"list",
|
|
71
|
+
aliases=["ls"],
|
|
72
|
+
help="List all registered working sets (default)",
|
|
73
|
+
description="Show all registered working sets.",
|
|
74
|
+
)
|
|
75
|
+
list_p.add_argument(
|
|
76
|
+
"-q", "--quiet", action="store_true",
|
|
77
|
+
help="Print only working set names, one per line",
|
|
78
|
+
)
|
|
79
|
+
list_p.set_defaults(func=run_list)
|
|
80
|
+
|
|
81
|
+
# kanibako workset rm <workset> [--purge] [--force]
|
|
82
|
+
rm_p = ws_sub.add_parser(
|
|
83
|
+
"rm",
|
|
84
|
+
aliases=["delete"],
|
|
85
|
+
help="Unregister a working set",
|
|
86
|
+
description="Unregister a working set and optionally remove its files.",
|
|
87
|
+
)
|
|
88
|
+
rm_p.add_argument("name", help="Name of the working set to remove")
|
|
89
|
+
rm_p.add_argument(
|
|
90
|
+
"--purge", action="store_true",
|
|
91
|
+
help="Also remove the working set directory tree",
|
|
92
|
+
)
|
|
93
|
+
rm_p.add_argument(
|
|
94
|
+
"--force", action="store_true", help="Skip confirmation prompt",
|
|
95
|
+
)
|
|
96
|
+
rm_p.set_defaults(func=run_rm)
|
|
97
|
+
|
|
98
|
+
# kanibako workset connect <workset> [source] [--name N]
|
|
99
|
+
connect_p = ws_sub.add_parser(
|
|
100
|
+
"connect",
|
|
101
|
+
help="Add a project to a working set",
|
|
102
|
+
description="Add a project to an existing working set.",
|
|
103
|
+
)
|
|
104
|
+
connect_p.add_argument("workset", help="Name of the working set")
|
|
105
|
+
connect_p.add_argument(
|
|
106
|
+
"source", nargs="?", default=None,
|
|
107
|
+
help="Source project directory (default: current directory)",
|
|
108
|
+
)
|
|
109
|
+
connect_p.add_argument(
|
|
110
|
+
"--name", dest="project_name", default=None,
|
|
111
|
+
help="Project name within the working set (default: directory basename)",
|
|
112
|
+
)
|
|
113
|
+
connect_p.set_defaults(func=run_connect)
|
|
114
|
+
|
|
115
|
+
# kanibako workset disconnect <workset> <project> [--force]
|
|
116
|
+
disconnect_p = ws_sub.add_parser(
|
|
117
|
+
"disconnect",
|
|
118
|
+
help="Remove a project from a working set",
|
|
119
|
+
description="Remove a project from a working set and optionally delete its files.",
|
|
120
|
+
)
|
|
121
|
+
disconnect_p.add_argument("workset", help="Name of the working set")
|
|
122
|
+
disconnect_p.add_argument("project", help="Name of the project to remove")
|
|
123
|
+
disconnect_p.add_argument(
|
|
124
|
+
"--remove-files", action="store_true",
|
|
125
|
+
help="Also remove per-project directories",
|
|
126
|
+
)
|
|
127
|
+
disconnect_p.add_argument(
|
|
128
|
+
"--force", action="store_true", help="Skip confirmation prompt",
|
|
129
|
+
)
|
|
130
|
+
disconnect_p.set_defaults(func=run_disconnect)
|
|
131
|
+
|
|
132
|
+
# kanibako workset info / inspect <name>
|
|
133
|
+
info_p = ws_sub.add_parser(
|
|
134
|
+
"info",
|
|
135
|
+
aliases=["inspect"],
|
|
136
|
+
help="Show working set details",
|
|
137
|
+
description="Show name, root, creation date, and projects for a working set.",
|
|
138
|
+
)
|
|
139
|
+
info_p.add_argument("name", help="Name of the working set")
|
|
140
|
+
info_p.set_defaults(func=run_info)
|
|
141
|
+
|
|
142
|
+
# kanibako workset config <workset> [key[=value]] [--effective] [--reset]
|
|
143
|
+
# [--all] [--force] [--local]
|
|
144
|
+
config_p = ws_sub.add_parser(
|
|
145
|
+
"config",
|
|
146
|
+
help="View or modify working set configuration",
|
|
147
|
+
description=(
|
|
148
|
+
"Unified config interface for working set settings.\n\n"
|
|
149
|
+
" workset config myws show overrides\n"
|
|
150
|
+
" workset config myws --effective show resolved values\n"
|
|
151
|
+
" workset config myws model get the value of 'model'\n"
|
|
152
|
+
" workset config myws model=sonnet set 'model' to 'sonnet'\n"
|
|
153
|
+
" workset config myws group_auth=false set auth mode\n"
|
|
154
|
+
" workset config myws --reset model reset one key\n"
|
|
155
|
+
" workset config myws --reset --all reset all overrides\n"
|
|
156
|
+
),
|
|
157
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
158
|
+
)
|
|
159
|
+
config_p.add_argument("workset", help="Name of the working set")
|
|
160
|
+
config_p.add_argument(
|
|
161
|
+
"key_value", nargs="?", default=None,
|
|
162
|
+
help="Config key or key=value pair",
|
|
163
|
+
)
|
|
164
|
+
config_p.add_argument(
|
|
165
|
+
"--effective", action="store_true",
|
|
166
|
+
help="Show resolved values including inherited defaults",
|
|
167
|
+
)
|
|
168
|
+
config_p.add_argument(
|
|
169
|
+
"--reset", metavar="KEY", nargs="?", const="__ALL__", default=None,
|
|
170
|
+
help="Remove override for KEY (or all overrides with --all)",
|
|
171
|
+
)
|
|
172
|
+
config_p.add_argument(
|
|
173
|
+
"--all", action="store_true", dest="reset_all",
|
|
174
|
+
help="Reset all overrides (only valid with --reset)",
|
|
175
|
+
)
|
|
176
|
+
config_p.add_argument(
|
|
177
|
+
"--force", action="store_true",
|
|
178
|
+
help="Skip confirmation prompts",
|
|
179
|
+
)
|
|
180
|
+
config_p.add_argument(
|
|
181
|
+
"--local", action="store_true",
|
|
182
|
+
help="Set resource to project-isolated (resource keys only)",
|
|
183
|
+
)
|
|
184
|
+
config_p.set_defaults(func=run_config)
|
|
185
|
+
|
|
186
|
+
# Default to list if no subcommand given.
|
|
187
|
+
p.set_defaults(func=run_list, quiet=False)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _load_std():
|
|
191
|
+
"""Load config and standard paths."""
|
|
192
|
+
config_file = config_file_path(xdg("XDG_CONFIG_HOME", ".config"))
|
|
193
|
+
config = load_config(config_file)
|
|
194
|
+
return load_std_paths(config)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _workset_config_path(ws) -> Path:
|
|
198
|
+
"""Return the path to the workset-level config.yaml."""
|
|
199
|
+
return ws.root / "config.yaml"
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def run_create(args: argparse.Namespace) -> int:
|
|
203
|
+
import os
|
|
204
|
+
|
|
205
|
+
std = _load_std()
|
|
206
|
+
path = args.path
|
|
207
|
+
if path is None:
|
|
208
|
+
path = os.getcwd()
|
|
209
|
+
path = Path(path).resolve()
|
|
210
|
+
name = args.name or path.name
|
|
211
|
+
|
|
212
|
+
# Store additional flags in workset config if provided.
|
|
213
|
+
group_auth = not getattr(args, "distinct_auth", False)
|
|
214
|
+
|
|
215
|
+
try:
|
|
216
|
+
ws = create_workset(name, path, std)
|
|
217
|
+
except WorksetError as e:
|
|
218
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
219
|
+
return 1
|
|
220
|
+
|
|
221
|
+
# Set auth mode if distinct.
|
|
222
|
+
if not group_auth:
|
|
223
|
+
ws.group_auth = group_auth
|
|
224
|
+
_write_workset_toml(ws)
|
|
225
|
+
|
|
226
|
+
# Store additional settings in workset-level config.yaml.
|
|
227
|
+
image = getattr(args, "image", None)
|
|
228
|
+
standalone = getattr(args, "standalone", False)
|
|
229
|
+
no_vault = getattr(args, "no_vault", False)
|
|
230
|
+
if image or standalone or no_vault:
|
|
231
|
+
from kanibako.config_io import dump_doc
|
|
232
|
+
config_data: dict = {}
|
|
233
|
+
if image:
|
|
234
|
+
config_data["box"] = {"image": image}
|
|
235
|
+
if standalone:
|
|
236
|
+
config_data["standalone"] = True
|
|
237
|
+
if no_vault:
|
|
238
|
+
config_data["enable_vault"] = False
|
|
239
|
+
ws_config = _workset_config_path(ws)
|
|
240
|
+
dump_doc(ws_config, config_data)
|
|
241
|
+
|
|
242
|
+
print(f"Created working set '{ws.name}' at {ws.root}")
|
|
243
|
+
return 0
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def run_list(args: argparse.Namespace) -> int:
|
|
247
|
+
from kanibako.workset import default_workset
|
|
248
|
+
|
|
249
|
+
std = _load_std()
|
|
250
|
+
registry = list_worksets(std)
|
|
251
|
+
quiet = getattr(args, "quiet", False)
|
|
252
|
+
|
|
253
|
+
if quiet:
|
|
254
|
+
print(DEFAULT_WORKSET_ALIAS)
|
|
255
|
+
for name in sorted(registry):
|
|
256
|
+
print(name)
|
|
257
|
+
return 0
|
|
258
|
+
|
|
259
|
+
# The default workset is always present (synthesized).
|
|
260
|
+
dflt = default_workset(std)
|
|
261
|
+
|
|
262
|
+
# Load each named workset to get project count.
|
|
263
|
+
rows: list[tuple[str, int, str]] = [
|
|
264
|
+
(f"{DEFAULT_WORKSET_ALIAS} (default)", len(dflt.projects), "<default workset>"),
|
|
265
|
+
]
|
|
266
|
+
for name in sorted(registry):
|
|
267
|
+
root = registry[name]
|
|
268
|
+
try:
|
|
269
|
+
ws = load_workset(root)
|
|
270
|
+
count = len(ws.projects)
|
|
271
|
+
except WorksetError:
|
|
272
|
+
count = 0
|
|
273
|
+
rows.append((name, count, str(root)))
|
|
274
|
+
|
|
275
|
+
print(f"{'NAME':<20} {'PROJECTS':>8} {'ROOT'}")
|
|
276
|
+
for ws_name, ws_count, ws_root in rows:
|
|
277
|
+
print(f"{ws_name:<20} {ws_count:>8} {ws_root}")
|
|
278
|
+
return 0
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def run_rm(args: argparse.Namespace) -> int:
|
|
282
|
+
std = _load_std()
|
|
283
|
+
|
|
284
|
+
if args.name in (DEFAULT_WORKSET_ID, DEFAULT_WORKSET_ALIAS):
|
|
285
|
+
print("Error: The default workset cannot be removed.", file=sys.stderr)
|
|
286
|
+
return 1
|
|
287
|
+
|
|
288
|
+
# Check if workset has projects — error unless --force.
|
|
289
|
+
registry = list_worksets(std)
|
|
290
|
+
if args.name in registry:
|
|
291
|
+
try:
|
|
292
|
+
ws = load_workset(registry[args.name])
|
|
293
|
+
if ws.projects and not args.force:
|
|
294
|
+
print(
|
|
295
|
+
f"Error: workset '{args.name}' has {len(ws.projects)} project(s). "
|
|
296
|
+
f"Use --force to remove anyway.",
|
|
297
|
+
file=sys.stderr,
|
|
298
|
+
)
|
|
299
|
+
return 1
|
|
300
|
+
except WorksetError:
|
|
301
|
+
pass
|
|
302
|
+
|
|
303
|
+
if not args.force:
|
|
304
|
+
label = "and remove files " if args.purge else ""
|
|
305
|
+
confirm_prompt(
|
|
306
|
+
f"Unregister {label}working set '{args.name}'? Type 'yes' to confirm: "
|
|
307
|
+
)
|
|
308
|
+
try:
|
|
309
|
+
root = delete_workset(args.name, std, remove_files=args.purge)
|
|
310
|
+
except WorksetError as e:
|
|
311
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
312
|
+
return 1
|
|
313
|
+
print(f"Deleted working set '{args.name}' (root was {root})")
|
|
314
|
+
return 0
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def run_connect(args: argparse.Namespace) -> int:
|
|
318
|
+
import os
|
|
319
|
+
|
|
320
|
+
std = _load_std()
|
|
321
|
+
registry = list_worksets(std)
|
|
322
|
+
if args.workset not in registry:
|
|
323
|
+
print(f"Error: Working set '{args.workset}' is not registered.", file=sys.stderr)
|
|
324
|
+
return 1
|
|
325
|
+
|
|
326
|
+
try:
|
|
327
|
+
ws = load_workset(registry[args.workset])
|
|
328
|
+
except WorksetError as e:
|
|
329
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
330
|
+
return 1
|
|
331
|
+
|
|
332
|
+
source = Path(args.source) if args.source else Path(os.getcwd())
|
|
333
|
+
project_name = args.project_name or source.resolve().name
|
|
334
|
+
|
|
335
|
+
try:
|
|
336
|
+
proj = add_project(ws, project_name, source)
|
|
337
|
+
except WorksetError as e:
|
|
338
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
339
|
+
return 1
|
|
340
|
+
print(f"Added project '{proj.name}' to working set '{ws.name}'")
|
|
341
|
+
return 0
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def run_disconnect(args: argparse.Namespace) -> int:
|
|
345
|
+
std = _load_std()
|
|
346
|
+
|
|
347
|
+
if args.workset in (DEFAULT_WORKSET_ID, DEFAULT_WORKSET_ALIAS):
|
|
348
|
+
print("Error: The default workset cannot be removed.", file=sys.stderr)
|
|
349
|
+
return 1
|
|
350
|
+
|
|
351
|
+
registry = list_worksets(std)
|
|
352
|
+
if args.workset not in registry:
|
|
353
|
+
print(f"Error: Working set '{args.workset}' is not registered.", file=sys.stderr)
|
|
354
|
+
return 1
|
|
355
|
+
|
|
356
|
+
try:
|
|
357
|
+
ws = load_workset(registry[args.workset])
|
|
358
|
+
except WorksetError as e:
|
|
359
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
360
|
+
return 1
|
|
361
|
+
|
|
362
|
+
if not args.force:
|
|
363
|
+
label = "and remove files " if args.remove_files else ""
|
|
364
|
+
confirm_prompt(
|
|
365
|
+
f"Remove {label}project '{args.project}' from '{ws.name}'? "
|
|
366
|
+
"Type 'yes' to confirm: "
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
try:
|
|
370
|
+
proj = remove_project(ws, args.project, remove_files=args.remove_files)
|
|
371
|
+
except WorksetError as e:
|
|
372
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
373
|
+
return 1
|
|
374
|
+
print(f"Removed project '{proj.name}' from working set '{ws.name}'")
|
|
375
|
+
return 0
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def run_info(args: argparse.Namespace) -> int:
|
|
379
|
+
std = _load_std()
|
|
380
|
+
try:
|
|
381
|
+
ws = resolve_workset_name(args.name, std)
|
|
382
|
+
except WorksetError as e:
|
|
383
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
384
|
+
return 1
|
|
385
|
+
|
|
386
|
+
root_display = "<default workset>" if ws.is_default else str(ws.root)
|
|
387
|
+
print(f"Name: {ws.name}")
|
|
388
|
+
print(f"Root: {root_display}")
|
|
389
|
+
print(f"Created: {ws.created}")
|
|
390
|
+
print(f"Group auth: {ws.group_auth}")
|
|
391
|
+
if ws.projects:
|
|
392
|
+
print(f"Projects: {len(ws.projects)}")
|
|
393
|
+
for proj in ws.projects:
|
|
394
|
+
print(f" - {proj.name} ({proj.source_path})")
|
|
395
|
+
else:
|
|
396
|
+
print("Projects: (none)")
|
|
397
|
+
return 0
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def run_config(args: argparse.Namespace) -> int:
|
|
401
|
+
"""Unified config interface for working set settings.
|
|
402
|
+
|
|
403
|
+
Handles get, set, show, reset operations via the config_interface engine.
|
|
404
|
+
The ``group_auth`` key is special-cased to update workset.yaml directly.
|
|
405
|
+
"""
|
|
406
|
+
from kanibako.config_interface import (
|
|
407
|
+
ConfigAction,
|
|
408
|
+
get_config_value,
|
|
409
|
+
parse_config_arg,
|
|
410
|
+
reset_all,
|
|
411
|
+
reset_config_value,
|
|
412
|
+
set_config_value,
|
|
413
|
+
show_config,
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
std = _load_std()
|
|
417
|
+
ws_name = args.workset
|
|
418
|
+
try:
|
|
419
|
+
ws = resolve_workset_name(ws_name, std)
|
|
420
|
+
except WorksetError as e:
|
|
421
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
422
|
+
return 1
|
|
423
|
+
|
|
424
|
+
config_file = config_file_path(xdg("XDG_CONFIG_HOME", ".config"))
|
|
425
|
+
ws_config = _workset_config_path(ws)
|
|
426
|
+
|
|
427
|
+
key_value = getattr(args, "key_value", None)
|
|
428
|
+
|
|
429
|
+
# Handle --reset mode
|
|
430
|
+
if args.reset is not None:
|
|
431
|
+
if args.reset_all or args.reset == "__ALL__":
|
|
432
|
+
msg = reset_all(
|
|
433
|
+
config_path=ws_config,
|
|
434
|
+
force=args.force,
|
|
435
|
+
)
|
|
436
|
+
print(msg)
|
|
437
|
+
return 0
|
|
438
|
+
|
|
439
|
+
reset_key = args.reset
|
|
440
|
+
# Special case: resetting group_auth reverts to True (shared)
|
|
441
|
+
if reset_key == "group_auth":
|
|
442
|
+
ws.group_auth = True
|
|
443
|
+
if ws.is_default:
|
|
444
|
+
# Default workset has no workset.yaml — clear the key in
|
|
445
|
+
# config.yaml's [project] section.
|
|
446
|
+
from kanibako.config_interface import _remove_toml_key
|
|
447
|
+
_remove_toml_key(ws_config, "project", "group_auth")
|
|
448
|
+
else:
|
|
449
|
+
_write_workset_toml(ws)
|
|
450
|
+
print("Reset group_auth (reverts to default: true)")
|
|
451
|
+
return 0
|
|
452
|
+
|
|
453
|
+
msg = reset_config_value(
|
|
454
|
+
reset_key,
|
|
455
|
+
config_path=ws_config,
|
|
456
|
+
)
|
|
457
|
+
print(msg)
|
|
458
|
+
return 0
|
|
459
|
+
|
|
460
|
+
# Parse the key/value argument
|
|
461
|
+
action, key, value = parse_config_arg(key_value)
|
|
462
|
+
|
|
463
|
+
# --local flag forces a set operation
|
|
464
|
+
if args.local and action == ConfigAction.get:
|
|
465
|
+
action = ConfigAction.set
|
|
466
|
+
|
|
467
|
+
if action == ConfigAction.show:
|
|
468
|
+
return show_config(
|
|
469
|
+
global_config_path=config_file,
|
|
470
|
+
config_path=ws_config,
|
|
471
|
+
effective=args.effective,
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
if action == ConfigAction.get:
|
|
475
|
+
# Special case: group_auth key lives in workset.yaml
|
|
476
|
+
if key == "group_auth":
|
|
477
|
+
print(ws.group_auth)
|
|
478
|
+
return 0
|
|
479
|
+
|
|
480
|
+
val = get_config_value(
|
|
481
|
+
key,
|
|
482
|
+
global_config_path=config_file,
|
|
483
|
+
project_toml=ws_config,
|
|
484
|
+
)
|
|
485
|
+
if val is not None:
|
|
486
|
+
print(val)
|
|
487
|
+
else:
|
|
488
|
+
print("(not set)", file=sys.stderr)
|
|
489
|
+
return 0
|
|
490
|
+
|
|
491
|
+
if action == ConfigAction.set:
|
|
492
|
+
# Special case: group_auth key updates workset.yaml directly
|
|
493
|
+
if key == "group_auth":
|
|
494
|
+
normalized = value.strip().lower()
|
|
495
|
+
if normalized in ("true", "1"):
|
|
496
|
+
new_group_auth = True
|
|
497
|
+
elif normalized in ("false", "0"):
|
|
498
|
+
new_group_auth = False
|
|
499
|
+
else:
|
|
500
|
+
print(
|
|
501
|
+
f"Error: group_auth must be 'true' or 'false', got '{value}'",
|
|
502
|
+
file=sys.stderr,
|
|
503
|
+
)
|
|
504
|
+
return 1
|
|
505
|
+
|
|
506
|
+
old_group_auth = ws.group_auth
|
|
507
|
+
ws.group_auth = new_group_auth
|
|
508
|
+
if ws.is_default:
|
|
509
|
+
# Default workset has no workset.yaml — write group_auth as a
|
|
510
|
+
# normal boolean key in config.yaml's [project] section.
|
|
511
|
+
from kanibako.config_interface import _write_toml_key
|
|
512
|
+
_write_toml_key(ws_config, "project", "group_auth", new_group_auth)
|
|
513
|
+
else:
|
|
514
|
+
_write_workset_toml(ws)
|
|
515
|
+
|
|
516
|
+
if (not new_group_auth) and old_group_auth:
|
|
517
|
+
# Switched shared→distinct: invalidate credentials in all shells.
|
|
518
|
+
from kanibako.targets import resolve_target
|
|
519
|
+
try:
|
|
520
|
+
target = resolve_target(None)
|
|
521
|
+
except KeyError:
|
|
522
|
+
target = None
|
|
523
|
+
if target:
|
|
524
|
+
for proj in ws.projects:
|
|
525
|
+
shell_path = ws.projects_dir / proj.name / "shell"
|
|
526
|
+
if shell_path.is_dir():
|
|
527
|
+
target.invalidate_credentials(shell_path)
|
|
528
|
+
print(
|
|
529
|
+
f"Set group_auth to false (distinct) for '{ws.name}'. "
|
|
530
|
+
f"Credentials cleared in {len(ws.projects)} project(s).",
|
|
531
|
+
)
|
|
532
|
+
else:
|
|
533
|
+
print(f"Set group_auth to {str(new_group_auth).lower()} for '{ws.name}'.")
|
|
534
|
+
return 0
|
|
535
|
+
|
|
536
|
+
# Handle --local for resource keys
|
|
537
|
+
if args.local:
|
|
538
|
+
from kanibako.config_interface import _is_resource_key, _resolve_key
|
|
539
|
+
canonical = _resolve_key(key)
|
|
540
|
+
if not _is_resource_key(canonical):
|
|
541
|
+
print("Error: --local only applies to resource.* keys", file=sys.stderr)
|
|
542
|
+
return 1
|
|
543
|
+
value = "project"
|
|
544
|
+
|
|
545
|
+
msg = set_config_value(
|
|
546
|
+
key, value,
|
|
547
|
+
config_path=ws_config,
|
|
548
|
+
)
|
|
549
|
+
print(msg)
|
|
550
|
+
return 0
|
|
551
|
+
|
|
552
|
+
return 0
|