mmoussa commited on
Commit
f83eb83
·
verified ·
1 Parent(s): d8a9762

Knack v3: GGUF for Ollama / llama.cpp + knack.py

Browse files
Files changed (7) hide show
  1. .gitattributes +2 -0
  2. README.md +113 -26
  3. knack-v3-Q4_K_M.gguf +3 -0
  4. knack-v3-Q8_0.gguf +3 -0
  5. knack.py +226 -0
  6. params +5 -0
  7. template +1 -0
.gitattributes CHANGED
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ knack-v3-Q4_K_M.gguf filter=lfs diff=lfs merge=lfs -text
37
+ knack-v3-Q8_0.gguf filter=lfs diff=lfs merge=lfs -text
README.md CHANGED
@@ -4,7 +4,10 @@ tags:
4
  - devshub
5
  - decision-model
6
  - developer-tools
 
 
7
  pipeline_tag: text-classification
 
8
  ---
9
 
10
  # CodeSync Knack
@@ -19,54 +22,138 @@ in about 10 milliseconds.
19
  - **Free.** Ships with every CodeSync install. No API key, no account, no usage limits.
20
  - **Private.** Runs locally. Your prompts and commands never leave your machine to be judged.
21
  - **Fast.** About 10 ms per decision on a GPU, about 250 ms on a plain CPU.
22
- - **Small.** About 1 GB on disk. Installs itself in the background after CodeSync starts.
23
  - **Accurate where it matters.** Trained on real CodeSync sessions, with extra focus on the
24
  rare cases that cost the most when missed: destructive deletes, commands that never exit,
25
  resuming interrupted work, refactor / test / documentation requests.
26
 
27
- ## Results
28
 
29
- Measured on CodeSync's own judgments (version v3, November 2026):
 
30
 
31
- | Benchmark | Knack v3 | What it measures |
32
- |---|---:|---|
33
- | Hand-labelled set | **97.9%** | 233 decisions labelled by hand |
34
- | Held-out set | **95.5%** | 3,267 decisions from real sessions, never trained on |
35
- | Rare-case set | **93.7%** | 4,120 decisions on the hardest, rarest cases |
36
- | Latency | **13 ms** | median per request on a laptop GPU |
37
 
38
- ## How to get it
 
 
 
 
 
39
 
40
- Install CodeSync. That's it.
41
 
42
  ```bash
43
- curl -fsSL https://app.devshub.ai/install.sh | bash
44
  ```
45
 
46
- The installer stays as fast as before. On first start, CodeSync downloads Knack in the
47
- background and keeps working while it does. Once Knack is ready it becomes CodeSync's first
48
- judgment engine, and new versions are picked up automatically.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
 
50
- To turn it off: `CODESYNC_KNACK_DISABLED=1`.
 
 
 
51
 
52
  ## What it decides
53
 
54
- Knack answers CodeSync's fixed set of 29 decisions:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
 
56
- - **Every turn (21):** task intent, routing and reasoning depth, plan / ask / build mode,
57
- new task vs continuing work, resume requests, screencast start and stop, browser and
58
- messaging needs, whether the request is chat or a task, and more.
59
- - **Every shell command (8):** risk level, activity type, long-running or not, whether it
60
- verifies, mutates the repo, deploys, needs a running app, or starts a server.
 
 
 
 
 
 
 
 
 
 
 
 
61
 
62
- It only answers the questions it was trained on. Anything else is passed to CodeSync's
63
- cloud judgment service, so a new kind of question never gets a guess.
 
64
 
65
  ## Files
66
 
67
  | File | Purpose |
68
  |---|---|
69
- | `manifest.json` | current version, archive URL, sha256 (read by CodeSync) |
70
- | `knack-<version>.tar.gz` | the model folder |
 
 
 
 
 
 
71
 
