@kidsinai/kids-client 0.0.6 → 0.0.8

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@kidsinai/kids-client",
4
- "version": "0.0.6",
4
+ "version": "0.0.8",
5
5
  "type": "module",
6
6
  "description": "Own-client TUI for Kids OpenCode — talks to local `opencode serve` via @opencode-ai/sdk v2 with kid-warm rendering, mission progress, permission dialog, and stderr-tail audit pipeline.",
7
7
  "license": "MIT",
package/src/core/env.ts CHANGED
@@ -60,9 +60,15 @@ export function validateEnv(env: KidsClientEnv): { ok: true } | { ok: false; rea
60
60
  }
61
61
  // Accept any supported provider's API key, not just DeepRouter. The
62
62
  // setup wizard writes whatever the parent picked into ~/.config/kids-opencode/env
63
- // which the wrapper sources before exec. KIDS_OAUTH_PROVIDER marks an
64
- // OAuth flow that opencode handles via its own auth.json store —
65
- // we trust opencode to gate on actual token validity at serve time.
63
+ // which the wrapper sources before exec.
64
+ //
65
+ // KIDS_OAUTH_PROVIDER signals that the wrapper finished
66
+ // `opencode auth login` and opencode now holds an OAuth token in its
67
+ // own auth.json store. Only trusted when paired with a provider opencode
68
+ // actually supports OAuth for — Anthropic Pro/Max stale markers from
69
+ // 0.0.7/0.0.8 are filtered out in hasAnyProviderKey via the
70
+ // OAUTH_PROVIDERS check, but here at the env level we accept the bare
71
+ // marker since validity will be re-checked by opencode at serve time.
66
72
  const hasAnyKey =
67
73
  env.deeprouterApiKey
68
74
  || process.env.ANTHROPIC_API_KEY
package/src/core/setup.ts CHANGED
@@ -19,13 +19,40 @@ export type ProviderId = "anthropic" | "openai" | "deeprouter"
19
19
  /**
20
20
  * Wire protocol between SetupScreen and bin/kids-opencode wrapper:
21
21
  * kids-client exits with this code to ask the wrapper to run
22
- * `opencode auth login --provider <p>` (interactive OAuth that needs the
23
- * TTY), then re-exec kids-client. See bin/kids-opencode §11 loop.
22
+ * `opencode auth login --provider <p> --method <m>` (interactive OAuth
23
+ * that needs the TTY), then re-exec kids-client. See bin/kids-opencode loop.
24
24
  */
25
25
  export const OAUTH_HANDOFF_EXIT_CODE = 123
26
26
 
27
- /** Providers that support OAuth login via the upstream opencode kernel. */
28
- export const OAUTH_PROVIDERS: ReadonlyArray<ProviderId> = ["anthropic"] as const
27
+ /**
28
+ * Providers for which upstream opencode currently ships an OAuth /
29
+ * subscription login method.
30
+ *
31
+ * - openai: ChatGPT Pro/Plus via OAuth. Verified in
32
+ * ~/Documents/sites/kidsinai/opencode-kernel/packages/opencode/src/plugin/codex.ts
33
+ * (methods: "ChatGPT Pro/Plus (browser)" + "(headless)"). Allowed models
34
+ * include gpt-5.4-mini / gpt-5.3-codex / gpt-5.5.
35
+ *
36
+ * - anthropic: NOT supported. Removed in opencode 1.3.0 because Anthropic's
37
+ * ToS prohibits Pro/Max in coding agents. Do not re-add.
38
+ *
39
+ * - deeprouter: own gateway, uses api-key.
40
+ */
41
+ export const OAUTH_PROVIDERS: ReadonlyArray<ProviderId> = ["openai"] as const
42
+
43
+ /** Per-provider OAuth method label expected by `opencode auth login --method`. */
44
+ export const OAUTH_METHOD_LABELS: Partial<Record<ProviderId, string>> = {
45
+ openai: "ChatGPT Pro/Plus (browser)",
46
+ }
47
+
48
+ /**
49
+ * Default model id to write into opencode.json when the user picks OAuth
50
+ * (the api-key default is in PROVIDERS[i].defaultModel below — different
51
+ * because the codex plugin restricts the model list when OAuth is active).
52
+ */
53
+ const OAUTH_DEFAULT_MODELS: Partial<Record<ProviderId, string>> = {
54
+ openai: "openai/gpt-5.4-mini",
55
+ }
29
56
 
