fix(FN-1952): use fusion storage for pi config
This commit is contained in:
7
packages/cli/src/commands/auth-paths.ts
Normal file
7
packages/cli/src/commands/auth-paths.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { homedir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
||||
export function getFusionAuthPath(home = process.env.HOME || process.env.USERPROFILE || homedir()): string {
|
||||
return join(home, ".fusion", "agent", "auth.json");
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ import {
|
||||
import { promptForPort } from "./port-prompt.js";
|
||||
import { createReadOnlyProviderSettingsView } from "./provider-settings.js";
|
||||
import { wrapAuthStorageWithApiKeyProviders } from "./provider-auth.js";
|
||||
import { getFusionAuthPath } from "./auth-paths.js";
|
||||
|
||||
const DIAGNOSTIC_INTERVAL_MS = 30 * 60 * 1000; // 30 minutes
|
||||
let daemonStartTime = 0;
|
||||
@@ -324,7 +325,7 @@ export async function runDaemon(opts: DaemonOptions = {}) {
|
||||
const missionExecutionLoop = cwdEngine.getRuntime().getMissionExecutionLoop();
|
||||
const automationStore = cwdEngine.getAutomationStore();
|
||||
|
||||
const authStorage = AuthStorage.create();
|
||||
const authStorage = AuthStorage.create(getFusionAuthPath());
|
||||
const modelRegistry = new ModelRegistry(authStorage);
|
||||
|
||||
// PackageManager may be used for skills adapter even if extension loading fails
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
import { promptForPort } from "./port-prompt.js";
|
||||
import { createReadOnlyProviderSettingsView, createProjectSettingsPersistence } from "./provider-settings.js";
|
||||
import { wrapAuthStorageWithApiKeyProviders } from "./provider-auth.js";
|
||||
import { getFusionAuthPath } from "./auth-paths.js";
|
||||
|
||||
// Re-export for backward compatibility with tests
|
||||
export { promptForPort };
|
||||
@@ -357,11 +358,11 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
|
||||
});
|
||||
|
||||
// ── Auth & model wiring ────────────────────────────────────────────
|
||||
// AuthStorage manages OAuth/API-key credentials (stored in ~/.pi/agent/auth.json).
|
||||
// AuthStorage manages OAuth/API-key credentials (stored in ~/.fusion/agent/auth.json).
|
||||
// ModelRegistry discovers available models from configured providers.
|
||||
// Passing these to createServer enables the dashboard's Authentication
|
||||
// tab (login/logout) and Model selector.
|
||||
const authStorage = AuthStorage.create();
|
||||
const authStorage = AuthStorage.create(getFusionAuthPath());
|
||||
const modelRegistry = new ModelRegistry(authStorage);
|
||||
|
||||
// PackageManager may be used for skills adapter even if extension loading fails
|
||||
|
||||
@@ -124,7 +124,7 @@ describe("init command", () => {
|
||||
expect(existsSync(join(fusionDir, "fusion.db"))).toBe(true);
|
||||
});
|
||||
|
||||
it("should add .fusion to .gitignore when it doesn't exist", async () => {
|
||||
it("should add local storage directories to .gitignore when it doesn't exist", async () => {
|
||||
const gitignorePath = join(tempProjectDir, ".gitignore");
|
||||
expect(existsSync(gitignorePath)).toBe(false);
|
||||
|
||||
@@ -133,9 +133,10 @@ describe("init command", () => {
|
||||
expect(existsSync(gitignorePath)).toBe(true);
|
||||
const content = readFileSync(gitignorePath, "utf-8");
|
||||
expect(content).toContain(".fusion");
|
||||
expect(content).toContain(".pi");
|
||||
});
|
||||
|
||||
it("should append .fusion to existing .gitignore", async () => {
|
||||
it("should append local storage directories to existing .gitignore", async () => {
|
||||
const gitignorePath = join(tempProjectDir, ".gitignore");
|
||||
writeFileSync(gitignorePath, "node_modules\ndist\n");
|
||||
|
||||
@@ -145,9 +146,23 @@ describe("init command", () => {
|
||||
expect(content).toContain("node_modules");
|
||||
expect(content).toContain("dist");
|
||||
expect(content).toContain(".fusion");
|
||||
expect(content).toContain(".pi");
|
||||
});
|
||||
|
||||
it("should not duplicate .fusion in .gitignore (idempotent)", async () => {
|
||||
it("should not duplicate local storage directories in .gitignore (idempotent)", async () => {
|
||||
const gitignorePath = join(tempProjectDir, ".gitignore");
|
||||
writeFileSync(gitignorePath, "node_modules\n.fusion\n.pi\n");
|
||||
|
||||
await runInit({ path: tempProjectDir });
|
||||
|
||||
const content = readFileSync(gitignorePath, "utf-8");
|
||||
const fusionMatches = content.match(/\.fusion/g);
|
||||
const piMatches = content.match(/\.pi/g);
|
||||
expect(fusionMatches).toHaveLength(1);
|
||||
expect(piMatches).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("should add .pi when .fusion is already ignored", async () => {
|
||||
const gitignorePath = join(tempProjectDir, ".gitignore");
|
||||
writeFileSync(gitignorePath, "node_modules\n.fusion\n");
|
||||
|
||||
@@ -155,6 +170,8 @@ describe("init command", () => {
|
||||
|
||||
const content = readFileSync(gitignorePath, "utf-8");
|
||||
const fusionMatches = content.match(/\.fusion/g);
|
||||
const piMatches = content.match(/\.pi/g);
|
||||
expect(fusionMatches).toHaveLength(1);
|
||||
expect(piMatches).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -75,8 +75,8 @@ export async function runInit(options: InitOptions = {}): Promise<void> {
|
||||
console.log(` ✓ Created .fusion/ directory`);
|
||||
}
|
||||
|
||||
// Add .fusion to .gitignore
|
||||
await addFusionToGitignore(cwd);
|
||||
// Add local Fusion/Pi storage directories to .gitignore
|
||||
await addLocalStorageToGitignore(cwd);
|
||||
|
||||
// Create fusion.db (empty SQLite file)
|
||||
if (!existsSync(dbPath)) {
|
||||
@@ -164,10 +164,10 @@ async function detectProjectName(dir: string): Promise<string> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add .fusion to .gitignore if not already present.
|
||||
* Idempotent: only adds if not already in the file.
|
||||
* Add local Fusion/Pi storage directories to .gitignore if not already present.
|
||||
* Idempotent: only adds missing entries.
|
||||
*/
|
||||
async function addFusionToGitignore(cwd: string): Promise<void> {
|
||||
async function addLocalStorageToGitignore(cwd: string): Promise<void> {
|
||||
const gitignorePath = join(cwd, ".gitignore");
|
||||
|
||||
let content = "";
|
||||
@@ -179,17 +179,19 @@ async function addFusionToGitignore(cwd: string): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
// Check if .fusion is already in the file
|
||||
const lines = content.split(/\r?\n/);
|
||||
if (lines.some((line) => line.trim() === ".fusion")) {
|
||||
return; // Already present, skip
|
||||
const existingEntries = new Set(lines.map((line) => line.trim()));
|
||||
const missingEntries = [".fusion", ".pi"].filter((entry) => !existingEntries.has(entry));
|
||||
|
||||
if (missingEntries.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Append .fusion to .gitignore
|
||||
const newContent = content.endsWith("\n") ? content + ".fusion\n" : content + "\n.fusion\n";
|
||||
const prefix = content.length === 0 || content.endsWith("\n") ? "" : "\n";
|
||||
const newContent = `${content}${prefix}${missingEntries.join("\n")}\n`;
|
||||
try {
|
||||
writeFileSync(gitignorePath, newContent);
|
||||
console.log(` ✓ Added .fusion to .gitignore`);
|
||||
console.log(` ✓ Added ${missingEntries.join(" and ")} to .gitignore`);
|
||||
} catch {
|
||||
// Best-effort: don't fail init if we can't write to .gitignore
|
||||
console.log(` ⚠ Could not update .gitignore (best-effort)`);
|
||||
|
||||
@@ -39,6 +39,7 @@ import {
|
||||
import { promptForPort } from "./port-prompt.js";
|
||||
import { createReadOnlyProviderSettingsView, createProjectSettingsPersistence } from "./provider-settings.js";
|
||||
import { wrapAuthStorageWithApiKeyProviders } from "./provider-auth.js";
|
||||
import { getFusionAuthPath } from "./auth-paths.js";
|
||||
|
||||
const DIAGNOSTIC_INTERVAL_MS = 30 * 60 * 1000; // 30 minutes
|
||||
let diagnosticIntervalHandle: ReturnType<typeof setInterval> | null = null;
|
||||
@@ -383,7 +384,7 @@ export async function runServe(
|
||||
const missionExecutionLoop = cwdEngine.getRuntime().getMissionExecutionLoop();
|
||||
const automationStore = cwdEngine.getAutomationStore();
|
||||
|
||||
const authStorage = AuthStorage.create();
|
||||
const authStorage = AuthStorage.create(getFusionAuthPath());
|
||||
const modelRegistry = new ModelRegistry(authStorage);
|
||||
|
||||
// PackageManager may be used for skills adapter even if extension loading fails
|
||||
|
||||
@@ -204,6 +204,6 @@ export async function runSkillsInstall(
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Installed skill from ${source}. Skills are discovered from .pi/skills/ and .agents/skills/.`,
|
||||
`Installed skill from ${source}. Skills are discovered from .fusion/skills/, legacy .pi/skills/, and .agents/skills/.`,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user