@hienlh/ppm 0.8.6 → 0.8.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/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.8.7] - 2026-03-23
4
+
5
+ ### Fixed
6
+ - **Auth env priority chain**: Properly resolve auth env vars with priority: PPM accounts > project `.env` > shell env. Previously project `.env` vars were neutralized (set empty) instead of parsed and used as overrides.
7
+ - **Env var diagnostics in timeout/error messages**: When SDK hangs or returns `unknown` error, error message guides user to check `ANTHROPIC_API_KEY`/`ANTHROPIC_BASE_URL` env vars with exact debug commands
8
+ - **Auth source logging**: Log which source each auth var comes from (project .env vs shell env) — helps diagnose SDK hangs
9
+
3
10
  ## [0.8.6] - 2026-03-23
4
11
 
5
12
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hienlh/ppm",
3
- "version": "0.8.6",
3
+ "version": "0.8.8",
4
4
  "description": "Personal Project Manager — mobile-first web IDE with AI assistance",
5
5
  "author": "hienlh",
6
6
  "license": "MIT",
@@ -50,40 +50,67 @@ export class ClaudeAgentSdkProvider implements AIProvider {
50
50
  /** Fork source: ppmSessionId → sourceSessionId (used on first message to fork) */
51
51
  private forkSources = new Map<string, string>();
52
52
 
53
- /** Env vars to neutralize only if project .env contains them (prevents .env poisoning) */
54
- private readonly SENSITIVE_ENV_KEYS = ["ANTHROPIC_API_KEY", "ANTHROPIC_BASE_URL", "ANTHROPIC_AUTH_TOKEN"];
53
+ /** Auth-related env keyspriority: account > project .env > shell env */
54
+ private readonly AUTH_ENV_KEYS = ["ANTHROPIC_API_KEY", "ANTHROPIC_BASE_URL", "ANTHROPIC_AUTH_TOKEN"];
55
55
 
56
- private getProjectEnvOverrides(projectPath?: string): Record<string, string> {
56
+ /**
57
+ * Parse project .env file and extract auth-related values.
58
+ * Returns actual values (not neutralized) so they can override shell env.
59
+ */
60
+ private parseProjectEnv(projectPath?: string): Record<string, string> {
57
61
  if (!projectPath) return {};
58
62
  try {
59
63
  const envPath = resolve(projectPath, ".env");
60
64
  if (!existsSync(envPath)) return {};
61
65
  const content = readFileSync(envPath, "utf-8");
62
- const overrides: Record<string, string> = {};
63
- for (const key of this.SENSITIVE_ENV_KEYS) {
64
- if (content.includes(key)) {
65
- overrides[key] = "";
66
- console.log(`[sdk] Neutralizing ${key} from project .env (prevents poisoning)`);
66
+ const parsed: Record<string, string> = {};
67
+ for (const line of content.split("\n")) {
68
+ const trimmed = line.trim();
69
+ if (!trimmed || trimmed.startsWith("#")) continue;
70
+ const eqIdx = trimmed.indexOf("=");
71
+ if (eqIdx < 0) continue;
72
+ const key = trimmed.slice(0, eqIdx).trim();
73
+ if (!this.AUTH_ENV_KEYS.includes(key)) continue;
74
+ // Strip surrounding quotes
75
+ let val = trimmed.slice(eqIdx + 1).trim();
76
+ if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
77
+ val = val.slice(1, -1);
67
78
  }
79
+ parsed[key] = val;
68
80
  }
69
- return overrides;
81
+ return parsed;
70
82
  } catch { return {}; }
71
83
  }
72
84
 
73
85
  /**
74
86
  * Build env for SDK query.
75
- * If account mode: detect token type and inject the correct env var.
76
- * - OAuth tokens (sk-ant-oat*) CLAUDE_CODE_OAUTH_TOKEN (SDK reads this for subscription auth)
77
- * - API keys (sk-ant-api* or other) → ANTHROPIC_API_KEY
78
- * Otherwise: pass through existing env (backward compatible).
87
+ * Priority: PPM accounts > project .env > shell env.
88
+ * - Account mode: inject account token, neutralize conflicting vars.
89
+ * TODO: support base_url from PPM AI settings.
90
+ * - No account: project .env overrides shell env for auth vars.
79
91
  */
80
92
  private buildQueryEnv(
81
93
  projectPath: string | undefined,
82
94
  account: { id: string; accessToken: string } | null,
83
95
  ): Record<string, string | undefined> {
84
- const base = { ...process.env, ...this.getProjectEnvOverrides(projectPath) };
96
+ const projectEnv = this.parseProjectEnv(projectPath);
97
+ // Merge: shell env ← project .env (project wins)
98
+ const base = { ...process.env, ...projectEnv };
99
+
100
+ // Log auth source for diagnostics
101
+ for (const key of this.AUTH_ENV_KEYS) {
102
+ if (projectEnv[key]) {
103
+ console.log(`[sdk] ${key} from project .env (length=${projectEnv[key].length})`);
104
+ } else if (process.env[key]) {
105
+ console.log(`[sdk] ${key} from shell env (length=${process.env[key]!.length})`);
106
+ }
107
+ }
108
+
85
109
  if (!account) return base;
110
+
111
+ // Account mode: account token takes highest priority
86
112
  const isOAuthToken = account.accessToken.startsWith("sk-ant-oat");
113
+ // TODO: support base_url from PPM AI provider settings
87
114
  if (isOAuthToken) {
88
115
  return {
89
116
  ...base,
@@ -813,7 +840,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
813
840
  rate_limit: "Rate limited by the API. Please wait and try again.",
814
841
  invalid_request: "Invalid request sent to the API.",
815
842
  server_error: "Anthropic API server error. Try again shortly.",
816
- unknown: `API error in project "${effectiveCwd}". Debug: run \`cd ${effectiveCwd} && claude -p "hi"\` in your terminal. If that also fails, the issue is Claude CLI auth or project config (.claude/ folder). Try: 1) \`claude login\`, 2) Remove .claude/settings.local.json, 3) Create a new chat session.`,
843
+ unknown: `API error in project "${effectiveCwd}". Debug:\n1. Run: \`cd ${effectiveCwd} && claude -p "hi"\`\n2. Check env: \`echo $ANTHROPIC_API_KEY $ANTHROPIC_BASE_URL\` stale/invalid keys cause this\n3. Try: \`ANTHROPIC_API_KEY="" ANTHROPIC_BASE_URL="" claude -p "hi"\`\n4. Refresh auth: \`claude login\``,
817
844
  };
818
845
  const hint = errorHints[assistantError] ?? `API error: ${assistantError}`;
819
846
  yield { type: "error", message: hint };
@@ -138,7 +138,7 @@ async function runStreamLoop(sessionId: string, providerId: string, content: str
138
138
  const debugCmd = projectPath ? `cd ${projectPath} && claude -p "hi"` : `claude -p "hi"`;
139
139
  safeSend(sessionId, {
140
140
  type: "error",
141
- message: `Claude SDK timed out after ${elapsed}s for project "${projectPath || "(no project)"}".${wslHint}\n\nDebug steps:\n1. Run in your terminal: \`${debugCmd}\`\n2. Check for hanging hooks/MCP servers: \`cat ${projectPath}/.claude/settings.local.json\`\n3. Try removing project Claude config: \`mv ${projectPath}/.claude ${projectPath}/.claude.bak\`\n4. If none of the above helps, try: \`claude login\` to refresh auth`,
141
+ message: `Claude SDK timed out after ${elapsed}s for project "${projectPath || "(no project)"}".${wslHint}\n\nDebug steps:\n1. Run: \`${debugCmd}\` — if it also hangs, the issue is your Claude CLI environment\n2. Check env vars: \`echo $ANTHROPIC_API_KEY $ANTHROPIC_BASE_URL\` — stale/invalid keys cause silent hang\n3. Try with env cleared: \`ANTHROPIC_API_KEY="" ANTHROPIC_BASE_URL="" ${debugCmd}\`\n4. Check hooks/MCP: \`cat ${projectPath}/.claude/settings.local.json\`\n5. Refresh auth: \`claude login\``,
142
142
  });
143
143
  abortController.abort();
144
144
  return;