opencode-puter-setup 1.0.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/README.md +88 -0
- package/bin/setup.js +178 -0
- package/package.json +32 -0
package/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# opencode-puter-setup
|
|
2
|
+
|
|
3
|
+
Auto-configure Puter as an OpenCode provider, giving you access to Claude Sonnet 4.6 for free — with optional prompt caching.
|
|
4
|
+
|
|
5
|
+
## What This Does
|
|
6
|
+
|
|
7
|
+
- Adds two Puter provider entries to your OpenCode config
|
|
8
|
+
- Stores your Puter auth token in the OpenCode auth file
|
|
9
|
+
- Zero external dependencies beyond Node.js 18+
|
|
10
|
+
|
|
11
|
+
| Provider entry | Model | Endpoint | Prompt caching |
|
|
12
|
+
|---|---|---|---|
|
|
13
|
+
| `puter-openai` | Claude Sonnet 4.6 (Puter) | OpenAI-compatible | No |
|
|
14
|
+
| `puter-anthropic` | Claude Sonnet 4.6 — Cached (Puter) | Anthropic-wire | Message-level only |
|
|
15
|
+
|
|
16
|
+
> **Caching limitation:** Puter's Anthropic endpoint normalises system prompts to plain strings, which strips `cache_control`. Caching works on user/assistant message content, but not on system prompts.
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# No install required
|
|
22
|
+
npx opencode-puter-setup
|
|
23
|
+
|
|
24
|
+
# Or install globally
|
|
25
|
+
npm install -g opencode-puter-setup
|
|
26
|
+
opencode-puter-setup
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
1. Get your token at `https://puter.com` → Settings → API Tokens
|
|
32
|
+
2. Run the setup and paste it in when prompted
|
|
33
|
+
3. Restart OpenCode, run `/models`, and select either Puter model
|
|
34
|
+
|
|
35
|
+
## What Gets Written
|
|
36
|
+
|
|
37
|
+
**`~/.config/opencode/opencode.json`** (or local `.opencode/config.json`):
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"$schema": "https://opencode.ai/config.json",
|
|
42
|
+
"provider": {
|
|
43
|
+
"puter-openai": {
|
|
44
|
+
"npm": "@ai-sdk/openai-compatible",
|
|
45
|
+
"name": "Puter",
|
|
46
|
+
"options": {
|
|
47
|
+
"baseURL": "https://api.puter.com/puterai/openai/v1/"
|
|
48
|
+
},
|
|
49
|
+
"models": {
|
|
50
|
+
"anthropic/claude-sonnet-4-6": {
|
|
51
|
+
"name": "Claude Sonnet 4.6 (Puter)"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"puter-anthropic": {
|
|
56
|
+
"npm": "@ai-sdk/anthropic",
|
|
57
|
+
"name": "Puter (Cached)",
|
|
58
|
+
"options": {
|
|
59
|
+
"baseURL": "https://api.puter.com/puterai/anthropic/v1/",
|
|
60
|
+
"apiKey": "puter"
|
|
61
|
+
},
|
|
62
|
+
"models": {
|
|
63
|
+
"anthropic/claude-sonnet-4-6": {
|
|
64
|
+
"name": "Claude Sonnet 4.6 — Cached (Puter)"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**`~/.local/share/opencode/auth.json`** — token stored with `0600` permissions.
|
|
73
|
+
|
|
74
|
+
## Billing
|
|
75
|
+
|
|
76
|
+
Puter uses a user-pays model. Your free tier covers typical usage. No charges to you as the developer.
|
|
77
|
+
|
|
78
|
+
## Troubleshooting
|
|
79
|
+
|
|
80
|
+
**Model not showing in `/models`** — restart OpenCode after setup.
|
|
81
|
+
|
|
82
|
+
**401 errors** — your token may have expired. Re-run the setup with a fresh token.
|
|
83
|
+
|
|
84
|
+
**Caching not reducing costs** — system prompt caching is not supported through Puter. Use message-level `cache_control` on large repeated user/assistant content instead.
|
|
85
|
+
|
|
86
|
+
## License
|
|
87
|
+
|
|
88
|
+
MIT
|
package/bin/setup.js
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import * as os from 'os';
|
|
6
|
+
import * as readline from 'readline/promises';
|
|
7
|
+
|
|
8
|
+
const CONFIG_PATHS = {
|
|
9
|
+
global: path.join(os.homedir(), '.config', 'opencode', 'opencode.json'),
|
|
10
|
+
local: '.opencode/config.json',
|
|
11
|
+
legacyGlobal: path.join(os.homedir(), '.opencode', 'config.json')
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// Two provider entries:
|
|
15
|
+
// 1. puter-openai — OpenAI-compatible endpoint, broad model access
|
|
16
|
+
// 2. puter-anthropic — Anthropic-wire endpoint, enables cache_control on messages
|
|
17
|
+
const PUTER_PROVIDER_CONFIG = {
|
|
18
|
+
"puter-openai": {
|
|
19
|
+
npm: "@ai-sdk/openai-compatible",
|
|
20
|
+
name: "Puter",
|
|
21
|
+
options: {
|
|
22
|
+
baseURL: "https://api.puter.com/puterai/openai/v1/"
|
|
23
|
+
},
|
|
24
|
+
models: {
|
|
25
|
+
"anthropic/claude-sonnet-4-6": {
|
|
26
|
+
name: "Claude Sonnet 4.6 (Puter)"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"puter-anthropic": {
|
|
31
|
+
npm: "@ai-sdk/anthropic",
|
|
32
|
+
name: "Puter (Cached)",
|
|
33
|
+
options: {
|
|
34
|
+
baseURL: "https://api.puter.com/puterai/anthropic/v1/",
|
|
35
|
+
apiKey: "puter"
|
|
36
|
+
},
|
|
37
|
+
models: {
|
|
38
|
+
"anthropic/claude-sonnet-4-6": {
|
|
39
|
+
name: "Claude Sonnet 4.6 — Cached (Puter)"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
async function getAuthToken(rl) {
|
|
46
|
+
console.log('\n🔐 Puter Authentication Token Setup\n');
|
|
47
|
+
console.log('To get your token:');
|
|
48
|
+
console.log(' 1. Visit: https://puter.com/dashboard#account');
|
|
49
|
+
console.log(' 2. Click "Create token" in the API token section');
|
|
50
|
+
console.log(' 3. Copy the token (keep it safe!)\n');
|
|
51
|
+
|
|
52
|
+
const token = await rl.question('📌 Paste your Puter auth token: ');
|
|
53
|
+
|
|
54
|
+
if (!token || token.trim().length === 0) {
|
|
55
|
+
throw new Error('❌ Auth token is required');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return token.trim();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function ensureConfigDir(configPath) {
|
|
62
|
+
const dir = path.dirname(configPath);
|
|
63
|
+
if (!fs.existsSync(dir)) {
|
|
64
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function loadConfig(configPath) {
|
|
69
|
+
if (fs.existsSync(configPath)) {
|
|
70
|
+
try {
|
|
71
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
72
|
+
return JSON.parse(content);
|
|
73
|
+
} catch (e) {
|
|
74
|
+
console.warn(`⚠️ Could not parse ${configPath}, starting fresh`);
|
|
75
|
+
return {};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return {};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function mergeConfig(existing, newProviders) {
|
|
82
|
+
return {
|
|
83
|
+
"$schema": "https://opencode.ai/config.json",
|
|
84
|
+
...existing,
|
|
85
|
+
provider: {
|
|
86
|
+
...existing.provider,
|
|
87
|
+
...newProviders
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function chooseConfigLocation(rl) {
|
|
93
|
+
const hasLocal = fs.existsSync(CONFIG_PATHS.local);
|
|
94
|
+
const hasGlobal = fs.existsSync(CONFIG_PATHS.global) || fs.existsSync(CONFIG_PATHS.legacyGlobal);
|
|
95
|
+
|
|
96
|
+
// No existing config anywhere — default to global
|
|
97
|
+
if (!hasLocal && !hasGlobal) {
|
|
98
|
+
console.log('No existing OpenCode config found. Creating global config.');
|
|
99
|
+
return 'global';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Only one exists — use it silently
|
|
103
|
+
if (hasLocal && !hasGlobal) return 'local';
|
|
104
|
+
if (!hasLocal && hasGlobal) return 'global';
|
|
105
|
+
|
|
106
|
+
// Both exist — ask
|
|
107
|
+
console.log('\nOpenCode config found in multiple locations:\n');
|
|
108
|
+
console.log(` [1] Local: ${CONFIG_PATHS.local}`);
|
|
109
|
+
console.log(` [2] Global: ${CONFIG_PATHS.global}`);
|
|
110
|
+
|
|
111
|
+
const choice = await rl.question('\nChoose location to update (1/2, default: 2 global): ');
|
|
112
|
+
return choice === '1' ? 'local' : 'global';
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function main() {
|
|
116
|
+
const rl = readline.createInterface({
|
|
117
|
+
input: process.stdin,
|
|
118
|
+
output: process.stdout
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
console.log('\n╔════════════════════════════════════════╗');
|
|
123
|
+
console.log('║ OpenCode + Puter Setup ║');
|
|
124
|
+
console.log('║ Claude Sonnet 4.6 Auto-Configuration ║');
|
|
125
|
+
console.log('╚════════════════════════════════════════╝\n');
|
|
126
|
+
|
|
127
|
+
const token = await getAuthToken(rl);
|
|
128
|
+
const configType = await chooseConfigLocation(rl);
|
|
129
|
+
const configPath = configType === 'global' ? CONFIG_PATHS.global : CONFIG_PATHS.local;
|
|
130
|
+
|
|
131
|
+
ensureConfigDir(configPath);
|
|
132
|
+
const existingConfig = loadConfig(configPath);
|
|
133
|
+
const updatedConfig = mergeConfig(existingConfig, PUTER_PROVIDER_CONFIG);
|
|
134
|
+
|
|
135
|
+
fs.writeFileSync(configPath, JSON.stringify(updatedConfig, null, 2));
|
|
136
|
+
|
|
137
|
+
const authPath = path.join(os.homedir(), '.local', 'share', 'opencode', 'auth.json');
|
|
138
|
+
ensureConfigDir(authPath);
|
|
139
|
+
|
|
140
|
+
let authConfig = {};
|
|
141
|
+
if (fs.existsSync(authPath)) {
|
|
142
|
+
try {
|
|
143
|
+
authConfig = JSON.parse(fs.readFileSync(authPath, 'utf-8'));
|
|
144
|
+
} catch (e) {
|
|
145
|
+
console.warn('⚠️ Could not parse auth.json, creating new');
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
authConfig.puter = token;
|
|
150
|
+
fs.writeFileSync(authPath, JSON.stringify(authConfig, null, 2));
|
|
151
|
+
fs.chmodSync(authPath, 0o600);
|
|
152
|
+
|
|
153
|
+
console.log('\nSetup Complete!\n');
|
|
154
|
+
console.log(`Config saved to: ${configPath}`);
|
|
155
|
+
console.log(`Token saved to: ${authPath}\n`);
|
|
156
|
+
console.log('Next steps:\n');
|
|
157
|
+
console.log(' 1. Run: opencode');
|
|
158
|
+
console.log(' 2. Use: /models');
|
|
159
|
+
console.log(' 3. Select one of:');
|
|
160
|
+
console.log(' Claude Sonnet 4.6 (Puter) — standard');
|
|
161
|
+
console.log(' Claude Sonnet 4.6 — Cached (Puter) — with prompt caching\n');
|
|
162
|
+
console.log('Note: prompt caching applies to message-level cache_control.');
|
|
163
|
+
console.log(' System prompt caching is not supported by the Puter endpoint.\n');
|
|
164
|
+
console.log('Your Puter free tier credits will be used.');
|
|
165
|
+
console.log('No setup fees, no OpenCode billing.\n');
|
|
166
|
+
|
|
167
|
+
} catch (error) {
|
|
168
|
+
console.error(`\n❌ Error: ${error.message}\n`);
|
|
169
|
+
process.exit(1);
|
|
170
|
+
} finally {
|
|
171
|
+
rl.close();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
main().catch(err => {
|
|
176
|
+
console.error(err);
|
|
177
|
+
process.exit(1);
|
|
178
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "opencode-puter-setup",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Auto-configure Puter as an OpenCode provider — Claude Sonnet 4.6 with prompt caching, free",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"opencode-puter-setup": "bin/setup.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node bin/setup.js"
|
|
11
|
+
},
|
|
12
|
+
"engines": {
|
|
13
|
+
"node": ">=18.0.0"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"opencode",
|
|
17
|
+
"puter",
|
|
18
|
+
"claude",
|
|
19
|
+
"claude-sonnet",
|
|
20
|
+
"ai",
|
|
21
|
+
"free",
|
|
22
|
+
"prompt-caching",
|
|
23
|
+
"setup",
|
|
24
|
+
"provider"
|
|
25
|
+
],
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"preferGlobal": true,
|
|
28
|
+
"files": [
|
|
29
|
+
"bin/",
|
|
30
|
+
"README.md"
|
|
31
|
+
]
|
|
32
|
+
}
|