fraim-framework 2.0.167 → 2.0.169
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/dist/src/ai-hub/catalog.js +28 -14
- package/dist/src/ai-hub/server.js +34 -406
- package/dist/src/cli/commands/init-project.js +1 -98
- package/dist/src/cli/commands/manager.js +40 -0
- package/dist/src/cli/commands/sync.js +17 -21
- package/dist/src/cli/fraim.js +2 -0
- package/dist/src/cli/utils/github-workflow-sync.js +12 -146
- package/dist/src/cli/utils/manager-pack-sync.js +188 -0
- package/dist/src/cli/utils/manager-publish.js +76 -0
- package/dist/src/cli/utils/user-config.js +20 -0
- package/dist/src/core/fraim-config-schema.generated.js +85 -10
- package/dist/src/core/manager-pack.js +26 -0
- package/dist/src/first-run/install-state.js +1 -0
- package/dist/src/first-run/server.js +9 -0
- package/dist/src/first-run/session-service.js +117 -23
- package/dist/src/first-run/types.js +2 -5
- package/dist/src/local-mcp-server/learning-context-builder.js +45 -8
- package/dist/src/local-mcp-server/stdio-server.js +28 -0
- package/package.json +1 -2
- package/public/ai-hub/index.html +0 -81
- package/public/ai-hub/script.js +3 -219
- package/public/ai-hub/styles.css +8 -36
- package/public/first-run/index.html +1 -1
- package/public/first-run/script.js +459 -530
- package/public/first-run/styles.css +288 -73
- package/dist/src/config/ai-manager-hiring.js +0 -121
- package/dist/src/config/compat.js +0 -16
- package/dist/src/config/feature-flags.js +0 -25
- package/dist/src/config/persona-capability-bundles.js +0 -273
- package/dist/src/config/persona-hiring.js +0 -270
- package/dist/src/config/portfolio-slug-overrides.js +0 -17
- package/dist/src/config/pricing.js +0 -37
- package/dist/src/config/stripe.js +0 -43
|
@@ -3,13 +3,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.initProjectCommand = exports.runInitProject =
|
|
6
|
+
exports.initProjectCommand = exports.runInitProject = void 0;
|
|
7
7
|
const commander_1 = require("commander");
|
|
8
8
|
const fs_1 = __importDefault(require("fs"));
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
10
10
|
const os_1 = __importDefault(require("os"));
|
|
11
11
|
const chalk_1 = __importDefault(require("chalk"));
|
|
12
|
-
const child_process_1 = require("child_process");
|
|
13
12
|
const sync_1 = require("./sync");
|
|
14
13
|
const platform_detection_1 = require("../utils/platform-detection");
|
|
15
14
|
const ide_detector_1 = require("../setup/ide-detector");
|
|
@@ -19,8 +18,6 @@ const fraim_gitignore_1 = require("../utils/fraim-gitignore");
|
|
|
19
18
|
const agent_adapters_1 = require("../utils/agent-adapters");
|
|
20
19
|
const project_fraim_paths_1 = require("../../core/utils/project-fraim-paths");
|
|
21
20
|
const project_bootstrap_1 = require("../utils/project-bootstrap");
|
|
22
|
-
const config_loader_1 = require("../../core/config-loader");
|
|
23
|
-
const github_workflow_sync_1 = require("../utils/github-workflow-sync");
|
|
24
21
|
const checkGlobalSetup = () => {
|
|
25
22
|
const fraimUserDir = process.env.FRAIM_USER_DIR || path_1.default.join(os_1.default.homedir(), '.fraim');
|
|
26
23
|
const globalConfigPath = path_1.default.join(fraimUserDir, 'config.json');
|
|
@@ -40,93 +37,6 @@ const checkGlobalSetup = () => {
|
|
|
40
37
|
return { exists: true, mode: 'integrated', tokens: {} };
|
|
41
38
|
}
|
|
42
39
|
};
|
|
43
|
-
// Robust path resolution utility - walks up directory tree to find target
|
|
44
|
-
const findProjectFile = (filename) => {
|
|
45
|
-
let currentDir = __dirname;
|
|
46
|
-
// Walk up the directory tree to find the target file/directory
|
|
47
|
-
for (let i = 0; i < 10; i++) { // Limit to prevent infinite loop
|
|
48
|
-
const targetPath = path_1.default.join(currentDir, filename);
|
|
49
|
-
if (fs_1.default.existsSync(targetPath)) {
|
|
50
|
-
return targetPath;
|
|
51
|
-
}
|
|
52
|
-
const parentDir = path_1.default.dirname(currentDir);
|
|
53
|
-
if (parentDir === currentDir)
|
|
54
|
-
break; // Reached root
|
|
55
|
-
currentDir = parentDir;
|
|
56
|
-
}
|
|
57
|
-
// Fallback: try from process.cwd()
|
|
58
|
-
const cwdTarget = path_1.default.join(process.cwd(), filename);
|
|
59
|
-
if (fs_1.default.existsSync(cwdTarget)) {
|
|
60
|
-
return cwdTarget;
|
|
61
|
-
}
|
|
62
|
-
// Last resort: use relative path from __dirname
|
|
63
|
-
return path_1.default.join(__dirname, '..', '..', '..', filename);
|
|
64
|
-
};
|
|
65
|
-
exports.findProjectFile = findProjectFile;
|
|
66
|
-
;
|
|
67
|
-
const installGitHubWorkflows = (projectRoot) => {
|
|
68
|
-
const registryDir = (0, exports.findProjectFile)('registry');
|
|
69
|
-
const sourceDir = path_1.default.join(registryDir, 'github', 'workflows');
|
|
70
|
-
if (!fs_1.default.existsSync(sourceDir)) {
|
|
71
|
-
console.log(chalk_1.default.yellow(`Warning: GitHub workflows source directory not found: ${sourceDir}`));
|
|
72
|
-
console.log(chalk_1.default.gray(`Registry directory: ${registryDir}`));
|
|
73
|
-
console.log(chalk_1.default.gray(`Current __dirname: ${__dirname}`));
|
|
74
|
-
console.log(chalk_1.default.gray(`Process cwd: ${process.cwd()}`));
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
const config = fs_1.default.existsSync((0, project_fraim_paths_1.getWorkspaceConfigPath)(projectRoot))
|
|
78
|
-
? (0, config_loader_1.loadFraimConfig)((0, project_fraim_paths_1.getWorkspaceConfigPath)(projectRoot))
|
|
79
|
-
: null;
|
|
80
|
-
if (!(0, github_workflow_sync_1.isGitHubWorkflowAutomationEnabled)(config)) {
|
|
81
|
-
console.log(chalk_1.default.gray('GitHub workflow automation disabled or not configured; skipping managed workflow install.'));
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
const bundle = (0, github_workflow_sync_1.loadLocalGitHubWorkflowBundle)(registryDir);
|
|
85
|
-
const result = (0, github_workflow_sync_1.reconcileGitHubWorkflowAssets)({
|
|
86
|
-
projectRoot,
|
|
87
|
-
files: bundle,
|
|
88
|
-
config
|
|
89
|
-
});
|
|
90
|
-
for (const line of (0, github_workflow_sync_1.formatGitHubWorkflowSyncSummary)(result)) {
|
|
91
|
-
console.log(chalk_1.default.green(line));
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
const createGitHubLabels = (projectRoot) => {
|
|
95
|
-
try {
|
|
96
|
-
(0, child_process_1.execSync)('gh --version', { stdio: 'ignore' });
|
|
97
|
-
}
|
|
98
|
-
catch {
|
|
99
|
-
console.log(chalk_1.default.yellow('GitHub CLI (gh) not found. Skipping label creation.'));
|
|
100
|
-
console.log(chalk_1.default.gray('Install gh CLI to enable automatic label creation: https://cli.github.com/'));
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
const labelsPath = (0, exports.findProjectFile)('labels.json');
|
|
104
|
-
if (!fs_1.default.existsSync(labelsPath)) {
|
|
105
|
-
console.log(chalk_1.default.yellow('labels.json not found. Skipping label creation.'));
|
|
106
|
-
console.log(chalk_1.default.gray(`Searched from: ${__dirname}`));
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
try {
|
|
110
|
-
const labels = JSON.parse(fs_1.default.readFileSync(labelsPath, 'utf8'));
|
|
111
|
-
labels.forEach((label) => {
|
|
112
|
-
try {
|
|
113
|
-
(0, child_process_1.execSync)(`gh label create "${label.name}" --color "${label.color}" --description "${label.description}"`, { cwd: projectRoot, stdio: 'ignore' });
|
|
114
|
-
console.log(chalk_1.default.green(`Created label: ${label.name}`));
|
|
115
|
-
}
|
|
116
|
-
catch (error) {
|
|
117
|
-
if (error.message && error.message.includes('already exists')) {
|
|
118
|
-
console.log(chalk_1.default.gray(`Label already exists: ${label.name}`));
|
|
119
|
-
}
|
|
120
|
-
else {
|
|
121
|
-
console.log(chalk_1.default.yellow(`Could not create label ${label.name}: ${error.message}`));
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
catch (error) {
|
|
127
|
-
console.log(chalk_1.default.yellow(`Error reading labels.json: ${error.message}`));
|
|
128
|
-
}
|
|
129
|
-
};
|
|
130
40
|
function formatPlatformLabel(provider) {
|
|
131
41
|
switch (provider) {
|
|
132
42
|
case 'ado':
|
|
@@ -261,13 +171,6 @@ const runInitProject = async (options = {}) => {
|
|
|
261
171
|
if (ignoreUpdate.gitignoreUpdated && !isMinimalConversationMode(result.mode)) {
|
|
262
172
|
console.log(chalk_1.default.green('Removed legacy FRAIM sync block from .gitignore'));
|
|
263
173
|
}
|
|
264
|
-
const detection = (0, platform_detection_1.detectPlatformFromGit)();
|
|
265
|
-
if (detection.provider === 'github' && !isMinimalConversationMode(result.mode)) {
|
|
266
|
-
console.log(chalk_1.default.blue('\nSetting up GitHub labels...'));
|
|
267
|
-
installGitHubWorkflows(projectRoot);
|
|
268
|
-
createGitHubLabels(projectRoot);
|
|
269
|
-
result.repositoryDetected = true;
|
|
270
|
-
}
|
|
271
174
|
if (!process.env.FRAIM_SKIP_SYNC) {
|
|
272
175
|
await (0, sync_1.runSync)({ projectRoot, failHard });
|
|
273
176
|
result.syncPerformed = true;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.managerCommand = void 0;
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
10
|
+
const manager_publish_1 = require("../utils/manager-publish");
|
|
11
|
+
exports.managerCommand = new commander_1.Command('manager')
|
|
12
|
+
.description('Manage your portable personal manager context');
|
|
13
|
+
exports.managerCommand
|
|
14
|
+
.command('publish')
|
|
15
|
+
.description('Publish manager context/rules/learnings files to the configured manager backend')
|
|
16
|
+
.argument('<files...>', 'Local files to publish: manager_context.md, manager_rules.md, and/or personal learning files')
|
|
17
|
+
.action(async (files) => {
|
|
18
|
+
try {
|
|
19
|
+
const artifacts = files.map((filePath) => {
|
|
20
|
+
if (!fs_1.default.existsSync(filePath))
|
|
21
|
+
throw new Error(`File not found: ${filePath}`);
|
|
22
|
+
return { relativePath: (0, manager_publish_1.managerPackRelativePathFor)(filePath), content: fs_1.default.readFileSync(filePath, 'utf8') };
|
|
23
|
+
});
|
|
24
|
+
const result = await (0, manager_publish_1.publishManagerArtifacts)(artifacts);
|
|
25
|
+
if (result.backend === 'fraim-cloud') {
|
|
26
|
+
console.log(chalk_1.default.green(`Published ${artifacts.length} manager artifact(s) to your FRAIM account (version ${result.version}).`));
|
|
27
|
+
console.log(chalk_1.default.gray('Run "fraim sync" on each machine to pull the update.'));
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
console.log(chalk_1.default.green(`Pushed ${artifacts.length} manager artifact(s) to branch "${result.branch}" in your manager repo.`));
|
|
31
|
+
if (result.prUrl)
|
|
32
|
+
console.log(chalk_1.default.cyan(`Open the pull request: ${result.prUrl}`));
|
|
33
|
+
console.log(chalk_1.default.gray('After it merges, run "fraim sync" to pull the update.'));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
console.error(chalk_1.default.red(`manager publish failed: ${error.message}`));
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
@@ -48,7 +48,6 @@ const git_utils_1 = require("../../core/utils/git-utils");
|
|
|
48
48
|
const agent_adapters_1 = require("../utils/agent-adapters");
|
|
49
49
|
const fraim_gitignore_1 = require("../utils/fraim-gitignore");
|
|
50
50
|
const project_fraim_paths_1 = require("../../core/utils/project-fraim-paths");
|
|
51
|
-
const github_workflow_sync_1 = require("../utils/github-workflow-sync");
|
|
52
51
|
function resolveExplicitLocalSyncUrl() {
|
|
53
52
|
const candidates = [
|
|
54
53
|
process.env.FRAIM_TEST_SERVER_URL,
|
|
@@ -180,6 +179,21 @@ const runSync = async (options) => {
|
|
|
180
179
|
}
|
|
181
180
|
}
|
|
182
181
|
};
|
|
182
|
+
const refreshManagerCache = async (remoteUrl, apiKey) => {
|
|
183
|
+
if (process.env.TEST_MODE === 'true')
|
|
184
|
+
return;
|
|
185
|
+
const { syncManagerCache } = await Promise.resolve().then(() => __importStar(require('../utils/manager-pack-sync')));
|
|
186
|
+
const outcome = await syncManagerCache({ remoteUrl, apiKey });
|
|
187
|
+
if (outcome.status === 'synced') {
|
|
188
|
+
console.log(chalk_1.default.green(`Manager context synced (${outcome.metadata.backend}, version ${outcome.metadata.version.slice(0, 12)})`));
|
|
189
|
+
}
|
|
190
|
+
else if (outcome.status === 'stale') {
|
|
191
|
+
console.log(chalk_1.default.yellow(`Manager source unreachable. Using cached manager context from ${Math.round(outcome.ageHours)}h ago. Will refresh when reachable.`));
|
|
192
|
+
}
|
|
193
|
+
else if (outcome.status === 'absent') {
|
|
194
|
+
console.log(chalk_1.default.yellow(`Manager context not synced: ${outcome.error}`));
|
|
195
|
+
}
|
|
196
|
+
};
|
|
183
197
|
const isNpx = process.env.npm_config_prefix === undefined || process.env.npm_lifecycle_event === 'npx';
|
|
184
198
|
const isGlobal = !isNpx && (process.env.npm_config_global === 'true' || process.env.npm_config_prefix);
|
|
185
199
|
if (isGlobal && !options.skipUpdates) {
|
|
@@ -214,17 +228,8 @@ const runSync = async (options) => {
|
|
|
214
228
|
if (adapterUpdates.length > 0) {
|
|
215
229
|
console.log(chalk_1.default.green(`Updated FRAIM agent adapter files: ${adapterUpdates.join(', ')}`));
|
|
216
230
|
}
|
|
217
|
-
if (config.repository?.provider === 'github' && (0, github_workflow_sync_1.isGitHubWorkflowAutomationEnabled)(config)) {
|
|
218
|
-
const workflowBundle = await (0, github_workflow_sync_1.fetchGitHubWorkflowBundle)({
|
|
219
|
-
remoteUrl: localUrl,
|
|
220
|
-
apiKey: 'local-dev'
|
|
221
|
-
});
|
|
222
|
-
const workflowResult = (0, github_workflow_sync_1.reconcileGitHubWorkflowAssets)({ projectRoot, files: workflowBundle, config });
|
|
223
|
-
for (const line of (0, github_workflow_sync_1.formatGitHubWorkflowSyncSummary)(workflowResult)) {
|
|
224
|
-
console.log(chalk_1.default.green(line));
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
231
|
await refreshOrgCache(localUrl, 'local-dev');
|
|
232
|
+
await refreshManagerCache(localUrl, 'local-dev');
|
|
228
233
|
return;
|
|
229
234
|
}
|
|
230
235
|
console.error(chalk_1.default.red(`Local sync failed: ${result.error}`));
|
|
@@ -264,21 +269,12 @@ const runSync = async (options) => {
|
|
|
264
269
|
removeLegacyVersionFromConfig(fraimDir);
|
|
265
270
|
writeSyncMetadata(fraimDir, 'remote', config.remoteUrl || process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me');
|
|
266
271
|
refreshLocalIgnoreConfig();
|
|
267
|
-
if (config.repository?.provider === 'github' && (0, github_workflow_sync_1.isGitHubWorkflowAutomationEnabled)(config)) {
|
|
268
|
-
const workflowBundle = await (0, github_workflow_sync_1.fetchGitHubWorkflowBundle)({
|
|
269
|
-
remoteUrl: config.remoteUrl || process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me',
|
|
270
|
-
apiKey
|
|
271
|
-
});
|
|
272
|
-
const workflowResult = (0, github_workflow_sync_1.reconcileGitHubWorkflowAssets)({ projectRoot, files: workflowBundle, config });
|
|
273
|
-
for (const line of (0, github_workflow_sync_1.formatGitHubWorkflowSyncSummary)(workflowResult)) {
|
|
274
|
-
console.log(chalk_1.default.green(line));
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
272
|
const adapterUpdates = (0, agent_adapters_1.ensureAgentAdapterFiles)(projectRoot);
|
|
278
273
|
if (adapterUpdates.length > 0) {
|
|
279
274
|
console.log(chalk_1.default.green(`Updated FRAIM agent adapter files: ${adapterUpdates.join(', ')}`));
|
|
280
275
|
}
|
|
281
276
|
await refreshOrgCache(config.remoteUrl || process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me', apiKey);
|
|
277
|
+
await refreshManagerCache(config.remoteUrl || process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me', apiKey);
|
|
282
278
|
};
|
|
283
279
|
exports.runSync = runSync;
|
|
284
280
|
exports.syncCommand = new commander_1.Command('sync')
|
package/dist/src/cli/fraim.js
CHANGED
|
@@ -54,6 +54,7 @@ const hub_1 = require("./commands/hub");
|
|
|
54
54
|
const first_run_1 = require("./commands/first-run");
|
|
55
55
|
const workspace_config_1 = require("./commands/workspace-config");
|
|
56
56
|
const org_1 = require("./commands/org");
|
|
57
|
+
const manager_1 = require("./commands/manager");
|
|
57
58
|
const fs_1 = __importDefault(require("fs"));
|
|
58
59
|
const path_1 = __importDefault(require("path"));
|
|
59
60
|
const program = new commander_1.Command();
|
|
@@ -97,6 +98,7 @@ program.addCommand(hub_1.hubCommand);
|
|
|
97
98
|
program.addCommand(first_run_1.firstRunCommand);
|
|
98
99
|
program.addCommand(workspace_config_1.workspaceConfigCommand);
|
|
99
100
|
program.addCommand(org_1.orgCommand);
|
|
101
|
+
program.addCommand(manager_1.managerCommand);
|
|
100
102
|
// Wait for async command initialization before parsing
|
|
101
103
|
(async () => {
|
|
102
104
|
// Import the initialization promise from setup command
|
|
@@ -3,67 +3,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.GITHUB_WORKFLOW_ASSET_PREFIX = exports.GITHUB_WORKFLOW_HEADER = exports.GITHUB_WORKFLOW_MANIFEST_RELATIVE_PATH = void 0;
|
|
7
|
-
exports.isGitHubWorkflowAutomationEnabled = isGitHubWorkflowAutomationEnabled;
|
|
8
|
-
exports.decorateManagedGitHubWorkflow = decorateManagedGitHubWorkflow;
|
|
9
|
-
exports.isFraimManagedGitHubWorkflow = isFraimManagedGitHubWorkflow;
|
|
10
|
-
exports.loadGitHubWorkflowManifest = loadGitHubWorkflowManifest;
|
|
11
6
|
exports.loadLocalGitHubWorkflowBundle = loadLocalGitHubWorkflowBundle;
|
|
12
7
|
exports.fetchGitHubWorkflowBundle = fetchGitHubWorkflowBundle;
|
|
13
|
-
exports.
|
|
8
|
+
exports.installGitHubWorkflowAssets = installGitHubWorkflowAssets;
|
|
14
9
|
exports.formatGitHubWorkflowSyncSummary = formatGitHubWorkflowSyncSummary;
|
|
15
10
|
const axios_1 = __importDefault(require("axios"));
|
|
16
|
-
const crypto_1 = __importDefault(require("crypto"));
|
|
17
11
|
const fs_1 = __importDefault(require("fs"));
|
|
18
12
|
const path_1 = __importDefault(require("path"));
|
|
19
13
|
const config_loader_1 = require("../../core/config-loader");
|
|
20
14
|
const project_fraim_paths_1 = require("../../core/utils/project-fraim-paths");
|
|
21
|
-
exports.GITHUB_WORKFLOW_MANIFEST_RELATIVE_PATH = path_1.default.join('fraim', 'managed-assets', 'github-workflows.json');
|
|
22
|
-
exports.GITHUB_WORKFLOW_HEADER = '# FRAIM-MANAGED: github-workflow';
|
|
23
|
-
exports.GITHUB_WORKFLOW_ASSET_PREFIX = '# FRAIM-ASSET-ID: ';
|
|
24
|
-
function sha256(content) {
|
|
25
|
-
return `sha256:${crypto_1.default.createHash('sha256').update(content).digest('hex')}`;
|
|
26
|
-
}
|
|
27
|
-
function getManifestPath(projectRoot) {
|
|
28
|
-
return path_1.default.join(projectRoot, exports.GITHUB_WORKFLOW_MANIFEST_RELATIVE_PATH);
|
|
29
|
-
}
|
|
30
|
-
function isGitHubWorkflowAutomationEnabled(config) {
|
|
31
|
-
return config?.customizations?.githubWorkflows?.enabled === true;
|
|
32
|
-
}
|
|
33
|
-
function decorateManagedGitHubWorkflow(assetId, content) {
|
|
34
|
-
const normalized = content.replace(/^\uFEFF/, '');
|
|
35
|
-
const assetHeader = `${exports.GITHUB_WORKFLOW_HEADER}\n${exports.GITHUB_WORKFLOW_ASSET_PREFIX}${assetId}`;
|
|
36
|
-
if (normalized.includes(exports.GITHUB_WORKFLOW_HEADER) && normalized.includes(`${exports.GITHUB_WORKFLOW_ASSET_PREFIX}${assetId}`)) {
|
|
37
|
-
return normalized;
|
|
38
|
-
}
|
|
39
|
-
return `${assetHeader}\n${normalized}`;
|
|
40
|
-
}
|
|
41
|
-
function isFraimManagedGitHubWorkflow(content, assetId) {
|
|
42
|
-
if (!content.includes(exports.GITHUB_WORKFLOW_HEADER)) {
|
|
43
|
-
return false;
|
|
44
|
-
}
|
|
45
|
-
if (!assetId) {
|
|
46
|
-
return true;
|
|
47
|
-
}
|
|
48
|
-
return content.includes(`${exports.GITHUB_WORKFLOW_ASSET_PREFIX}${assetId}`);
|
|
49
|
-
}
|
|
50
|
-
function loadGitHubWorkflowManifest(projectRoot) {
|
|
51
|
-
const manifestPath = getManifestPath(projectRoot);
|
|
52
|
-
if (!fs_1.default.existsSync(manifestPath)) {
|
|
53
|
-
return {
|
|
54
|
-
version: 1,
|
|
55
|
-
enabled: true,
|
|
56
|
-
provider: 'github',
|
|
57
|
-
managedFiles: []
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
return JSON.parse(fs_1.default.readFileSync(manifestPath, 'utf8'));
|
|
61
|
-
}
|
|
62
|
-
function writeGitHubWorkflowManifest(projectRoot, manifest) {
|
|
63
|
-
const manifestPath = getManifestPath(projectRoot);
|
|
64
|
-
fs_1.default.mkdirSync(path_1.default.dirname(manifestPath), { recursive: true });
|
|
65
|
-
fs_1.default.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
|
|
66
|
-
}
|
|
67
15
|
function loadLocalGitHubWorkflowBundle(registryRoot) {
|
|
68
16
|
const workflowsRoot = path_1.default.join(registryRoot, 'github', 'workflows');
|
|
69
17
|
if (!fs_1.default.existsSync(workflowsRoot)) {
|
|
@@ -77,8 +25,7 @@ function loadLocalGitHubWorkflowBundle(registryRoot) {
|
|
|
77
25
|
return {
|
|
78
26
|
path: entry.name,
|
|
79
27
|
content,
|
|
80
|
-
type: 'github-workflow'
|
|
81
|
-
digest: sha256(content)
|
|
28
|
+
type: 'github-workflow'
|
|
82
29
|
};
|
|
83
30
|
});
|
|
84
31
|
return files.sort((a, b) => a.path.localeCompare(b.path));
|
|
@@ -98,7 +45,7 @@ function readProjectConfig(projectRoot, config) {
|
|
|
98
45
|
}
|
|
99
46
|
return (0, config_loader_1.loadFraimConfig)((0, project_fraim_paths_1.getWorkspaceConfigPath)(projectRoot));
|
|
100
47
|
}
|
|
101
|
-
function
|
|
48
|
+
function installGitHubWorkflowAssets(options) {
|
|
102
49
|
const config = readProjectConfig(options.projectRoot, options.config);
|
|
103
50
|
const provider = config.repository?.provider || null;
|
|
104
51
|
if (provider !== 'github') {
|
|
@@ -109,108 +56,27 @@ function reconcileGitHubWorkflowAssets(options) {
|
|
|
109
56
|
results: []
|
|
110
57
|
};
|
|
111
58
|
}
|
|
112
|
-
if (!isGitHubWorkflowAutomationEnabled(config)) {
|
|
113
|
-
return {
|
|
114
|
-
enabled: false,
|
|
115
|
-
provider,
|
|
116
|
-
skippedReason: 'GitHub workflow automation disabled',
|
|
117
|
-
results: []
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
const manifest = loadGitHubWorkflowManifest(options.projectRoot);
|
|
121
|
-
const manifestEntries = new Map(manifest.managedFiles.map((entry) => [entry.path, entry]));
|
|
122
59
|
const workflowsDir = path_1.default.join(options.projectRoot, '.github', 'workflows');
|
|
123
60
|
fs_1.default.mkdirSync(workflowsDir, { recursive: true });
|
|
124
61
|
const results = [];
|
|
125
|
-
const nextManagedFiles = [];
|
|
126
62
|
for (const file of options.files) {
|
|
127
|
-
const assetId = path_1.default.basename(file.path);
|
|
128
63
|
const relativeWorkflowPath = path_1.default.join('.github', 'workflows', file.path).replace(/[\\/]/g, '/');
|
|
129
|
-
const destinationPath = path_1.default.join(
|
|
130
|
-
|
|
131
|
-
const desiredInstalledDigest = sha256(desiredContent);
|
|
132
|
-
const previousEntry = manifestEntries.get(relativeWorkflowPath);
|
|
133
|
-
if (!fs_1.default.existsSync(destinationPath)) {
|
|
134
|
-
fs_1.default.mkdirSync(path_1.default.dirname(destinationPath), { recursive: true });
|
|
135
|
-
fs_1.default.writeFileSync(destinationPath, desiredContent, 'utf8');
|
|
136
|
-
nextManagedFiles.push({
|
|
137
|
-
path: relativeWorkflowPath,
|
|
138
|
-
assetId,
|
|
139
|
-
sourceDigest: file.digest,
|
|
140
|
-
installedDigest: desiredInstalledDigest
|
|
141
|
-
});
|
|
142
|
-
results.push({
|
|
143
|
-
path: relativeWorkflowPath,
|
|
144
|
-
assetId,
|
|
145
|
-
status: 'installed'
|
|
146
|
-
});
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
149
|
-
const currentContent = fs_1.default.readFileSync(destinationPath, 'utf8');
|
|
150
|
-
const currentDigest = sha256(currentContent);
|
|
151
|
-
const ownedByFraim = isFraimManagedGitHubWorkflow(currentContent, assetId) || Boolean(previousEntry);
|
|
152
|
-
if (!ownedByFraim) {
|
|
153
|
-
if (previousEntry) {
|
|
154
|
-
nextManagedFiles.push(previousEntry);
|
|
155
|
-
}
|
|
156
|
-
results.push({
|
|
157
|
-
path: relativeWorkflowPath,
|
|
158
|
-
assetId,
|
|
159
|
-
status: 'conflict',
|
|
160
|
-
reason: 'same-named file exists without FRAIM ownership marker'
|
|
161
|
-
});
|
|
162
|
-
continue;
|
|
163
|
-
}
|
|
164
|
-
if (currentDigest === desiredInstalledDigest) {
|
|
165
|
-
nextManagedFiles.push({
|
|
166
|
-
path: relativeWorkflowPath,
|
|
167
|
-
assetId,
|
|
168
|
-
sourceDigest: file.digest,
|
|
169
|
-
installedDigest: desiredInstalledDigest
|
|
170
|
-
});
|
|
64
|
+
const destinationPath = path_1.default.join(workflowsDir, file.path);
|
|
65
|
+
if (fs_1.default.existsSync(destinationPath)) {
|
|
171
66
|
results.push({
|
|
172
67
|
path: relativeWorkflowPath,
|
|
173
|
-
|
|
174
|
-
|
|
68
|
+
status: 'skipped',
|
|
69
|
+
reason: 'already exists'
|
|
175
70
|
});
|
|
176
71
|
continue;
|
|
177
72
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
nextManagedFiles.push({
|
|
181
|
-
path: relativeWorkflowPath,
|
|
182
|
-
assetId,
|
|
183
|
-
sourceDigest: file.digest,
|
|
184
|
-
installedDigest: desiredInstalledDigest
|
|
185
|
-
});
|
|
186
|
-
results.push({
|
|
187
|
-
path: relativeWorkflowPath,
|
|
188
|
-
assetId,
|
|
189
|
-
status: 'updated'
|
|
190
|
-
});
|
|
191
|
-
continue;
|
|
192
|
-
}
|
|
193
|
-
nextManagedFiles.push(previousEntry || {
|
|
194
|
-
path: relativeWorkflowPath,
|
|
195
|
-
assetId,
|
|
196
|
-
sourceDigest: file.digest,
|
|
197
|
-
installedDigest: currentDigest
|
|
198
|
-
});
|
|
73
|
+
fs_1.default.mkdirSync(path_1.default.dirname(destinationPath), { recursive: true });
|
|
74
|
+
fs_1.default.writeFileSync(destinationPath, file.content, 'utf8');
|
|
199
75
|
results.push({
|
|
200
76
|
path: relativeWorkflowPath,
|
|
201
|
-
|
|
202
|
-
status: 'conflict',
|
|
203
|
-
reason: previousEntry
|
|
204
|
-
? 'managed file has local edits that do not match the last installed digest'
|
|
205
|
-
: 'FRAIM-managed header exists without manifest baseline'
|
|
77
|
+
status: 'installed'
|
|
206
78
|
});
|
|
207
79
|
}
|
|
208
|
-
writeGitHubWorkflowManifest(options.projectRoot, {
|
|
209
|
-
version: 1,
|
|
210
|
-
enabled: true,
|
|
211
|
-
provider: 'github',
|
|
212
|
-
managedFiles: nextManagedFiles.sort((a, b) => a.path.localeCompare(b.path))
|
|
213
|
-
});
|
|
214
80
|
return {
|
|
215
81
|
enabled: true,
|
|
216
82
|
provider,
|
|
@@ -219,10 +85,10 @@ function reconcileGitHubWorkflowAssets(options) {
|
|
|
219
85
|
}
|
|
220
86
|
function formatGitHubWorkflowSyncSummary(result) {
|
|
221
87
|
if (!result.enabled) {
|
|
222
|
-
return [`GitHub workflow
|
|
88
|
+
return [`GitHub workflow install skipped: ${result.skippedReason || 'not enabled'}`];
|
|
223
89
|
}
|
|
224
90
|
if (result.results.length === 0) {
|
|
225
|
-
return ['GitHub workflow
|
|
91
|
+
return ['GitHub workflow install: no workflow assets found'];
|
|
226
92
|
}
|
|
227
93
|
return result.results.map((item) => {
|
|
228
94
|
const suffix = item.reason ? ` (${item.reason})` : '';
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.MANAGER_CACHE_MANAGED_HEADER = exports.MANAGER_SYNC_METADATA_FILE = exports.MANAGER_CACHE_DIRNAME = void 0;
|
|
7
|
+
exports.getManagerCacheDir = getManagerCacheDir;
|
|
8
|
+
exports.readManagerCacheMetadata = readManagerCacheMetadata;
|
|
9
|
+
exports.getManagerCacheAgeHours = getManagerCacheAgeHours;
|
|
10
|
+
exports.syncManagerCache = syncManagerCache;
|
|
11
|
+
/**
|
|
12
|
+
* Manager cache sync (issue #580).
|
|
13
|
+
*
|
|
14
|
+
* Materializes the manager's personal context/rules/learnings into the managed
|
|
15
|
+
* cache at ~/.fraim/manager/, from either backend:
|
|
16
|
+
* - git: shallow snapshot of a manager-owned repo
|
|
17
|
+
* - fraim-cloud: GET /api/manager/pack from the FRAIM server
|
|
18
|
+
*
|
|
19
|
+
* Cache files are managed content: marked, overwritten on every sync, and
|
|
20
|
+
* never a direct write target for agents.
|
|
21
|
+
*/
|
|
22
|
+
const axios_1 = __importDefault(require("axios"));
|
|
23
|
+
const fs_1 = __importDefault(require("fs"));
|
|
24
|
+
const path_1 = __importDefault(require("path"));
|
|
25
|
+
const project_fraim_paths_1 = require("../../core/utils/project-fraim-paths");
|
|
26
|
+
const manager_pack_1 = require("../../core/manager-pack");
|
|
27
|
+
const git_org_sync_1 = require("./git-org-sync");
|
|
28
|
+
const user_config_1 = require("./user-config");
|
|
29
|
+
exports.MANAGER_CACHE_DIRNAME = 'manager';
|
|
30
|
+
exports.MANAGER_SYNC_METADATA_FILE = '.manager-sync-metadata.json';
|
|
31
|
+
exports.MANAGER_CACHE_MANAGED_HEADER = '<!-- FRAIM_MANAGER_SYNC_MANAGED_CONTENT -->';
|
|
32
|
+
const MANAGER_PACK_DIRS = ['context', 'rules', 'learnings'];
|
|
33
|
+
function getManagerCacheDir() {
|
|
34
|
+
return path_1.default.join((0, project_fraim_paths_1.getUserFraimDirPath)(), exports.MANAGER_CACHE_DIRNAME);
|
|
35
|
+
}
|
|
36
|
+
function readManagerCacheMetadata() {
|
|
37
|
+
try {
|
|
38
|
+
const metadataPath = path_1.default.join(getManagerCacheDir(), exports.MANAGER_SYNC_METADATA_FILE);
|
|
39
|
+
if (!fs_1.default.existsSync(metadataPath))
|
|
40
|
+
return null;
|
|
41
|
+
return JSON.parse(fs_1.default.readFileSync(metadataPath, 'utf8'));
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function getManagerCacheAgeHours() {
|
|
48
|
+
const metadata = readManagerCacheMetadata();
|
|
49
|
+
if (!metadata?.syncedAt)
|
|
50
|
+
return null;
|
|
51
|
+
const syncedAt = Date.parse(metadata.syncedAt);
|
|
52
|
+
if (Number.isNaN(syncedAt))
|
|
53
|
+
return null;
|
|
54
|
+
return Math.max(0, (Date.now() - syncedAt) / 3_600_000);
|
|
55
|
+
}
|
|
56
|
+
function decorateManagedManagerFile(content, backend) {
|
|
57
|
+
const normalized = content.replace(/^\uFEFF/, '');
|
|
58
|
+
if (normalized.startsWith(exports.MANAGER_CACHE_MANAGED_HEADER))
|
|
59
|
+
return normalized;
|
|
60
|
+
const writePath = backend === 'git'
|
|
61
|
+
? 'open a pull request against your manager storage repo'
|
|
62
|
+
: 'publish the change through your FRAIM manager storage flow';
|
|
63
|
+
const marker = [
|
|
64
|
+
exports.MANAGER_CACHE_MANAGED_HEADER,
|
|
65
|
+
'> [!IMPORTANT]',
|
|
66
|
+
'> Synced from your personal manager storage. Local edits are overwritten on the next sync.',
|
|
67
|
+
`> To change this content, ${writePath}.`,
|
|
68
|
+
''
|
|
69
|
+
].join('\n');
|
|
70
|
+
return `${marker}\n${normalized}`;
|
|
71
|
+
}
|
|
72
|
+
function collectGitPackFiles(snapshotDir) {
|
|
73
|
+
const files = [];
|
|
74
|
+
for (const dirName of MANAGER_PACK_DIRS) {
|
|
75
|
+
const dirPath = path_1.default.join(snapshotDir, dirName);
|
|
76
|
+
if (!fs_1.default.existsSync(dirPath))
|
|
77
|
+
continue;
|
|
78
|
+
for (const entry of fs_1.default.readdirSync(dirPath, { withFileTypes: true })) {
|
|
79
|
+
if (!entry.isFile() || !entry.name.endsWith('.md'))
|
|
80
|
+
continue;
|
|
81
|
+
files.push({
|
|
82
|
+
relativePath: `${dirName}/${entry.name}`,
|
|
83
|
+
content: fs_1.default.readFileSync(path_1.default.join(dirPath, entry.name), 'utf8')
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return files;
|
|
88
|
+
}
|
|
89
|
+
async function fetchCloudPack(remoteUrl, apiKey) {
|
|
90
|
+
const response = await axios_1.default.get(`${remoteUrl.replace(/\/$/, '')}/api/manager/pack`, {
|
|
91
|
+
headers: { 'x-api-key': apiKey },
|
|
92
|
+
timeout: 30_000
|
|
93
|
+
});
|
|
94
|
+
const files = Array.isArray(response.data?.files) ? response.data.files : [];
|
|
95
|
+
return {
|
|
96
|
+
files: files.filter((f) => typeof f?.relativePath === 'string' &&
|
|
97
|
+
(0, manager_pack_1.isManagerPackRelativePath)(f.relativePath) &&
|
|
98
|
+
typeof f?.content === 'string'),
|
|
99
|
+
version: String(response.data?.version ?? '0')
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function materializeCache(files, metadata) {
|
|
103
|
+
const cacheDir = getManagerCacheDir();
|
|
104
|
+
const stagingDir = `${cacheDir}.staging-${process.pid}`;
|
|
105
|
+
fs_1.default.rmSync(stagingDir, { recursive: true, force: true });
|
|
106
|
+
fs_1.default.mkdirSync(stagingDir, { recursive: true });
|
|
107
|
+
for (const file of files) {
|
|
108
|
+
if (!(0, manager_pack_1.isManagerPackRelativePath)(file.relativePath))
|
|
109
|
+
continue;
|
|
110
|
+
const destination = path_1.default.join(stagingDir, file.relativePath);
|
|
111
|
+
fs_1.default.mkdirSync(path_1.default.dirname(destination), { recursive: true });
|
|
112
|
+
fs_1.default.writeFileSync(destination, decorateManagedManagerFile(file.content, metadata.backend));
|
|
113
|
+
}
|
|
114
|
+
fs_1.default.writeFileSync(path_1.default.join(stagingDir, exports.MANAGER_SYNC_METADATA_FILE), JSON.stringify(metadata, null, 2));
|
|
115
|
+
fs_1.default.rmSync(cacheDir, { recursive: true, force: true });
|
|
116
|
+
fs_1.default.renameSync(stagingDir, cacheDir);
|
|
117
|
+
}
|
|
118
|
+
async function discoverCloudManagerStorage(options) {
|
|
119
|
+
try {
|
|
120
|
+
const apiKey = options?.apiKey || (0, user_config_1.readUserFraimConfig)().apiKey;
|
|
121
|
+
if (!apiKey || apiKey === 'local-dev' || apiKey === 'test-mode-key')
|
|
122
|
+
return null;
|
|
123
|
+
const remoteUrl = options?.remoteUrl || process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me';
|
|
124
|
+
const pack = await fetchCloudPack(remoteUrl, apiKey);
|
|
125
|
+
if (pack.files.length === 0)
|
|
126
|
+
return null;
|
|
127
|
+
(0, user_config_1.writeUserFraimConfig)({ managerStorage: { backend: 'fraim-cloud' } });
|
|
128
|
+
return (0, user_config_1.getManagerStorageConfig)();
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function failureOutcome(error) {
|
|
135
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
136
|
+
const existing = readManagerCacheMetadata();
|
|
137
|
+
if (existing) {
|
|
138
|
+
return {
|
|
139
|
+
status: 'stale',
|
|
140
|
+
metadata: existing,
|
|
141
|
+
ageHours: getManagerCacheAgeHours() ?? 0,
|
|
142
|
+
error: message
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
return { status: 'absent', error: message };
|
|
146
|
+
}
|
|
147
|
+
async function syncManagerCache(options) {
|
|
148
|
+
let managerStorage = (0, user_config_1.getManagerStorageConfig)();
|
|
149
|
+
if (!managerStorage) {
|
|
150
|
+
managerStorage = await discoverCloudManagerStorage(options);
|
|
151
|
+
if (!managerStorage)
|
|
152
|
+
return { status: 'disabled' };
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
if (managerStorage.backend === 'git') {
|
|
156
|
+
const snapshot = (0, git_org_sync_1.fetchOrgRepoSnapshot)(managerStorage.gitUrl);
|
|
157
|
+
try {
|
|
158
|
+
const metadata = {
|
|
159
|
+
version: snapshot.sha,
|
|
160
|
+
backend: 'git',
|
|
161
|
+
source: managerStorage.gitUrl,
|
|
162
|
+
syncedAt: new Date().toISOString()
|
|
163
|
+
};
|
|
164
|
+
materializeCache(collectGitPackFiles(snapshot.dir), metadata);
|
|
165
|
+
return { status: 'synced', metadata };
|
|
166
|
+
}
|
|
167
|
+
finally {
|
|
168
|
+
snapshot.cleanup();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const remoteUrl = options?.remoteUrl || process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me';
|
|
172
|
+
const apiKey = options?.apiKey || (0, user_config_1.readUserFraimConfig)().apiKey;
|
|
173
|
+
if (!apiKey)
|
|
174
|
+
throw new Error('No FRAIM API key available for the fraim-cloud manager backend.');
|
|
175
|
+
const pack = await fetchCloudPack(remoteUrl, apiKey);
|
|
176
|
+
const metadata = {
|
|
177
|
+
version: pack.version,
|
|
178
|
+
backend: 'fraim-cloud',
|
|
179
|
+
source: remoteUrl,
|
|
180
|
+
syncedAt: new Date().toISOString()
|
|
181
|
+
};
|
|
182
|
+
materializeCache(pack.files, metadata);
|
|
183
|
+
return { status: 'synced', metadata };
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
return failureOutcome(error);
|
|
187
|
+
}
|
|
188
|
+
}
|