30
57
  export interface ProviderChoice {
31
58
  id: ProviderId
@@ -53,8 +80,8 @@ export const PROVIDERS: ProviderChoice[] = [
53
80
  },
54
81
  {
55
82
  id: "openai",
56
- label: "OpenAI GPT-4",
57
- hint: "Also works. ~$5-10/month for typical kid use.",
83
+ label: "OpenAI GPT (ChatGPT Plus/Pro 可直接登录)",
84
+ hint: "Already pay for ChatGPT Plus/Pro? Sign in with that — no API key. Otherwise pay-as-you-go ~$5-10/month.",
58
85
  envVar: "OPENAI_API_KEY",
59
86
  apiKeyUrl: "https://platform.openai.com/api-keys",
60
87
  config: (env) => ({
@@ -121,38 +148,49 @@ export interface SaveOauthOptions {
121
148
  /**
122
149
  * Stage the OAuth handoff. We write opencode.json without an apiKey block
123
150
  * (opencode reads OAuth tokens from its own auth.json), drop any stale
124
- * provider API keys, and write a KIDS_OAUTH_PROVIDER marker so the
125
- * wrapper knows which provider to log into. The actual
126
- * `opencode auth login --provider <p>` invocation happens in
127
- * bin/kids-opencode after kids-client exits with OAUTH_HANDOFF_EXIT_CODE.
151
+ * provider API keys, and write KIDS_OAUTH_PROVIDER + KIDS_OAUTH_METHOD
152
+ * markers so the wrapper knows which provider+method to log into. The
153
+ * actual `opencode auth login --provider <p> --method <m>` invocation
154
+ * happens in bin/kids-opencode after kids-client exits with
155
+ * OAUTH_HANDOFF_EXIT_CODE.
128
156
  */
129
157
  export function saveSetupOauth(opts: SaveOauthOptions): void {
130
158
  const provider = findProvider(opts.provider)
159
+ const methodLabel = OAUTH_METHOD_LABELS[opts.provider]
160
+ if (!methodLabel) {
161
+ throw new Error(`No OAuth method label registered for provider '${opts.provider}'.`)
162
+ }
131
163
  ensureConfigDir(opts.configDir)
132
164
 
133
165
  // 1. Update env file: clear this provider's API key (avoid stale leakage),
134
- // set marker pointing at the OAuth provider.
166
+ // set markers pointing at the OAuth provider + method.
135
167
  const envPath = join(opts.configDir, "env")
136
168
  const existing = readEnvFile(envPath)
137
169
  delete existing[provider.envVar]
138
170
  existing.KIDS_OAUTH_PROVIDER = opts.provider
171
+ existing.KIDS_OAUTH_METHOD = methodLabel
139
172
  writeEnvFile(envPath, existing)
140
173
 
141
174
  // 2. opencode.json without apiKey — opencode uses its auth.json store.
142
- writeOpencodeConfig(opts.configDir, provider, { withApiKey: false })
175
+ // Use the OAuth-specific default model since the codex plugin restricts
176
+ // the model list when OAuth is the active credential.
177
+ writeOpencodeConfig(opts.configDir, provider, {
178
+ withApiKey: false,
179
+ modelOverride: OAUTH_DEFAULT_MODELS[opts.provider],
180
+ })
143
181
  }
144
182
 
145
183
  function writeOpencodeConfig(
146
184
  configDir: string,
147
185
  provider: ProviderChoice,
148
- flags: { withApiKey: boolean },
186
+ flags: { withApiKey: boolean; modelOverride?: string },
149
187
  ): void {
150
188
  const configPath = join(configDir, "opencode.json")
151
189
  const config = readJsonOrEmpty(configPath)
152
190
  config.provider = flags.withApiKey
153
191
  ? provider.config(provider.envVar)
154
192
  : { [provider.id]: {} }
155
- config.model = provider.defaultModel
193
+ config.model = flags.modelOverride ?? provider.defaultModel
156
194
  if (!config.permission) {
157
195
  config.permission = {
158
196
  default: "ask",
@@ -173,15 +211,17 @@ function writeOpencodeConfig(
173
211
  export function hasAnyProviderKey(configDir: string): boolean {
174
212
  const env = readEnvFile(join(configDir, "env"))
175
213
  if (env.ANTHROPIC_API_KEY || env.OPENAI_API_KEY || env.DEEPROUTER_API_KEY) return true
176
- // OAuth handoff completed earlier? The marker means opencode has its own
177
- // auth.json credentials; opencode itself will gate on actual token validity.
178
- if (env.KIDS_OAUTH_PROVIDER) return true
214
+ // KIDS_OAUTH_PROVIDER means setup wizard staged an OAuth handoff and the
215
+ // wrapper successfully completed `opencode auth login`. opencode keeps the
216
+ // real token in its own auth.json we trust it to gate on validity at
217
+ // serve time. (Only meaningful for providers in OAUTH_PROVIDERS; stale
218
+ // markers from removed providers are cleaned up at next setup.)
219
+ if (env.KIDS_OAUTH_PROVIDER && OAUTH_PROVIDERS.includes(env.KIDS_OAUTH_PROVIDER as ProviderId)) return true
179
220
  // Also accept keys present in the parent shell env (advanced users).
180
221
  return !!(
181
222
  process.env.ANTHROPIC_API_KEY
182
223
  || process.env.OPENAI_API_KEY
183
224
  || process.env.DEEPROUTER_API_KEY
184
- || process.env.KIDS_OAUTH_PROVIDER
185
225
  )
186
226
  }
187
227
 
@@ -427,8 +427,8 @@ const STRINGS = {
427
427
  keys: "[↑↓] 选 · [Enter] 确认 · [Esc] 返回",
428
428
  options: [
429
429
  {
430
- label: "用我的 Claude Pro/Max 订阅(推荐)",
431
- hint: "不用 API key、不用充值;用现有 claude.ai 账号一键登录",
430
+ label: "用我的 ChatGPT Plus / Pro 订阅(推荐)",
431
+ hint: "不用 API key、不用充值;用现有 chatgpt.com 账号一键登录",
432
432
  },
433
433
  {
434
434
  label: "用 API key(按量计费 ~$5/月)",
@@ -437,8 +437,8 @@ const STRINGS = {
437
437
  ],
438
438
  },
439
439
  oauthHandoff: {
440
- title: "正在让 Claude 登录接管屏幕…",
441
- line1: "马上会跳出浏览器让你登录 claude.ai 账号。",
440
+ title: "正在让 ChatGPT 登录接管屏幕…",
441
+ line1: "马上会跳出浏览器让你登录 chatgpt.com 账号。",
442
442
  line2: "登录完后我会自动接回来,给孩子继续。",
443
443
  },
444
444
  apiKeyTitle: (label: string) => `输入 ${label} 的 API key`,
@@ -496,8 +496,8 @@ const STRINGS = {
496
496
  keys: "[↑↓] choose · [Enter] confirm · [Esc] back",
497
497
  options: [
498
498
  {
499
- label: "Use my Claude Pro/Max subscription (recommended)",
500
- hint: "No API key, no top-up — sign in with your existing claude.ai account",
499
+ label: "Use my ChatGPT Plus / Pro subscription (recommended)",
500
+ hint: "No API key, no top-up — sign in with your existing chatgpt.com account",
501
501
  },
502
502
  {
503
503
  label: "Use an API key (pay-as-you-go ~$5/month)",
@@ -506,8 +506,8 @@ const STRINGS = {
506
506
  ],
507
507
  },
508
508
  oauthHandoff: {
509
- title: "Handing off to Claude login…",
510
- line1: "A browser window will open to sign in to your claude.ai account.",
509
+ title: "Handing off to ChatGPT login…",
510
+ line1: "A browser window will open to sign in to your chatgpt.com account.",
511
511
  line2: "I'll pick back up automatically once you're done.",
512
512
  },
513
513
  apiKeyTitle: (label: string) => `Enter your ${label} API key`,