@phi-code-admin/phi-code 0.85.0 → 0.87.0

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.
Files changed (86) hide show
  1. package/CHANGELOG.md +83 -0
  2. package/README.md +92 -123
  3. package/config/routing.example.json +129 -0
  4. package/config/routing.schema.json +58 -0
  5. package/dist/cli/args.d.ts.map +1 -1
  6. package/dist/cli/args.js +1 -1
  7. package/dist/cli/args.js.map +1 -1
  8. package/dist/config.d.ts.map +1 -1
  9. package/dist/config.js +1 -1
  10. package/dist/config.js.map +1 -1
  11. package/dist/core/sdk.d.ts.map +1 -1
  12. package/dist/core/sdk.js +1 -1
  13. package/dist/core/sdk.js.map +1 -1
  14. package/dist/core/system-prompt.d.ts.map +1 -1
  15. package/dist/core/system-prompt.js +6 -6
  16. package/dist/core/system-prompt.js.map +1 -1
  17. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
  18. package/dist/modes/interactive/components/config-selector.js +1 -1
  19. package/dist/modes/interactive/components/config-selector.js.map +1 -1
  20. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  21. package/dist/modes/interactive/interactive-mode.js +2 -2
  22. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  23. package/dist/package-manager-cli.d.ts.map +1 -1
  24. package/dist/package-manager-cli.js +9 -8
  25. package/dist/package-manager-cli.js.map +1 -1
  26. package/dist/utils/pi-user-agent.d.ts.map +1 -1
  27. package/dist/utils/pi-user-agent.js +4 -1
  28. package/dist/utils/pi-user-agent.js.map +1 -1
  29. package/docs/compaction.md +11 -11
  30. package/docs/custom-provider.md +4 -4
  31. package/docs/development.md +2 -2
  32. package/docs/extensions.md +47 -47
  33. package/docs/fork-policy.md +81 -0
  34. package/docs/index.md +7 -7
  35. package/docs/json.md +3 -3
  36. package/docs/keybindings.md +5 -5
  37. package/docs/models.md +6 -6
  38. package/docs/packages.md +37 -37
  39. package/docs/prompt-templates.md +4 -4
  40. package/docs/providers.md +9 -9
  41. package/docs/quickstart.md +25 -25
  42. package/docs/rpc.md +3 -3
  43. package/docs/sdk.md +34 -34
  44. package/docs/session-format.md +4 -4
  45. package/docs/sessions.md +11 -11
  46. package/docs/settings.md +9 -9
  47. package/docs/shell-aliases.md +2 -2
  48. package/docs/skills.md +9 -9
  49. package/docs/terminal-setup.md +7 -7
  50. package/docs/termux.md +6 -6
  51. package/docs/themes.md +9 -9
  52. package/docs/tmux.md +3 -3
  53. package/docs/tui.md +8 -8
  54. package/docs/usage.md +39 -39
  55. package/docs/windows.md +2 -2
  56. package/extensions/phi/agents.ts +12 -117
  57. package/extensions/phi/benchmark.ts +129 -49
  58. package/extensions/phi/browser.ts +10 -37
  59. package/extensions/phi/btw/btw.ts +1 -7
  60. package/extensions/phi/chrome/index.ts +1283 -741
  61. package/extensions/phi/commit.ts +9 -14
  62. package/extensions/phi/goal/index.ts +10 -33
  63. package/extensions/phi/init.ts +37 -47
  64. package/extensions/phi/keys.ts +2 -7
  65. package/extensions/phi/mcp/callback-server.ts +162 -165
  66. package/extensions/phi/mcp/config.ts +122 -136
  67. package/extensions/phi/mcp/errors.ts +18 -23
  68. package/extensions/phi/mcp/index.ts +322 -355
  69. package/extensions/phi/mcp/oauth-provider.ts +289 -289
  70. package/extensions/phi/mcp/server-manager.ts +390 -413
  71. package/extensions/phi/mcp/tool-bridge.ts +381 -415
  72. package/extensions/phi/memory.ts +2 -2
  73. package/extensions/phi/models.ts +27 -26
  74. package/extensions/phi/orchestrator.ts +343 -266
  75. package/extensions/phi/productivity.ts +4 -2
  76. package/extensions/phi/providers/agent-def.ts +128 -0
  77. package/extensions/phi/providers/alibaba.ts +56 -7
  78. package/extensions/phi/providers/context-window.ts +4 -1
  79. package/extensions/phi/providers/live-models.ts +5 -20
  80. package/extensions/phi/providers/orchestrator-helpers.ts +19 -5
  81. package/extensions/phi/providers/phase-machine.ts +220 -0
  82. package/extensions/phi/setup.ts +196 -169
  83. package/extensions/phi/skill-loader.ts +18 -21
  84. package/extensions/phi/smart-router.ts +6 -6
  85. package/extensions/phi/web-search.ts +90 -50
  86. package/package.json +2 -1
