@naarang/ccc 3.3.0-beta.2 → 3.3.0-beta.4

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.
@@ -0,0 +1,236 @@
1
+ /**
2
+ * debug-pi-models.ts — runs the exact code path of
3
+ * `handleProvidersList` against the locally-installed Pi SDK and
4
+ * dumps everything it finds. Use to diagnose why the model picker
5
+ * is empty.
6
+ *
7
+ * bun run scripts/debug-pi-models.ts
8
+ *
9
+ * Run on the same machine where the C3 backend lives (the one with
10
+ * `~/.pi/agent/auth.json`). On a Raspberry Pi, scp this file over
11
+ * and `bun run` it next to the installed backend.
12
+ */
13
+
14
+ import { loadPiSdk } from '../src/services/pi-agent/sdk-loader';
15
+ import * as fs from 'node:fs';
16
+ import * as path from 'node:path';
17
+ import * as os from 'node:os';
18
+
19
+ function header(label: string): void {
20
+ console.log('\n' + '='.repeat(70));
21
+ console.log(label);
22
+ console.log('='.repeat(70));
23
+ }
24
+
25
+ async function main(): Promise<void> {
26
+ header('1. Where is Pi installed and what version?');
27
+ const resolved = await loadPiSdk();
28
+ console.log('install path:', resolved.installPath);
29
+ console.log('version :', resolved.version);
30
+
31
+ header('2. What does ~/.pi/agent/auth.json look like?');
32
+ const authPath = path.join(os.homedir(), '.pi', 'agent', 'auth.json');
33
+ console.log('path:', authPath);
34
+ console.log('exists?', fs.existsSync(authPath));
35
+ if (fs.existsSync(authPath)) {
36
+ try {
37
+ const raw = JSON.parse(fs.readFileSync(authPath, 'utf-8'));
38
+ // Don't dump the credential values themselves — just shape.
39
+ const summary: Record<string, { type?: string; keys?: string[] }> = {};
40
+ for (const [provider, cred] of Object.entries(raw)) {
41
+ const c = cred as any;
42
+ summary[provider] = {
43
+ type: c?.type,
44
+ keys: c && typeof c === 'object' ? Object.keys(c).filter((k) => k !== 'access' && k !== 'refresh' && k !== 'key') : undefined,
45
+ };
46
+ }
47
+ console.log('top-level keys:', Object.keys(raw));
48
+ console.log('credential summary:', JSON.stringify(summary, null, 2));
49
+ } catch (err) {
50
+ console.log('parse error:', err);
51
+ }
52
+ }
53
+
54
+ header('3. What does AuthStorage.list() return?');
55
+ const { piSdk } = resolved;
56
+ const authStorage = piSdk.AuthStorage.create();
57
+ console.log('list():', authStorage.list());
58
+ for (const provider of authStorage.list()) {
59
+ try {
60
+ const status = authStorage.getAuthStatus(provider);
61
+ console.log(` ${provider}:`, status);
62
+ } catch (err) {
63
+ console.log(` ${provider}: getAuthStatus threw:`, err);
64
+ }
65
+ }
66
+
67
+ header('4. What does ModelRegistry.create() do?');
68
+ let registry: any;
69
+ try {
70
+ registry = piSdk.ModelRegistry.create(authStorage);
71
+ console.log('registry created OK');
72
+ } catch (err) {
73
+ console.log('registry creation threw:', err);
74
+ return;
75
+ }
76
+
77
+ const registryError = registry.getError?.();
78
+ console.log('registry.getError():', registryError ?? '(none)');
79
+
80
+ header('5. registry.getAll() — every model in catalog');
81
+ try {
82
+ const all = registry.getAll();
83
+ console.log('count:', all.length);
84
+ const byProvider: Record<string, number> = {};
85
+ for (const m of all) {
86
+ byProvider[m.provider] = (byProvider[m.provider] ?? 0) + 1;
87
+ }
88
+ console.log('by provider:', byProvider);
89
+ } catch (err) {
90
+ console.log('getAll() threw:', err);
91
+ }
92
+
93
+ header('6. registry.getAvailable() — only auth-configured models');
94
+ try {
95
+ const available = registry.getAvailable();
96
+ console.log('count:', available.length);
97
+ const byProvider: Record<string, number> = {};
98
+ for (const m of available) {
99
+ byProvider[m.provider] = (byProvider[m.provider] ?? 0) + 1;
100
+ }
101
+ console.log('by provider:', byProvider);
102
+
103
+ // Detailed sample
104
+ if (available.length > 0) {
105
+ console.log('first 3 models:');
106
+ for (const m of available.slice(0, 3)) {
107
+ console.log(' ', {
108
+ id: m.id,
109
+ provider: m.provider,
110
+ name: m.name,
111
+ reasoning: m.reasoning,
112
+ oauth: registry.isUsingOAuth?.(m),
113
+ });
114
+ }
115
+ }
116
+ } catch (err) {
117
+ console.log('getAvailable() threw:', err);
118
+ }
119
+
120
+ header('7. Per-provider auth status (registry.getProviderAuthStatus)');
121
+ // Try each unique catalog provider seen
122
+ const seen = new Set<string>();
123
+ try {
124
+ for (const m of registry.getAll()) seen.add(m.provider);
125
+ } catch {}
126
+ for (const provider of seen) {
127
+ try {
128
+ const status = registry.getProviderAuthStatus(provider);
129
+ const display = registry.getProviderDisplayName?.(provider);
130
+ console.log(` ${provider} (${display}):`, status);
131
+ } catch (err) {
132
+ console.log(` ${provider}: threw`, err);
133
+ }
134
+ }
135
+
136
+ header('8. Is there a models.json customizing things?');
137
+ const modelsPath = path.join(os.homedir(), '.pi', 'agent', 'models.json');
138
+ console.log('path:', modelsPath, 'exists?', fs.existsSync(modelsPath));
139
+ if (fs.existsSync(modelsPath)) {
140
+ try {
141
+ console.log(fs.readFileSync(modelsPath, 'utf-8'));
142
+ } catch (err) {
143
+ console.log('read error:', err);
144
+ }
145
+ }
146
+
147
+ header('!!! BACKEND VERSION CHECK !!!');
148
+ try {
149
+ const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf-8'));
150
+ console.log('this scripts dir backend version:', pkg.version);
151
+ } catch (err) {
152
+ console.log('could not read backend package.json:', err);
153
+ }
154
+ // What's actually installed globally on this machine?
155
+ try {
156
+ const which = process.platform === 'win32' ? 'where' : 'which';
157
+ const { execSync } = await import('node:child_process');
158
+ const cccBinPath = execSync(`${which} ccc`, { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }).split(/\r?\n/)[0]?.trim();
159
+ console.log('global ccc binary:', cccBinPath);
160
+ if (cccBinPath) {
161
+ // Walk up to find the package.json of the global install
162
+ let dir = path.dirname(cccBinPath);
163
+ for (let i = 0; i < 8; i++) {
164
+ const candidate = path.join(dir, 'node_modules', '@naarang', 'ccc', 'package.json');
165
+ if (fs.existsSync(candidate)) {
166
+ const cccPkg = JSON.parse(fs.readFileSync(candidate, 'utf-8'));
167
+ console.log('GLOBALLY INSTALLED @naarang/ccc version:', cccPkg.version);
168
+ break;
169
+ }
170
+ const parent = path.dirname(dir);
171
+ if (parent === dir) break;
172
+ dir = parent;
173
+ }
174
+ }
175
+ } catch (err) {
176
+ console.log('could not check installed ccc version:', err);
177
+ }
178
+
179
+ header('=== SIMULATING handleProvidersList output ===');
180
+ try {
181
+ const datedPattern = /^claude-.*-\d{8}$/;
182
+ const available: any[] = registry.getAvailable();
183
+ const models: any[] = [];
184
+ const providerDisplayNames: Record<string, string> = {};
185
+ for (const m of available) {
186
+ if (datedPattern.test(m.id)) continue;
187
+ if (m.id.startsWith('claude-3-')) continue;
188
+ models.push({
189
+ id: m.id,
190
+ displayName: m.name || m.id,
191
+ provider: m.provider,
192
+ reasoning_variants: m.reasoning ? ['minimal','low','medium','high','xhigh'] : undefined,
193
+ oauth: registry.isUsingOAuth?.(m),
194
+ });
195
+ if (!providerDisplayNames[m.provider]) {
196
+ try { providerDisplayNames[m.provider] = registry.getProviderDisplayName(m.provider); } catch { providerDisplayNames[m.provider] = m.provider; }
197
+ }
198
+ }
199
+ const providerMap: Record<string, any[]> = {};
200
+ for (const m of models) (providerMap[m.provider] ??= []).push(m);
201
+ console.log('FINAL provider names:', providerDisplayNames);
202
+ console.log('FINAL provider counts:');
203
+ for (const [p, list] of Object.entries(providerMap)) console.log(` ${p}: ${list.length}`);
204
+ console.log('FINAL models per provider:');
205
+ for (const [p, list] of Object.entries(providerMap)) {
206
+ console.log(`\n${p}:`);
207
+ for (const m of list) console.log(` ${m.id} (${m.displayName}) reasoning=${!!m.reasoning_variants} oauth=${m.oauth}`);
208
+ }
209
+ } catch (err) {
210
+ console.log('simulation threw:', err);
211
+ }
212
+
213
+ header('9. Full available list (id + provider)');
214
+ try {
215
+ for (const m of registry.getAvailable()) {
216
+ console.log(` ${m.provider.padEnd(20)} ${m.id}`);
217
+ }
218
+ } catch {}
219
+
220
+ header('10. Anthropic models — all vs available');
221
+ try {
222
+ const allAnthropic = registry.getAll().filter((m: any) => m.provider === 'anthropic');
223
+ const availAnthropic = registry.getAvailable().filter((m: any) => m.provider === 'anthropic');
224
+ console.log('all anthropic models in catalog:');
225
+ for (const m of allAnthropic) console.log(` ${m.id} (reasoning: ${m.reasoning})`);
226
+ console.log('available anthropic models:');
227
+ for (const m of availAnthropic) console.log(` ${m.id}`);
228
+ } catch {}
229
+
230
+ header('done');
231
+ }
232
+
233
+ main().catch((err) => {
234
+ console.error('fatal:', err);
235
+ process.exit(1);
236
+ });
@@ -0,0 +1,252 @@
1
+ /**
2
+ * pi-models-pi-diagnose.ts
3
+ *
4
+ * Run this on the Raspberry Pi (or whatever machine hosts the C3
5
+ * backend) to diagnose why the Pi model picker is empty.
6
+ *
7
+ * Usage on the Pi:
8
+ *
9
+ * 1. Make sure Pi is installed globally:
10
+ * npm i -g @mariozechner/pi-coding-agent
11
+ *
12
+ * 2. Save this file as `pi-diagnose.ts` somewhere on the Pi.
13
+ *
14
+ * 3. Run with Bun (the C3 backend already requires Bun):
15
+ * bun run pi-diagnose.ts
16
+ *
17
+ * 4. Paste the entire output back to me. Credential values are
18
+ * redacted, but everything else is shown.
19
+ *
20
+ * What this checks:
21
+ * 1. Where Pi is installed and what version
22
+ * 2. The shape of ~/.pi/agent/auth.json (no secrets dumped)
23
+ * 3. AuthStorage.list() — what credentials Pi sees
24
+ * 4. ModelRegistry creation
25
+ * 5. registry.getAll() — every model in the catalog
26
+ * 6. registry.getAvailable() — only models with usable auth
27
+ * 7. Per-provider auth status
28
+ * 8. Whether ~/.pi/agent/models.json customizes anything
29
+ * 9. Globally installed @naarang/ccc backend version
30
+ *
31
+ * If `getAvailable()` returns models but the mobile app picker is
32
+ * still empty, the issue is in the MQTT response shape — paste me
33
+ * the output of step 6 and the picker contents and I can diagnose.
34
+ */
35
+
36
+ import * as fs from 'node:fs';
37
+ import * as path from 'node:path';
38
+ import * as os from 'node:os';
39
+ import { execSync } from 'node:child_process';
40
+
41
+ function header(label: string): void {
42
+ console.log('\n' + '='.repeat(70));
43
+ console.log(label);
44
+ console.log('='.repeat(70));
45
+ }
46
+
47
+ /** Resolve the user-installed Pi package the same way C3 backend does. */
48
+ function locatePiCodingAgent(): string | null {
49
+ let piBinPath: string | null = null;
50
+ try {
51
+ const which = process.platform === 'win32' ? 'where' : 'which';
52
+ const out = execSync(`${which} pi`, {
53
+ encoding: 'utf-8',
54
+ stdio: ['ignore', 'pipe', 'ignore'],
55
+ });
56
+ piBinPath = out.split(/\r?\n/).find((p) => p.trim().length > 0)?.trim() || null;
57
+ } catch {
58
+ /* pi not on PATH */
59
+ }
60
+ if (piBinPath && fs.existsSync(piBinPath)) {
61
+ try {
62
+ const realBin = fs.realpathSync(piBinPath);
63
+ let dir = path.dirname(realBin);
64
+ for (let i = 0; i < 6; i++) {
65
+ const candidate = path.join(dir, 'node_modules', '@mariozechner', 'pi-coding-agent');
66
+ if (fs.existsSync(path.join(candidate, 'package.json'))) return candidate;
67
+ const parent = path.dirname(dir);
68
+ if (parent === dir) break;
69
+ dir = parent;
70
+ }
71
+ } catch {
72
+ /* fall through */
73
+ }
74
+ }
75
+ try {
76
+ const prefix = execSync('npm config get prefix', {
77
+ encoding: 'utf-8',
78
+ stdio: ['ignore', 'pipe', 'ignore'],
79
+ }).trim();
80
+ if (prefix) {
81
+ const candidates = [
82
+ path.join(prefix, 'node_modules', '@mariozechner', 'pi-coding-agent'),
83
+ path.join(prefix, 'lib', 'node_modules', '@mariozechner', 'pi-coding-agent'),
84
+ ];
85
+ for (const c of candidates) {
86
+ if (fs.existsSync(path.join(c, 'package.json'))) return c;
87
+ }
88
+ }
89
+ } catch {
90
+ /* ignore */
91
+ }
92
+ return null;
93
+ }
94
+
95
+ async function main(): Promise<void> {
96
+ header('1. Pi install location and version');
97
+ const piPath = locatePiCodingAgent();
98
+ console.log('Pi package path:', piPath);
99
+ if (!piPath) {
100
+ console.error('FATAL: cannot find Pi install. Run: npm i -g @mariozechner/pi-coding-agent');
101
+ process.exit(1);
102
+ }
103
+ const piPkg = JSON.parse(fs.readFileSync(path.join(piPath, 'package.json'), 'utf-8'));
104
+ console.log('Pi version :', piPkg.version);
105
+
106
+ // Dynamic import the user's installed Pi SDK
107
+ const piEntry = path.resolve(piPath, 'dist', 'index.js');
108
+ const piUrl = process.platform === 'win32' ? `file:///${piEntry.replace(/\\/g, '/')}` : piEntry;
109
+ const piSdk = await import(piUrl);
110
+
111
+ header('2. ~/.pi/agent/auth.json shape (no secrets)');
112
+ const authPath = path.join(os.homedir(), '.pi', 'agent', 'auth.json');
113
+ console.log('path :', authPath);
114
+ console.log('exists:', fs.existsSync(authPath));
115
+ if (fs.existsSync(authPath)) {
116
+ try {
117
+ const raw = JSON.parse(fs.readFileSync(authPath, 'utf-8'));
118
+ console.log('top-level keys:', Object.keys(raw));
119
+ const summary: Record<string, { type?: string; keys?: string[] }> = {};
120
+ for (const [provider, cred] of Object.entries(raw)) {
121
+ const c = cred as any;
122
+ summary[provider] = {
123
+ type: c?.type,
124
+ keys:
125
+ c && typeof c === 'object'
126
+ ? Object.keys(c).filter((k) => k !== 'access' && k !== 'refresh' && k !== 'key')
127
+ : undefined,
128
+ };
129
+ }
130
+ console.log('credential summary:', JSON.stringify(summary, null, 2));
131
+ } catch (err) {
132
+ console.log('parse error:', err);
133
+ }
134
+ } else {
135
+ console.log('No auth.json found — log in to Pi first via: pi /login');
136
+ }
137
+
138
+ header('3. AuthStorage.list()');
139
+ const authStorage = piSdk.AuthStorage.create();
140
+ console.log('list():', authStorage.list());
141
+ for (const provider of authStorage.list()) {
142
+ try {
143
+ console.log(` ${provider}:`, authStorage.getAuthStatus(provider));
144
+ } catch (err) {
145
+ console.log(` ${provider}: getAuthStatus threw:`, err);
146
+ }
147
+ }
148
+
149
+ header('4. ModelRegistry.create(authStorage)');
150
+ let registry: any;
151
+ try {
152
+ registry = piSdk.ModelRegistry.create(authStorage);
153
+ console.log('registry created OK');
154
+ } catch (err) {
155
+ console.log('registry creation threw:', err);
156
+ return;
157
+ }
158
+ const registryError = registry.getError?.();
159
+ console.log('registry.getError():', registryError ?? '(none)');
160
+
161
+ header('5. registry.getAll() — total catalog count');
162
+ try {
163
+ const all = registry.getAll();
164
+ console.log('total count:', all.length);
165
+ const byProvider: Record<string, number> = {};
166
+ for (const m of all) byProvider[m.provider] = (byProvider[m.provider] ?? 0) + 1;
167
+ console.log('by provider:', byProvider);
168
+ } catch (err) {
169
+ console.log('getAll() threw:', err);
170
+ }
171
+
172
+ header('6. registry.getAvailable() — auth-configured models');
173
+ try {
174
+ const available = registry.getAvailable();
175
+ console.log('count:', available.length);
176
+ const byProvider: Record<string, number> = {};
177
+ for (const m of available) byProvider[m.provider] = (byProvider[m.provider] ?? 0) + 1;
178
+ console.log('by provider:', byProvider);
179
+ console.log('full list (id + provider + oauth):');
180
+ for (const m of available) {
181
+ console.log(` ${(m.provider as string).padEnd(20)} ${m.id} oauth=${registry.isUsingOAuth?.(m)}`);
182
+ }
183
+ } catch (err) {
184
+ console.log('getAvailable() threw:', err);
185
+ }
186
+
187
+ header('7. Per-provider auth status');
188
+ const seen = new Set<string>();
189
+ try {
190
+ for (const m of registry.getAll()) seen.add(m.provider);
191
+ } catch {
192
+ /* ignore */
193
+ }
194
+ for (const provider of seen) {
195
+ try {
196
+ const status = registry.getProviderAuthStatus(provider);
197
+ const display = registry.getProviderDisplayName?.(provider);
198
+ console.log(` ${provider} (${display}):`, status);
199
+ } catch (err) {
200
+ console.log(` ${provider}: threw`, err);
201
+ }
202
+ }
203
+
204
+ header('8. ~/.pi/agent/models.json (custom providers)');
205
+ const modelsPath = path.join(os.homedir(), '.pi', 'agent', 'models.json');
206
+ console.log('path :', modelsPath, ' exists:', fs.existsSync(modelsPath));
207
+ if (fs.existsSync(modelsPath)) {
208
+ try {
209
+ console.log(fs.readFileSync(modelsPath, 'utf-8'));
210
+ } catch (err) {
211
+ console.log('read error:', err);
212
+ }
213
+ }
214
+
215
+ header('9. Globally installed @naarang/ccc backend version');
216
+ try {
217
+ const which = process.platform === 'win32' ? 'where' : 'which';
218
+ const cccBinPath = execSync(`${which} ccc`, {
219
+ encoding: 'utf-8',
220
+ stdio: ['ignore', 'pipe', 'ignore'],
221
+ })
222
+ .split(/\r?\n/)[0]
223
+ ?.trim();
224
+ console.log('ccc binary path:', cccBinPath);
225
+ if (cccBinPath) {
226
+ let dir = path.dirname(fs.realpathSync(cccBinPath));
227
+ let found = false;
228
+ for (let i = 0; i < 8; i++) {
229
+ const candidate = path.join(dir, 'node_modules', '@naarang', 'ccc', 'package.json');
230
+ if (fs.existsSync(candidate)) {
231
+ const cccPkg = JSON.parse(fs.readFileSync(candidate, 'utf-8'));
232
+ console.log('@naarang/ccc version:', cccPkg.version);
233
+ found = true;
234
+ break;
235
+ }
236
+ const parent = path.dirname(dir);
237
+ if (parent === dir) break;
238
+ dir = parent;
239
+ }
240
+ if (!found) console.log('(could not find @naarang/ccc package.json — may be a custom install layout)');
241
+ }
242
+ } catch (err) {
243
+ console.log('ccc not on PATH or version unknown');
244
+ }
245
+
246
+ header('done — paste this entire output back');
247
+ }
248
+
249
+ main().catch((err) => {
250
+ console.error('fatal:', err);
251
+ process.exit(1);
252
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naarang/ccc",
3
- "version": "3.3.0-beta.2",
3
+ "version": "3.3.0-beta.4",
4
4
  "description": "Code Chat Connect - Control Claude Code from your mobile device",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",