@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.
- package/extensions/phi/init.ts +106 -14
- package/package.json +1 -1
- package/scripts/postinstall.cjs +53 -13
- package/skills/api-design/SKILL.md +3 -0
- package/skills/coding-standards/SKILL.md +3 -0
- package/skills/database/SKILL.md +3 -0
- package/skills/devops/SKILL.md +3 -0
- package/skills/docker-ops/SKILL.md +3 -0
- package/skills/git-workflow/SKILL.md +3 -0
- package/skills/github/SKILL.md +3 -0
- package/skills/performance/SKILL.md +3 -0
- package/skills/prompt-architect/SKILL.md +3 -0
- package/skills/security/SKILL.md +3 -0
- package/skills/self-improving/SKILL.md +3 -0
- package/skills/testing/SKILL.md +3 -0
package/extensions/phi/init.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
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
|
|
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
|
|
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
package/scripts/postinstall.cjs
CHANGED
|
@@ -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/
|
|
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(/[
|
|
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(
|
|
28
|
+
cpSync(join(srcDir, file), join(dest, file), { recursive: true, force: true });
|
|
32
29
|
copied++;
|
|
33
|
-
} catch (e) {
|
|
34
|
-
|
|
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
|
-
|
|
38
|
-
|
|
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
|
}
|
package/skills/database/SKILL.md
CHANGED
package/skills/devops/SKILL.md
CHANGED
package/skills/github/SKILL.md
CHANGED
package/skills/security/SKILL.md
CHANGED