@@ -19,10 +19,10 @@
19
19
  * - Storage chmod 0600 garantit la sécurité au repos
20
20
  */
21
21
 
22
- import { ApiKeyStore, type ExtensionAPI, type ExtensionUIContext, getApiKeyStore, getConfigWatcher } from "phi-code";
23
- import { writeFile, mkdir } from "node:fs/promises";
24
- import { join } from "node:path";
22
+ import { mkdir, writeFile } from "node:fs/promises";
25
23
  import { homedir } from "node:os";
24
+ import { join } from "node:path";
25
+ import { ApiKeyStore, type ExtensionAPI, type ExtensionUIContext, getApiKeyStore, getConfigWatcher } from "phi-code";
26
26
  import {
27
27
  ALIBABA_ENV_VAR,
28
28
  ALIBABA_MODELS,
@@ -32,16 +32,16 @@ import {
32
32
  pingAlibaba,
33
33
  validateAlibabaApiKey,
34
34
  } from "./providers/alibaba.js";
35
+ import { fetchLiveModels, pingProvider, toPersistedModel } from "./providers/live-models.js";
35
36
  import {
36
- OPENCODE_GO_AUTH_URL,
37
- OPENCODE_GO_ENV_VAR,
38
37
  buildOpenCodeGoAnthropicProviderConfig,
39
38
  buildOpenCodeGoProviderConfig,
40
39
  getOpenCodeGoModels,
40
+ OPENCODE_GO_AUTH_URL,
41
+ OPENCODE_GO_ENV_VAR,
41
42
  pingOpenCodeGo,
42
43
  validateOpenCodeGoApiKey,
43
44
  } from "./providers/opencode-go.js";
44
- import { fetchLiveModels, pingProvider, toPersistedModel } from "./providers/live-models.js";
45
45
 
46
46
  // ─── Types ───────────────────────────────────────────────────────────────
47
47
 
@@ -78,11 +78,41 @@ interface RoutingConfigOut {
78
78
  }
79
79
 
80
80
  const ORCHESTRATION_ROLES = [
81
- { key: "explore", label: "Explore", desc: "Read-only analysis", agent: "explore", keywords: ["read", "analyze", "explain", "understand", "find", "search", "look", "show", "what", "how"] },
82
- { key: "plan", label: "Plan", desc: "Architecture and design", agent: "plan", keywords: ["plan", "design", "architect", "spec", "structure", "organize", "strategy", "approach"] },
83
- { key: "code", label: "Code", desc: "Implementation", agent: "code", keywords: ["implement", "create", "build", "refactor", "write", "add", "modify", "update", "generate"] },
84
- { key: "test", label: "Test", desc: "Validation and tests", agent: "test", keywords: ["test", "verify", "validate", "check", "assert", "coverage"] },
85
- { key: "review", label: "Review", desc: "Quality and security review", agent: "review", keywords: ["review", "audit", "quality", "security", "improve", "optimize"] },
81
+ {
82
+ key: "explore",
83
+ label: "Explore",
84
+ desc: "Read-only analysis",
85
+ agent: "explore",
86
+ keywords: ["read", "analyze", "explain", "understand", "find", "search", "look", "show", "what", "how"],
87
+ },
88
+ {
89
+ key: "plan",
90
+ label: "Plan",
91
+ desc: "Architecture and design",
92
+ agent: "plan",
93
+ keywords: ["plan", "design", "architect", "spec", "structure", "organize", "strategy", "approach"],
94
+ },
95
+ {
96
+ key: "code",
97
+ label: "Code",
98
+ desc: "Implementation",
99
+ agent: "code",
100
+ keywords: ["implement", "create", "build", "refactor", "write", "add", "modify", "update", "generate"],
101
+ },
102
+ {
103
+ key: "test",
104
+ label: "Test",
105
+ desc: "Validation and tests",
106
+ agent: "test",
107
+ keywords: ["test", "verify", "validate", "check", "assert", "coverage"],
108
+ },
109
+ {
110
+ key: "review",
111
+ label: "Review",
112
+ desc: "Quality and security review",
113
+ agent: "review",
114
+ keywords: ["review", "audit", "quality", "security", "improve", "optimize"],
115
+ },
86
116
  ] as const;
87
117
 
88
118
  const DEBUG_KEYWORDS = ["fix", "bug", "error", "debug", "crash", "broken", "failing", "issue", "troubleshoot"];
@@ -226,10 +256,7 @@ function maskKeyForDisplay(key: string | undefined): string {
226
256
  return ApiKeyStore.maskKey(key);
227
257
  }
228
258
 
229
- function buildRoutingConfig(
230
- defaultModel: string,
231
- orchestration: Record<string, RouteAssignment>,
232
- ): RoutingConfigOut {
259
+ function buildRoutingConfig(defaultModel: string, orchestration: Record<string, RouteAssignment>): RoutingConfigOut {
233
260
  const routes: RoutingConfigOut["routes"] = {};
234
261
  for (const role of ORCHESTRATION_ROLES) {
235
262
  const a = orchestration[role.key] ?? { preferred: defaultModel, fallback: defaultModel };
@@ -258,7 +285,11 @@ async function writeRoutingConfig(routing: RoutingConfigOut): Promise<string> {
258
285
  const dir = join(homedir(), ".phi", "agent");
259
286
  await mkdir(dir, { recursive: true });
260
287
  const path = join(dir, "routing.json");
261
- await writeFile(path, `${JSON.stringify({ ...routing, $schema: "./routing.schema.json", version: 1 }, null, 2)}\n`, "utf-8");
288
+ await writeFile(
289
+ path,
290
+ `${JSON.stringify({ ...routing, $schema: "./routing.schema.json", version: 1 }, null, 2)}\n`,
291
+ "utf-8",
292
+ );
262
293
  return path;
263
294
  }
264
295
 
@@ -427,10 +458,7 @@ async function configureGenericCloud(
427
458
  ]);
428
459
  if (!authChoice || authChoice === "Cancel") return undefined;
429
460
  if (authChoice.startsWith("OAuth")) {
430
- ui.notify(
431
- `Run \`/login ${provider.id}\` after setup to authenticate via OAuth.`,
432
- "info",
433
- );
461
+ ui.notify(`Run \`/login ${provider.id}\` after setup to authenticate via OAuth.`, "info");
434
462
  store.setKey(provider.id, "$OAUTH", {
435
463
  baseUrl: provider.baseUrl,
436
464
  api: provider.api,
@@ -489,9 +517,8 @@ async function configureGenericCloud(
489
517
  });
490
518
  ui.setStatus("setup-fetch", undefined);
491
519
 
492
- const models = (live.models.length > 0
493
- ? live.models
494
- : provider.staticModels.map((id) => ({ id, name: id, reasoning: true }))
520
+ const models = (
521
+ live.models.length > 0 ? live.models : provider.staticModels.map((id) => ({ id, name: id, reasoning: true }))
495
522
  ).map(toPersistedModel);
496
523
 
497
524
  watcher.muteForWrite("models_json_changed");
@@ -582,12 +609,12 @@ async function configureAssignments(
582
609
  const orchestration: Record<string, RouteAssignment> = {};
583
610
  for (const role of ORCHESTRATION_ROLES) {
584
611
  const preferred =
585
- (await pickModelFromCatalog(ui, `${role.label} - preferred model (${role.desc})`, models)) ??
586
- models[0].ref;
612
+ (await pickModelFromCatalog(ui, `${role.label} - preferred model (${role.desc})`, models)) ?? models[0].ref;
587
613
  const fallbackModels = models.filter((m) => m.ref !== preferred);
588
- const fallback = fallbackModels.length > 0
589
- ? (await pickModelFromCatalog(ui, `${role.label} - fallback model`, fallbackModels)) ?? preferred
590
- : preferred;
614
+ const fallback =
615
+ fallbackModels.length > 0
616
+ ? ((await pickModelFromCatalog(ui, `${role.label} - fallback model`, fallbackModels)) ?? preferred)
617
+ : preferred;
591
618
  orchestration[role.key] = { preferred, fallback };
592
619
  ui.notify(` ${role.label}: ${preferred} / ${fallback}`, "info");
593
620
  }
@@ -626,165 +653,165 @@ export default function setupExtension(pi: ExtensionAPI) {
626
653
  }
627
654
 
628
655
  try {
629
- ui.notify(
630
- "**φ Phi Code Setup Wizard**\n\n" +
631
- "This wizard configures providers and assigns models to **orchestration roles** " +
632
- "(used by `/plan`).\n" +
633
- "The **chat default model is controlled via `/model`** and stays sticky across " +
634
- "prompts — this wizard will never change it.\n\n" +
635
- "Keys are stored in `~/.phi/agent/models.json` (chmod 0600 on Unix). " +
636
- "Edit that file directly later to hot-reload (no restart needed).",
637
- "info",
638
- );
639
-
640
- const available = new Map<string, { source: "key" | "env" | "local"; modelCount: number }>();
641
- const assignments: { default?: string; orchestration: Record<string, RouteAssignment> } = {
642
- orchestration: {},
643
- };
644
-
645
- const refreshAvailable = (): void => {
646
- const catalog = getProviderCatalog();
647
- available.clear();
648
- for (const p of catalog) {
649
- const stored = store.getProvider(p.id);
650
- if (stored?.apiKey) {
651
- const source = stored.apiKey === "$OAUTH" ? "key" : stored.apiKey === "local" ? "local" : "key";
652
- const modelCount = Array.isArray(stored.models) ? stored.models.length : p.staticModels.length;
653
- available.set(p.id, { source, modelCount });
654
- continue;
655
- }
656
- if (!p.local && process.env[p.envVar]) {
657
- available.set(p.id, { source: "env", modelCount: p.staticModels.length });
656
+ ui.notify(
657
+ "**φ Phi Code Setup Wizard**\n\n" +
658
+ "This wizard configures providers and assigns models to **orchestration roles** " +
659
+ "(used by `/plan`).\n" +
660
+ "The **chat default model is controlled via `/model`** and stays sticky across " +
661
+ "prompts — this wizard will never change it.\n\n" +
662
+ "Keys are stored in `~/.phi/agent/models.json` (chmod 0600 on Unix). " +
663
+ "Edit that file directly later to hot-reload (no restart needed).",
664
+ "info",
665
+ );
666
+
667
+ const available = new Map<string, { source: "key" | "env" | "local"; modelCount: number }>();
668
+ const assignments: { default?: string; orchestration: Record<string, RouteAssignment> } = {
669
+ orchestration: {},
670
+ };
671
+
672
+ const refreshAvailable = (): void => {
673
+ const catalog = getProviderCatalog();
674
+ available.clear();
675
+ for (const p of catalog) {
676
+ const stored = store.getProvider(p.id);
677
+ if (stored?.apiKey) {
678
+ const source = stored.apiKey === "$OAUTH" ? "key" : stored.apiKey === "local" ? "local" : "key";
679
+ const modelCount = Array.isArray(stored.models) ? stored.models.length : p.staticModels.length;
680
+ available.set(p.id, { source, modelCount });
681
+ continue;
682
+ }
683
+ if (!p.local && process.env[p.envVar]) {
684
+ available.set(p.id, { source: "env", modelCount: p.staticModels.length });
685
+ }
658
686
  }
659
- }
660
- ui.setWidget("setup-status", buildStatusWidget(store, available, assignments));
661
- };
662
-
663
- refreshAvailable();
664
-
665
- let done = false;
666
- while (!done) {
667
- const catalog = getProviderCatalog();
668
- const choices: string[] = [];
669
- for (const p of catalog) {
670
- const state = available.get(p.id);
671
- const tag = state ? "[ok]" : "[--]";
672
- const modelTag = state ? ` (${state.modelCount} models)` : "";
673
- choices.push(`${tag} ${p.displayName}${modelTag}`);
674
- }
675
- choices.push("---");
676
- choices.push("Assign models to agent roles");
677
- choices.push("Finish and save");
678
- choices.push("Quit without saving");
679
-
680
- const action = await ui.select("Phi Code Setup - choose an action", choices);
681
- if (!action) {
682
- done = true;
683
- break;
684
- }
687
+ ui.setWidget("setup-status", buildStatusWidget(store, available, assignments));
688
+ };
685
689
 
686
- if (action === "Quit without saving") {
687
- const confirm = await ui.confirm("Quit", "Discard all in-memory assignments? (provider keys already saved are kept)");
688
- if (confirm) {
689
- ui.setWidget("setup-status", undefined);
690
- ui.notify("Setup cancelled (saved provider keys are unchanged).", "warning");
691
- return;
690
+ refreshAvailable();
691
+
692
+ let done = false;
693
+ while (!done) {
694
+ const catalog = getProviderCatalog();
695
+ const choices: string[] = [];
696
+ for (const p of catalog) {
697
+ const state = available.get(p.id);
698
+ const tag = state ? "[ok]" : "[--]";
699
+ const modelTag = state ? ` (${state.modelCount} models)` : "";
700
+ choices.push(`${tag} ${p.displayName}${modelTag}`);
701
+ }
702
+ choices.push("---");
703
+ choices.push("Assign models to agent roles");
704
+ choices.push("Finish and save");
705
+ choices.push("Quit without saving");
706
+
707
+ const action = await ui.select("Phi Code Setup - choose an action", choices);
708
+ if (!action) {
709
+ done = true;
710
+ break;
692
711
  }
693
- continue;
694
- }
695
712
 
696
- if (action === "Finish and save") {
697
- if (!assignments.default || Object.keys(assignments.orchestration).length === 0) {
698
- const proceed = await ui.confirm(
699
- "Assignments incomplete",
700
- "You have not assigned all roles. Save current state anyway?",
713
+ if (action === "Quit without saving") {
714
+ const confirm = await ui.confirm(
715
+ "Quit",
716
+ "Discard all in-memory assignments? (provider keys already saved are kept)",
701
717
  );
702
- if (!proceed) continue;
718
+ if (confirm) {
719
+ ui.setWidget("setup-status", undefined);
720
+ ui.notify("Setup cancelled (saved provider keys are unchanged).", "warning");
721
+ return;
722
+ }
723
+ continue;
703
724
  }
704
- done = true;
705
- break;
706
- }
707
725
 
708
- if (action === "Assign models to agent roles") {
709
- const allModels: Array<{ ref: string; display: string }> = [];
710
- const seenRefs = new Set<string>();
711
- for (const p of catalog) {
712
- const stored = store.getProvider(p.id);
713
- if (!stored) continue;
714
- const models = Array.isArray(stored.models) ? stored.models : [];
715
- for (const m of models) {
716
- const id = (m as { id?: string }).id;
717
- if (typeof id !== "string") continue;
718
- // Provider-qualified ref so the same id from different providers stays distinct.
719
- const ref = `${p.id}/${id}`;
720
- if (seenRefs.has(ref)) continue;
721
- seenRefs.add(ref);
722
- allModels.push({ ref, display: `${id} [${p.id}]` });
726
+ if (action === "Finish and save") {
727
+ if (!assignments.default || Object.keys(assignments.orchestration).length === 0) {
728
+ const proceed = await ui.confirm(
729
+ "Assignments incomplete",
730
+ "You have not assigned all roles. Save current state anyway?",
731
+ );
732
+ if (!proceed) continue;
723
733
  }
734
+ done = true;
735
+ break;
736
+ }
737
+
738
+ if (action === "Assign models to agent roles") {
739
+ const allModels: Array<{ ref: string; display: string }> = [];
740
+ const seenRefs = new Set<string>();
741
+ for (const p of catalog) {
742
+ const stored = store.getProvider(p.id);
743
+ if (!stored) continue;
744
+ const models = Array.isArray(stored.models) ? stored.models : [];
745
+ for (const m of models) {
746
+ const id = (m as { id?: string }).id;
747
+ if (typeof id !== "string") continue;
748
+ // Provider-qualified ref so the same id from different providers stays distinct.
749
+ const ref = `${p.id}/${id}`;
750
+ if (seenRefs.has(ref)) continue;
751
+ seenRefs.add(ref);
752
+ allModels.push({ ref, display: `${id} [${p.id}]` });
753
+ }
754
+ }
755
+ const { defaultModel, orchestration } = await configureAssignments(ui, allModels);
756
+ assignments.default = defaultModel;
757
+ assignments.orchestration = orchestration;
758
+ refreshAvailable();
759
+ continue;
724
760
  }
725
- const { defaultModel, orchestration } = await configureAssignments(ui, allModels);
726
- assignments.default = defaultModel;
727
- assignments.orchestration = orchestration;
728
- refreshAvailable();
729
- continue;
730
- }
731
761
 
732
- if (action === "---") continue;
733
-
734
- const providerIndex = choices.indexOf(action);
735
- if (providerIndex < 0 || providerIndex >= catalog.length) continue;
736
- const provider = catalog[providerIndex];
737
-
738
- let result: { providerId: string; modelCount: number } | undefined;
739
- try {
740
- if (provider.id === "alibaba-codingplan") {
741
- result = await configureAlibaba(ui, store);
742
- } else if (provider.id === "opencode-go") {
743
- result = await configureOpenCodeGo(ui, store);
744
- } else if (provider.local) {
745
- result = await configureLocal(ui, store, provider);
746
- } else {
747
- result = await configureGenericCloud(ui, store, provider);
762
+ if (action === "---") continue;
763
+
764
+ const providerIndex = choices.indexOf(action);
765
+ if (providerIndex < 0 || providerIndex >= catalog.length) continue;
766
+ const provider = catalog[providerIndex];
767
+
768
+ let result: { providerId: string; modelCount: number } | undefined;
769
+ try {
770
+ if (provider.id === "alibaba-codingplan") {
771
+ result = await configureAlibaba(ui, store);
772
+ } else if (provider.id === "opencode-go") {
773
+ result = await configureOpenCodeGo(ui, store);
774
+ } else if (provider.local) {
775
+ result = await configureLocal(ui, store, provider);
776
+ } else {
777
+ result = await configureGenericCloud(ui, store, provider);
778
+ }
779
+ } catch (err) {
780
+ ui.notify(
781
+ `Provider configuration failed: ${err instanceof Error ? err.message : String(err)}`,
782
+ "error",
783
+ );
748
784
  }
749
- } catch (err) {
750
- ui.notify(
751
- `Provider configuration failed: ${err instanceof Error ? err.message : String(err)}`,
752
- "error",
753
- );
754
- }
755
785
 
756
- if (result) refreshAvailable();
757
- }
786
+ if (result) refreshAvailable();
787
+ }
758
788
 
759
- // Save routing if assignments were made
760
- if (assignments.default) {
761
- try {
762
- const routing = buildRoutingConfig(assignments.default, assignments.orchestration);
763
- const routingPath = await writeRoutingConfig(routing);
764
- ui.notify(`Routing config written to \`${routingPath}\`.`, "info");
765
- } catch (err) {
766
- ui.notify(`Failed to write routing.json: ${err}`, "error");
789
+ // Save routing if assignments were made
790
+ if (assignments.default) {
791
+ try {
792
+ const routing = buildRoutingConfig(assignments.default, assignments.orchestration);
793
+ const routingPath = await writeRoutingConfig(routing);
794
+ ui.notify(`Routing config written to \`${routingPath}\`.`, "info");
795
+ } catch (err) {
796
+ ui.notify(`Failed to write routing.json: ${err}`, "error");
797
+ }
767
798
  }
768
- }
769
799
 
770
- ui.setWidget("setup-status", undefined);
771
- ui.notify(
772
- "**Setup complete.**\n\n" +
773
- "Next steps:\n" +
774
- " - `/keys` to list/manage saved keys\n" +
775
- " - `/models refresh` to re-fetch the catalog from each provider's API\n" +
776
- " - `/routing` to inspect routing\n" +
777
- " - `/agents` to list sub-agents\n" +
778
- " - `/skills` to list skills\n" +
779
- " - Edit `~/.phi/agent/models.json` or `routing.json` directly: hot-reload kicks in",
780
- "info",
781
- );
782
- } catch (err) {
783
800
  ui.setWidget("setup-status", undefined);
784
801
  ui.notify(
785
- `Setup wizard error: ${err instanceof Error ? err.message : String(err)}`,
786
- "error",
802
+ "**Setup complete.**\n\n" +
803
+ "Next steps:\n" +
804
+ " - `/keys` to list/manage saved keys\n" +
805
+ " - `/models refresh` to re-fetch the catalog from each provider's API\n" +
806
+ " - `/routing` to inspect routing\n" +
807
+ " - `/agents` to list sub-agents\n" +
808
+ " - `/skills` to list skills\n" +
809
+ " - Edit `~/.phi/agent/models.json` or `routing.json` directly: hot-reload kicks in",
810
+ "info",
787
811
  );
812
+ } catch (err) {
813
+ ui.setWidget("setup-status", undefined);
814
+ ui.notify(`Setup wizard error: ${err instanceof Error ? err.message : String(err)}`, "error");
788
815
  }
789
816
  },
790
817
  });
@@ -20,22 +20,19 @@
20
20
  * - /skills command to list available skills
21
21
  */
22
22
 
23
- import type { ExtensionAPI } from "phi-code";
24
- import { SkillScanner, SkillLoader } from "sigma-skills";
25
- import type { SkillsConfig } from "sigma-skills";
26
23
  import { existsSync } from "node:fs";
27
- import { join } from "node:path";
28
24
  import { homedir } from "node:os";
25
+ import { join } from "node:path";
26
+ import type { ExtensionAPI } from "phi-code";
27
+ import type { SkillsConfig } from "sigma-skills";
28
+ import { SkillLoader, SkillScanner } from "sigma-skills";
29
29
 
30
30
  export default function skillLoaderExtension(pi: ExtensionAPI) {
31
- // Bundled skills live inside the package in the repo layout; postinstall
32
- // copies them to ~/.phi/agent/skills (== globalDir) in the installed layout,
33
- // where the relative hop from ~/.phi/agent/extensions/ would point at the
34
- // non-existent ~/skills. Probe both (the scanner dedupes by skill name).
35
- const bundledCandidates = [
36
- join(__dirname, "..", "..", "..", "skills"),
37
- join(homedir(), ".phi", "agent", "skills"),
38
- ];
31
+ // Bundled skills live at <package>/skills in the repo layout (two hops up
32
+ // from extensions/phi/); postinstall copies them to ~/.phi/agent/skills
33
+ // (== globalDir) in the installed layout. Probe both (the scanner dedupes
34
+ // by skill name).
35
+ const bundledCandidates = [join(__dirname, "..", "..", "skills"), join(homedir(), ".phi", "agent", "skills")];
39
36
  const config: SkillsConfig = {
40
37
  globalDir: join(homedir(), ".phi", "agent", "skills"),
41
38
  projectDir: join(process.cwd(), ".phi", "skills"),
@@ -103,10 +100,10 @@ export default function skillLoaderExtension(pi: ExtensionAPI) {
103
100
  if (skills.length === 0) {
104
101
  ctx.ui.notify(
105
102
  "No skills found. Create skill directories with SKILL.md files in:\n" +
106
- `- \`${config.projectDir}\` (project-local)\n` +
107
- `- \`${config.globalDir}\` (global)\n` +
108
- "Or install bundled skills via `/phi-init`.",
109
- "info"
103
+ `- \`${config.projectDir}\` (project-local)\n` +
104
+ `- \`${config.globalDir}\` (global)\n` +
105
+ "Or install bundled skills via `/phi-init`.",
106
+ "info",
110
107
  );
111
108
  return;
112
109
  }
@@ -122,13 +119,13 @@ export default function skillLoaderExtension(pi: ExtensionAPI) {
122
119
  // Show specific skill
123
120
  const content = loader.getSkillContext(query);
124
121
  if (content) {
125
- const skill = loader.listSkills().find(s => s.name === query);
122
+ const skill = loader.listSkills().find((s) => s.name === query);
126
123
  ctx.ui.notify(
127
124
  `**📚 Skill: ${query}**\n\n` +
128
- `Path: \`${skill?.path || "unknown"}\`\n` +
129
- `Keywords: ${skill?.keywords.slice(0, 10).join(", ") || "none"}\n\n` +
130
- `---\n\n${content.slice(0, 2000)}${content.length > 2000 ? "\n\n... (truncated, use `read` for full content)" : ""}`,
131
- "info"
125
+ `Path: \`${skill?.path || "unknown"}\`\n` +
126
+ `Keywords: ${skill?.keywords.slice(0, 10).join(", ") || "none"}\n\n` +
127
+ `---\n\n${content.slice(0, 2000)}${content.length > 2000 ? "\n\n... (truncated, use `read` for full content)" : ""}`,
128
+ "info",
132
129
  );
133
130
  } else {
134
131
  ctx.ui.notify(`Skill "${query}" not found. Use \`/skills\` to list available skills.`, "warning");
@@ -8,12 +8,12 @@
8
8
  * Command: /routing — show config, enable/disable, test, reload
9
9
  */
10
10
 
11
+ import { mkdir, writeFile } from "node:fs/promises";
12
+ import { homedir } from "node:os";
13
+ import { join } from "node:path";
11
14
  import type { ExtensionAPI } from "phi-code";
15
+ import type { RoutingConfig } from "sigma-agents";
12
16
  import { SmartRouter } from "sigma-agents";
13
- import type { RoutingConfig, TaskCategory } from "sigma-agents";
14
- import { readFile, mkdir, writeFile, access } from "node:fs/promises";
15
- import { join } from "node:path";
16
- import { homedir } from "node:os";
17
17
 
18
18
  // ─── Extension Config ────────────────────────────────────────────────────
19
19
 
@@ -40,7 +40,7 @@ export default function smartRouterExtension(pi: ExtensionAPI) {
40
40
  const configPath = join(configDir, "routing.json");
41
41
 
42
42
  let router = new SmartRouter(SmartRouter.defaultConfig());
43
- let extConfig: ExtensionConfig = {
43
+ const extConfig: ExtensionConfig = {
44
44
  enabled: true,
45
45
  autoSwitch: false,
46
46
  notifyOnRecommendation: false,
@@ -70,7 +70,7 @@ export default function smartRouterExtension(pi: ExtensionAPI) {
70
70
  * If the preferred model exists in the registry, use it.
71
71
  * Otherwise, fall back to the current model.
72
72
  */
73
- function resolveModel(preferredModel: string, ctx: any): string {
73
+ function _resolveModel(preferredModel: string, ctx: any): string {
74
74
  try {
75
75
  const available = ctx.modelRegistry?.getAvailable?.() || [];
76
76
  if (available.some((m: any) => m.id === preferredModel)) {