@phi-code-admin/phi-code 0.59.6 → 0.59.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.
@@ -10,10 +10,10 @@
10
10
  */
11
11
 
12
12
  import type { ExtensionAPI } from "phi-code";
13
- import { writeFile, mkdir, copyFile, readdir, access } from "node:fs/promises";
13
+ import { writeFile, mkdir, copyFile, readdir, access, readFile } from "node:fs/promises";
14
14
  import { join, resolve } from "node:path";
15
15
  import { homedir } from "node:os";
16
- import { existsSync } from "node:fs";
16
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
17
17
 
18
18
  // ─── Types ───────────────────────────────────────────────────────────────
19
19
 
@@ -616,18 +616,86 @@ _Edit this file to customize Phi Code's behavior for your project._
616
616
  ctx.ui.notify("🔍 Probing local model servers...", "info");
617
617
  await detectLocalProviders(providers);
618
618
 
619
- const available = providers.filter(p => p.available);
619
+ let available = providers.filter(p => p.available);
620
620
 
621
+ // If no providers found, offer interactive API key setup
621
622
  if (available.length === 0) {
623
+ ctx.ui.notify("⚠️ No API keys detected. Let's set one up!\n", "info");
624
+ ctx.ui.notify("**Available providers:**", "info");
622
625
  const cloudProviders = providers.filter(p => !p.local);
623
- ctx.ui.notify("❌ No providers found. Options:\n\n" +
624
- "**Cloud providers** (set API key):\n" +
625
- cloudProviders.map(p => ` export ${p.envVar}="your-key" # ${p.name}`).join("\n") +
626
- "\n\n**Local providers** (start the server):\n" +
627
- " Ollama: `ollama serve` (default port 11434)\n" +
628
- " • LM Studio: Start server in app (default port 1234)\n" +
629
- "\n💡 Options: Alibaba Coding Plan (cloud), OpenAI, Anthropic, or Ollama/LM Studio (local)", "error");
630
- return;
626
+ cloudProviders.forEach((p, i) => {
627
+ ctx.ui.notify(` ${i + 1}. ${p.name}`, "info");
628
+ });
629
+ ctx.ui.notify(` ${cloudProviders.length + 1}. Ollama (local)`, "info");
630
+ ctx.ui.notify(` ${cloudProviders.length + 2}. LM Studio (local)\n`, "info");
631
+
632
+ const providerChoice = await ctx.ui.input(
633
+ "Choose provider (number)",
634
+ `1-${cloudProviders.length + 2}`
635
+ );
636
+ const choiceNum = parseInt(providerChoice ?? "0");
637
+
638
+ if (choiceNum >= 1 && choiceNum <= cloudProviders.length) {
639
+ // Cloud provider — ask for API key
640
+ const chosen = cloudProviders[choiceNum - 1];
641
+ ctx.ui.notify(`\n🔑 **${chosen.name}** selected.`, "info");
642
+
643
+ const apiKey = await ctx.ui.input(
644
+ `Enter your ${chosen.name} API key`,
645
+ "sk-..."
646
+ );
647
+
648
+ if (!apiKey || apiKey.trim().length < 5) {
649
+ ctx.ui.notify("❌ Invalid API key. Cancelled.", "error");
650
+ return;
651
+ }
652
+
653
+ // Save to models.json for persistence
654
+ const modelsJsonPath = join(agentDir, "models.json");
655
+ let modelsConfig: any = { providers: {} };
656
+ try {
657
+ const existing = await readFile(modelsJsonPath, "utf-8");
658
+ modelsConfig = JSON.parse(existing);
659
+ } catch { /* file doesn't exist yet */ }
660
+
661
+ // Build provider config with correct provider ID
662
+ const providerId = chosen.name.toLowerCase().replace(/\s+/g, "-");
663
+ modelsConfig.providers[providerId] = {
664
+ baseUrl: chosen.baseUrl,
665
+ api: "openai-completions",
666
+ apiKey: apiKey.trim(),
667
+ models: chosen.models.map((id: string) => ({
668
+ id,
669
+ name: id,
670
+ reasoning: true,
671
+ input: ["text"],
672
+ contextWindow: 131072,
673
+ maxTokens: 16384,
674
+ })),
675
+ };
676
+
677
+ await writeFile(modelsJsonPath, JSON.stringify(modelsConfig, null, 2), "utf-8");
678
+
679
+ // Also set in current session
680
+ process.env[chosen.envVar] = apiKey.trim();
681
+ chosen.available = true;
682
+
683
+ ctx.ui.notify(`\n✅ API key saved to \`~/.phi/agent/models.json\``, "info");
684
+ ctx.ui.notify(` ${chosen.models.length} models configured.`, "info");
685
+ ctx.ui.notify(` ⚠️ **Restart phi** for models to load.\n`, "warning");
686
+ ctx.ui.notify("Run `phi` again, then `/phi-init` to complete setup.", "info");
687
+ return;
688
+
689
+ } else if (choiceNum === cloudProviders.length + 1 || choiceNum === cloudProviders.length + 2) {
690
+ const localName = choiceNum === cloudProviders.length + 1 ? "Ollama" : "LM Studio";
691
+ const port = choiceNum === cloudProviders.length + 1 ? 11434 : 1234;
692
+ ctx.ui.notify(`\n💡 **${localName}** selected. Make sure it's running on port ${port}.`, "info");
693
+ ctx.ui.notify(`Then restart phi and run /phi-init again.`, "info");
694
+ return;
695
+ } else {
696
+ ctx.ui.notify("❌ Invalid choice. Cancelled.", "error");
697
+ return;
698
+ }
631
699
  }