72
  Built by [DevsHub](https://app.devshub.ai) for [CodeSync](https://app.devshub.ai).
 
4
  - devshub
5
  - decision-model
6
  - developer-tools
7
+ - gguf
8
+ - ollama
9
  pipeline_tag: text-classification
10
+ inference: false
11
  ---
12
 
13
  # CodeSync Knack
 
22
  - **Free.** Ships with every CodeSync install. No API key, no account, no usage limits.
23
  - **Private.** Runs locally. Your prompts and commands never leave your machine to be judged.
24
  - **Fast.** About 10 ms per decision on a GPU, about 250 ms on a plain CPU.
25
+ - **Small.** 0.5B parameters. 546 MB (Q8_0) or 371 MB (Q4_K_M) in Ollama.
26
  - **Accurate where it matters.** Trained on real CodeSync sessions, with extra focus on the
27
  rare cases that cost the most when missed: destructive deletes, commands that never exit,
28
  resuming interrupted work, refactor / test / documentation requests.
29
 
30
+ ## Try it in one line
31
 
32
+ You need [Ollama](https://ollama.com/download) running. Nothing else to install: the script
33
+ uses only the Python standard library and pulls the model on first run.
34
 
35
+ ```bash
36
+ curl -fsSL https://huggingface.co/devshub-ai/knack/resolve/main/knack.py | python3 - "rm -rf build/"
37
+ ```
 
 
 
38
 
39
+ ```
40
+ risk 2 0.93 0 safe, 1 needs care, 2 destructive / dangerous
41
+ long_running no 0.97 keeps running (server, watch, poll, tail -f)
42
+ activity running 0.95 what kind of command it is
43
+ ...
44
+ ```
45
 
46
+ Judge a user message instead of a command:
47
 
48
  ```bash
49
+ curl -fsSL https://huggingface.co/devshub-ai/knack/resolve/main/knack.py | python3 - --turn "fix the failing login test"
50
  ```
51
 
52
+ Or save the script and use it as a tool or a local server:
53
+
54
+ ```bash
55
+ curl -fsSLO https://huggingface.co/devshub-ai/knack/resolve/main/knack.py
56
+ python3 knack.py "docker compose up" --json # machine-readable
57
+ python3 knack.py --turn "add dark mode" -q route.mode -q task.intent
58
+ python3 knack.py serve --port 33727 # POST /v1/systemone (CodeSync format)
59
+ ```
60
+
61
+ ## Use it with Ollama directly
62
+
63
+ ```bash
64
+ ollama pull hf.co/devshub-ai/knack:Q8_0 # or :Q4_K_M (smaller, near-identical accuracy)
65
+ ```
66
+
67
+ Knack is a classifier, not a chat model. A request is a **state** (one `field: value` line
68
+ per field) followed by one **question id**. The answer is the next token: a letter `A`, `B`,
69
+ `C`... that picks the question's option in the order listed below.
70
+
71
+ ```bash
72
+ curl -s localhost:11434/api/generate -d '{
73
+ "model": "hf.co/devshub-ai/knack:Q8_0", "raw": true, "stream": false,
74
+ "logprobs": true, "top_logprobs": 20, "options": {"temperature": 0, "num_predict": 1},
75
+ "prompt": "<|im_start|>user\ncommand: tail -f app.log\ndescription: <|im_end|>\n<|im_start|>assistant\nlong_running:"
76
+ }'
77
+ ```
78
 
79
+ **Read the answer from `logprobs`, not from `response`.** A question with N options uses
80
+ only the first N letters. Take the probabilities of those letters, renormalise them, and
81
+ pick the highest (for `long_running` above: `A` = yes, `B` = no). The generated letter on
82
+ its own can be an unused letter. `knack.py` does this for you.
83
 
84
  ## What it decides
85
 
86
+ **Every shell command** (state fields: `command`, `description`):
87
+
88
+ | id | options (A, B, C...) |
89
+ |---|---|
90
+ | `risk` | 0 safe, 1 needs care, 2 destructive |
91
+ | `long_running` | yes, no |
92
+ | `activity` | testing, building, installing, git, inspecting, running |
93
+ | `verifies` | yes, no |
94
+ | `mutates_repo` | yes, no |
95
+ | `deploys` | yes, no |
96
+ | `runtime_check` | yes, no |
97
+ | `serves` | yes, no |
98
+
99
+ **Every user turn** (state fields, in this order: `prompt`, `current_mode`,
100
+ `recent_activity`, `conversation_size`, `open_task`, `turns_on_open_task`,
101
+ `interrupted_work`, `browser_context`; see `turn_state()` in `knack.py` for defaults):
102
+
103
+ | id | options (A, B, C...) |
104
+ |---|---|
105
+ | `turn.chat_or_task` | chat, task |
106
+ | `turn.actionable` / `turn.question_shaped` / `turn.new_task` / `turn.resume` / `turn.mission` / `turn.plan_authoring` / `turn.demo` / `turn.skip_orientation` | yes, no |
107
+ | `turn.change_intent` | create, adjust, enhance, fix, refactor, none |
108
+ | `turn.screencast` | start, stop, none |
109
+ | `task.intent` | bug_fix, feature, refactor, api_schema, config_deploy, test, documentation, investigation, unknown |
110
+ | `task.execution_strategy` | direct, plan, parallel |
111
+ | `route.mode` | plan, build, ask, debug |
112
+ | `route.tier` | light, medium, heavy |
113
+ | `route.thinking` | off, low, medium, high, xhigh, max |
114
+ | `route.risk` | low, medium, high |
115
+ | `browser` / `messaging` / `delivery.feature_task` / `delivery.slash_task` | yes, no |
116
+
117
+ Knack only knows these 29 questions. Inside CodeSync, anything else goes to CodeSync's cloud
118
+ judgment service, so a new kind of question never gets a guess.
119
+
120
+ ## Results
121
+
122
+ Measured on CodeSync's own judgments (version v3, November 2026):
123
 
124
+ | Benchmark | Knack v3 | Ollama Q8_0 | Ollama Q4_K_M | What it measures |
125
+ |---|---:|---:|---:|---|
126
+ | Hand-labelled set | **97.9%** | 97.9% | 97.4% | 233 decisions labelled by hand |
127
+ | Held-out set | **95.5%** | 95.7% | 95.7% | 3,267 decisions from real sessions, never trained on |
128
+ | Rare-case set | **93.7%** | 93.6% | 93.6% | 4,120 decisions on the hardest, rarest cases |
129
+ | Latency | **13 ms** | ~80 ms | ~80 ms | median per command (8 questions) on a laptop GPU |
130
+
131
+ The Ollama files run one request per question; CodeSync's built-in runtime answers all
132
+ questions of a turn in a single forward pass, which is why it is faster.
133
+
134
+ ## Use it inside CodeSync
135
+
136
+ Install CodeSync. That's it.
137
+
138
+ ```bash
139
+ curl -fsSL https://app.devshub.ai/install.sh | bash
140
+ ```
141
 
142
+ On first start, CodeSync downloads Knack in the background and keeps working while it
143
+ does. Once Knack is ready it becomes CodeSync's first judgment engine, and new versions are
144
+ picked up automatically. To turn it off: `CODESYNC_KNACK_DISABLED=1`.
145
 
146
  ## Files
147
 
148
  | File | Purpose |
149
  |---|---|
150
+ | `knack-v3-Q8_0.gguf`, `knack-v3-Q4_K_M.gguf` | Ollama / llama.cpp edition |
151
+ | `knack.py` | standalone runner (CLI and local server), standard library only |
152
+ | `template`, `params` | Ollama prompt template and parameters (raw prompt, 1 token, temperature 0) |
153
+ | `manifest.json`, `knack-<version>.tar.gz` | the PyTorch model folder CodeSync installs |
154
+
155
+ The GGUF edition is the same weights with an untied output head: the rows of the answer
156
+ letters carry a shared offset, so the letters always lead the vocabulary while their relative
157
+ probabilities, and so every answer, stay the same.
158
 
159
  Built by [DevsHub](https://app.devshub.ai) for [CodeSync](https://app.devshub.ai).
knack-v3-Q4_K_M.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c55e4831fc36f7adf5bd90573bb3c2c81c0409451ddebd2e13bdce9f49ea1095
3
+ size 515961664
knack-v3-Q8_0.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2eed77e11930251a46eed2bf1cf6401e8fe499c2a953bca5e8fdaa9bc4eeeb6b
3
+ size 816653120
knack.py ADDED
@@ -0,0 +1,226 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """CodeSync Knack, standalone: ask the model CodeSync's judgment questions through Ollama.
3
+
4
+ Standard library only. Needs Ollama (https://ollama.com) running; the model (546 MB) is
5
+ pulled on first use. One-liner, nothing to install:
6
+
7
+ curl -fsSL https://huggingface.co/devshub-ai/knack/resolve/main/knack.py | python3 - "rm -rf build/"
8
+
9
+ Usage:
10
+
11
+ python knack.py "rm -rf build/" # the 8 shell-command questions
12
+ python knack.py --turn "fix the failing login test" # the 21 per-turn questions
13
+ python knack.py --turn "add dark mode" --mode plan --json
14
+ python knack.py serve --port 33727 # CodeSync-compatible POST /v1/systemone
15
+
16
+ How Knack answers: the prompt is a state (``field: value`` lines) followed by one question
17
+ id, and the answer is the next token, a letter ``A``, ``B``, ``C``... picking the question's
18
+ option in order. Only the first N letters belong to a question with N options, so the
19
+ answer is the most likely of THOSE letters, renormalised (this script does that from
20
+ Ollama's logprobs). Do not read the raw generated letter on its own.
21
+
22
+ Env: KNACK_OLLAMA_HOST (default http://127.0.0.1:11434),
23
+ KNACK_MODEL (default hf.co/devshub-ai/knack:Q8_0; :Q4_K_M is smaller, 371 MB).
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ import argparse
29
+ import json
30
+ import math
31
+ import os
32
+ import sys
33
+ import urllib.error
34
+ import urllib.request
35
+ from concurrent.futures import ThreadPoolExecutor
36
+ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
37
+ from typing import Any
38
+
39
+ HOST = os.environ.get("KNACK_OLLAMA_HOST", "http://127.0.0.1:11434").rstrip("/")
40
+ MODEL = os.environ.get("KNACK_MODEL", "hf.co/devshub-ai/knack:Q8_0")
41
+ PREFIX = "<|im_start|>user\n"
42
+ SUFFIX = "<|im_end|>\n<|im_start|>assistant\n"
43
+ LETTERS = [chr(65 + i) for i in range(26)]
44
+ _READY: set[str] = set()
45
+
46
+ # question id -> (type, option keys in letter order, what it decides)
47
+ QUESTIONS: dict[str, tuple[str, list[str], str]] = {
48
+ # every shell command (state: command, description)
49
+ "risk": ("score", ["0", "1", "2"], "0 safe, 1 needs care, 2 destructive / dangerous"),
50
+ "long_running": ("noul", ["yes", "no"], "keeps running (server, watch, poll, tail -f)"),
51
+ "activity": ("choice", ["testing", "building", "installing", "git", "inspecting", "running"],
52
+ "what kind of command it is"),
53
+ "verifies": ("noul", ["yes", "no"], "checks that work is correct (tests, lint, build)"),
54
+ "mutates_repo": ("noul", ["yes", "no"], "changes files or git state of the repo"),
55
+ "deploys": ("noul", ["yes", "no"], "deploys or publishes"),
56
+ "runtime_check": ("noul", ["yes", "no"], "needs a running app to check"),
57
+ "serves": ("noul", ["yes", "no"], "starts a server"),
58
+ # every user turn (state: prompt, current_mode, ...)
59
+ "turn.chat_or_task": ("choice", ["chat", "task"], "conversation or work request"),
60
+ "turn.actionable": ("noul", ["yes", "no"], "asks for something to be done"),
61
+ "turn.question_shaped": ("noul", ["yes", "no"], "phrased as a question"),
62
+ "turn.change_intent": ("choice", ["create", "adjust", "enhance", "fix", "refactor", "none"],
63
+ "kind of code change asked for"),
64
+ "turn.new_task": ("noul", ["yes", "no"], "starts a new task (vs continuing)"),
65
+ "turn.resume": ("noul", ["yes", "no"], "asks to resume interrupted work"),
66
+ "turn.mission": ("noul", ["yes", "no"], "long multi-step mission"),
67
+ "turn.plan_authoring": ("noul", ["yes", "no"], "asks to write a plan"),
68
+ "turn.screencast": ("choice", ["start", "stop", "none"], "start / stop screen recording"),
69
+ "turn.demo": ("noul", ["yes", "no"], "asks for a demo"),
70
+ "turn.skip_orientation": ("noul", ["yes", "no"], "no codebase exploration needed"),
71
+ "task.intent": ("choice", ["bug_fix", "feature", "refactor", "api_schema", "config_deploy",
72
+ "test", "documentation", "investigation", "unknown"], "task type"),
73
+ "task.execution_strategy": ("choice", ["direct", "plan", "parallel"], "how to execute"),
74
+ "route.mode": ("choice", ["plan", "build", "ask", "debug"], "best CodeSync mode"),
75
+ "route.tier": ("choice", ["light", "medium", "heavy"], "model size needed"),
76
+ "route.thinking": ("choice", ["off", "low", "medium", "high", "xhigh", "max"],
77
+ "reasoning effort needed"),
78
+ "route.risk": ("choice", ["low", "medium", "high"], "risk of the requested work"),
79
+ "browser": ("choice", ["yes", "no"], "needs a browser"),
80
+ "messaging": ("choice", ["yes", "no"], "needs to send a message (email, chat)"),
81
+ "delivery.feature_task": ("choice", ["yes", "no"], "a feature delivery task"),
82
+ "delivery.slash_task": ("choice", ["yes", "no"], "a slash-command style task"),
83
+ }
84
+ BASH = list(QUESTIONS)[:8] # the shell-command questions above
85
+ TURN = list(QUESTIONS)[8:] # the per-turn questions
86
+
87
+
88
+ def bash_state(command: str, description: str = "") -> dict[str, Any]:
89
+ return {"command": command, "description": description}
90
+
91
+
92
+ def turn_state(prompt: str, mode: str = "build") -> dict[str, Any]:
93
+ return {"prompt": prompt, "current_mode": mode, "recent_activity": "none",
94
+ "conversation_size": "unknown", "open_task": "none", "turns_on_open_task": 0,
95
+ "interrupted_work": "none", "browser_context": False}
96
+
97
+
98
+ def render_state(state: dict[str, Any]) -> str:
99
+ return "\n".join(f"{k}: {v if isinstance(v, str) else json.dumps(v, ensure_ascii=False)}"
100
+ for k, v in state.items())
101
+
102
+
103
+ def _post(path: str, body: dict[str, Any], timeout: float = 60.0) -> dict[str, Any]:
104
+ req = urllib.request.Request(HOST + path, data=json.dumps(body).encode(),
105
+ headers={"Content-Type": "application/json"})
106
+ with urllib.request.urlopen(req, timeout=timeout) as r:
107
+ return json.loads(r.read())
108
+
109
+
110
+ def ask_one(state: dict[str, Any], qid: str, model: str = MODEL) -> dict[str, Any]:
111
+ """{answer, confidence, probabilities} for one question."""
112
+ kind, keys, _ = QUESTIONS[qid]
113
+ out = _post("/api/generate", {
114
+ "model": model, "prompt": PREFIX + render_state(state) + SUFFIX + qid + ":",
115
+ "raw": True, "stream": False, "logprobs": True, "top_logprobs": 20,
116
+ "options": {"temperature": 0, "num_predict": 1}})
117
+ letters = LETTERS[: len(keys)]
118
+ logp = {e["token"].strip(): e["logprob"]
119
+ for e in (out.get("logprobs") or [{}])[0].get("top_logprobs", [])}
120
+ raw = {k: math.exp(logp[c]) if c in logp else 0.0 for k, c in zip(keys, letters)}
121
+ total = sum(raw.values()) or 1.0
122
+ probs = {k: v / total for k, v in raw.items()}
123
+ best = max(probs, key=probs.__getitem__)
124
+ return {"answer": best, "confidence": probs[best], "probabilities": probs, "type": kind}
125
+
126
+
127
+ def ensure_model(model: str = MODEL) -> None:
128
+ """Pull the model into Ollama if it is not there yet (first run only)."""
129
+ if model in _READY:
130
+ return
131
+ _READY.add(model)
132
+ try:
133
+ _post("/api/show", {"model": model}, timeout=10)
134
+ return
135
+ except urllib.error.HTTPError as exc:
136
+ if exc.code != 404:
137
+ raise
138
+ print(f"pulling {model} into Ollama (first run only)...", file=sys.stderr, flush=True)
139
+ _post("/api/pull", {"model": model, "stream": False}, timeout=3600)
140
+
141
+
142
+ def ask(state: dict[str, Any], qids: list[str], model: str = MODEL) -> dict[str, dict[str, Any]]:
143
+ unknown = [q for q in qids if q not in QUESTIONS]
144
+ if unknown:
145
+ raise KeyError(f"Knack was not trained on: {unknown}")
146
+ ensure_model(model)
147
+ with ThreadPoolExecutor(max_workers=8) as pool:
148
+ return dict(zip(qids, pool.map(lambda q: ask_one(state, q, model), qids)))
149
+
150
+
151
+ def to_systemone(qid: str, a: dict[str, Any]) -> dict[str, Any]:
152
+ """CodeSync / TypeSafe System One answer shape."""
153
+ p, kind = a["probabilities"], a["type"]
154
+ if kind == "noul":
155
+ return {"noul": p["yes"], "confidence": a["confidence"]}
156
+ if kind == "score":
157
+ return {"score": sum(int(k) * v for k, v in p.items()), "confidence": a["confidence"],
158
+ "probabilities": p}
159
+ return {"choice": a["answer"], "confidence": a["confidence"], "probabilities": p}
160
+
161
+
162
+ def serve(port: int, model: str) -> None:
163
+ class H(BaseHTTPRequestHandler):
164
+ def _send(self, code: int, body: dict[str, Any]) -> None:
165
+ data = json.dumps(body).encode()
166
+ self.send_response(code)
167
+ self.send_header("Content-Type", "application/json")
168
+ self.send_header("Content-Length", str(len(data)))
169
+ self.end_headers()
170
+ self.wfile.write(data)
171
+
172
+ def do_GET(self) -> None: # noqa: N802
173
+ self._send(200, {"ok": True, "model": model, "questions": len(QUESTIONS)})
174
+
175
+ def do_POST(self) -> None: # noqa: N802
176
+ body = json.loads(self.rfile.read(int(self.headers.get("Content-Length") or 0)))
177
+ qids = [q for q in body.get("questions", {}) if q in QUESTIONS] # others: omitted
178
+ try:
179
+ res = ask(body["state"], qids, model)
180
+ except Exception as exc: # noqa: BLE001
181
+ return self._send(502, {"error": f"{type(exc).__name__}: {exc}"})
182
+ self._send(200, {"answers": {q: to_systemone(q, a) for q, a in res.items()}})
183
+
184
+ def log_message(self, *a: Any) -> None:
185
+ pass
186
+
187
+ print(f"Knack ({model} via {HOST}) on http://127.0.0.1:{port}/v1/systemone", flush=True)
188
+ ThreadingHTTPServer(("127.0.0.1", port), H).serve_forever()
189
+
190
+
191
+ def main(argv: list[str] | None = None) -> int:
192
+ argv = sys.argv[1:] if argv is None else argv
193
+ if argv[:1] == ["serve"]:
194
+ sp = argparse.ArgumentParser(prog="knack.py serve")
195
+ sp.add_argument("--port", type=int, default=33727)
196
+ sp.add_argument("--model", default=MODEL)
197
+ a = sp.parse_args(argv[1:])
198
+ serve(a.port, a.model)
199
+ return 0
200
+ ap = argparse.ArgumentParser(description="Ask CodeSync Knack (via Ollama).")
201
+ ap.add_argument("text", help="a shell command, or with --turn a user message")
202
+ ap.add_argument("--turn", action="store_true", help="judge a user message, not a command")
203
+ ap.add_argument("--mode", default="build", help="current CodeSync mode for --turn")
204
+ ap.add_argument("--description", default="", help="what the command is for")
205
+ ap.add_argument("-q", "--question", action="append", help="only these question ids")
206
+ ap.add_argument("--model", default=MODEL)
207
+ ap.add_argument("--json", action="store_true")
208
+ a = ap.parse_args(argv)
209
+ state = turn_state(a.text, a.mode) if a.turn else bash_state(a.text, a.description)
210
+ qids = a.question or (TURN if a.turn else BASH)
211
+ try:
212
+ res = ask(state, qids, a.model)
213
+ except OSError as exc:
214
+ print(f"cannot reach Ollama at {HOST} ({exc}). Run: ollama pull {a.model}", file=sys.stderr)
215
+ return 1
216
+ if a.json:
217
+ print(json.dumps(res, indent=1))
218
+ return 0
219
+ w = max(map(len, qids))
220
+ for q, r in res.items():
221
+ print(f"{q:<{w}} {r['answer']:<13} {r['confidence']:.2f} {QUESTIONS[q][2]}")
222
+ return 0
223
+
224
+
225
+ if __name__ == "__main__":
226
+ sys.exit(main())
params ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ {
2
+ "temperature": 0,
3
+ "num_predict": 1,
4
+ "num_ctx": 2048
5
+ }
template ADDED
@@ -0,0 +1 @@
 
 
1
+ {{ .Prompt }}