devsync 0.5.5__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.
- aiconfigkit/__init__.py +0 -0
- aiconfigkit/__main__.py +6 -0
- aiconfigkit/ai_tools/__init__.py +0 -0
- aiconfigkit/ai_tools/base.py +236 -0
- aiconfigkit/ai_tools/capability_registry.py +262 -0
- aiconfigkit/ai_tools/claude.py +91 -0
- aiconfigkit/ai_tools/claude_desktop.py +97 -0
- aiconfigkit/ai_tools/cline.py +92 -0
- aiconfigkit/ai_tools/copilot.py +92 -0
- aiconfigkit/ai_tools/cursor.py +109 -0
- aiconfigkit/ai_tools/detector.py +169 -0
- aiconfigkit/ai_tools/kiro.py +85 -0
- aiconfigkit/ai_tools/mcp_syncer.py +291 -0
- aiconfigkit/ai_tools/roo.py +110 -0
- aiconfigkit/ai_tools/translator.py +390 -0
- aiconfigkit/ai_tools/winsurf.py +102 -0
- aiconfigkit/cli/__init__.py +0 -0
- aiconfigkit/cli/delete.py +118 -0
- aiconfigkit/cli/download.py +274 -0
- aiconfigkit/cli/install.py +237 -0
- aiconfigkit/cli/install_new.py +937 -0
- aiconfigkit/cli/list.py +275 -0
- aiconfigkit/cli/main.py +454 -0
- aiconfigkit/cli/mcp_configure.py +232 -0
- aiconfigkit/cli/mcp_install.py +166 -0
- aiconfigkit/cli/mcp_sync.py +165 -0
- aiconfigkit/cli/package.py +383 -0
- aiconfigkit/cli/package_create.py +323 -0
- aiconfigkit/cli/package_install.py +472 -0
- aiconfigkit/cli/template.py +19 -0
- aiconfigkit/cli/template_backup.py +261 -0
- aiconfigkit/cli/template_init.py +499 -0
- aiconfigkit/cli/template_install.py +261 -0
- aiconfigkit/cli/template_list.py +172 -0
- aiconfigkit/cli/template_uninstall.py +146 -0
- aiconfigkit/cli/template_update.py +225 -0
- aiconfigkit/cli/template_validate.py +234 -0
- aiconfigkit/cli/tools.py +47 -0
- aiconfigkit/cli/uninstall.py +125 -0
- aiconfigkit/cli/update.py +309 -0
- aiconfigkit/core/__init__.py +0 -0
- aiconfigkit/core/checksum.py +211 -0
- aiconfigkit/core/component_detector.py +905 -0
- aiconfigkit/core/conflict_resolution.py +329 -0
- aiconfigkit/core/git_operations.py +539 -0
- aiconfigkit/core/mcp/__init__.py +1 -0
- aiconfigkit/core/mcp/credentials.py +279 -0
- aiconfigkit/core/mcp/manager.py +308 -0
- aiconfigkit/core/mcp/set_manager.py +1 -0
- aiconfigkit/core/mcp/validator.py +1 -0
- aiconfigkit/core/models.py +1661 -0
- aiconfigkit/core/package_creator.py +743 -0
- aiconfigkit/core/package_manifest.py +248 -0
- aiconfigkit/core/repository.py +298 -0
- aiconfigkit/core/secret_detector.py +438 -0
- aiconfigkit/core/template_manifest.py +283 -0
- aiconfigkit/core/version.py +201 -0
- aiconfigkit/storage/__init__.py +0 -0
- aiconfigkit/storage/library.py +429 -0
- aiconfigkit/storage/mcp_tracker.py +1 -0
- aiconfigkit/storage/package_tracker.py +234 -0
- aiconfigkit/storage/template_library.py +229 -0
- aiconfigkit/storage/template_tracker.py +296 -0
- aiconfigkit/storage/tracker.py +416 -0
- aiconfigkit/tui/__init__.py +5 -0
- aiconfigkit/tui/installer.py +511 -0
- aiconfigkit/utils/__init__.py +0 -0
- aiconfigkit/utils/atomic_write.py +90 -0
- aiconfigkit/utils/backup.py +169 -0
- aiconfigkit/utils/dotenv.py +128 -0
- aiconfigkit/utils/git_helpers.py +187 -0
- aiconfigkit/utils/logging.py +60 -0
- aiconfigkit/utils/namespace.py +134 -0
- aiconfigkit/utils/paths.py +205 -0
- aiconfigkit/utils/project.py +109 -0
- aiconfigkit/utils/streaming.py +216 -0
- aiconfigkit/utils/ui.py +194 -0
- aiconfigkit/utils/validation.py +187 -0
- devsync-0.5.5.dist-info/LICENSE +21 -0
- devsync-0.5.5.dist-info/METADATA +477 -0
- devsync-0.5.5.dist-info/RECORD +84 -0
- devsync-0.5.5.dist-info/WHEEL +5 -0
- devsync-0.5.5.dist-info/entry_points.txt +2 -0
- devsync-0.5.5.dist-info/top_level.txt +1 -0
aiconfigkit/cli/main.py
ADDED
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
"""Main CLI application entry point."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from aiconfigkit.cli.delete import delete_from_library
|
|
8
|
+
from aiconfigkit.cli.download import download_instructions
|
|
9
|
+
from aiconfigkit.cli.install_new import install_instruction_unified
|
|
10
|
+
from aiconfigkit.cli.list import list_available, list_installed, list_library
|
|
11
|
+
from aiconfigkit.cli.mcp_configure import mcp_configure_command
|
|
12
|
+
from aiconfigkit.cli.mcp_install import mcp_install_command
|
|
13
|
+
from aiconfigkit.cli.mcp_sync import mcp_sync_command
|
|
14
|
+
from aiconfigkit.cli.package import package_app
|
|
15
|
+
from aiconfigkit.cli.template import template_app
|
|
16
|
+
from aiconfigkit.cli.template_backup import (
|
|
17
|
+
backup_cleanup_command,
|
|
18
|
+
backup_list_command,
|
|
19
|
+
backup_restore_command,
|
|
20
|
+
)
|
|
21
|
+
from aiconfigkit.cli.template_init import init_command as template_init_command
|
|
22
|
+
from aiconfigkit.cli.template_install import install_command as template_install_command
|
|
23
|
+
from aiconfigkit.cli.template_list import list_command as template_list_command
|
|
24
|
+
from aiconfigkit.cli.template_uninstall import uninstall_command as template_uninstall_command
|
|
25
|
+
from aiconfigkit.cli.template_update import update_command as template_update_command
|
|
26
|
+
from aiconfigkit.cli.template_validate import validate_command as template_validate_command
|
|
27
|
+
from aiconfigkit.cli.tools import show_tools
|
|
28
|
+
from aiconfigkit.cli.uninstall import uninstall_instruction
|
|
29
|
+
from aiconfigkit.cli.update import update_repository
|
|
30
|
+
|
|
31
|
+
app = typer.Typer(
|
|
32
|
+
name="instructionkit",
|
|
33
|
+
help="CLI tool for managing AI coding tool instructions",
|
|
34
|
+
add_completion=False,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
# Create list subcommand group
|
|
38
|
+
list_app = typer.Typer(help="List instructions")
|
|
39
|
+
app.add_typer(list_app, name="list")
|
|
40
|
+
|
|
41
|
+
# Create package subcommand group
|
|
42
|
+
app.add_typer(package_app, name="package")
|
|
43
|
+
|
|
44
|
+
# Create template subcommand group
|
|
45
|
+
app.add_typer(template_app, name="template")
|
|
46
|
+
|
|
47
|
+
# Create backup subcommand group under template
|
|
48
|
+
backup_app = typer.Typer(help="Manage template backups")
|
|
49
|
+
template_app.add_typer(backup_app, name="backup")
|
|
50
|
+
|
|
51
|
+
# Create mcp subcommand group
|
|
52
|
+
mcp_app = typer.Typer(help="Manage MCP server configurations")
|
|
53
|
+
app.add_typer(mcp_app, name="mcp")
|
|
54
|
+
|
|
55
|
+
# Register mcp commands
|
|
56
|
+
mcp_app.command(name="install")(mcp_install_command)
|
|
57
|
+
mcp_app.command(name="configure")(mcp_configure_command)
|
|
58
|
+
mcp_app.command(name="sync")(mcp_sync_command)
|
|
59
|
+
|
|
60
|
+
# Register template commands
|
|
61
|
+
template_app.command(name="init")(template_init_command)
|
|
62
|
+
template_app.command(name="install")(template_install_command)
|
|
63
|
+
template_app.command(name="list")(template_list_command)
|
|
64
|
+
template_app.command(name="update")(template_update_command)
|
|
65
|
+
template_app.command(name="uninstall")(template_uninstall_command)
|
|
66
|
+
template_app.command(name="validate")(template_validate_command)
|
|
67
|
+
|
|
68
|
+
# Register backup commands
|
|
69
|
+
backup_app.command(name="list")(backup_list_command)
|
|
70
|
+
backup_app.command(name="cleanup")(backup_cleanup_command)
|
|
71
|
+
backup_app.command(name="restore")(backup_restore_command)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@list_app.callback(invoke_without_command=True)
|
|
75
|
+
def list_callback(ctx: typer.Context) -> None:
|
|
76
|
+
"""List instructions (available, installed, or in library)."""
|
|
77
|
+
# If no subcommand was provided, show help
|
|
78
|
+
if ctx.invoked_subcommand is None:
|
|
79
|
+
typer.echo(ctx.get_help())
|
|
80
|
+
raise typer.Exit(0)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@app.command()
|
|
84
|
+
def install(
|
|
85
|
+
names: Optional[list[str]] = typer.Argument(
|
|
86
|
+
None,
|
|
87
|
+
help="Instruction name(s) to install (use source/name for disambiguation). Can specify multiple.",
|
|
88
|
+
),
|
|
89
|
+
source: Optional[str] = typer.Option(
|
|
90
|
+
None,
|
|
91
|
+
"--from",
|
|
92
|
+
"-f",
|
|
93
|
+
help="Source URL or path for direct install (bypasses library)",
|
|
94
|
+
),
|
|
95
|
+
tools: Optional[list[str]] = typer.Option(
|
|
96
|
+
None,
|
|
97
|
+
"--tool",
|
|
98
|
+
"-t",
|
|
99
|
+
help="AI tool(s) to install to (cursor, copilot, windsurf, claude). Can specify multiple times.",
|
|
100
|
+
),
|
|
101
|
+
conflict: str = typer.Option(
|
|
102
|
+
"prompt",
|
|
103
|
+
"--conflict",
|
|
104
|
+
"-c",
|
|
105
|
+
help="Conflict resolution strategy (prompt [default], skip, rename, overwrite)",
|
|
106
|
+
),
|
|
107
|
+
bundle: bool = typer.Option(
|
|
108
|
+
False,
|
|
109
|
+
"--bundle",
|
|
110
|
+
"-b",
|
|
111
|
+
help="Install as bundle (multiple instructions)",
|
|
112
|
+
),
|
|
113
|
+
) -> None:
|
|
114
|
+
"""
|
|
115
|
+
Install instructions from your library or directly from a source.
|
|
116
|
+
|
|
117
|
+
LIBRARY WORKFLOW (Recommended):
|
|
118
|
+
# Browse and select instructions with TUI
|
|
119
|
+
inskit install
|
|
120
|
+
|
|
121
|
+
# Install specific instruction from library
|
|
122
|
+
inskit install python-best-practices
|
|
123
|
+
|
|
124
|
+
# Install from specific source (if multiple sources have same name)
|
|
125
|
+
inskit install company/python-best-practices
|
|
126
|
+
|
|
127
|
+
# Install multiple instructions at once
|
|
128
|
+
inskit install python-style testing-guide api-design
|
|
129
|
+
|
|
130
|
+
# Install to specific tools only
|
|
131
|
+
inskit install python-style --tool cursor --tool windsurf
|
|
132
|
+
|
|
133
|
+
DIRECT INSTALL (Bypasses library):
|
|
134
|
+
# Install directly from source URL
|
|
135
|
+
inskit install python-style --from https://github.com/company/instructions
|
|
136
|
+
|
|
137
|
+
# Install bundle directly
|
|
138
|
+
inskit install python-backend --bundle --from https://github.com/company/instructions
|
|
139
|
+
|
|
140
|
+
TIP: Download to your library first for better management:
|
|
141
|
+
inskit download --from https://github.com/company/instructions
|
|
142
|
+
"""
|
|
143
|
+
exit_code = install_instruction_unified(
|
|
144
|
+
names=names,
|
|
145
|
+
repo=source, # Keep backend param name for now
|
|
146
|
+
tools=tools,
|
|
147
|
+
conflict_strategy=conflict,
|
|
148
|
+
bundle=bundle,
|
|
149
|
+
)
|
|
150
|
+
raise typer.Exit(code=exit_code)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@app.command()
|
|
154
|
+
def download(
|
|
155
|
+
source: str = typer.Option(
|
|
156
|
+
...,
|
|
157
|
+
"--from",
|
|
158
|
+
"-f",
|
|
159
|
+
help="Source URL or local directory path",
|
|
160
|
+
),
|
|
161
|
+
ref: Optional[str] = typer.Option(
|
|
162
|
+
None,
|
|
163
|
+
"--ref",
|
|
164
|
+
"-r",
|
|
165
|
+
help="Git reference (tag, branch, or commit) to download",
|
|
166
|
+
),
|
|
167
|
+
alias: Optional[str] = typer.Option(
|
|
168
|
+
None,
|
|
169
|
+
"--as",
|
|
170
|
+
"-a",
|
|
171
|
+
help="Friendly alias for this source (auto-generated if not provided)",
|
|
172
|
+
),
|
|
173
|
+
force: bool = typer.Option(
|
|
174
|
+
False,
|
|
175
|
+
"--force",
|
|
176
|
+
help="Re-download even if already in library",
|
|
177
|
+
),
|
|
178
|
+
) -> None:
|
|
179
|
+
"""
|
|
180
|
+
Download instructions from a source into your local library.
|
|
181
|
+
|
|
182
|
+
This downloads and caches instructions locally without installing them.
|
|
183
|
+
After downloading, use 'inskit install' to install instructions.
|
|
184
|
+
|
|
185
|
+
Examples:
|
|
186
|
+
|
|
187
|
+
# Download from GitHub (auto-generates alias)
|
|
188
|
+
inskit download --from github.com/company/instructions
|
|
189
|
+
|
|
190
|
+
# Download specific version
|
|
191
|
+
inskit download --from github.com/company/instructions --ref v1.0.0
|
|
192
|
+
|
|
193
|
+
# Download from branch
|
|
194
|
+
inskit download --from github.com/company/instructions --ref main
|
|
195
|
+
|
|
196
|
+
# Download with custom alias
|
|
197
|
+
inskit download --from github.com/company/instructions --as company
|
|
198
|
+
|
|
199
|
+
# Download from local folder
|
|
200
|
+
inskit download --from ./my-instructions --as local
|
|
201
|
+
|
|
202
|
+
# Force re-download
|
|
203
|
+
inskit download --from github.com/company/instructions --force
|
|
204
|
+
"""
|
|
205
|
+
exit_code = download_instructions(repo=source, ref=ref, force=force, alias=alias)
|
|
206
|
+
raise typer.Exit(code=exit_code)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
@list_app.command("available")
|
|
210
|
+
def list_available_cmd(
|
|
211
|
+
source: str = typer.Option(..., "--from", "-f", help="Source URL or local directory path"),
|
|
212
|
+
tag: Optional[str] = typer.Option(None, "--tag", "-t", help="Filter by tag"),
|
|
213
|
+
bundles_only: bool = typer.Option(False, "--bundles-only", help="Show only bundles"),
|
|
214
|
+
instructions_only: bool = typer.Option(False, "--instructions-only", help="Show only instructions"),
|
|
215
|
+
) -> None:
|
|
216
|
+
"""
|
|
217
|
+
List available instructions from a source (without downloading).
|
|
218
|
+
|
|
219
|
+
Examples:
|
|
220
|
+
|
|
221
|
+
# List from Git repository
|
|
222
|
+
inskit list available --from github.com/company/instructions
|
|
223
|
+
|
|
224
|
+
# List from local folder
|
|
225
|
+
inskit list available --from ./my-instructions
|
|
226
|
+
|
|
227
|
+
# Filter by tag
|
|
228
|
+
inskit list available --from github.com/company/instructions --tag python
|
|
229
|
+
|
|
230
|
+
# Show only bundles
|
|
231
|
+
inskit list available --from github.com/company/instructions --bundles-only
|
|
232
|
+
"""
|
|
233
|
+
exit_code = list_available(
|
|
234
|
+
repo=source, # Keep backend param name for now
|
|
235
|
+
tag=tag,
|
|
236
|
+
bundles_only=bundles_only,
|
|
237
|
+
instructions_only=instructions_only,
|
|
238
|
+
)
|
|
239
|
+
raise typer.Exit(code=exit_code)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
@list_app.command("installed")
|
|
243
|
+
def list_installed_cmd(
|
|
244
|
+
tool: Optional[str] = typer.Option(
|
|
245
|
+
None,
|
|
246
|
+
"--tool",
|
|
247
|
+
"-t",
|
|
248
|
+
help="Filter by AI tool (cursor, copilot, windsurf, claude)",
|
|
249
|
+
),
|
|
250
|
+
source: Optional[str] = typer.Option(
|
|
251
|
+
None,
|
|
252
|
+
"--source",
|
|
253
|
+
"-s",
|
|
254
|
+
help="Filter by source alias or name",
|
|
255
|
+
),
|
|
256
|
+
) -> None:
|
|
257
|
+
"""
|
|
258
|
+
List installed instructions in your AI tools.
|
|
259
|
+
|
|
260
|
+
Examples:
|
|
261
|
+
|
|
262
|
+
# List all installed instructions
|
|
263
|
+
inskit list installed
|
|
264
|
+
|
|
265
|
+
# Filter by AI tool
|
|
266
|
+
inskit list installed --tool cursor
|
|
267
|
+
|
|
268
|
+
# Filter by source
|
|
269
|
+
inskit list installed --source company
|
|
270
|
+
"""
|
|
271
|
+
exit_code = list_installed(tool=tool, repo=source) # Keep backend param name for now
|
|
272
|
+
raise typer.Exit(code=exit_code)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
@list_app.command("library")
|
|
276
|
+
def list_library_cmd(
|
|
277
|
+
source: Optional[str] = typer.Option(
|
|
278
|
+
None,
|
|
279
|
+
"--source",
|
|
280
|
+
"-s",
|
|
281
|
+
help="Filter by source alias",
|
|
282
|
+
),
|
|
283
|
+
instructions: bool = typer.Option(
|
|
284
|
+
False,
|
|
285
|
+
"--instructions",
|
|
286
|
+
"-i",
|
|
287
|
+
help="Show individual instructions instead of repositories",
|
|
288
|
+
),
|
|
289
|
+
) -> None:
|
|
290
|
+
"""
|
|
291
|
+
List sources and instructions in your local library.
|
|
292
|
+
|
|
293
|
+
Examples:
|
|
294
|
+
|
|
295
|
+
# List all sources in library
|
|
296
|
+
inskit list library
|
|
297
|
+
|
|
298
|
+
# Show individual instructions
|
|
299
|
+
inskit list library --instructions
|
|
300
|
+
|
|
301
|
+
# Filter by source
|
|
302
|
+
inskit list library --source company
|
|
303
|
+
"""
|
|
304
|
+
exit_code = list_library(repo_filter=source, show_instructions=instructions)
|
|
305
|
+
raise typer.Exit(code=exit_code)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
@app.command()
|
|
309
|
+
def update(
|
|
310
|
+
namespace: Optional[str] = typer.Option(
|
|
311
|
+
None,
|
|
312
|
+
"--namespace",
|
|
313
|
+
"-n",
|
|
314
|
+
help="Repository namespace to update",
|
|
315
|
+
),
|
|
316
|
+
all_repos: bool = typer.Option(
|
|
317
|
+
False,
|
|
318
|
+
"--all",
|
|
319
|
+
"-a",
|
|
320
|
+
help="Update all repositories in library",
|
|
321
|
+
),
|
|
322
|
+
) -> None:
|
|
323
|
+
"""
|
|
324
|
+
Update downloaded instructions to their latest versions.
|
|
325
|
+
|
|
326
|
+
This re-downloads instructions from their sources,
|
|
327
|
+
ensuring you have the latest versions in your library.
|
|
328
|
+
|
|
329
|
+
Examples:
|
|
330
|
+
|
|
331
|
+
# Update a specific source
|
|
332
|
+
inskit update --namespace github.com_company_instructions
|
|
333
|
+
|
|
334
|
+
# Update all sources
|
|
335
|
+
inskit update --all
|
|
336
|
+
|
|
337
|
+
# List sources to find namespace
|
|
338
|
+
inskit list library
|
|
339
|
+
"""
|
|
340
|
+
exit_code = update_repository(namespace=namespace, all_repos=all_repos)
|
|
341
|
+
raise typer.Exit(code=exit_code)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
@app.command()
|
|
345
|
+
def delete(
|
|
346
|
+
namespace: str = typer.Argument(
|
|
347
|
+
...,
|
|
348
|
+
help="Repository namespace to delete from library",
|
|
349
|
+
),
|
|
350
|
+
force: bool = typer.Option(
|
|
351
|
+
False,
|
|
352
|
+
"--force",
|
|
353
|
+
"-f",
|
|
354
|
+
help="Skip confirmation prompt",
|
|
355
|
+
),
|
|
356
|
+
) -> None:
|
|
357
|
+
"""
|
|
358
|
+
Delete a source from your local library.
|
|
359
|
+
|
|
360
|
+
This removes the downloaded instructions from your library but does NOT
|
|
361
|
+
uninstall them from your AI tools. To uninstall, use 'inskit uninstall'.
|
|
362
|
+
|
|
363
|
+
Examples:
|
|
364
|
+
|
|
365
|
+
# Delete a source
|
|
366
|
+
inskit delete github.com_company_instructions
|
|
367
|
+
|
|
368
|
+
# Skip confirmation
|
|
369
|
+
inskit delete github.com_company_instructions --force
|
|
370
|
+
|
|
371
|
+
# List sources to find namespace
|
|
372
|
+
inskit list library
|
|
373
|
+
"""
|
|
374
|
+
exit_code = delete_from_library(namespace=namespace, force=force)
|
|
375
|
+
raise typer.Exit(code=exit_code)
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
@app.command()
|
|
379
|
+
def uninstall(
|
|
380
|
+
name: str = typer.Argument(..., help="Instruction name to uninstall"),
|
|
381
|
+
tool: Optional[str] = typer.Option(
|
|
382
|
+
None,
|
|
383
|
+
"--tool",
|
|
384
|
+
"-t",
|
|
385
|
+
help="AI tool to uninstall from (cursor, copilot, windsurf, claude)",
|
|
386
|
+
),
|
|
387
|
+
force: bool = typer.Option(
|
|
388
|
+
False,
|
|
389
|
+
"--force",
|
|
390
|
+
"-f",
|
|
391
|
+
help="Skip confirmation prompt",
|
|
392
|
+
),
|
|
393
|
+
) -> None:
|
|
394
|
+
"""
|
|
395
|
+
Uninstall an instruction from your AI tools.
|
|
396
|
+
|
|
397
|
+
Removes instructions from project level only.
|
|
398
|
+
|
|
399
|
+
Examples:
|
|
400
|
+
|
|
401
|
+
# Uninstall from all tools
|
|
402
|
+
inskit uninstall python-best-practices
|
|
403
|
+
|
|
404
|
+
# Uninstall from specific tool
|
|
405
|
+
inskit uninstall python-best-practices --tool cursor
|
|
406
|
+
|
|
407
|
+
# Skip confirmation
|
|
408
|
+
inskit uninstall python-best-practices --force
|
|
409
|
+
"""
|
|
410
|
+
exit_code = uninstall_instruction(name=name, tool=tool, force=force)
|
|
411
|
+
raise typer.Exit(code=exit_code)
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
@app.command()
|
|
415
|
+
def tools() -> None:
|
|
416
|
+
"""
|
|
417
|
+
Show detected AI coding tools.
|
|
418
|
+
|
|
419
|
+
Display which AI coding tools are installed on your system
|
|
420
|
+
and where their configuration directories are located.
|
|
421
|
+
"""
|
|
422
|
+
exit_code = show_tools()
|
|
423
|
+
raise typer.Exit(code=exit_code)
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
@app.command()
|
|
427
|
+
def version() -> None:
|
|
428
|
+
"""Show Config Sync version."""
|
|
429
|
+
from importlib.metadata import version as get_version
|
|
430
|
+
|
|
431
|
+
try:
|
|
432
|
+
version = get_version("configsync")
|
|
433
|
+
except Exception:
|
|
434
|
+
version = "unknown"
|
|
435
|
+
|
|
436
|
+
typer.echo(f"Config Sync version {version}")
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
@app.callback(invoke_without_command=True)
|
|
440
|
+
def main(ctx: typer.Context) -> None:
|
|
441
|
+
"""
|
|
442
|
+
InstructionKit - Manage AI coding tool instructions from Git repositories.
|
|
443
|
+
|
|
444
|
+
Install instructions for Cursor, GitHub Copilot, Windsurf, and Claude Code
|
|
445
|
+
from any Git repository (public or private).
|
|
446
|
+
"""
|
|
447
|
+
# If no command was provided, show help
|
|
448
|
+
if ctx.invoked_subcommand is None:
|
|
449
|
+
typer.echo(ctx.get_help())
|
|
450
|
+
raise typer.Exit(0)
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
if __name__ == "__main__":
|
|
454
|
+
app()
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"""CLI command for configuring MCP server credentials."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.table import Table
|
|
9
|
+
|
|
10
|
+
from aiconfigkit.core.mcp.credentials import CredentialManager
|
|
11
|
+
from aiconfigkit.core.mcp.manager import MCPManager
|
|
12
|
+
from aiconfigkit.core.models import InstallationScope
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def mcp_configure_command(
|
|
19
|
+
server_ref: str = typer.Argument(..., help="Server reference in format 'namespace.server' or just 'namespace'"),
|
|
20
|
+
scope: str = typer.Option("project", "--scope", help="Installation scope: project or global"),
|
|
21
|
+
non_interactive: bool = typer.Option(False, "--non-interactive", help="Read credentials from environment"),
|
|
22
|
+
show_current: bool = typer.Option(False, "--show-current", help="Show current credential values (masked)"),
|
|
23
|
+
json_output: bool = typer.Option(False, "--json", help="Output results as JSON"),
|
|
24
|
+
) -> int:
|
|
25
|
+
"""
|
|
26
|
+
Configure credentials for MCP servers.
|
|
27
|
+
|
|
28
|
+
This command helps you securely configure required environment variables for
|
|
29
|
+
MCP servers. Credentials are stored in .instructionkit/.env (gitignored).
|
|
30
|
+
|
|
31
|
+
Examples:
|
|
32
|
+
|
|
33
|
+
# Configure specific server interactively
|
|
34
|
+
inskit mcp configure backend.github
|
|
35
|
+
|
|
36
|
+
# Configure all servers in a namespace
|
|
37
|
+
inskit mcp configure backend
|
|
38
|
+
|
|
39
|
+
# Configure with non-interactive mode (read from env)
|
|
40
|
+
export GITHUB_TOKEN=ghp_xxxxx
|
|
41
|
+
inskit mcp configure backend.github --non-interactive
|
|
42
|
+
|
|
43
|
+
# Show current credentials (masked)
|
|
44
|
+
inskit mcp configure backend.github --show-current
|
|
45
|
+
|
|
46
|
+
# Configure globally (available in all projects)
|
|
47
|
+
inskit mcp configure backend.github --scope global
|
|
48
|
+
"""
|
|
49
|
+
try:
|
|
50
|
+
# Parse scope
|
|
51
|
+
try:
|
|
52
|
+
install_scope = InstallationScope(scope)
|
|
53
|
+
except ValueError:
|
|
54
|
+
console.print(f"[red]Error:[/red] Invalid scope '{scope}'. Must be 'project' or 'global'")
|
|
55
|
+
return 1
|
|
56
|
+
|
|
57
|
+
# Parse server reference
|
|
58
|
+
if "." in server_ref:
|
|
59
|
+
namespace, server_name = server_ref.rsplit(".", 1)
|
|
60
|
+
else:
|
|
61
|
+
namespace = server_ref
|
|
62
|
+
server_name = None
|
|
63
|
+
|
|
64
|
+
# Get library root and managers
|
|
65
|
+
library_root = _get_library_root()
|
|
66
|
+
mcp_manager = MCPManager(library_root)
|
|
67
|
+
cred_manager = CredentialManager()
|
|
68
|
+
|
|
69
|
+
# Load template
|
|
70
|
+
template = mcp_manager.load_template(namespace, scope=install_scope)
|
|
71
|
+
|
|
72
|
+
if not template:
|
|
73
|
+
console.print(f"[red]Error:[/red] Template '{namespace}' not found in {install_scope.value} scope")
|
|
74
|
+
console.print(f"\nInstall it first: [cyan]inskit mcp install <source> --as {namespace}[/cyan]")
|
|
75
|
+
return 1
|
|
76
|
+
|
|
77
|
+
# Get servers to configure
|
|
78
|
+
if server_name:
|
|
79
|
+
# Configure specific server
|
|
80
|
+
server = template.get_server_by_name(server_name)
|
|
81
|
+
if not server:
|
|
82
|
+
console.print(f"[red]Error:[/red] Server '{server_name}' not found in template '{namespace}'")
|
|
83
|
+
console.print(f"\nAvailable servers: {', '.join(s.name for s in template.servers)}")
|
|
84
|
+
return 1
|
|
85
|
+
servers = [server]
|
|
86
|
+
else:
|
|
87
|
+
# Configure all servers in namespace
|
|
88
|
+
servers = template.servers
|
|
89
|
+
|
|
90
|
+
if not servers:
|
|
91
|
+
console.print(f"[yellow]Warning:[/yellow] No MCP servers found in template '{namespace}'")
|
|
92
|
+
return 0
|
|
93
|
+
|
|
94
|
+
# Show current credentials if requested
|
|
95
|
+
if show_current:
|
|
96
|
+
return _show_current_credentials(servers, cred_manager, install_scope, json_output)
|
|
97
|
+
|
|
98
|
+
# Configure each server
|
|
99
|
+
configured_count = 0
|
|
100
|
+
skipped_count = 0
|
|
101
|
+
|
|
102
|
+
for server in servers:
|
|
103
|
+
required_vars = server.get_required_env_vars()
|
|
104
|
+
|
|
105
|
+
if not required_vars:
|
|
106
|
+
console.print(f"[dim]Server '{server.name}' requires no credentials[/dim]")
|
|
107
|
+
skipped_count += 1
|
|
108
|
+
continue
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
cred_manager.configure_server(
|
|
112
|
+
server,
|
|
113
|
+
scope=install_scope,
|
|
114
|
+
non_interactive=non_interactive,
|
|
115
|
+
)
|
|
116
|
+
configured_count += 1
|
|
117
|
+
|
|
118
|
+
except ValueError as e:
|
|
119
|
+
console.print(f"[red]Error configuring '{server.name}':[/red] {e}")
|
|
120
|
+
return 1
|
|
121
|
+
|
|
122
|
+
# Output results
|
|
123
|
+
if json_output:
|
|
124
|
+
import json
|
|
125
|
+
|
|
126
|
+
result = {
|
|
127
|
+
"success": True,
|
|
128
|
+
"namespace": namespace,
|
|
129
|
+
"configured": configured_count,
|
|
130
|
+
"skipped": skipped_count,
|
|
131
|
+
"total": len(servers),
|
|
132
|
+
"scope": install_scope.value,
|
|
133
|
+
}
|
|
134
|
+
console.print(json.dumps(result, indent=2))
|
|
135
|
+
else:
|
|
136
|
+
console.print("\n[green]✓[/green] Credential configuration complete!")
|
|
137
|
+
console.print(f" Configured: {configured_count} server(s)")
|
|
138
|
+
if skipped_count > 0:
|
|
139
|
+
console.print(f" Skipped: {skipped_count} server(s) (no credentials needed)")
|
|
140
|
+
|
|
141
|
+
# Show env file location
|
|
142
|
+
if install_scope == InstallationScope.GLOBAL:
|
|
143
|
+
env_path = Path.home() / ".instructionkit" / "global" / ".env"
|
|
144
|
+
else:
|
|
145
|
+
env_path = Path.cwd() / ".instructionkit" / ".env"
|
|
146
|
+
|
|
147
|
+
console.print(f"\n[dim]Credentials saved to: {env_path}[/dim]")
|
|
148
|
+
console.print("[dim](This file is automatically gitignored)[/dim]")
|
|
149
|
+
|
|
150
|
+
# Show next steps
|
|
151
|
+
console.print("\n[bold]Next step:[/bold]")
|
|
152
|
+
console.print(" Sync to AI tools: [cyan]inskit mcp sync --tool all[/cyan]")
|
|
153
|
+
|
|
154
|
+
return 0
|
|
155
|
+
|
|
156
|
+
except Exception as e:
|
|
157
|
+
logger.exception("Unexpected error during credential configuration")
|
|
158
|
+
if json_output:
|
|
159
|
+
import json
|
|
160
|
+
|
|
161
|
+
console.print(json.dumps({"success": False, "error": str(e)}, indent=2))
|
|
162
|
+
else:
|
|
163
|
+
console.print(f"[red]Unexpected error:[/red] {e}")
|
|
164
|
+
return 1
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _show_current_credentials(
|
|
168
|
+
servers: list,
|
|
169
|
+
cred_manager: CredentialManager,
|
|
170
|
+
scope: InstallationScope,
|
|
171
|
+
json_output: bool,
|
|
172
|
+
) -> int:
|
|
173
|
+
"""Show current credential values for servers."""
|
|
174
|
+
if json_output:
|
|
175
|
+
import json
|
|
176
|
+
from typing import Any
|
|
177
|
+
|
|
178
|
+
result: dict[str, Any] = {
|
|
179
|
+
"scope": scope.value,
|
|
180
|
+
"servers": [],
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
for server in servers:
|
|
184
|
+
credentials = cred_manager.show_current_credentials(server, scope)
|
|
185
|
+
is_valid, missing = cred_manager.validate_credentials(server, scope)
|
|
186
|
+
|
|
187
|
+
result["servers"].append(
|
|
188
|
+
{
|
|
189
|
+
"name": server.get_fully_qualified_name(),
|
|
190
|
+
"credentials": credentials,
|
|
191
|
+
"is_configured": is_valid,
|
|
192
|
+
"missing": missing,
|
|
193
|
+
}
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
console.print(json.dumps(result, indent=2))
|
|
197
|
+
else:
|
|
198
|
+
table = Table(title=f"Current Credentials ({scope.value} scope)")
|
|
199
|
+
table.add_column("Server", style="cyan")
|
|
200
|
+
table.add_column("Variable", style="yellow")
|
|
201
|
+
table.add_column("Value", style="dim")
|
|
202
|
+
table.add_column("Status", style="green")
|
|
203
|
+
|
|
204
|
+
for server in servers:
|
|
205
|
+
credentials = cred_manager.show_current_credentials(server, scope)
|
|
206
|
+
is_valid, missing = cred_manager.validate_credentials(server, scope)
|
|
207
|
+
|
|
208
|
+
for i, (var_name, masked_value) in enumerate(credentials.items()):
|
|
209
|
+
server_name = server.get_fully_qualified_name() if i == 0 else ""
|
|
210
|
+
status = "✓" if var_name not in missing else "⚠ Missing"
|
|
211
|
+
status_style = "green" if var_name not in missing else "yellow"
|
|
212
|
+
|
|
213
|
+
table.add_row(
|
|
214
|
+
server_name,
|
|
215
|
+
var_name,
|
|
216
|
+
masked_value,
|
|
217
|
+
f"[{status_style}]{status}[/{status_style}]",
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
console.print(table)
|
|
221
|
+
|
|
222
|
+
return 0
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _get_library_root() -> Path:
|
|
226
|
+
"""
|
|
227
|
+
Get the library root directory.
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
Path to library root (~/.instructionkit/library/)
|
|
231
|
+
"""
|
|
232
|
+
return Path.home() / ".instructionkit" / "library"
|