@zerotimedev/thinker 0.0.0-stage → 0.1.37

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