632
700
 
633
701
  ctx.ui.notify(`✅ Found ${available.length} provider(s):`, "info");
@@ -786,7 +854,7 @@ For persistence, set environment variables:
786
854
 
787
855
  if (action === "set") {
788
856
  const provider = parts[1]?.toLowerCase();
789
- const key = parts[2];
857
+ const key = parts.slice(2).join(" ").trim();
790
858
 
791
859
  if (!provider || !key) {
792
860
  ctx.ui.notify("Usage: /api-key set <provider> <key>\nExample: /api-key set alibaba sk-sp-xxx", "warning");
@@ -801,13 +869,37 @@ For persistence, set environment variables:
801
869
 
802
870
  // Set in current process environment
803
871
  process.env[info.envVar] = key;
804
- // Also set common aliases
805
872
  if (provider === "alibaba") {
806
873
  process.env.DASHSCOPE_API_KEY = key;
807
874
  }
808
875
 
876
+ // Persist to models.json
877
+ const modelsJsonPath = join(homedir(), ".phi", "agent", "models.json");
878
+ let modelsConfig: any = { providers: {} };
879
+ try {
880
+ const existing = readFileSync(modelsJsonPath, "utf-8");
881
+ modelsConfig = JSON.parse(existing);
882
+ } catch { /* file doesn't exist yet */ }
883
+
884
+ // Find provider config from detectProviders
885
+ const providerDefs = detectProviders();
886
+ const providerDef = providerDefs.find(p => p.envVar === info.envVar);
887
+ if (providerDef) {
888
+ const providerId = providerDef.name.toLowerCase().replace(/\s+/g, "-");
889
+ modelsConfig.providers[providerId] = {
890
+ baseUrl: providerDef.baseUrl,
891
+ api: "openai-completions",
892
+ apiKey: key,
893
+ models: providerDef.models.map((id: string) => ({
894
+ id, name: id, reasoning: true, input: ["text"],
895
+ contextWindow: 131072, maxTokens: 16384,
896
+ })),
897
+ };
898
+ writeFileSync(modelsJsonPath, JSON.stringify(modelsConfig, null, 2), "utf-8");
899
+ }
900
+
809
901
  const masked = key.substring(0, 6) + "..." + key.substring(key.length - 4);
810
- ctx.ui.notify(`✅ **${info.name}** API key set: ${masked}\n\n⚠️ Active for this session only. For persistence:\n • Windows: \`setx ${info.envVar} "${key.substring(0, 6)}..."\`\n Linux/Mac: Add \`export ${info.envVar}="..."\` to ~/.bashrc`, "info");
902
+ ctx.ui.notify(`✅ **${info.name}** API key saved: ${masked}\n\n📁 Saved to \`~/.phi/agent/models.json\` (persistent)\n⚠️ **Restart phi** for the new models to load.`, "info");
811
903
  return;
812
904
  }
813
905
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phi-code-admin/phi-code",
3
- "version": "0.59.6",
3
+ "version": "0.59.8",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "piConfig": {
@@ -1,15 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
3
  * Post-install script: copies bundled extensions, agents, and skills
4
- * to ~/.phi/agent/ so Pi discovers them automatically.
4
+ * to ~/.phi/agent/ and makes sigma packages resolvable from there.
5
5
  */
6
- const { existsSync, mkdirSync, cpSync, readdirSync } = require("fs");
7
- const { join } = require("path");
6
+ const { existsSync, mkdirSync, cpSync, readdirSync, symlinkSync, unlinkSync, readlinkSync } = require("fs");
7
+ const { join, dirname } = require("path");
8
8
  const { homedir } = require("os");
9
9
 
10
10
  const agentDir = join(homedir(), ".phi", "agent");
11
- const packageDir = __dirname.replace(/[/\\]scripts$/, "");
11
+ const packageDir = __dirname.replace(/[\\/]scripts$/, "");
12
12
 
13
+ // 1. Copy extensions, agents, skills
13
14
  const copies = [
14
15
  { src: "extensions/phi", dest: join(agentDir, "extensions"), label: "extensions" },
15
16
  { src: "agents", dest: join(agentDir, "agents"), label: "agents" },
@@ -19,22 +20,61 @@ const copies = [
19
20
  for (const { src, dest, label } of copies) {
20
21
  const srcDir = join(packageDir, src);
21
22
  if (!existsSync(srcDir)) continue;
22
-
23
23
  mkdirSync(dest, { recursive: true });
24
-
25
24
  const files = readdirSync(srcDir);
26
25
  let copied = 0;
27
26
  for (const file of files) {
28
- const srcPath = join(srcDir, file);
29
- const destPath = join(dest, file);
30
27
  try {
31
- cpSync(srcPath, destPath, { recursive: true, force: true });
28
+ cpSync(join(srcDir, file), join(dest, file), { recursive: true, force: true });
32
29
  copied++;
33
- } catch (e) {
34
- // Skip files that can't be copied (permissions, etc.)
30
+ } catch (e) { /* skip */ }
31
+ }
32
+ if (copied > 0) console.log(` Φ Installed ${copied} ${label} to ${dest}`);
33
+ }
34
+
35
+ // 2. Make sigma packages resolvable from ~/.phi/agent/extensions/
36
+ // Create node_modules with symlinks to the actual packages
37
+ const sigmaPackages = ["sigma-memory", "sigma-agents", "sigma-skills"];
38
+ const extensionsNodeModules = join(agentDir, "extensions", "node_modules");
39
+ mkdirSync(extensionsNodeModules, { recursive: true });
40
+
41
+ for (const pkg of sigmaPackages) {
42
+ const srcPkg = join(packageDir, "node_modules", pkg);
43
+ const destLink = join(extensionsNodeModules, pkg);
44
+
45
+ if (!existsSync(srcPkg)) {
46
+ // Try parent node_modules (hoisted)
47
+ let parent = dirname(packageDir);
48
+ while (parent !== dirname(parent)) {
49
+ const hoisted = join(parent, "node_modules", pkg);
50
+ if (existsSync(hoisted)) {
51
+ createLink(hoisted, destLink, pkg);
52
+ break;
53
+ }
54
+ parent = dirname(parent);
35
55
  }
56
+ continue;
36
57
  }
37
- if (copied > 0) {
38
- console.log(` Φ Installed ${copied} ${label} to ${dest}`);
58
+ createLink(srcPkg, destLink, pkg);
59
+ }
60
+
61
+ function createLink(src, dest, name) {
62
+ try {
63
+ // Remove existing (symlink or directory)
64
+ if (existsSync(dest)) {
65
+ try { unlinkSync(dest); } catch {
66
+ try { cpSync(src, dest, { recursive: true, force: true }); return; } catch { return; }
67
+ }
68
+ }
69
+ // Try symlink first, fall back to copy (Windows may not support symlinks)
70
+ try {
71
+ symlinkSync(src, dest, "junction");
72
+ console.log(` Φ Linked ${name}`);
73
+ } catch {
74
+ cpSync(src, dest, { recursive: true, force: true });
75
+ console.log(` Φ Copied ${name}`);
76
+ }
77
+ } catch (e) {
78
+ console.log(` ⚠ Could not install ${name}: ${e.message}`);
39
79
  }
40
80
  }
@@ -1,3 +1,6 @@
1
+ ---
2
+ description: "REST API design patterns, versioning, error handling, and documentation"
3
+ ---
1
4
  # API Design Skill
2
5
 
3
6
  ## When to use
@@ -1,3 +1,6 @@
1
+ ---
2
+ description: "Code quality standards, naming conventions, and best practices"
3
+ ---
1
4
  # Coding Standards Skill
2
5
 
3
6
  ## When to use
@@ -1,3 +1,6 @@
1
+ ---
2
+ description: "Database design, queries, migrations, and optimization"
3
+ ---
1
4
  # Database Skill
2
5
 
3
6
  ## When to use
@@ -1,3 +1,6 @@
1
+ ---
2
+ description: "CI/CD pipelines, deployment, monitoring, and infrastructure"
3
+ ---
1
4
  # DevOps Skill
2
5
 
3
6
  ## When to use
@@ -1,3 +1,6 @@
1
+ ---
2
+ description: "Docker containers, Compose, images, and orchestration"
3
+ ---
1
4
  # Docker Operations Skill
2
5
 
3
6
  ## When to use
@@ -1,3 +1,6 @@
1
+ ---
2
+ description: "Git branching, commits, merges, and collaboration workflows"
3
+ ---
1
4
  # Git Workflow Skill
2
5
 
3
6
  ## When to use
@@ -1,3 +1,6 @@
1
+ ---
2
+ description: "GitHub Actions, PRs, issues, releases, and repository management"
3
+ ---
1
4
  # GitHub Workflow Skill
2
5
 
3
6
  ## When to use
@@ -1,3 +1,6 @@
1
+ ---
2
+ description: "Performance profiling, optimization, caching, and benchmarking"
3
+ ---
1
4
  # Performance Optimization Skill
2
5
 
3
6
  ## When to use
@@ -1,3 +1,6 @@
1
+ ---
2
+ description: "Crafting production-grade structured prompts for AI systems"
3
+ ---
1
4
  # Prompt Architect Skill
2
5
 
3
6
  ## When to use
@@ -1,3 +1,6 @@
1
+ ---
2
+ description: "Security auditing, vulnerability scanning, and hardening"
3
+ ---
1
4
  # Security Skill
2
5
 
3
6
  ## When to use
@@ -1,3 +1,6 @@
1
+ ---
2
+ description: "Continuous learning from errors, corrections, and discoveries"
3
+ ---
1
4
  # Self-Improving Agent Skill
2
5
 
3
6
  ## When to use
@@ -1,3 +1,6 @@
1
+ ---
2
+ description: "Test strategy, unit tests, integration tests, and test automation"
3
+ ---
1
4
  # Testing Skill
2
5
 
3
6
  ## When to use