fraim-framework 2.0.166 → 2.0.168
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 +43 -36
- package/dist/src/ai-hub/server.js +28 -5
- 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/config-loader.js +9 -5
- package/dist/src/core/fraim-config-schema.generated.js +85 -31
- package/dist/src/core/manager-pack.js +26 -0
- package/dist/src/core/utils/local-registry-resolver.js +8 -1
- 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/index.js +1 -1
- package/package.json +4 -1
- package/public/ai-hub/powerpoint-taskpane/index.html +236 -236
- package/public/ai-hub/powerpoint-taskpane/manifest.xml +29 -29
- package/public/ai-hub/review.css +13 -0
- package/public/ai-hub/script.js +199 -5
- package/public/ai-hub/styles.css +28 -0
- 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/public/portfolio/ashley.html +523 -0
- package/public/portfolio/auditya.html +83 -0
- package/public/portfolio/banke.html +83 -0
- package/public/portfolio/beza.html +659 -0
- package/public/portfolio/careena.html +632 -0
- package/public/portfolio/casey.html +568 -0
- package/public/portfolio/celia.html +490 -0
- package/public/portfolio/deidre.html +642 -0
- package/public/portfolio/gautam.html +597 -0
- package/public/portfolio/hari.html +469 -0
- package/public/portfolio/huxley.html +1354 -0
- package/public/portfolio/index.html +741 -0
- package/public/portfolio/maestro.html +518 -0
- package/public/portfolio/mandy.html +590 -0
- package/public/portfolio/mona.html +597 -0
- package/public/portfolio/pam.html +887 -0
- package/public/portfolio/procella.html +107 -0
- package/public/portfolio/qasm.html +569 -0
- package/public/portfolio/ricardo.html +489 -0
- package/public/portfolio/sade.html +560 -0
- package/public/portfolio/sam.html +654 -0
- package/public/portfolio/sechar.html +580 -0
- package/public/portfolio/sreya.html +599 -0
- package/public/portfolio/swen.html +601 -0
- package/dist/src/ai-hub/word-sideload.js +0 -95
- package/dist/src/cli/commands/test-mcp.js +0 -171
- package/dist/src/cli/setup/first-run.js +0 -242
- package/dist/src/core/config-writer.js +0 -75
- package/dist/src/core/utils/job-aliases.js +0 -47
- package/dist/src/core/utils/workflow-parser.js +0 -174
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
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.managerPackRelativePathFor = managerPackRelativePathFor;
|
|
7
|
+
exports.publishManagerArtifacts = publishManagerArtifacts;
|
|
8
|
+
/**
|
|
9
|
+
* Publish manager context/rules/learnings to the configured manager backend
|
|
10
|
+
* (issue #580).
|
|
11
|
+
*/
|
|
12
|
+
const axios_1 = __importDefault(require("axios"));
|
|
13
|
+
const child_process_1 = require("child_process");
|
|
14
|
+
const fs_1 = __importDefault(require("fs"));
|
|
15
|
+
const os_1 = __importDefault(require("os"));
|
|
16
|
+
const path_1 = __importDefault(require("path"));
|
|
17
|
+
const manager_pack_1 = require("../../core/manager-pack");
|
|
18
|
+
const user_config_1 = require("./user-config");
|
|
19
|
+
const ALLOWED_GIT_URL = /^(https?:\/\/|ssh:\/\/|git:\/\/|file:\/\/|[\w.-]+@[\w.-]+:)/;
|
|
20
|
+
function managerPackRelativePathFor(filePath) {
|
|
21
|
+
const relativePath = (0, manager_pack_1.managerPackRelativePathForFileName)(filePath);
|
|
22
|
+
if (relativePath)
|
|
23
|
+
return relativePath;
|
|
24
|
+
const base = path_1.default.basename(filePath);
|
|
25
|
+
throw new Error(`Cannot infer manager-pack location for '${base}' (expected manager_context.md, manager_rules.md, or a personal learning file)`);
|
|
26
|
+
}
|
|
27
|
+
function gitCompareUrl(gitUrl, branch) {
|
|
28
|
+
const httpUrl = gitUrl.replace(/\.git$/, '').replace(/^git@([^:]+):/, 'https://$1/');
|
|
29
|
+
if (/^https?:\/\//.test(httpUrl))
|
|
30
|
+
return `${httpUrl}/compare/${branch}?expand=1`;
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
async function publishManagerArtifacts(artifacts, opts) {
|
|
34
|
+
const managerStorage = (0, user_config_1.getManagerStorageConfig)();
|
|
35
|
+
if (!managerStorage) {
|
|
36
|
+
throw new Error('No manager storage backend configured. Set the managerStorage block in ~/.fraim/config.json.');
|
|
37
|
+
}
|
|
38
|
+
if (artifacts.length === 0)
|
|
39
|
+
throw new Error('No artifacts to publish.');
|
|
40
|
+
for (const artifact of artifacts) {
|
|
41
|
+
if (!(0, manager_pack_1.isManagerPackRelativePath)(artifact.relativePath)) {
|
|
42
|
+
throw new Error(`Invalid manager-pack path '${artifact.relativePath}' (must be context/manager_context.md, rules/manager_rules.md, or learnings/<user>-*.md).`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (managerStorage.backend === 'fraim-cloud') {
|
|
46
|
+
const remoteUrl = opts?.remoteUrl || process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me';
|
|
47
|
+
const apiKey = opts?.apiKey || (0, user_config_1.readUserFraimConfig)().apiKey;
|
|
48
|
+
if (!apiKey)
|
|
49
|
+
throw new Error('No FRAIM API key available for the fraim-cloud manager backend.');
|
|
50
|
+
const res = await axios_1.default.post(`${remoteUrl.replace(/\/$/, '')}/api/manager/publish`, { artifacts }, { headers: { 'x-api-key': apiKey }, timeout: 30_000 });
|
|
51
|
+
return { backend: 'fraim-cloud', version: String(res.data?.version ?? '') };
|
|
52
|
+
}
|
|
53
|
+
const gitUrl = managerStorage.gitUrl;
|
|
54
|
+
if (!ALLOWED_GIT_URL.test(gitUrl))
|
|
55
|
+
throw new Error(`Manager repo URL has an unsupported scheme: ${gitUrl}`);
|
|
56
|
+
const dir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'fraim-manager-pub-'));
|
|
57
|
+
try {
|
|
58
|
+
(0, child_process_1.execFileSync)('git', ['clone', '--depth=1', '--quiet', '--', gitUrl, '.'], {
|
|
59
|
+
cwd: dir, stdio: ['ignore', 'pipe', 'pipe'], timeout: 60_000
|
|
60
|
+
});
|
|
61
|
+
const branch = `fraim-manager-update-${opts?.branchSuffix || `${process.pid}-${Date.now()}`}`;
|
|
62
|
+
(0, child_process_1.execFileSync)('git', ['checkout', '-b', branch], { cwd: dir, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
63
|
+
for (const artifact of artifacts) {
|
|
64
|
+
const dest = path_1.default.join(dir, artifact.relativePath);
|
|
65
|
+
fs_1.default.mkdirSync(path_1.default.dirname(dest), { recursive: true });
|
|
66
|
+
fs_1.default.writeFileSync(dest, artifact.content);
|
|
67
|
+
}
|
|
68
|
+
(0, child_process_1.execFileSync)('git', ['add', '-A'], { cwd: dir, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
69
|
+
(0, child_process_1.execFileSync)('git', ['commit', '-m', 'Update manager context'], { cwd: dir, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
70
|
+
(0, child_process_1.execFileSync)('git', ['push', '--quiet', 'origin', branch], { cwd: dir, stdio: ['ignore', 'pipe', 'pipe'], timeout: 60_000 });
|
|
71
|
+
return { backend: 'git', branch, prUrl: gitCompareUrl(gitUrl, branch) };
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
fs_1.default.rmSync(dir, { recursive: true, force: true });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.readUserFraimConfig = readUserFraimConfig;
|
|
7
7
|
exports.writeUserFraimConfig = writeUserFraimConfig;
|
|
8
8
|
exports.getOrganizationConfig = getOrganizationConfig;
|
|
9
|
+
exports.getManagerStorageConfig = getManagerStorageConfig;
|
|
9
10
|
/**
|
|
10
11
|
* Typed read/write helpers for the user-level FRAIM config (~/.fraim/config.json),
|
|
11
12
|
* including the organization block introduced by issue #563.
|
|
@@ -66,3 +67,22 @@ function getOrganizationConfig() {
|
|
|
66
67
|
}
|
|
67
68
|
return null;
|
|
68
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Resolve the validated manager storage configuration, or null when no portable
|
|
72
|
+
* manager backend is configured on this machine.
|
|
73
|
+
*/
|
|
74
|
+
function getManagerStorageConfig() {
|
|
75
|
+
const raw = readUserFraimConfig().managerStorage;
|
|
76
|
+
if (!raw || typeof raw !== 'object')
|
|
77
|
+
return null;
|
|
78
|
+
if (raw.backend === 'git') {
|
|
79
|
+
const gitUrl = typeof raw.gitUrl === 'string' ? raw.gitUrl.trim() : '';
|
|
80
|
+
if (!gitUrl)
|
|
81
|
+
return null;
|
|
82
|
+
return { backend: 'git', gitUrl, id: raw.id };
|
|
83
|
+
}
|
|
84
|
+
if (raw.backend === 'fraim-cloud') {
|
|
85
|
+
return { backend: 'fraim-cloud', id: raw.id };
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
@@ -33,7 +33,8 @@ function normalizeIntegrations(config) {
|
|
|
33
33
|
itsm: current.itsm && typeof current.itsm === 'object'
|
|
34
34
|
? {
|
|
35
35
|
provider: current.itsm.provider,
|
|
36
|
-
instanceUrl: current.itsm.instanceUrl
|
|
36
|
+
instanceUrl: current.itsm.instanceUrl,
|
|
37
|
+
accessScript: current.itsm.accessScript
|
|
37
38
|
}
|
|
38
39
|
: undefined,
|
|
39
40
|
identity: current.identity && typeof current.identity === 'object'
|
|
@@ -49,13 +50,16 @@ function normalizeAutomation(config) {
|
|
|
49
50
|
support: {
|
|
50
51
|
startMode: support.startMode,
|
|
51
52
|
defaultDecisionMode: support.defaultDecisionMode,
|
|
52
|
-
|
|
53
|
+
playbooks: support.playbooks && typeof support.playbooks === 'object'
|
|
53
54
|
? {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
directory: support.playbooks.directory,
|
|
56
|
+
decisionCommand: support.playbooks.decisionCommand,
|
|
57
|
+
decisionTimeoutMs: support.playbooks.decisionTimeoutMs
|
|
57
58
|
}
|
|
58
59
|
: undefined,
|
|
60
|
+
actionAdapters: support.actionAdapters && typeof support.actionAdapters === 'object'
|
|
61
|
+
? support.actionAdapters
|
|
62
|
+
: undefined,
|
|
59
63
|
queue: support.queue && typeof support.queue === 'object'
|
|
60
64
|
? {
|
|
61
65
|
provider: support.queue.provider,
|