tinker-agent 1.3.0 → 1.5.0
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/CHANGELOG.md +39 -1
- package/README.md +271 -72
- package/bin/tinker.js +75 -25
- package/package.json +12 -3
- package/src/agent/runtime-session.ts +113 -15
- package/src/cli/command-line.ts +291 -0
- package/src/cli/config.ts +158 -262
- package/src/cli/index.ts +33 -21
- package/src/cli/main.ts +213 -0
- package/src/cli/model-profiles.ts +226 -72
- package/src/cli/output.ts +113 -0
- package/src/cli/package-metadata.ts +36 -0
- package/src/cli/prompt-source.ts +229 -0
- package/src/cli/public-cli-contract.ts +69 -0
- package/src/cli/public-config-contract.ts +732 -0
- package/src/cli/run-runner.ts +17 -12
- package/src/cli/runner-dependencies.ts +108 -0
- package/src/cli/tui-memory.ts +67 -0
- package/src/cli/tui-runner.tsx +79 -49
- package/src/context/context-policy.ts +2 -2
- package/src/events/stdout-event-printer.ts +1 -0
- package/src/mcp/mcp-manager.ts +2 -19
- package/src/mcp/mcp-tool-executor.ts +3 -4
- package/src/memory/contracts.ts +148 -0
- package/src/memory/embedding-client.ts +105 -0
- package/src/memory/memory-coordinator.ts +556 -0
- package/src/memory/memory-extractor.ts +231 -0
- package/src/memory/memory-log.ts +88 -0
- package/src/memory/memory-search-tool.ts +100 -0
- package/src/memory/memory-store.ts +687 -0
- package/src/memory/vector.ts +153 -0
- package/src/model/fake-model-client.ts +971 -3
- package/src/model/model-context-profile.ts +0 -30
- package/src/observation/observation-builder.ts +20 -0
- package/src/session/session-store.ts +123 -0
- package/src/tools/bash.ts +8 -25
- package/src/tools/grep.ts +9 -1
- package/src/tools/registry.ts +19 -1
- package/src/tools/ripgrep.ts +24 -27
- package/src/tools/types.ts +16 -0
- package/src/tools/web-fetch/index.ts +2 -15
- package/src/tui/app.tsx +72 -2
- package/src/tui/clipboard.ts +22 -0
- package/src/tui/components/footer.tsx +9 -4
- package/src/tui/components/memory-browser.tsx +151 -0
- package/src/tui/components/prompt-input.tsx +6 -3
- package/src/tui/event-store.ts +9 -2
- package/src/tui/slash-commands.ts +88 -24
- package/src/tui/workspace-file-search.ts +78 -71
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,42 @@ All notable user-facing changes to Tinker are documented here. The project follo
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [1.5.0] - 2026-07-27
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Add optional global Memory for the interactive TUI. When configured, Tinker
|
|
13
|
+
extracts durable facts from completed turns, stores them locally, and exposes
|
|
14
|
+
relevant memories to the agent through `MemorySearch`.
|
|
15
|
+
- Add a read-only `/memory` browser for reviewing the memories stored in the
|
|
16
|
+
current global Memory database.
|
|
17
|
+
- Show live elapsed time in the TUI footer while a turn is running.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- Compact and retire context toward 30% input utilization instead of 60%, leaving
|
|
22
|
+
more headroom for continued work after automatic or manual maintenance.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- Let the Tinker Chrome MCP server exit promptly when its client closes stdin,
|
|
27
|
+
avoiding the shutdown timeout during ordinary CLI exit.
|
|
28
|
+
|
|
29
|
+
## [1.4.0] - 2026-07-24
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
|
|
33
|
+
- Add stable `tinker --help`, `tinker --version`, and `tinker help run` command
|
|
34
|
+
surfaces with explicit usage errors and exit codes.
|
|
35
|
+
- Let one-shot runs read an exact Prompt from explicit stdin or a UTF-8 text file,
|
|
36
|
+
with bounded input and strict encoding validation.
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
|
|
40
|
+
- Require `tinker run` to receive exactly one shell-quoted Prompt argument,
|
|
41
|
+
`--stdin`, or `--file <path>`. The former unquoted variadic form is no longer
|
|
42
|
+
joined automatically; quote a short Prompt or use stdin/file input instead.
|
|
43
|
+
|
|
8
44
|
## [1.3.0] - 2026-07-22
|
|
9
45
|
|
|
10
46
|
### Added
|
|
@@ -48,7 +84,9 @@ All notable user-facing changes to Tinker are documented here. The project follo
|
|
|
48
84
|
- First formal npm release under the `tinker-agent` package name with the `tinker`
|
|
49
85
|
executable.
|
|
50
86
|
|
|
51
|
-
[Unreleased]: https://github.com/ishowshao/tinker/compare/v1.
|
|
87
|
+
[Unreleased]: https://github.com/ishowshao/tinker/compare/v1.5.0...HEAD
|
|
88
|
+
[1.5.0]: https://github.com/ishowshao/tinker/releases/tag/v1.5.0
|
|
89
|
+
[1.4.0]: https://github.com/ishowshao/tinker/releases/tag/v1.4.0
|
|
52
90
|
[1.3.0]: https://github.com/ishowshao/tinker/releases/tag/v1.3.0
|
|
53
91
|
[1.2.1]: https://github.com/ishowshao/tinker/releases/tag/v1.2.1
|
|
54
92
|
[1.2.0]: https://github.com/ishowshao/tinker/releases/tag/v1.2.0
|
package/README.md
CHANGED
|
@@ -29,7 +29,9 @@ Built with [Bun](https://bun.sh) + TypeScript ESM, powered by [Ink](https://gith
|
|
|
29
29
|
context revisions, Recall-addressable cold state, and qualified prefix retirement
|
|
30
30
|
keep long-running sessions recoverable without pretending the model has infinite
|
|
31
31
|
tokens. See the [technical design](docs/infinite-context-technical-design-a.md).
|
|
32
|
-
- **Choice of models**:
|
|
32
|
+
- **Choice of models**: Uses an OpenAI-compatible Chat Completions transport with
|
|
33
|
+
explicit model and context limits. Actual provider support must be established
|
|
34
|
+
by a qualification matrix; transport compatibility alone is not a guarantee.
|
|
33
35
|
|
|
34
36
|
## Quick Start
|
|
35
37
|
|
|
@@ -41,31 +43,65 @@ tinker
|
|
|
41
43
|
|
|
42
44
|
# Run a one-shot prompt
|
|
43
45
|
tinker run "explain the project structure"
|
|
46
|
+
|
|
47
|
+
# Inspect the installed command surface
|
|
48
|
+
tinker --help
|
|
49
|
+
tinker --version
|
|
44
50
|
```
|
|
45
51
|
|
|
46
52
|
The npm package includes its own Bun runtime. To run Tinker from a source checkout,
|
|
47
|
-
install Bun 1.3 or later, then use `bun install` followed by `bun run tinker`.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
53
|
+
install Bun 1.3.14 or later, then use `bun install` followed by `bun run tinker`.
|
|
54
|
+
|
|
55
|
+
`run` accepts exactly one Prompt source: one shell-quoted argument, explicit stdin,
|
|
56
|
+
or a UTF-8 text file. Use stdin for multiline code, private patches, or Prompt text
|
|
57
|
+
containing secrets so it does not become a command-line argument:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
printf '%s\n' 'Review `$HOME`, *.ts, and "quotes" literally.' | tinker run --stdin
|
|
61
|
+
tinker run --file prompts/review.md
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Tinker does not reconstruct shell expansions or join multiple positional arguments.
|
|
65
|
+
Use `tinker run "one quoted prompt"`; the former unquoted variadic form now fails
|
|
66
|
+
with a usage error. File paths are resolved from the current directory and may point
|
|
67
|
+
outside the configured workspace. You are responsible for Prompt-file permissions
|
|
68
|
+
and cleanup.
|
|
69
|
+
|
|
70
|
+
## Commands
|
|
71
|
+
|
|
72
|
+
The installed package exposes this public CLI:
|
|
73
|
+
|
|
74
|
+
<!-- BEGIN GENERATED: PUBLIC CLI COMMANDS -->
|
|
75
|
+
| Command | Description |
|
|
76
|
+
| --- | --- |
|
|
77
|
+
| `tinker` | Start the interactive terminal interface. |
|
|
78
|
+
| `tinker --profile <profile-name>` | Start the TUI with a selected model profile. |
|
|
79
|
+
| `tinker run [--profile <profile-name>] <prompt>` | Submit one shell-quoted prompt argument. |
|
|
80
|
+
| `tinker run [--profile <profile-name>] --stdin` | Read the prompt from standard input until EOF. |
|
|
81
|
+
| `tinker run [--profile <profile-name>] --file <path>` | Read the prompt from a UTF-8 text file. |
|
|
82
|
+
| `tinker --help` | Show top-level CLI help. |
|
|
83
|
+
| `tinker help run` | Show one-shot command help. |
|
|
84
|
+
| `tinker --version` | Print the installed package version. |
|
|
85
|
+
<!-- END GENERATED: PUBLIC CLI COMMANDS -->
|
|
86
|
+
|
|
87
|
+
Repository development commands are separate from the installed CLI:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
bun install # Install dependencies
|
|
91
|
+
bun run tinker # Start the interactive TUI
|
|
92
|
+
bun test # Run test suite
|
|
93
|
+
bun run typecheck # TypeScript type checking (tsc --noEmit)
|
|
94
|
+
bun run lint # ESLint (zero warnings required)
|
|
95
|
+
bun run format # Biome code formatting
|
|
96
|
+
bun run check # Full repository quality gate
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`bun run check` runs type checking, formatting verification, linting, the README
|
|
100
|
+
contract check, the full test suite, and the benchmark smoke check. Documentation
|
|
101
|
+
checking is read-only; use `bun run docs:generate` explicitly after changing a
|
|
102
|
+
public declaration.
|
|
103
|
+
|
|
104
|
+
## Agent Skills
|
|
69
105
|
|
|
70
106
|
Tinker scans `<workspace>/.agents/skills/` and `~/.agents/skills/` once when a new
|
|
71
107
|
or resumed runtime starts. Each direct child skill must contain a strictly valid
|
|
@@ -87,61 +123,186 @@ read-only and does not rescan, install, activate, or remove skills.
|
|
|
87
123
|
|
|
88
124
|
## Configuration
|
|
89
125
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
|
96
|
-
|
|
|
97
|
-
| `TINKER_MODELS` | — |
|
|
98
|
-
| `
|
|
99
|
-
| `
|
|
100
|
-
| `
|
|
101
|
-
| `
|
|
102
|
-
| `
|
|
103
|
-
| `
|
|
104
|
-
| `
|
|
105
|
-
| `
|
|
106
|
-
| `
|
|
107
|
-
| `
|
|
126
|
+
Set `TINKER_MODELS` to use a profile file. Without it, the env-mode model fields
|
|
127
|
+
are required. Boolean environment values accept case-insensitive `true/false`,
|
|
128
|
+
`1/0`, `yes/no`, and `on/off`.
|
|
129
|
+
|
|
130
|
+
<!-- BEGIN GENERATED: PUBLIC ENVIRONMENT VARIABLES -->
|
|
131
|
+
| Variable | Area | Applies | Required | Type | Default | Secret | Description |
|
|
132
|
+
| --- | --- | --- | --- | --- | --- | --- | --- |
|
|
133
|
+
| `TINKER_MODELS` | Model | All modes | No | Non-empty string | — | No | Optional model profiles JSON path. Relative paths resolve from the process cwd. |
|
|
134
|
+
| `TINKER_MODEL` | Model | Env mode | Env mode | Non-empty string | — | No | Model name used when model profiles are not configured. |
|
|
135
|
+
| `TINKER_BASE_URL` | Model | Env mode | Env mode | Non-empty string | — | No | OpenAI-compatible Chat Completions API base URL. |
|
|
136
|
+
| `TINKER_API_KEY` | Model | Env mode | Env mode | Non-empty string | — | Yes | API credential for the configured model endpoint. |
|
|
137
|
+
| `TINKER_CONTEXT_WINDOW_TOKENS` | Model | Env mode | Env mode | Positive integer | — | No | Model context-window size in tokens. |
|
|
138
|
+
| `TINKER_MAX_SUPPORTED_OUTPUT_TOKENS` | Model | Env mode | Env mode | Positive integer | — | No | Maximum output-token count supported by the model; must not exceed the context window. |
|
|
139
|
+
| `TINKER_INCLUDE_REASONING_CONTENT` | Model | Env mode | No | Boolean | `false` | No | Include provider reasoning content in the model response mapping. |
|
|
140
|
+
| `TINKER_STREAM` | Model | Env mode | No | Boolean | `true` | No | Use streaming Chat Completions transport. |
|
|
141
|
+
| `TINKER_WEBFETCH_REFINE_MODEL` | Model | Env mode | No | Non-empty string | — | No | Optional WebFetch refiner model; currently must match TINKER_MODEL. |
|
|
142
|
+
| `TINKER_WORKSPACE` | Workspace | All modes | No | Non-empty string | Process cwd | No | Workspace path. Relative paths resolve from the process cwd. |
|
|
143
|
+
| `TINKER_MAX_ITERATIONS` | Workspace | All modes | No | Positive integer | `512` | No | Maximum agent-loop iterations per turn. |
|
|
144
|
+
| `EXA_API_KEY` | Tooling | All modes | No | Non-empty string | — | Yes | Enables WebSearch and the Exa WebFetch backend when set. |
|
|
145
|
+
| `TINKER_MCP_TIMEOUT_MS` | Tooling | All modes | No | Positive integer | `60000` | No | MCP tool-call timeout in milliseconds. |
|
|
146
|
+
| `TINKER_MCP_MAX_OBSERVATION_CHARS` | Tooling | All modes | No | Positive integer | `40000` | No | Maximum model-visible characters in one MCP result. |
|
|
147
|
+
| `TINKER_BASH_DEFAULT_TIMEOUT_MS` | Tooling | All modes | No | Positive integer | `5000` | No | Default Bash foreground timeout in milliseconds. |
|
|
148
|
+
| `TINKER_BASH_MAX_TIMEOUT_MS` | Tooling | All modes | No | Positive integer | `600000` | No | Maximum Bash foreground timeout in milliseconds. |
|
|
149
|
+
| `TINKER_GREP_TIMEOUT_MS` | Tooling | All modes | No | Positive integer | `20000` | No | Bundled ripgrep invocation timeout in milliseconds. |
|
|
150
|
+
| `TINKER_GREP_MAX_BUFFER_BYTES` | Tooling | All modes | No | Positive integer | `20000000` | No | Maximum buffered output from one ripgrep invocation. |
|
|
151
|
+
| `TINKER_WEBFETCH_REFINE_THRESHOLD` | Tooling | All modes | No | Positive integer | `2000` | No | Content-length threshold that enables WebFetch refinement. |
|
|
152
|
+
| `TINKER_RIPGREP_PATH` | Tooling | All modes | No | Non-empty string | Bundled ripgrep | No | Explicit diagnostic override for the bundled ripgrep executable. |
|
|
153
|
+
<!-- END GENERATED: PUBLIC ENVIRONMENT VARIABLES -->
|
|
154
|
+
|
|
155
|
+
Model and estimator API keys are sent to their configured external endpoints.
|
|
156
|
+
`EXA_API_KEY` is sent to Exa when WebSearch or the Exa WebFetch backend is used.
|
|
157
|
+
Keep configuration files private and review each service's data-handling policy.
|
|
108
158
|
|
|
109
159
|
### Multi-Model Profiles
|
|
110
160
|
|
|
111
|
-
Set `TINKER_MODELS`
|
|
161
|
+
Set `TINKER_MODELS` to point to a JSON file with multiple named model
|
|
112
162
|
profiles. When set, the profile's `default` field selects the startup model, and
|
|
113
163
|
the `/model` slash command (available in new sessions before any turns) lets you
|
|
114
164
|
switch to another profile. If `TINKER_MODELS` is not set, Tinker falls back to
|
|
115
165
|
the individual `TINKER_*` environment variables. A configured profiles file must
|
|
116
166
|
exist and be valid; Tinker does not silently fall back when it cannot be loaded.
|
|
117
167
|
|
|
168
|
+
<!-- BEGIN GENERATED: MODEL PROFILE FIELDS -->
|
|
169
|
+
Profile fields:
|
|
170
|
+
|
|
171
|
+
| Field | Required | Type / constraint | Default | Secret | Description |
|
|
172
|
+
| --- | --- | --- | --- | --- | --- |
|
|
173
|
+
| `model` | Yes | Non-empty string | — | No | Provider model name. |
|
|
174
|
+
| `apiBase` | Yes | Non-empty string | — | No | OpenAI-compatible API base URL. |
|
|
175
|
+
| `apiKey` | Yes | Non-empty string | — | Yes | API credential for this profile. |
|
|
176
|
+
| `contextWindowTokens` | Yes | Positive integer | — | No | Model context-window size in tokens. |
|
|
177
|
+
| `maxSupportedOutputTokens` | Yes | Positive integer | — | No | Maximum output-token count supported by the model; must not exceed contextWindowTokens. |
|
|
178
|
+
| `includeReasoningContent` | No | JSON boolean | `false` | No | Include provider reasoning content in response mapping. |
|
|
179
|
+
| `stream` | No | JSON boolean | `true` | No | Use streaming Chat Completions transport. |
|
|
180
|
+
| `inputModalities` | No | Normalized modality array | `["text"]` | No | Accepted model input modalities; normalizes to ["text"] or ["text", "image"]. |
|
|
181
|
+
| `tokenEstimator` | With image | Object | — | Yes | Independent token estimator required for image profiles. |
|
|
182
|
+
|
|
183
|
+
`tokenEstimator` fields:
|
|
184
|
+
|
|
185
|
+
| Field | Type / constraint | Secret | Description |
|
|
186
|
+
| --- | --- | --- | --- |
|
|
187
|
+
| `kind` | Literal `"moonshot-estimate-token-count-v1"` | No | Estimator protocol discriminator. |
|
|
188
|
+
| `model` | Non-empty string | No | Estimator model name. |
|
|
189
|
+
| `apiBase` | Non-empty string | No | Estimator API base URL. |
|
|
190
|
+
| `apiKey` | Non-empty string | Yes | Estimator API credential. |
|
|
191
|
+
| `timeoutMs` | Integer 1000–60000 | No | Estimator request timeout in milliseconds. |
|
|
192
|
+
| `maxRetries` | Literal `0` | No | Estimator retry count; retries are disabled. |
|
|
193
|
+
|
|
194
|
+
Text-only profile example:
|
|
195
|
+
|
|
196
|
+
```json
|
|
197
|
+
{
|
|
198
|
+
"default": "text",
|
|
199
|
+
"profiles": {
|
|
200
|
+
"text": {
|
|
201
|
+
"model": "example-text-model",
|
|
202
|
+
"apiBase": "https://api.example.com/v1",
|
|
203
|
+
"apiKey": "your-model-api-key",
|
|
204
|
+
"contextWindowTokens": 128000,
|
|
205
|
+
"maxSupportedOutputTokens": 8192,
|
|
206
|
+
"includeReasoningContent": false,
|
|
207
|
+
"stream": true,
|
|
208
|
+
"inputModalities": [
|
|
209
|
+
"text"
|
|
210
|
+
]
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Image-capable profile example:
|
|
217
|
+
|
|
118
218
|
```json
|
|
119
219
|
{
|
|
120
|
-
"default": "
|
|
220
|
+
"default": "image",
|
|
121
221
|
"profiles": {
|
|
122
|
-
"
|
|
123
|
-
"model": "
|
|
124
|
-
"apiBase": "https://api.
|
|
125
|
-
"apiKey": "
|
|
222
|
+
"image": {
|
|
223
|
+
"model": "example-vision-model",
|
|
224
|
+
"apiBase": "https://api.example.com/v1",
|
|
225
|
+
"apiKey": "your-model-api-key",
|
|
126
226
|
"contextWindowTokens": 128000,
|
|
127
|
-
"maxSupportedOutputTokens": 8192
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
"
|
|
131
|
-
|
|
132
|
-
|
|
227
|
+
"maxSupportedOutputTokens": 8192,
|
|
228
|
+
"includeReasoningContent": false,
|
|
229
|
+
"stream": true,
|
|
230
|
+
"inputModalities": [
|
|
231
|
+
"text",
|
|
232
|
+
"image"
|
|
233
|
+
],
|
|
234
|
+
"tokenEstimator": {
|
|
235
|
+
"kind": "moonshot-estimate-token-count-v1",
|
|
236
|
+
"model": "example-token-estimator",
|
|
237
|
+
"apiBase": "https://estimator.example.com/v1",
|
|
238
|
+
"apiKey": "your-estimator-api-key",
|
|
239
|
+
"timeoutMs": 30000,
|
|
240
|
+
"maxRetries": 0
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
The top-level `memory` object is optional. When present, every field below is required. It enables completed-turn extraction and `MemorySearch` only in the TUI; one-shot runs do not load memory.
|
|
248
|
+
|
|
249
|
+
| Field | Required | Type / constraint | Secret | Description |
|
|
250
|
+
| --- | --- | --- | --- | --- |
|
|
251
|
+
| `profile` | Yes | Non-empty string | No | Existing model profile used for completed-turn atomic-memory extraction. |
|
|
252
|
+
| `embedding` | Yes | Object | Yes | Single embedding profile for the global memory database. |
|
|
253
|
+
|
|
254
|
+
`memory.embedding` fields:
|
|
255
|
+
|
|
256
|
+
| Field | Required | Type / constraint | Secret | Description |
|
|
257
|
+
| --- | --- | --- | --- | --- |
|
|
258
|
+
| `name` | Yes | Non-empty string | No | Stable identity for the embedding space. |
|
|
259
|
+
| `kind` | Yes | Literal `"openai-compatible"` | No | Embedding transport kind. |
|
|
260
|
+
| `model` | Yes | Non-empty string | No | Embedding provider model name. |
|
|
261
|
+
| `apiBase` | Yes | Non-empty string | No | OpenAI-compatible API base URL. |
|
|
262
|
+
| `apiKey` | Yes | Non-empty string | Yes | Embedding provider credential. |
|
|
263
|
+
| `dimensions` | Yes | Positive integer | No | Fixed vector dimensions for the global memory database. |
|
|
264
|
+
|
|
265
|
+
Enabling memory sends completed-turn text (not image bytes) to `memory.profile`, and sends extracted candidates plus search queries to the embedding endpoint. Derived memories are stored in `~/.tinker/memory/memory.sqlite`; newly inserted memory text is appended to the private development log `~/.tinker/memory/extracted-memories.log`.
|
|
266
|
+
|
|
267
|
+
Atomic-memory profile example:
|
|
268
|
+
|
|
269
|
+
```json
|
|
270
|
+
{
|
|
271
|
+
"default": "text",
|
|
272
|
+
"profiles": {
|
|
273
|
+
"text": {
|
|
274
|
+
"model": "example-text-model",
|
|
275
|
+
"apiBase": "https://api.example.com/v1",
|
|
276
|
+
"apiKey": "your-model-api-key",
|
|
133
277
|
"contextWindowTokens": 128000,
|
|
134
|
-
"maxSupportedOutputTokens":
|
|
135
|
-
"includeReasoningContent":
|
|
278
|
+
"maxSupportedOutputTokens": 8192,
|
|
279
|
+
"includeReasoningContent": false,
|
|
280
|
+
"stream": true,
|
|
281
|
+
"inputModalities": [
|
|
282
|
+
"text"
|
|
283
|
+
]
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
"memory": {
|
|
287
|
+
"profile": "text",
|
|
288
|
+
"embedding": {
|
|
289
|
+
"name": "example-embedding-space",
|
|
290
|
+
"kind": "openai-compatible",
|
|
291
|
+
"model": "example-embedding-model",
|
|
292
|
+
"apiBase": "https://embeddings.example.com/v1",
|
|
293
|
+
"apiKey": "your-embedding-api-key",
|
|
294
|
+
"dimensions": 1024
|
|
136
295
|
}
|
|
137
296
|
}
|
|
138
297
|
}
|
|
139
298
|
```
|
|
299
|
+
<!-- END GENERATED: MODEL PROFILE FIELDS -->
|
|
140
300
|
|
|
141
|
-
You can also
|
|
301
|
+
You can also select profiles explicitly for the TUI or one-shot command:
|
|
142
302
|
|
|
143
303
|
```bash
|
|
144
|
-
|
|
304
|
+
tinker --profile text
|
|
305
|
+
tinker run --profile text "explain the project structure"
|
|
145
306
|
```
|
|
146
307
|
|
|
147
308
|
Switching models creates a new session. The previous session is preserved and
|
|
@@ -151,6 +312,47 @@ longer present or its runtime contract has changed. Older sessions without a
|
|
|
151
312
|
stored profile name can resume only when their model name uniquely matches one
|
|
152
313
|
configured profile.
|
|
153
314
|
|
|
315
|
+
### Image Input
|
|
316
|
+
|
|
317
|
+
Image attachment is enabled only for a profile whose `inputModalities` explicitly
|
|
318
|
+
includes `image` and which supplies a valid `tokenEstimator`. In the interactive
|
|
319
|
+
TUI, type `@` and select a file that is inside the workspace and visible to the
|
|
320
|
+
workspace search rules. One-shot commands, clipboard image bytes, remote URLs, and
|
|
321
|
+
files outside or ignored by the workspace search are not supported.
|
|
322
|
+
|
|
323
|
+
Tinker accepts PNG (not APNG), JPEG, and static WebP. It rejects GIF, animated
|
|
324
|
+
WebP, and other formats. A message and provider request may contain at most eight
|
|
325
|
+
images; each image may be at most 20 MiB, 4096 pixels on either edge, and 8,847,360
|
|
326
|
+
pixels in total. See the
|
|
327
|
+
[`multimodal image input design`](docs/multimodal-image-input-design.md) for the
|
|
328
|
+
complete fixed policy and persistence contract.
|
|
329
|
+
|
|
330
|
+
### Built-in Slash Commands
|
|
331
|
+
|
|
332
|
+
<!-- BEGIN GENERATED: BUILT-IN SLASH COMMANDS -->
|
|
333
|
+
| Command | Description |
|
|
334
|
+
| --- | --- |
|
|
335
|
+
| `/status` | Show session and context details |
|
|
336
|
+
| `/skills` | Show available and active Agent Skills |
|
|
337
|
+
| `/mcp` | Show MCP servers and runtime tools |
|
|
338
|
+
| `/memory` | Browse stored global memories |
|
|
339
|
+
| `/compact [retire]` | Swap tool output or retire a cold history prefix |
|
|
340
|
+
| `/clear` | Start a new session and clear conversation |
|
|
341
|
+
| `/fork` | Clone the current session |
|
|
342
|
+
| `/view <path>` | View a local UTF-8 text file |
|
|
343
|
+
| `/copy` | Copy the last response as Markdown |
|
|
344
|
+
| `/model [profile-name]` | Switch model profile (new session) |
|
|
345
|
+
| `/resume [session-id]` | Choose or resume a session |
|
|
346
|
+
| `/session delete <session-id> --confirm` | Manage stored sessions |
|
|
347
|
+
| `/quit` | Exit the TUI |
|
|
348
|
+
<!-- END GENERATED: BUILT-IN SLASH COMMANDS -->
|
|
349
|
+
|
|
350
|
+
`/view` opens a readable UTF-8 text file in a full-window viewer. Relative paths
|
|
351
|
+
must remain inside the workspace; absolute paths may point outside it. `/compact`
|
|
352
|
+
swaps eligible historical tool output, while `/compact retire` retires a complete
|
|
353
|
+
cold prefix whose original history remains available through `Recall`. `/copy`
|
|
354
|
+
copies the last completed assistant response as raw Markdown.
|
|
355
|
+
|
|
154
356
|
### Project Custom Slash Commands
|
|
155
357
|
|
|
156
358
|
The TUI loads optional project-scoped prompt aliases from `.tinker.json` in the
|
|
@@ -178,24 +380,20 @@ one-shot `tinker run` command. See
|
|
|
178
380
|
[`docs/project-custom-slash-commands-design.md`](docs/project-custom-slash-commands-design.md)
|
|
179
381
|
for the full contract.
|
|
180
382
|
|
|
383
|
+
### Workspace-Local Runtime Data
|
|
384
|
+
|
|
385
|
+
Tinker keeps private runtime state under `<workspace>/.tinker/`: each session has
|
|
386
|
+
its own SQLite database, event log, and observation log, while image assets and
|
|
387
|
+
Prompt history are stored at the workspace level. These locations are fixed and
|
|
388
|
+
have no public path-override environment variables. MCP server configuration is
|
|
389
|
+
loaded from `<workspace>/.mcp.json`; project slash commands are loaded from
|
|
390
|
+
`<workspace>/.tinker.json`.
|
|
391
|
+
|
|
181
392
|
`Read` has a fixed 262144-byte (256 KiB) content limit per call. A successful
|
|
182
393
|
call always returns the complete requested line range. Use `offset` and `limit`
|
|
183
394
|
to page through larger files; oversized requests fail instead of returning
|
|
184
395
|
truncated content.
|
|
185
396
|
|
|
186
|
-
## Commands
|
|
187
|
-
|
|
188
|
-
```bash
|
|
189
|
-
bun install # Install dependencies
|
|
190
|
-
bun run tinker # Start the interactive TUI
|
|
191
|
-
bun run tinker run "..." # Run a one-shot CLI prompt
|
|
192
|
-
bun test # Run test suite
|
|
193
|
-
bun run typecheck # TypeScript type checking (tsc --noEmit)
|
|
194
|
-
bun run lint # ESLint (zero warnings required)
|
|
195
|
-
bun run format # Biome code formatting
|
|
196
|
-
bun run check # Full check: typecheck + format + lint + test
|
|
197
|
-
```
|
|
198
|
-
|
|
199
397
|
## Project Structure
|
|
200
398
|
|
|
201
399
|
```
|
|
@@ -228,8 +426,9 @@ tinker/
|
|
|
228
426
|
## Requirements
|
|
229
427
|
|
|
230
428
|
- Node.js 20 or later and npm for the global package installation
|
|
231
|
-
- [Bun](https://bun.sh) 1.3 or later for development from source
|
|
232
|
-
- A compatible
|
|
429
|
+
- [Bun](https://bun.sh) 1.3.14 or later for development from source
|
|
430
|
+
- A configured OpenAI-compatible Chat Completions endpoint; transport compatibility
|
|
431
|
+
alone does not establish provider qualification
|
|
233
432
|
|
|
234
433
|
## Security
|
|
235
434
|
|
package/bin/tinker.js
CHANGED
|
@@ -1,39 +1,89 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import { realpathSync } from "node:fs";
|
|
4
5
|
import { createRequire } from "node:module";
|
|
5
6
|
import { dirname, join } from "node:path";
|
|
6
7
|
import { fileURLToPath } from "node:url";
|
|
7
8
|
|
|
8
9
|
const require = createRequire(import.meta.url);
|
|
10
|
+
const START_FAILURE_MESSAGE =
|
|
11
|
+
"Tinker failed to start. Reinstall tinker-agent.\n";
|
|
9
12
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
export async function launchTinker(
|
|
14
|
+
input = {
|
|
15
|
+
args: process.argv.slice(2),
|
|
16
|
+
stderr: process.stderr,
|
|
17
|
+
},
|
|
18
|
+
injected = {},
|
|
19
|
+
) {
|
|
20
|
+
const resolveBundledBun =
|
|
21
|
+
injected.resolveBundledBun ?? (() => require.resolve("bun/package.json"));
|
|
22
|
+
const startChild = injected.spawn ?? spawn;
|
|
23
|
+
const forwardSignal =
|
|
24
|
+
injected.forwardSignal ?? ((signal) => process.kill(process.pid, signal));
|
|
25
|
+
|
|
26
|
+
let bunPackageJson;
|
|
27
|
+
try {
|
|
28
|
+
bunPackageJson = resolveBundledBun();
|
|
29
|
+
} catch {
|
|
30
|
+
input.stderr.write(START_FAILURE_MESSAGE);
|
|
31
|
+
return 1;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const bunExecutable = join(dirname(bunPackageJson), "bin", "bun.exe");
|
|
35
|
+
const tinkerEntryPoint = fileURLToPath(
|
|
36
|
+
new URL("../src/cli/index.ts", import.meta.url),
|
|
16
37
|
);
|
|
17
|
-
|
|
18
|
-
|
|
38
|
+
let child;
|
|
39
|
+
try {
|
|
40
|
+
child = startChild(
|
|
41
|
+
bunExecutable,
|
|
42
|
+
[tinkerEntryPoint, ...input.args],
|
|
43
|
+
{ stdio: "inherit" },
|
|
44
|
+
);
|
|
45
|
+
} catch {
|
|
46
|
+
input.stderr.write(START_FAILURE_MESSAGE);
|
|
47
|
+
return 1;
|
|
48
|
+
}
|
|
19
49
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
);
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
50
|
+
return new Promise((resolve) => {
|
|
51
|
+
let settled = false;
|
|
52
|
+
child.once("error", () => {
|
|
53
|
+
if (settled) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
settled = true;
|
|
57
|
+
input.stderr.write(START_FAILURE_MESSAGE);
|
|
58
|
+
resolve(1);
|
|
59
|
+
});
|
|
60
|
+
child.once("exit", (code, signal) => {
|
|
61
|
+
if (settled) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
settled = true;
|
|
65
|
+
if (signal !== null) {
|
|
66
|
+
forwardSignal(signal);
|
|
67
|
+
resolve(1);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
resolve(code ?? 1);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
33
73
|
}
|
|
34
74
|
|
|
35
|
-
|
|
36
|
-
process.
|
|
75
|
+
function isExecutableEntryPoint() {
|
|
76
|
+
const entryPoint = process.argv[1];
|
|
77
|
+
if (entryPoint === undefined) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
return realpathSync(entryPoint) === realpathSync(fileURLToPath(import.meta.url));
|
|
82
|
+
} catch {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
37
85
|
}
|
|
38
86
|
|
|
39
|
-
|
|
87
|
+
if (isExecutableEntryPoint()) {
|
|
88
|
+
process.exitCode = await launchTinker();
|
|
89
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tinker-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "A personal coding agent with an interactive TUI and one-shot CLI.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"src/image",
|
|
21
21
|
"src/instructions",
|
|
22
22
|
"src/mcp",
|
|
23
|
+
"src/memory",
|
|
23
24
|
"src/model",
|
|
24
25
|
"src/observation",
|
|
25
26
|
"src/session",
|
|
@@ -58,13 +59,18 @@
|
|
|
58
59
|
"chrome:diagnose": "bun packages/tinker-chrome/src/cli.ts diagnose",
|
|
59
60
|
"chrome:install-host": "bun packages/tinker-chrome/src/cli.ts install-host",
|
|
60
61
|
"chrome:mcp": "bun packages/tinker-chrome/src/cli.ts mcp",
|
|
62
|
+
"docs:generate": "bun scripts/render-public-contract-docs.ts --write",
|
|
63
|
+
"docs:check": "bun scripts/render-public-contract-docs.ts --check",
|
|
61
64
|
"tinker": "bun src/cli/index.ts",
|
|
62
|
-
"check": "bun run typecheck && bun run format:check && bun run lint && bun test && bun run bench:smoke",
|
|
65
|
+
"check": "bun run typecheck && bun run format:check && bun run lint && bun run docs:check && bun run test && bun run bench:smoke",
|
|
66
|
+
"check:fast": "bun run typecheck && bun run format:check && bun run lint && bun run test:fast",
|
|
63
67
|
"format": "biome format --write .",
|
|
64
68
|
"format:check": "biome format .",
|
|
65
69
|
"lint": "eslint \"bin/**/*.js\" \"src/**/*.{ts,tsx}\" \"scripts/**/*.ts\" \"packages/**/*.ts\" --max-warnings=0",
|
|
66
70
|
"lint:fix": "eslint \"bin/**/*.js\" \"src/**/*.{ts,tsx}\" \"scripts/**/*.ts\" \"packages/**/*.ts\" --fix",
|
|
67
|
-
"test": "bun test",
|
|
71
|
+
"test": "FORCE_COLOR=0 bun test",
|
|
72
|
+
"test:fast": "FORCE_COLOR=0 bun test --path-ignore-patterns='**/*pty*.test.ts' --path-ignore-patterns='**/native-host-integration.test.ts'",
|
|
73
|
+
"test:e2e": "FORCE_COLOR=0 bun test src/__tests__/cli-pty src/__tests__/pty-tui-harness packages/tinker-chrome/__tests__/native-host-integration",
|
|
68
74
|
"typecheck": "tsc --noEmit"
|
|
69
75
|
},
|
|
70
76
|
"dependencies": {
|
|
@@ -76,6 +82,7 @@
|
|
|
76
82
|
"@vscode/ripgrep": "1.18.0",
|
|
77
83
|
"bun": "1.3.14",
|
|
78
84
|
"clipboardy": "^5.3.1",
|
|
85
|
+
"commander": "^14.0.3",
|
|
79
86
|
"diff": "^9.0.0",
|
|
80
87
|
"glob": "^13.0.6",
|
|
81
88
|
"ink": "^7.1.0",
|
|
@@ -96,6 +103,8 @@
|
|
|
96
103
|
"@types/chrome": "^0.2.2",
|
|
97
104
|
"@types/react": "^19.2.17",
|
|
98
105
|
"@types/turndown": "^5.0.6",
|
|
106
|
+
"@xterm/addon-unicode11": "0.9.0",
|
|
107
|
+
"@xterm/headless": "6.0.0",
|
|
99
108
|
"eslint": "^10.6.0",
|
|
100
109
|
"eslint-plugin-react-hooks": "^7.1.1",
|
|
101
110
|
"globals": "^17.7.0",
|