ws-aitoolkit 0.1.2__tar.gz → 0.1.4__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.
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/PKG-INFO +1 -1
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/pyproject.toml +1 -1
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/pyproject.toml.orig +1 -1
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/_build.py +2 -2
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/extcmd.py +5 -2
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/guards/syntax_check.py +17 -1
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/policy.py +25 -4
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/LICENSE +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/NOTICE +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/README.md +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/__init__.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/__main__.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/adapters/__init__.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/adapters/claude_code.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/adapters/opencode.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/adapters/opencode_plugin.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/approvals.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/broker.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/canary.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/cli.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/config_cmd.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/devices.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/doctor.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/eval.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/evalstats.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/frontmatter.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/gate.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/globals.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/guards/__init__.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/guards/_paths.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/guards/bulk_staging.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/guards/claims.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/guards/record_calls.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/guards/session_context.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/guards/skill_gate.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/guards/spawn_cap.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/guards/test_weakening.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/install.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/jobs.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/jsonl.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/mcp_server.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/memory.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/modules.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/notes.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/notesdetect.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/osinfo.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/output.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/profile.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/rating.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/roles/__init__.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/roles/_schedule.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/roles/anthropic.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/roles/commands.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/roles/launchd.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/roles/markdown_git.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/roles/notify_none.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/roles/ntfy.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/roles/opencode.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/roles/restic.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/roles/secrets_env.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/roles/secrets_ws.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/roles/sops_age.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/roles/systemd.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/run.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/runrecord.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/runschema.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/sandbox.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/secrets.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/selfcmd.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/session.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/setup_ui/__init__.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/setup_ui/brand.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/setup_ui/flow.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/setup_ui/inline.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/setup_ui/web.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/skills/__init__.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/skills/align.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/skills/checkpoint.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/skills/dataviz.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/skills/resume.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/skills/ship.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/skills/taste_gate.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/status.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/testing/__init__.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/testing/mockprovider.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/testing/scenarios.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/usage.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/userconfig.py +0 -0
- {ws_aitoolkit-0.1.2 → ws_aitoolkit-0.1.4}/src/ws/version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Written by the release workflow before uv build; absent in a checkout."""
|
|
2
2
|
|
|
3
|
-
git_sha = "
|
|
3
|
+
git_sha = "9ed7a1d78df76dd7e28498dd93f03890e6b1efb1"
|
|
4
4
|
dirty = False
|
|
5
|
-
build_date = "2026-11-
|
|
5
|
+
build_date = "2026-11-01T15:26:17Z"
|
|
6
6
|
source = "release"
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Precedence, first match wins: a built-in command; `ws-<word>` on PATH (a word is `[a-z][a-z0-9-]{0,40}`); the
|
|
4
4
|
profile's `[cli] fallback`; the usual usage error with "did you mean". An external gets the arguments after its word,
|
|
5
|
-
`WS_OUTPUT=json` when `--json` was given, and `-C PATH` as its working folder (the way git runs `git-<word>`). The
|
|
5
|
+
`--json` (before any `--`) and `WS_OUTPUT=json` when `--json` was given, and `-C PATH` as its working folder (the way git runs `git-<word>`). The
|
|
6
6
|
fallback gets the whole original argument list, leading flags and the empty list (a bare `ws`) included, and runs with
|
|
7
7
|
`WS_FALLBACK=1`; a run that already has it set is not handed on again. The fallback is an absolute path (or `~/...`) to a
|
|
8
8
|
file that is yours, executable and closed to other writers, the same test a job file passes, in a folder that other
|
|
@@ -177,9 +177,12 @@ def plan(
|
|
|
177
177
|
found = find_external(first, env)
|
|
178
178
|
if found:
|
|
179
179
|
child = dict(env)
|
|
180
|
+
args = rest[1:]
|
|
180
181
|
if g.json:
|
|
181
182
|
child["WS_OUTPUT"] = "json"
|
|
182
|
-
|
|
183
|
+
cut = args.index("--") if "--" in args else len(args)
|
|
184
|
+
args = [*args[:cut], "--json", *args[cut:]] # passed on as well: most programs read the flag
|
|
185
|
+
return Launch(found, args, child)
|
|
183
186
|
elif first is not None and first.split("=")[0] in flags:
|
|
184
187
|
return None
|
|
185
188
|
try:
|
|
@@ -104,6 +104,22 @@ def _run(argv: list[str], path: str) -> tuple[str | None, str | None]:
|
|
|
104
104
|
return f"{path}: {argv[0]} found a problem:\n{_brief(done.stdout + done.stderr)}", None
|
|
105
105
|
|
|
106
106
|
|
|
107
|
+
def _bash_shebang(path: str) -> bool:
|
|
108
|
+
"""Does the script ask for bash on its first line (`#!/bin/bash`, `#!/usr/bin/env bash`)? Then `sh -n` would
|
|
109
|
+
report bash syntax such as `>(...)` as an error."""
|
|
110
|
+
try:
|
|
111
|
+
with open(path, "rb") as f:
|
|
112
|
+
first = f.readline(256)
|
|
113
|
+
except OSError:
|
|
114
|
+
return False
|
|
115
|
+
if not first.startswith(b"#!"):
|
|
116
|
+
return False
|
|
117
|
+
words = first[2:].split()
|
|
118
|
+
if words and words[0].endswith(b"/env"):
|
|
119
|
+
words = [w for w in words[1:] if not w.startswith(b"-")]
|
|
120
|
+
return bool(words) and words[0].rsplit(b"/", 1)[-1] == b"bash"
|
|
121
|
+
|
|
122
|
+
|
|
107
123
|
def judge(call, cfg: Config, ctx) -> Finding | None:
|
|
108
124
|
path = os.path.abspath(os.path.join(ctx.cwd or "", call.value))
|
|
109
125
|
ext = path.rsplit(".", 1)[-1].lower() if "." in os.path.basename(path) else ""
|
|
@@ -118,7 +134,7 @@ def judge(call, cfg: Config, ctx) -> Finding | None:
|
|
|
118
134
|
data = f.read()
|
|
119
135
|
problem, note = {"py": _python, "json": _json, "toml": _toml}[ext](path, data), None
|
|
120
136
|
elif ext in ("sh", "bash"):
|
|
121
|
-
shell = "bash" if ext == "bash" else "sh"
|
|
137
|
+
shell = "bash" if ext == "bash" or _bash_shebang(path) else "sh"
|
|
122
138
|
if shutil.which(shell) is None and shutil.which("bash") is not None:
|
|
123
139
|
shell = "bash"
|
|
124
140
|
problem, note = _run([shell, "-n"], path)
|
|
@@ -1131,6 +1131,28 @@ def _cluster(flags: list[str], letter: str) -> bool:
|
|
|
1131
1131
|
# --- paths: deny_read, protected files ----------------------------------------------------------------------------
|
|
1132
1132
|
|
|
1133
1133
|
|
|
1134
|
+
def _glob_class(pattern: str, i: int) -> tuple[str, int] | None:
|
|
1135
|
+
"""The bracket class opening at `pattern[i]` as a regex class and the index of its `]`, or None when it is not
|
|
1136
|
+
one (no `]` before a `/`, or a range like `s-c` that runs backwards), so the `[` is matched as itself."""
|
|
1137
|
+
j = pattern.find("]", i + 2)
|
|
1138
|
+
if j < 0 or "/" in pattern[i + 1 : j]:
|
|
1139
|
+
return None
|
|
1140
|
+
body = pattern[i + 1 : j]
|
|
1141
|
+
neg = body[0] in "!^"
|
|
1142
|
+
body = body[1:] if neg else body
|
|
1143
|
+
parts, k = [], 0
|
|
1144
|
+
while k < len(body):
|
|
1145
|
+
if k + 2 < len(body) and body[k + 1] == "-":
|
|
1146
|
+
if body[k] > body[k + 2]:
|
|
1147
|
+
return None
|
|
1148
|
+
parts.append(re.escape(body[k]) + "-" + re.escape(body[k + 2]))
|
|
1149
|
+
k += 3
|
|
1150
|
+
else:
|
|
1151
|
+
parts.append(re.escape(body[k]))
|
|
1152
|
+
k += 1
|
|
1153
|
+
return "[" + ("^" if neg else "") + "".join(parts) + "]", j
|
|
1154
|
+
|
|
1155
|
+
|
|
1134
1156
|
def _glob_regex_path(pattern: str) -> re.Pattern:
|
|
1135
1157
|
out, i = [], 0
|
|
1136
1158
|
while i < len(pattern):
|
|
@@ -1147,10 +1169,9 @@ def _glob_regex_path(pattern: str) -> re.Pattern:
|
|
|
1147
1169
|
out.append("[^/]*")
|
|
1148
1170
|
elif c == "?":
|
|
1149
1171
|
out.append("[^/]")
|
|
1150
|
-
elif c == "[" and (
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
i = j
|
|
1172
|
+
elif c == "[" and (cls := _glob_class(pattern, i)) is not None:
|
|
1173
|
+
out.append(cls[0])
|
|
1174
|
+
i = cls[1]
|
|
1154
1175
|
else:
|
|
1155
1176
|
out.append(re.escape(c))
|
|
1156
1177
|
i += 1
|
|
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
|
|
File without changes
|
|
File without changes
|