pipulate 2.45__tar.gz → 2.47__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.
- {pipulate-2.45/pipulate.egg-info → pipulate-2.47}/PKG-INFO +1 -1
- {pipulate-2.45 → pipulate-2.47}/__init__.py +2 -2
- {pipulate-2.45 → pipulate-2.47}/cli.py +31 -8
- {pipulate-2.45 → pipulate-2.47/pipulate.egg-info}/PKG-INFO +1 -1
- {pipulate-2.45 → pipulate-2.47}/pipulate.egg-info/SOURCES.txt +1 -0
- {pipulate-2.45 → pipulate-2.47}/pyproject.toml +1 -1
- {pipulate-2.45 → pipulate-2.47}/server.py +0 -1
- {pipulate-2.45 → pipulate-2.47}/tools/__init__.py +32 -0
- pipulate-2.47/tools/connector_tools.py +98 -0
- {pipulate-2.45 → pipulate-2.47}/LICENSE +0 -0
- {pipulate-2.45 → pipulate-2.47}/MANIFEST.in +0 -0
- {pipulate-2.45 → pipulate-2.47}/README.md +0 -0
- {pipulate-2.45 → pipulate-2.47}/apps/010_introduction.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/apps/015_config.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/apps/020_profiles.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/apps/025_aspect.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/apps/030_roles.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/apps/040_hello_workflow.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/apps/050_documentation.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/apps/060_tasks.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/apps/070_history.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/apps/110_parameter_buster.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/apps/120_link_graph.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/apps/130_gap_analysis.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/apps/200_workflow_genesis.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/apps/210_widget_examples.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/apps/220_roadmap.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/apps/230_dev_assistant.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/apps/240_simon_mcp.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/apps/300_blank_placeholder.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/apps/400_botify_trifecta.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/apps/440_browser_automation.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/apps/450_stream_simulator.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/apps/510_text_field.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/apps/520_text_area.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/apps/530_dropdown.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/apps/540_checkboxes.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/apps/550_radios.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/apps/560_range.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/apps/570_switch.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/apps/580_upload.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/apps/610_markdown.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/apps/620_mermaid.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/apps/630_prism.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/apps/640_javascript.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/apps/710_pandas.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/apps/720_rich.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/apps/730_matplotlib.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/apps/810_webbrowser.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/apps/820_selenium.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/apps/830_pico_slider.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/config.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/imports/__init__.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/imports/ai_dictdb.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/imports/ai_tool_discovery_simple_parser.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/imports/append_only_conversation.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/imports/ascii_displays.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/imports/botify/__init__.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/imports/botify/code_generators.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/imports/botify/true_schema_discoverer.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/imports/botify_code_generation.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/imports/crud.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/imports/database_safety_wrapper.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/imports/dom_processing/__init__.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/imports/dom_processing/ai_dom_beautifier.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/imports/dom_processing/enhanced_dom_processor.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/imports/durable_backup_system.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/imports/mcp_orchestrator.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/imports/server_logging.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/imports/stream_orchestrator.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/imports/voice_synthesis.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/pipulate/__init__.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/pipulate/core.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/pipulate.egg-info/dependency_links.txt +0 -0
- {pipulate-2.45 → pipulate-2.47}/pipulate.egg-info/entry_points.txt +0 -0
- {pipulate-2.45 → pipulate-2.47}/pipulate.egg-info/requires.txt +0 -0
- {pipulate-2.45 → pipulate-2.47}/pipulate.egg-info/top_level.txt +0 -0
- {pipulate-2.45 → pipulate-2.47}/setup.cfg +0 -0
- {pipulate-2.45 → pipulate-2.47}/tests/test_apply_airlock.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/tests/test_mck_rep2.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/tools/advanced_automation_tools.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/tools/botify_tools.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/tools/conversation_tools.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/tools/dom_tools.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/tools/keychain_tools.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/tools/llm_optics.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/tools/mcp_tools.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/tools/scraper_tools.py +0 -0
- {pipulate-2.45 → pipulate-2.47}/tools/system_tools.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pipulate
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.47
|
|
4
4
|
Summary: AI-readiness for the agentic web — local-first, Nix-reproducible workflows. The successor to AI SEO software.
|
|
5
5
|
Author-email: Mike Levin <pipulate@gmail.com>
|
|
6
6
|
License-Expression: AGPL-3.0-or-later
|
|
@@ -12,7 +12,7 @@ Usage:
|
|
|
12
12
|
pipulate
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
|
-
__version__ = "2.
|
|
15
|
+
__version__ = "2.47"
|
|
16
16
|
# APOSTROPHES RESTORED (2026-08-04). They were stripped as a workaround for
|
|
17
17
|
# flake.nix's descMatch regex, whose character class excluded ' from the
|
|
18
18
|
# CAPTURE and truncated the banner to "(So)". That regex was fixed in the same
|
|
@@ -23,7 +23,7 @@ __version__ = "2.45"
|
|
|
23
23
|
# is not a property of the system; the regex is. Blast radius is one banner:
|
|
24
24
|
# nothing but flake.nix reads this name -- version_sync.py syncs __version__
|
|
25
25
|
# and __description__, never this. So'wI' chu' -- "engage the cloaking device."
|
|
26
|
-
__version_description__ = "
|
|
26
|
+
__version_description__ = "Shoshin Kata Begins"
|
|
27
27
|
# SPDX expression, single source of truth, synced into pyproject.toml by
|
|
28
28
|
# scripts/release/version_sync.py. "-or-later" (not bare AGPL-3.0, which is
|
|
29
29
|
# deprecated SPDX) because the header below grants "any later version".
|
|
@@ -109,7 +109,13 @@ def discover_tools(show_all=False, tool_name=None):
|
|
|
109
109
|
border_style="cyan"
|
|
110
110
|
))
|
|
111
111
|
|
|
112
|
-
|
|
112
|
+
# The Rule of 7 is a string-literal list, not a registry read, so
|
|
113
|
+
# a denied tool would still be NAMED here and a model told to run
|
|
114
|
+
# it would waste a call on "not found". Filter without importing
|
|
115
|
+
# the registry (that import costs seconds); denied_tools() is cheap.
|
|
116
|
+
from tools import denied_tools
|
|
117
|
+
shown = [tool for tool in essential_tools if tool not in denied_tools()]
|
|
118
|
+
for i, tool in enumerate(shown, 1):
|
|
113
119
|
console.print(f" {i}. [bold cyan]{tool}[/bold cyan]")
|
|
114
120
|
|
|
115
121
|
console.print(f"\n[italic]Use `.venv/bin/python cli.py mcp-discover --all` to see all available tools.[/italic]")
|
|
@@ -133,9 +139,17 @@ def discover_tools(show_all=False, tool_name=None):
|
|
|
133
139
|
console.print(f"❌ [bold red]Error running discovery:[/bold red] {e}")
|
|
134
140
|
sys.exit(1)
|
|
135
141
|
|
|
136
|
-
async def call_mcp_tool(tool_name: str, tool_args: dict):
|
|
137
|
-
"""Execute an MCP tool with the given arguments.
|
|
138
|
-
|
|
142
|
+
async def call_mcp_tool(tool_name: str, tool_args: dict, raw: bool = False):
|
|
143
|
+
"""Execute an MCP tool with the given arguments.
|
|
144
|
+
|
|
145
|
+
raw=True prints the tool's returned dict as JSON and nothing else: no
|
|
146
|
+
panel, no Rich table. Added 2026-08-30 for the two-arm experiment, where
|
|
147
|
+
the registry arm must see what a tools/call would actually return rather
|
|
148
|
+
than an 80-column table cell that wraps line-oriented stdout. In raw mode
|
|
149
|
+
the exit code carries the tool's own success field.
|
|
150
|
+
"""
|
|
151
|
+
if not raw:
|
|
152
|
+
console.print(Panel(f"🔧 [bold cyan]Executing MCP Tool: {tool_name}[/bold cyan]", border_style="cyan"))
|
|
139
153
|
|
|
140
154
|
try:
|
|
141
155
|
# Import MCP tools module
|
|
@@ -149,10 +163,14 @@ async def call_mcp_tool(tool_name: str, tool_args: dict):
|
|
|
149
163
|
|
|
150
164
|
# Execute the tool
|
|
151
165
|
tool_handler = registry[tool_name]
|
|
152
|
-
|
|
166
|
+
if not raw:
|
|
167
|
+
console.print(f"⚡ Executing '{tool_name}' with args: {tool_args}")
|
|
153
168
|
|
|
154
169
|
result = await tool_handler(tool_args)
|
|
155
170
|
|
|
171
|
+
if raw:
|
|
172
|
+
print(json.dumps(result, indent=2, default=str))
|
|
173
|
+
return bool(result.get("success", True)) if isinstance(result, dict) else True
|
|
156
174
|
# Display results
|
|
157
175
|
console.print(f"✅ [bold green]Tool execution complete![/bold green]")
|
|
158
176
|
|
|
@@ -506,9 +524,13 @@ def main():
|
|
|
506
524
|
help='🎯 GOLDEN PATH: A JSON string containing all tool arguments. '
|
|
507
525
|
'Use this for complex parameters to ensure perfect data transmission.')
|
|
508
526
|
|
|
527
|
+
call_parser.add_argument('--raw', action='store_true',
|
|
528
|
+
help='Print the tool result as JSON and nothing else; '
|
|
529
|
+
'the exit code carries the tool\'s success field.')
|
|
509
530
|
args = parser.parse_args()
|
|
510
531
|
|
|
511
|
-
|
|
532
|
+
if not getattr(args, 'raw', False):
|
|
533
|
+
console.print(Panel("🚀 [bold cyan]Pipulate :: The Local-First AI-Readiness & Automation Workshop[/bold cyan] 🚀", border_style="cyan"))
|
|
512
534
|
|
|
513
535
|
if args.command == 'install':
|
|
514
536
|
if not check_nix_installed():
|
|
@@ -556,7 +578,8 @@ def main():
|
|
|
556
578
|
if args.json_args:
|
|
557
579
|
try:
|
|
558
580
|
params = json.loads(args.json_args)
|
|
559
|
-
|
|
581
|
+
if not args.raw:
|
|
582
|
+
console.print(f"🎯 [bold green]Golden Path: Using JSON arguments[/bold green]")
|
|
560
583
|
except json.JSONDecodeError as e:
|
|
561
584
|
console.print(f"❌ [bold red]Error: Invalid JSON provided to --json-args.[/bold red]")
|
|
562
585
|
console.print(f"JSON Error: {e}")
|
|
@@ -578,7 +601,7 @@ def main():
|
|
|
578
601
|
|
|
579
602
|
# Execute the tool
|
|
580
603
|
try:
|
|
581
|
-
success = asyncio.run(call_mcp_tool(args.tool_name, params))
|
|
604
|
+
success = asyncio.run(call_mcp_tool(args.tool_name, params, raw=args.raw))
|
|
582
605
|
if not success:
|
|
583
606
|
sys.exit(1)
|
|
584
607
|
except KeyboardInterrupt:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pipulate
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.47
|
|
4
4
|
Summary: AI-readiness for the agentic web — local-first, Nix-reproducible workflows. The successor to AI SEO software.
|
|
5
5
|
Author-email: Mike Levin <pipulate@gmail.com>
|
|
6
6
|
License-Expression: AGPL-3.0-or-later
|
|
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pipulate"
|
|
7
7
|
# VERSION NOTE: Synced from pipulate/__init__.py.__version__ via python version_sync.py
|
|
8
|
-
version = "2.
|
|
8
|
+
version = "2.47"
|
|
9
9
|
authors = [
|
|
10
10
|
{ name="Mike Levin", email="pipulate@gmail.com" },
|
|
11
11
|
]
|
|
@@ -4138,7 +4138,6 @@ async def reset_python_env(request):
|
|
|
4138
4138
|
await pipulate.stream('📝 **Next Steps Required:**', verbatim=True, role='system')
|
|
4139
4139
|
await pipulate.stream(' 1. Type `exit` to leave the current nix shell', verbatim=True, role='system')
|
|
4140
4140
|
await pipulate.stream(' 2. Type `nix develop` to rebuild the environment', verbatim=True, role='system')
|
|
4141
|
-
await pipulate.stream(' 3. The fresh environment build will take 2-3 minutes', verbatim=True, role='system')
|
|
4142
4141
|
await pipulate.stream('', verbatim=True, role='system') # Empty line for spacing
|
|
4143
4142
|
await pipulate.stream('🚪 Server will exit in 3 seconds...', verbatim=True, role='system')
|
|
4144
4143
|
|
|
@@ -44,9 +44,41 @@ def get_all_tools():
|
|
|
44
44
|
except ImportError as e:
|
|
45
45
|
print(f"Could not import tool module: {module_name} - {e}")
|
|
46
46
|
|
|
47
|
+
denied = denied_tools()
|
|
48
|
+
if denied:
|
|
49
|
+
return {name: func for name, func in AUTO_REGISTERED_TOOLS.items()
|
|
50
|
+
if name not in denied}
|
|
47
51
|
return AUTO_REGISTERED_TOOLS
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def denied_tools():
|
|
55
|
+
"""Names withheld from the registry by PIPULATE_TOOL_DENY (comma-separated).
|
|
56
|
+
|
|
57
|
+
THE ESCAPE HATCH, NAMED. execute_shell_command is a registry tool, so any
|
|
58
|
+
experiment that pits the registry against the shell has a registry arm that
|
|
59
|
+
can shell out and become the other arm. Withholding is an ENV VAR on
|
|
60
|
+
purpose: the registry itself never changes, the denial is visible in the
|
|
61
|
+
harness command that set it, and an empty variable is exactly today.
|
|
62
|
+
Applied at get_all_tools()'s return because every cli.py path -- call,
|
|
63
|
+
mcp-discover --all, mcp-discover --tool -- reads that one dict.
|
|
64
|
+
"""
|
|
65
|
+
raw = os.environ.get("PIPULATE_TOOL_DENY", "")
|
|
66
|
+
return {name.strip() for name in raw.split(",") if name.strip()}
|
|
48
67
|
# --- END NEW ---
|
|
49
68
|
|
|
69
|
+
# EARMARK (2026-08-30): TWO REGISTRATION LINEAGES, ONE READER. get_all_tools()
|
|
70
|
+
# serves only @auto_tool functions. tools/advanced_automation_tools.py still
|
|
71
|
+
# defines its own execute_shell_command (undecorated -- receipt: rg -n -B2
|
|
72
|
+
# '^async def execute_shell_command' shows a comment and a blank line above
|
|
73
|
+
# it) and hands it to a register_mcp_tool() that feeds a registry THIS package
|
|
74
|
+
# never reads; tools/mcp_tools.py (42 async defs) and tools/botify_tools.py
|
|
75
|
+
# (six core functions on aiohttp and an ai_dictdb credential path, zero
|
|
76
|
+
# decorators) are the same pre-@auto_tool lineage. cli.py cannot reach any of
|
|
77
|
+
# them. Whether server.py's bracket-command path still can is UNWITNESSED.
|
|
78
|
+
# Gut or migrate: each function either gains @auto_tool plus the current
|
|
79
|
+
# credential path (config.get_botify_token, or the subprocess-the-connector
|
|
80
|
+
# pattern in connector_tools.py) or is deleted, and the botify_exports import
|
|
81
|
+
# below leaves with botify_tools.py. Not this ride.
|
|
50
82
|
__version__ = "1.0.0"
|
|
51
83
|
|
|
52
84
|
# Import shared constants to eliminate duplication
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""
|
|
2
|
+
connector_tools.py -- the registry face of scripts/connectors/*.py: one tool
|
|
3
|
+
per connector, SAME argument shape, SAME backend, SAME docstring.
|
|
4
|
+
|
|
5
|
+
This file exists for the two-arm experiment (shell verb vs registry tool) and
|
|
6
|
+
for nothing else yet. Arm S runs the connector as a command line; Arm R calls
|
|
7
|
+
the tool below through `cli.py call <name> --json-args '{...}'`. Both arrive at
|
|
8
|
+
the identical subprocess, so the only thing the experiment can measure is the
|
|
9
|
+
invocation grammar and the discovery surface, which is the variable under test.
|
|
10
|
+
|
|
11
|
+
THREE SURFACES, ONE SOURCE. sources_menu.py reads each connector's module
|
|
12
|
+
docstring with ast.get_docstring and prints its first line as the menu row;
|
|
13
|
+
the connector's own --help prints the same docstring (argparse
|
|
14
|
+
description=__doc__); this file reads the same docstring the same way and
|
|
15
|
+
installs it as the tool's __doc__ at import time, which is what
|
|
16
|
+
`cli.py mcp-discover --tool <name>` prints. A description that is better on
|
|
17
|
+
one arm than the other is the confound that would decide the experiment for
|
|
18
|
+
the wrong reason, so no hand-authored description is allowed to exist here.
|
|
19
|
+
|
|
20
|
+
PARAMS MIRROR ARGPARSE DESTS, deliberately, so the JSON a model must write is
|
|
21
|
+
readable off the same --help text the other arm reads: query (the single
|
|
22
|
+
positional; omit it for the identity walk), org, project, max, check.
|
|
23
|
+
|
|
24
|
+
KNOWN SEAM: the AST-derived Tool Roster in prompt_foo.py cannot see a __doc__
|
|
25
|
+
assigned at runtime; it shows the literal placeholder on the function. Named
|
|
26
|
+
here rather than papered over with a copied sentence that would drift.
|
|
27
|
+
"""
|
|
28
|
+
import ast
|
|
29
|
+
import asyncio
|
|
30
|
+
import sys
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
|
|
33
|
+
from tools import auto_tool
|
|
34
|
+
|
|
35
|
+
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
36
|
+
CONNECTORS = REPO_ROOT / "scripts" / "connectors"
|
|
37
|
+
TIMEOUT = 120
|
|
38
|
+
KEY_TRAILER = (
|
|
39
|
+
"\n\nRegistry form: --json-args keys mirror the connector's argument names --"
|
|
40
|
+
" query (the positional; omit for the identity walk), org, project, max, check."
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _connector_doc(name):
|
|
45
|
+
"""The connector's module docstring, read the way sources_menu.py reads it."""
|
|
46
|
+
path = CONNECTORS / f"{name}.py"
|
|
47
|
+
try:
|
|
48
|
+
doc = ast.get_docstring(ast.parse(path.read_text(encoding="utf-8")))
|
|
49
|
+
except (OSError, UnicodeDecodeError, SyntaxError) as exc:
|
|
50
|
+
return f"(could not read {path}: {exc.__class__.__name__})"
|
|
51
|
+
return (doc or f"(NO MODULE DOCSTRING in {path})") + KEY_TRAILER
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
async def _run_connector(name, params):
|
|
55
|
+
"""Run scripts/connectors/<name>.py as a subprocess; return its three channels."""
|
|
56
|
+
if not isinstance(params, dict):
|
|
57
|
+
return {"success": False, "error": "params must be a JSON object"}
|
|
58
|
+
argv = [sys.executable, str(CONNECTORS / f"{name}.py")]
|
|
59
|
+
if params.get("query") is not None:
|
|
60
|
+
argv.append(str(params["query"]))
|
|
61
|
+
for flag in ("org", "project"):
|
|
62
|
+
if params.get(flag):
|
|
63
|
+
argv += [f"--{flag}", str(params[flag])]
|
|
64
|
+
if params.get("max") is not None:
|
|
65
|
+
try:
|
|
66
|
+
argv += ["--max", str(int(params["max"]))]
|
|
67
|
+
except (TypeError, ValueError):
|
|
68
|
+
return {"success": False, "error": "max must be an integer"}
|
|
69
|
+
if params.get("check"):
|
|
70
|
+
argv.append("--check")
|
|
71
|
+
try:
|
|
72
|
+
proc = await asyncio.create_subprocess_exec(
|
|
73
|
+
*argv, cwd=str(REPO_ROOT),
|
|
74
|
+
stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
|
|
75
|
+
out, err = await asyncio.wait_for(proc.communicate(), timeout=TIMEOUT)
|
|
76
|
+
except asyncio.TimeoutError:
|
|
77
|
+
proc.kill()
|
|
78
|
+
await proc.wait()
|
|
79
|
+
return {"success": False, "error": f"timed out after {TIMEOUT}s", "argv": argv[1:]}
|
|
80
|
+
except OSError as exc:
|
|
81
|
+
return {"success": False, "error": str(exc), "argv": argv[1:]}
|
|
82
|
+
return {
|
|
83
|
+
"success": proc.returncode == 0,
|
|
84
|
+
"argv": argv[1:],
|
|
85
|
+
"exit_code": proc.returncode,
|
|
86
|
+
"stdout": out.decode("utf-8", errors="replace"),
|
|
87
|
+
"stderr": err.decode("utf-8", errors="replace"),
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@auto_tool
|
|
92
|
+
async def botify(params: dict) -> dict:
|
|
93
|
+
"""Registry face of scripts/connectors/botify.py; __doc__ is replaced at
|
|
94
|
+
import with that file's own docstring so both arms read one description."""
|
|
95
|
+
return await _run_connector("botify", params)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
botify.__doc__ = _connector_doc("botify")
|
|
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
|
|
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
|
|
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
|