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.
Files changed (3) hide show
  1. package/README.md +88 -0
  2. package/bin/setup.js +178 -0
  3. 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
+ }