@zerotimedev/thinker 0.0.0-stage → 0.1.36

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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yoav Shmariahu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,16 @@
1
- # Temporary Holding Version
1
+ # @zerotimedev/thinker
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Installs [thinker](https://github.com/yoavshmariahu/thinker), a knowledge cache for coding agents.
4
+
5
+ ```bash
6
+ npx @zerotimedev/thinker
7
+ ```
8
+
9
+ This runs the same installer as the `curl ... | bash` command in the thinker README, carried in
10
+ the package instead of fetched at run time. It downloads the latest thinker release, verifies it
11
+ against the release's sha256, installs it into `~/.thinker`, wires it into the coding agents on
12
+ the machine and opens the warm-up page. The installer's options pass through, for example
13
+ `npx @zerotimedev/thinker --uninstall`.
14
+
15
+ Requires bash, git, curl, tar and Node 20+. After the install, `thinker update` and the daily
16
+ auto-update keep the tool current; npm is not involved again.
package/install.mjs ADDED
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+ // npx @zerotimedev/thinker: runs the installer this package carries (install.sh, the same file the
3
+ // curl command pipes to bash), with the arguments passed through. Nothing of thinker runs from
4
+ // the npx cache, which npm may clear: the installer puts the tool in THINKER_HOME (~/.thinker),
5
+ // where the hooks and `thinker update` find it. scripts/pack.sh copies install.sh in beside this
6
+ // file and sets the version; the package is not meant to be installed as a dependency.
7
+ import fs from 'node:fs';
8
+ import path from 'node:path';
9
+ import { spawnSync } from 'node:child_process';
10
+ import { fileURLToPath } from 'node:url';
11
+
12
+ const script = path.join(path.dirname(fileURLToPath(import.meta.url)), 'install.sh');
13
+ const die = (msg) => { process.stderr.write(`thinker: ${msg}\n`); process.exit(1); };
14
+ if (!fs.existsSync(script)) die(`the installer is missing from this package (${script})`);
15
+
16
+ const r = spawnSync('bash', [script, ...process.argv.slice(2)], { stdio: 'inherit' });
17
+ if (r.error?.code === 'ENOENT') {
18
+ die(process.platform === 'win32'
19
+ ? 'the installer is a bash script: run this from WSL or Git Bash'
20
+ : 'bash is required to run the installer');
21
+ }
22
+ if (r.error) die(r.error.message);
23
+ if (r.signal) process.kill(process.pid, r.signal);
24
+ process.exit(r.status ?? 1);
package/install.sh ADDED
@@ -0,0 +1,408 @@
1
+ #!/usr/bin/env bash
2
+ # thinker install: installs the thinker tool and wires it into the coding agents on
3
+ # this machine (their own settings: hooks and the MCP server, once). Thinker then
4
+ # runs in every git repository on this machine: a cache starts with the first
5
+ # session in a repository, and the warm-up page that opens at the end (thinker ui)
6
+ # builds caches for the repositories your agents have worked in, from their merged
7
+ # pull requests. Nothing is asked in the terminal.
8
+ #
9
+ # Thinker is open source; no access code or GitHub token is required. It installs the latest
10
+ # GitHub release (https://github.com/yoavshmariahu/thinker/releases: a tag vX.Y.Z carrying
11
+ # thinker.tgz, version.json and install.sh), verified against the sha256 in version.json.
12
+ #
13
+ # curl -fsSL https://raw.githubusercontent.com/yoavshmariahu/thinker/main/install.sh | bash
14
+ #
15
+ # --build from inside a repository builds its cache right here, in the terminal,
16
+ # instead of opening the page. If a cache was already built for the repository,
17
+ # pass --cache instead and nothing has to be built.
18
+ #
19
+ # Options
20
+ # --build build the cache of this repository now, in the terminal (the page is not opened)
21
+ # --no-build accepted from old scripts: nothing is built without --build anyway
22
+ # --no-seed, --areas <n> accepted from old scripts and ignored: a build no longer explores the code
23
+ # --no-behaviors accepted from old scripts and ignored: there is no behaviors prompt to print
24
+ # --depth <d> with --build: full (default) or shallow, 30% of the full build
25
+ # --prs <n> with --build: merged pull requests to mine (default 60; skipped without the gh CLI)
26
+ # --pr <number> specific PR number to target for the paired benchmark
27
+ # --benchmark run paired PR benchmark during setup
28
+ # --no-benchmark skip the paired PR benchmark step
29
+ # -y, --yes accept defaults and skip interactive confirmation prompts
30
+ # --clients <list> coding agents to wire up: claude, codex, cursor, gemini, pi, windsurf, copilot, opencode, all or auto
31
+ # (default auto)
32
+ # --cache <source> cache built for this repo. One of: gh:<path in the thinker repo>, an https URL, a local file.
33
+ # Omit if .thinker/notes is already in the repo.
34
+ # --no-learn do not distill your own sessions into new notes (learning is on by default, for any of the
35
+ # agents, and uses that agent's login; switch it off for evals)
36
+ # --late also serve notes about files as the agent opens them
37
+ # --mcp accepted for compatibility: the MCP server is always registered (needs npm)
38
+ # --no-git-hook do not install the git post-commit hook (it re-checks and maintains notes after each commit)
39
+ # --ref <tag> release to install, a tag such as v0.1.34 (default latest; --branch also accepted)
40
+ # --update update an installed thinker to the latest release and exit
41
+ # --no-auto-update do not schedule daily background auto-updates
42
+ # --no-modify-path do not add THINKER_HOME/bin to PATH in your shell's startup file
43
+ # --uninstall remove hooks and registration from this repo (add --purge to delete notes too); run outside
44
+ # a repository, it removes the wiring from your agents' own settings
45
+ #
46
+ # Environment
47
+ # THINKER_HOME where the tool is installed (default ~/.thinker)
48
+ # THINKER_GH_REPO GitHub repository whose releases to install from (default yoavshmariahu/thinker)
49
+ # THINKER_REF release tag (default latest)
50
+ # THINKER_DIST_URL install from this tarball URL (with version.json beside it) instead of a GitHub release
51
+ # GITHUB_TOKEN only for --cache gh:<path>: a token for the GitHub API (GH_TOKEN works too;
52
+ # if neither is set and the GitHub CLI is logged in, its token is used)
53
+ #
54
+ # The script writes to THINKER_HOME, to .thinker/ and the agents' config in this repository, and adds one
55
+ # line putting THINKER_HOME/bin on PATH to your shell's startup file (--no-modify-path leaves it alone). No sudo.
56
+ set -euo pipefail
57
+ export COPYFILE_DISABLE=1
58
+ export COPY_EXTENDED_ATTRIBUTES_DISABLE=1
59
+
60
+ # add_to_path <dir>: put <dir> on PATH in the startup file of the user's shell, once.
61
+ # Prints the file it wrote to, or nothing when it could not tell which one.
62
+ add_to_path() {
63
+ local dir="$1" rc line
64
+ case "$(basename "${SHELL:-}")" in
65
+ zsh) rc="${ZDOTDIR:-$HOME}/.zshrc"; line="export PATH=\"$dir:\$PATH\"" ;;
66
+ bash) # macOS terminals start login shells, which read .bash_profile and not .bashrc
67
+ if [ "$(uname)" = Darwin ]; then rc="$HOME/.bash_profile"; else rc="$HOME/.bashrc"; fi
68
+ line="export PATH=\"$dir:\$PATH\"" ;;
69
+ fish) rc="${XDG_CONFIG_HOME:-$HOME/.config}/fish/conf.d/thinker.fish"; line="fish_add_path \"$dir\"" ;;
70
+ *) return 0 ;;
71
+ esac
72
+ if ! grep -qsF "$line" "$rc"; then
73
+ mkdir -p "$(dirname "$rc")" && printf '\n# thinker\n%s\n' "$line" >> "$rc" || return 0
74
+ fi
75
+ printf '%s' "$rc"
76
+ }
77
+
78
+ # path_hint: how to reach `thinker` from this shell; reads main's $home and $rcfile
79
+ path_hint() {
80
+ case ":$PATH:" in
81
+ *":$home/bin:"*) ;;
82
+ *) if [ -n "$rcfile" ]; then say " 'thinker' is on your PATH in new terminals (added to $rcfile); in this one run: export PATH=\"$home/bin:\$PATH\""
83
+ else say " Optional: add $home/bin to your PATH to run 'thinker' directly."; fi ;;
84
+ esac
85
+ }
86
+
87
+ main() {
88
+ local cache="" build="" areas="" depth="" prs="" clients="" learn=1 late=0 mcp=0 githook=0 uninstall=0 purge=0 update=0 autoupdate=1 modpath=1 ref="${THINKER_REF:-latest}" benchmark="" pr_target="" yes=0 no_seed=0
89
+ while [ $# -gt 0 ]; do
90
+ case "$1" in
91
+ --cache) cache="${2:-}"; build=0; shift 2 ;;
92
+ --build) build=1; shift ;;
93
+ --no-build) build=0; shift ;;
94
+ --no-seed) shift ;;
95
+ --no-behaviors) shift ;;
96
+ -y|--yes) yes=1; shift ;;
97
+ --areas) shift 2 ;;
98
+ --depth) depth="${2:-}"; shift 2 ;;
99
+ --prs) prs="${2:-}"; shift 2 ;;
100
+ --pr) pr_target="${2:-}"; shift 2 ;;
101
+ --benchmark) benchmark=1; shift ;;
102
+ --no-benchmark) benchmark=0; shift ;;
103
+ --clients) clients="${2:-}"; shift 2 ;;
104
+ --branch|--ref) ref="${2:-}"; shift 2 ;;
105
+ --learn) learn=1; shift ;;
106
+ --no-learn) learn=0; shift ;;
107
+ --late) late=1; shift ;;
108
+ --shared) echo "Team sharing is no longer supported." >&2; return 1 ;;
109
+ --mcp) mcp=1; shift ;;
110
+ --git-hook) githook=1; shift ;;
111
+ --no-git-hook) githook=0; shift ;;
112
+ --update) update=1; shift ;;
113
+ --auto-update) autoupdate=1; shift ;;
114
+ --no-auto-update) autoupdate=0; shift ;;
115
+ --no-modify-path) modpath=0; shift ;;
116
+ --uninstall) uninstall=1; shift ;;
117
+ --purge) purge=1; shift ;;
118
+ -h|--help) echo "see the header of install.sh or ONBOARDING.md for options"; exit 0 ;;
119
+ *) echo "unknown option: $1" >&2; exit 2 ;;
120
+ esac
121
+ done
122
+
123
+ local home="${THINKER_HOME:-$HOME/.thinker}"
124
+ local ghrepo="${THINKER_GH_REPO:-yoavshmariahu/thinker}"
125
+ local ref="${ref:-${THINKER_REF:-latest}}"
126
+ local dist="${THINKER_DIST_URL:-}"
127
+ # the GitHub release for the ref: the latest one, or the tag named
128
+ if [ -z "$dist" ]; then
129
+ case "$ref" in
130
+ latest|main) dist="https://github.com/$ghrepo/releases/latest/download/thinker.tgz" ;;
131
+ *) dist="https://github.com/$ghrepo/releases/download/$ref/thinker.tgz" ;;
132
+ esac
133
+ fi
134
+ local token="${GITHUB_TOKEN:-${GH_TOKEN:-}}"
135
+ say() { printf '%s\n' "$*"; }
136
+ die() { printf 'thinker: %s\n' "$*" >&2; exit 1; }
137
+ # --- look: colors, boxes and spinners on a terminal, plain lines anywhere else --------------
138
+ local fancy=0 bold="" dim="" cyan="" green="" yellow="" red="" magenta="" reset=""
139
+ if [ -t 1 ] && [ -z "${NO_COLOR:-}" ] && [ "${TERM:-}" != dumb ] && [ -z "${CI:-}" ]; then
140
+ fancy=1 bold=$'\033[1m' dim=$'\033[2m' cyan=$'\033[36m' green=$'\033[32m' yellow=$'\033[33m' red=$'\033[31m' magenta=$'\033[35m' reset=$'\033[0m'
141
+ fi
142
+ # box <color> <line>...: a rounded box 74 columns wide, as setup draws it; a line may carry color codes, padded on its plain text
143
+ # box <color> <line>...: the lines in a rounded frame, at least 68 columns wide and as wide as the
144
+ # longest line (a path can run past 68, and a frame narrower than its text comes out ragged)
145
+ box() {
146
+ local color="$1"; shift
147
+ local width=68 line plain
148
+ for line in "$@"; do
149
+ plain="$(printf '%s' "$line" | sed $'s/\033\\[[0-9;]*m//g')"
150
+ [ "${#plain}" -gt "$width" ] && width=${#plain}
151
+ done
152
+ printf '%s╭%s╮%s\n' "$color" "$(printf '─%.0s' $(seq 1 $((width + 4))))" "$reset"
153
+ for line in "$@"; do
154
+ plain="$(printf '%s' "$line" | sed $'s/\033\\[[0-9;]*m//g')"
155
+ printf '%s│%s %s%*s %s│%s\n' "$color" "$reset" "$line" $((width - ${#plain})) "" "$color" "$reset"
156
+ done
157
+ printf '%s╰%s╯%s\n' "$color" "$(printf '─%.0s' $(seq 1 $((width + 4))))" "$reset"
158
+ }
159
+ # spin <label> <command...>: runs the command with a turning frame and the time so far, then a ✓ or ✗
160
+ # line; its output is kept and shown only if it fails. Off a terminal: the label, then the command.
161
+ spin() {
162
+ local label="$1"; shift
163
+ if [ "$fancy" != 1 ]; then say " ${label}…"; "$@"; return; fi
164
+ local log; log="$(mktemp)"
165
+ "$@" >"$log" 2>&1 &
166
+ local pid=$! i=0 start=$SECONDS code=0
167
+ local frames=(⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏)
168
+ while kill -0 "$pid" 2>/dev/null; do
169
+ printf '\r\033[2K %s%s%s %s %s· %ss%s' "$cyan" "${frames[$((i % 10))]}" "$reset" "$label" "$dim" "$((SECONDS - start))" "$reset"
170
+ i=$((i + 1)); sleep 0.1
171
+ done
172
+ wait "$pid" || code=$?
173
+ if [ "$code" -eq 0 ]; then
174
+ printf '\r\033[2K %s✓%s %s %s· %ss%s\n' "$green" "$reset" "$label" "$dim" "$((SECONDS - start))" "$reset"
175
+ else
176
+ printf '\r\033[2K %s✗%s %s\n' "$red" "$reset" "$label"
177
+ cat "$log" >&2
178
+ fi
179
+ rm -f "$log"
180
+ return "$code"
181
+ }
182
+ # the GitHub CLI is optional: it is only asked for a token when none is set
183
+ if [ -z "$token" ] && command -v gh >/dev/null; then token="$(gh auth token 2>/dev/null || true)"; fi
184
+ # gh_fetch <api endpoint in the thinker repo> <output file>
185
+ gh_fetch() {
186
+ local url="https://api.github.com/$1" out="$2"
187
+ if [ -n "$token" ]; then
188
+ curl -fsSL -H "Authorization: Bearer $token" -H "Accept: application/vnd.github.raw" -o "$out" "$url" && return 0
189
+ die "could not download $url with the token provided (does it have read access to $ghrepo?)"
190
+ fi
191
+ curl -fsSL -H "Accept: application/vnd.github.raw" -o "$out" "$url" 2>/dev/null && return 0
192
+ die "could not download $url: check connectivity and the repository/ref, or set GITHUB_TOKEN if GitHub rate-limits the request"
193
+ }
194
+
195
+ # --- prerequisites -----------------------------------------------------
196
+ command -v git >/dev/null || die "git is required"
197
+ command -v node >/dev/null || die "Node.js 20 or newer is required (https://nodejs.org)"
198
+ command -v curl >/dev/null || die "curl is required"
199
+ command -v tar >/dev/null || die "tar is required"
200
+ local nodemajor; nodemajor="$(node -p 'process.versions.node.split(".")[0]')"
201
+ [ "$nodemajor" -ge 20 ] || die "Node.js 20 or newer is required (found $(node -v))"
202
+ # outside a git repository only the tool is installed; `thinker setup` sets a repository up later
203
+ local repo; repo="$(git rev-parse --show-toplevel 2>/dev/null)" || repo=""
204
+ [ -n "$repo" ] && cd "$repo"
205
+ [ -n "$cache" ] && [ -z "$repo" ] && die "--cache needs a repository to put the cache in: run this from inside it"
206
+
207
+ local thinker="$home/bin/thinker"
208
+
209
+ if [ "$uninstall" = 1 ]; then
210
+ [ -x "$thinker" ] || die "thinker is not installed in $home"
211
+ if [ -z "$repo" ]; then "$thinker" uninstall --user
212
+ elif [ "$purge" = 1 ]; then "$thinker" uninstall --purge --repo "$repo"; else "$thinker" uninstall --repo "$repo"; fi
213
+ say "To remove the tool itself: rm -rf \"$home\", and delete the '# thinker' PATH line from your shell's startup file"
214
+ exit 0
215
+ fi
216
+
217
+ if [ "$update" = 1 ]; then
218
+ [ -x "$thinker" ] || die "thinker is not installed in $home"
219
+ fi
220
+
221
+ # --- install the tool ----------------------------------------------------
222
+ local tmp; tmp="$(mktemp -d)"
223
+ # expand now: the variable is local and gone by the time the trap runs
224
+ # shellcheck disable=SC2064
225
+ trap "rm -rf '$tmp'" EXIT
226
+ say ""
227
+ if [ "$fancy" = 1 ]; then
228
+ local sparkle="${yellow}*${reset} ${magenta}~${reset} ${yellow}*${reset}"
229
+ box "$cyan" "$sparkle ${bold}${cyan}Thinker${reset} $sparkle" "${bold}A knowledge cache for coding & review agents${reset}" \
230
+ "${magenta}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~${reset}" "" \
231
+ "${dim}Learns from your merged fixes, flags the change that would undo one,${reset}" \
232
+ "${dim}and hands your coding agents what the repository already knows.${reset}"
233
+ else
234
+ say "Thinker"
235
+ say "A knowledge cache for coding & review agents"
236
+ fi
237
+ say ""
238
+ case "$dist" in https://*) ;; *) die "releases must be downloaded over HTTPS" ;; esac
239
+ # GitHub answers with a redirect to its asset host; curl follows it over HTTPS only
240
+ download_release() {
241
+ curl -fsSL --proto-redir '=https' -o "$tmp/thinker.tgz" "$dist" &&
242
+ curl -fsSL --proto-redir '=https' -o "$tmp/version.json" "${dist%/*}/version.json"
243
+ }
244
+ spin "Downloading the release" download_release || die "could not download $dist or its version.json (is there a release for $ref at https://github.com/$ghrepo/releases?)"
245
+ say " Verifying download…"
246
+ node --input-type=module - "$tmp/thinker.tgz" "$tmp/version.json" <<'JS'
247
+ import fs from 'node:fs';
248
+ import crypto from 'node:crypto';
249
+ const [archive, manifest] = process.argv.slice(2);
250
+ const release = JSON.parse(fs.readFileSync(manifest, 'utf8'));
251
+ if (release.schemaVersion !== 1 || !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(release.version || '') ||
252
+ !/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(release.commit || '') || !/^[a-f0-9]{64}$/.test(release.sha256 || '')) {
253
+ throw new Error('invalid or unsupported release manifest');
254
+ }
255
+ const actual = crypto.createHash('sha256').update(fs.readFileSync(archive)).digest('hex');
256
+ if (actual !== release.sha256) throw new Error('release checksum mismatch; refusing to extract');
257
+ JS
258
+ tar_extract() {
259
+ local archive="$1" dest="$2"
260
+ local warn_opt=""
261
+ if tar --warning=no-unknown-keyword --version >/dev/null 2>&1; then
262
+ warn_opt="--warning=no-unknown-keyword"
263
+ fi
264
+ local code=0
265
+ local err
266
+ # shellcheck disable=SC2086
267
+ err="$(COPYFILE_DISABLE=1 COPY_EXTENDED_ATTRIBUTES_DISABLE=1 tar ${warn_opt} -xzf "$archive" -C "$dest" 2>&1)" || code=$?
268
+ if [ -n "$err" ]; then
269
+ local filtered
270
+ filtered="$(printf '%s\n' "$err" | grep -v "Ignoring unknown extended header keyword" || true)"
271
+ if [ -n "$filtered" ]; then
272
+ printf '%s\n' "$filtered" >&2
273
+ fi
274
+ fi
275
+ [ "$code" -eq 0 ] || return "$code"
276
+ }
277
+
278
+ say " Installing CLI…"
279
+ mkdir -p "$tmp/app" && tar_extract "$tmp/thinker.tgz" "$tmp/app"
280
+ # GitHub archives wrap everything in one top-level directory
281
+ if [ ! -f "$tmp/app/src/cli.js" ]; then
282
+ local inner; inner="$(find "$tmp/app" -mindepth 1 -maxdepth 1 -type d | head -1)"
283
+ [ -n "$inner" ] && [ -f "$inner/src/cli.js" ] || die "downloaded archive does not look like thinker"
284
+ mv "$inner" "$tmp/app.inner" && rm -rf "$tmp/app" && mv "$tmp/app.inner" "$tmp/app"
285
+ fi
286
+ node --input-type=module - "$tmp/app/package.json" "$tmp/version.json" <<'JS'
287
+ import fs from 'node:fs';
288
+ const pkg = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
289
+ const release = JSON.parse(fs.readFileSync(process.argv[3], 'utf8'));
290
+ if (pkg.version !== release.version) throw new Error(`release version ${release.version} does not match archive version ${pkg.version}`);
291
+ JS
292
+ # --update: the downloaded copy's updater installs itself, so an older install (one that still
293
+ # downloads from the old host, say) moves to the releases. The home and its settings are kept.
294
+ if [ "$update" = 1 ]; then
295
+ node --input-type=module - "$tmp/app/src/update.js" "$home" "$dist" "$ref" <<'JS'
296
+ import path from 'node:path';
297
+ import { pathToFileURL } from 'node:url';
298
+ const [modulePath, home, dist, ref] = process.argv.slice(2);
299
+ const { applyUpdate } = await import(pathToFileURL(modulePath));
300
+ const result = await applyUpdate({ home, rootDir: path.join(home, 'app'), dist, ref });
301
+ console.log(`Updated thinker to v${result.version}`);
302
+ JS
303
+ exit 0
304
+ fi
305
+ # the tool needs only its source and manifest at runtime
306
+ rm -rf "$tmp/app/bench" "$tmp/app/test" "$tmp/app/caches"
307
+ mkdir -p "$home/bin"
308
+ rm -rf "$home/app.new" && mv "$tmp/app" "$home/app.new"
309
+ rm -rf "$home/app.old"; [ -d "$home/app" ] && mv "$home/app" "$home/app.old"
310
+ mv "$home/app.new" "$home/app" && rm -rf "$home/app.old"
311
+ cat > "$thinker" <<SHIM
312
+ #!/bin/sh
313
+ exec node "$home/app/src/cli.js" "\$@"
314
+ SHIM
315
+ chmod +x "$thinker"
316
+ cat > "$home/install.json" <<EOF
317
+ {
318
+ "source": "archive",
319
+ "ghrepo": "$ghrepo",
320
+ "ref": "$ref",
321
+ "dist": "$dist",
322
+ "installedAt": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
323
+ }
324
+ EOF
325
+ if [ "$autoupdate" = 1 ]; then
326
+ "$thinker" update --schedule --quiet 2>/dev/null || true
327
+ fi
328
+ local rcfile=""
329
+ if [ "$modpath" = 1 ]; then rcfile="$(add_to_path "$home/bin")"; fi
330
+
331
+ # whatever agents are on this machine are wired up, in their own settings
332
+ [ -z "$clients" ] && clients="auto"
333
+ # The dependencies are always installed: the hooks rank notes with a local cross-encoder whose runtime is one
334
+ # of them, and the MCP server, registered for every agent, needs its SDK. Without npm the hooks still work,
335
+ # ranking by words alone, and the MCP server is left out (thinker connect registers it once npm ci has run).
336
+ if command -v npm >/dev/null; then
337
+ mcp=1
338
+ install_deps() { cd "$home/app" && npm ci --omit=dev --ignore-scripts --no-audit --no-fund --silent; }
339
+ spin "Installing dependencies (the ranking runtime is most of it)" install_deps || die "npm ci failed in $home/app"
340
+ spin "Fetching the ranking model" "$thinker" ranker fetch --quiet || say " The ranking model could not be fetched (offline?); notes are ranked by words until 'thinker ranker fetch' succeeds."
341
+ else
342
+ say "npm was not found: the dependencies were not installed (cd \"$home/app\" && npm ci --omit=dev --ignore-scripts, then thinker ranker fetch and thinker connect). Notes are ranked by words alone until then."
343
+ mcp=0
344
+ fi
345
+
346
+ # --- wire the agents, once, in their own settings ---------------------------
347
+ # Nothing is asked here: Codex's hook-review marks are written outright, and every other choice
348
+ # (which repositories to build a cache for, how deep, with which model) is made on the warm-up
349
+ # page that opens next. Thinker then runs in every git repository on this machine.
350
+ local cargs="--clients $clients --yes"
351
+ [ "$learn" = 1 ] || cargs="$cargs --no-learn"
352
+ [ "$late" = 1 ] && cargs="$cargs --late"
353
+ [ "$mcp" = 1 ] || cargs="$cargs --no-mcp"
354
+ # shellcheck disable=SC2086
355
+ "$thinker" connect $cargs
356
+
357
+ # --- a cache handed over, or a build asked for by flag -------------------------
358
+ if [ -n "$cache" ]; then
359
+ case "$cache" in
360
+ gh:*) gh_fetch "repos/$ghrepo/contents/${cache#gh:}?ref=$ref" "$tmp/cache.tgz"; "$thinker" import "$tmp/cache.tgz" --repo "$repo" ;;
361
+ *) "$thinker" import "$cache" --repo "$repo" ;;
362
+ esac
363
+ elif [ "$build" = 1 ] && [ -n "$repo" ]; then
364
+ # --build from inside a repository: the build runs here, without the page, as before
365
+ local args="--build --yes --no-ui"
366
+ [ -n "$clients" ] && args="$args --clients $clients"
367
+ [ "$learn" = 1 ] || args="$args --no-learn"
368
+ [ "$githook" = 0 ] && args="$args --no-git-hook"
369
+ [ "$mcp" = 1 ] || args="$args --no-mcp"
370
+ [ -n "$depth" ] && args="$args --depth $depth"
371
+ [ -n "$prs" ] && args="$args --prs $prs"
372
+ [ -n "$pr_target" ] && args="$args --pr $pr_target"
373
+ [ "$benchmark" = 1 ] && args="$args --benchmark"
374
+ [ "$benchmark" = 0 ] && args="$args --no-benchmark"
375
+ # shellcheck disable=SC2086
376
+ THINKER_INSTALLER=1 "$thinker" setup $args --repo "$repo"
377
+ fi
378
+
379
+ # --- the warm-up page -----------------------------------------------------------
380
+ # The page lists the repositories your agents have worked in; pick the ones to build a cache for.
381
+ # Opened detached (the server exits on its own once idle); not from CI or a pipe with no terminal.
382
+ local opened=0
383
+ if [ -z "${CI:-}" ] && [ -t 1 ] && [ "$build" != 1 ]; then
384
+ ( "$thinker" ui --from-setup --tab warmup >/dev/null 2>&1 & ) && opened=1
385
+ fi
386
+ local version; version="$(node -p "require('$home/app/package.json').version" 2>/dev/null || echo '?')"
387
+ path_hint
388
+ say ""
389
+ local how="Pick the repositories to warm up on the page that just opened"
390
+ [ "$opened" = 1 ] || how="Pick the repositories to warm up: ${cyan}thinker ui${reset}"
391
+ if [ "$fancy" = 1 ]; then
392
+ box "$green" "${green}✓${reset} ${bold}Install complete${reset} ${dim}· Thinker v$version${reset}" "" \
393
+ "Installed into ${home/#$HOME/~} and wired into your agents." \
394
+ "Thinker now runs in every git repository on this machine." \
395
+ "$how" \
396
+ "${dim}(open it again any time with thinker ui).${reset}" \
397
+ "${dim}Notes stay on this machine; Thinker uploads none.${reset}" "" \
398
+ "${yellow}*${reset} ${magenta}~${reset} ${yellow}*${reset} ${bold}all done · happy shipping${reset} ${yellow}*${reset} ${magenta}~${reset} ${yellow}*${reset}"
399
+ else
400
+ say "Install complete: Thinker v$version, installed into $home and wired into your agents."
401
+ say "Thinker now runs in every git repository on this machine."
402
+ if [ "$opened" = 1 ]; then say "Pick the repositories to warm up on the page that just opened (open it again any time with thinker ui)."
403
+ else say "Pick the repositories to warm up: thinker ui"; fi
404
+ say "Notes stay on this machine; Thinker uploads none."
405
+ fi
406
+ }
407
+
408
+ main "$@"
package/package.json CHANGED
@@ -1,6 +1,22 @@
1
1
  {
2
2
  "name": "@zerotimedev/thinker",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.36",
4
+ "description": "Installer for thinker, a knowledge cache for coding agents: npx @zerotimedev/thinker",
5
+ "type": "module",
6
+ "bin": {
7
+ "thinker-install": "install.mjs"
8
+ },
9
+ "files": [
10
+ "install.mjs",
11
+ "install.sh"
12
+ ],
13
+ "engines": {
14
+ "node": ">=20"
15
+ },
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/yoavshmariahu/thinker.git"
19
+ },
20
+ "homepage": "https://github.com/yoavshmariahu/thinker#readme",
21
+ "license": "MIT"
22
+ }