fraim 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
|
@@ -13,29 +13,29 @@ exports.getAiHubCategories = getAiHubCategories;
|
|
|
13
13
|
const fs_1 = __importDefault(require("fs"));
|
|
14
14
|
const path_1 = __importDefault(require("path"));
|
|
15
15
|
const project_fraim_paths_1 = require("../core/utils/project-fraim-paths");
|
|
16
|
-
// Directories scanned for employee jobs, in lowest-to-highest
|
|
17
|
-
// order. Later entries win on {categoryId, jobId} collision.
|
|
18
|
-
// layers contribute their category structure.
|
|
16
|
+
// Directories scanned for employee jobs at runtime, in lowest-to-highest
|
|
17
|
+
// precedence order. Later entries win on {categoryId, jobId} collision.
|
|
19
18
|
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
// -
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
// - 'fraim' → <project>/fraim/personalized-employee/jobs/<category>/
|
|
27
|
-
// (per-project override layer; CLAUDE.md says it
|
|
28
|
-
// "takes precedence over synced baseline content")
|
|
19
|
+
// FRAIM execution normally reads only the two `fraim/` layers: the synced
|
|
20
|
+
// baseline and the personalized override. The Hub can explicitly opt into the
|
|
21
|
+
// source `registry/` fallback when running against the FRAIM repo before sync.
|
|
22
|
+
// - <project>/fraim/ai-employee/jobs/<category>/ - synced baseline
|
|
23
|
+
// - <project>/fraim/personalized-employee/jobs/<category>/ - taught/customized override
|
|
24
|
+
// Only the personalized-employee layer is "personalized" (issue #566).
|
|
29
25
|
const EMPLOYEE_JOB_LAYERS = [
|
|
30
|
-
{
|
|
31
|
-
{
|
|
32
|
-
{ base: 'fraim', segments: ['personalized-employee', 'jobs'] },
|
|
26
|
+
{ segments: ['ai-employee', 'jobs'], personalized: false },
|
|
27
|
+
{ segments: ['personalized-employee', 'jobs'], personalized: true },
|
|
33
28
|
];
|
|
34
|
-
// Manager templates use the matching
|
|
29
|
+
// Manager templates use the matching layer model.
|
|
35
30
|
const MANAGER_JOB_LAYERS = [
|
|
36
|
-
{
|
|
37
|
-
{
|
|
38
|
-
|
|
31
|
+
{ segments: ['ai-manager', 'jobs'], personalized: false },
|
|
32
|
+
{ segments: ['personalized-employee', 'manager-jobs'], personalized: true },
|
|
33
|
+
];
|
|
34
|
+
const REGISTRY_EMPLOYEE_JOB_LAYERS = [
|
|
35
|
+
{ base: 'project', segments: ['registry', 'jobs', 'ai-employee'], personalized: false },
|
|
36
|
+
];
|
|
37
|
+
const REGISTRY_MANAGER_JOB_LAYERS = [
|
|
38
|
+
{ base: 'project', segments: ['registry', 'jobs', 'ai-manager'], personalized: false },
|
|
39
39
|
];
|
|
40
40
|
const KNOWN_LABEL_OVERRIDES = {
|
|
41
41
|
// Project conventions where a directory name should render as a recognized
|
|
@@ -93,7 +93,7 @@ function readMarkdownFileNames(dirPath) {
|
|
|
93
93
|
.map((entry) => entry.name)
|
|
94
94
|
.sort((a, b) => a.localeCompare(b));
|
|
95
95
|
}
|
|
96
|
-
function parseJobStub(filePath, categoryId, categoryLabel, projectPath) {
|
|
96
|
+
function parseJobStub(filePath, categoryId, categoryLabel, projectPath, personalized) {
|
|
97
97
|
const content = fs_1.default.readFileSync(filePath, 'utf8');
|
|
98
98
|
const fileName = path_1.default.basename(filePath, '.md');
|
|
99
99
|
const intent = sectionValue(content, 'Intent')[0] || 'No intent summary available.';
|
|
@@ -106,6 +106,7 @@ function parseJobStub(filePath, categoryId, categoryLabel, projectPath) {
|
|
|
106
106
|
intent,
|
|
107
107
|
outcome,
|
|
108
108
|
stubPath: toPosix(path_1.default.relative(projectPath, filePath)),
|
|
109
|
+
personalized: !!personalized,
|
|
109
110
|
};
|
|
110
111
|
}
|
|
111
112
|
function parseManagerStub(filePath, groupId, groupLabel, projectPath) {
|
|
@@ -139,8 +140,7 @@ function summarizeProject(projectPath) {
|
|
|
139
140
|
};
|
|
140
141
|
}
|
|
141
142
|
const fraimDir = (0, project_fraim_paths_1.getWorkspaceFraimDir)(projectPath);
|
|
142
|
-
const
|
|
143
|
-
const hasFraim = fs_1.default.existsSync(fraimDir) || fs_1.default.existsSync(registryJobsDir);
|
|
143
|
+
const hasFraim = fs_1.default.existsSync(fraimDir);
|
|
144
144
|
if (!hasFraim) {
|
|
145
145
|
return {
|
|
146
146
|
path: projectPath,
|
|
@@ -157,9 +157,8 @@ function summarizeProject(projectPath) {
|
|
|
157
157
|
};
|
|
158
158
|
}
|
|
159
159
|
function resolveLayerRoot(projectPath, layer) {
|
|
160
|
-
if (layer.base === '
|
|
161
|
-
return path_1.default.join(projectPath,
|
|
162
|
-
}
|
|
160
|
+
if (layer.base === 'project')
|
|
161
|
+
return path_1.default.join(projectPath, ...layer.segments);
|
|
163
162
|
return path_1.default.join((0, project_fraim_paths_1.getWorkspaceFraimDir)(projectPath), ...layer.segments);
|
|
164
163
|
}
|
|
165
164
|
function discoverLayers(projectPath, layers) {
|
|
@@ -171,25 +170,29 @@ function discoverLayers(projectPath, layers) {
|
|
|
171
170
|
layerRoot,
|
|
172
171
|
categoryId: categoryName,
|
|
173
172
|
categoryDir: path_1.default.join(layerRoot, categoryName),
|
|
173
|
+
personalized: layer.personalized,
|
|
174
174
|
});
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
177
|
return out;
|
|
178
178
|
}
|
|
179
|
-
function discoverEmployeeJobs(projectPath) {
|
|
179
|
+
function discoverEmployeeJobs(projectPath, options = {}) {
|
|
180
180
|
const project = summarizeProject(projectPath);
|
|
181
|
-
if (!project.exists || !project.hasFraim)
|
|
181
|
+
if (!project.exists || (!project.hasFraim && !options.includeRegistry))
|
|
182
182
|
return [];
|
|
183
|
-
const layers = discoverLayers(projectPath,
|
|
183
|
+
const layers = discoverLayers(projectPath, options.includeRegistry
|
|
184
|
+
? [...REGISTRY_EMPLOYEE_JOB_LAYERS, ...EMPLOYEE_JOB_LAYERS]
|
|
185
|
+
: EMPLOYEE_JOB_LAYERS);
|
|
184
186
|
// Group by categoryId so all layers contribute to the same labelled category.
|
|
185
187
|
const jobsByKey = new Map();
|
|
186
188
|
for (const layer of layers) {
|
|
187
189
|
const categoryLabel = humanizeName(layer.categoryId);
|
|
188
190
|
for (const fileName of readMarkdownFileNames(layer.categoryDir)) {
|
|
189
191
|
const filePath = path_1.default.join(layer.categoryDir, fileName);
|
|
190
|
-
const job = parseJobStub(filePath, layer.categoryId, categoryLabel, projectPath);
|
|
192
|
+
const job = parseJobStub(filePath, layer.categoryId, categoryLabel, projectPath, layer.personalized);
|
|
191
193
|
// Later layers override earlier layers on {category, jobId} collision —
|
|
192
|
-
// personalized-employee wins over the synced ai-employee baseline.
|
|
194
|
+
// personalized-employee wins over the synced ai-employee baseline. The
|
|
195
|
+
// winning job carries its own layer's `personalized` flag (issue #566).
|
|
193
196
|
jobsByKey.set(`${job.categoryId}::${job.id}`, job);
|
|
194
197
|
}
|
|
195
198
|
}
|
|
@@ -200,11 +203,13 @@ function discoverEmployeeJobs(projectPath) {
|
|
|
200
203
|
return a.title.localeCompare(b.title);
|
|
201
204
|
});
|
|
202
205
|
}
|
|
203
|
-
function discoverManagerTemplates(projectPath) {
|
|
206
|
+
function discoverManagerTemplates(projectPath, options = {}) {
|
|
204
207
|
const project = summarizeProject(projectPath);
|
|
205
|
-
if (!project.exists || !project.hasFraim)
|
|
208
|
+
if (!project.exists || (!project.hasFraim && !options.includeRegistry))
|
|
206
209
|
return [];
|
|
207
|
-
const layers = discoverLayers(projectPath,
|
|
210
|
+
const layers = discoverLayers(projectPath, options.includeRegistry
|
|
211
|
+
? [...REGISTRY_MANAGER_JOB_LAYERS, ...MANAGER_JOB_LAYERS]
|
|
212
|
+
: MANAGER_JOB_LAYERS);
|
|
208
213
|
const templatesByKey = new Map();
|
|
209
214
|
for (const layer of layers) {
|
|
210
215
|
const groupLabel = humanizeName(layer.categoryId);
|
|
@@ -379,12 +384,14 @@ function labelForPhaseId(phaseId, jobId, projectPath) {
|
|
|
379
384
|
}
|
|
380
385
|
return friendlyPhaseLabel(phaseId);
|
|
381
386
|
}
|
|
382
|
-
function getAiHubCategories(projectPath) {
|
|
387
|
+
function getAiHubCategories(projectPath, options = {}) {
|
|
383
388
|
const project = summarizeProject(projectPath);
|
|
384
|
-
if (!project.exists || !project.hasFraim)
|
|
389
|
+
if (!project.exists || (!project.hasFraim && !options.includeRegistry))
|
|
385
390
|
return [];
|
|
386
391
|
// A category is any directory found at any layer; deduplicate by id.
|
|
387
|
-
const layers = discoverLayers(projectPath,
|
|
392
|
+
const layers = discoverLayers(projectPath, options.includeRegistry
|
|
393
|
+
? [...REGISTRY_EMPLOYEE_JOB_LAYERS, ...EMPLOYEE_JOB_LAYERS]
|
|
394
|
+
: EMPLOYEE_JOB_LAYERS);
|
|
388
395
|
const seen = new Map();
|
|
389
396
|
for (const layer of layers) {
|
|
390
397
|
if (!seen.has(layer.categoryId)) {
|
|
@@ -977,12 +977,16 @@ class AiHubServer {
|
|
|
977
977
|
}
|
|
978
978
|
}
|
|
979
979
|
const project = (0, catalog_1.summarizeProject)(normalizedProjectPath);
|
|
980
|
-
const
|
|
980
|
+
const catalogOptions = { includeRegistry: true };
|
|
981
|
+
const rawJobs = (0, catalog_1.discoverEmployeeJobs)(normalizedProjectPath, catalogOptions);
|
|
982
|
+
// Issue #566 (R7): jobs already carry `personalized` from catalog discovery
|
|
983
|
+
// (true for the fraim/personalized-employee layer). The Hub renders a plain
|
|
984
|
+
// "Personalized" marking from that flag — no author/attribution is tracked.
|
|
981
985
|
const jobs = rawJobs.map((job) => ({
|
|
982
986
|
...job,
|
|
983
987
|
requiredPersonaKey: getProtectedPersonaForHubJob(job.id),
|
|
984
988
|
}));
|
|
985
|
-
const managerTemplates = (0, catalog_1.discoverManagerTemplates)(normalizedProjectPath);
|
|
989
|
+
const managerTemplates = (0, catalog_1.discoverManagerTemplates)(normalizedProjectPath, catalogOptions);
|
|
986
990
|
const { personas, subscriptionActive, workspaceId, userKey } = await this.computePersonas(apiKey || preferences.apiKey);
|
|
987
991
|
const managerTeam = await this.computeManagerTeam(workspaceId, userKey);
|
|
988
992
|
// Issue #347: enrich the activeRun the same way GET /runs/:id does
|
|
@@ -994,7 +998,7 @@ class AiHubServer {
|
|
|
994
998
|
title: 'AI Hub',
|
|
995
999
|
project,
|
|
996
1000
|
preferences,
|
|
997
|
-
categories: (0, catalog_1.getAiHubCategories)(normalizedProjectPath),
|
|
1001
|
+
categories: (0, catalog_1.getAiHubCategories)(normalizedProjectPath, catalogOptions),
|
|
998
1002
|
jobs,
|
|
999
1003
|
managerTemplates,
|
|
1000
1004
|
employees,
|
|
@@ -1552,12 +1556,14 @@ class AiHubServer {
|
|
|
1552
1556
|
? path_1.default.resolve(body.projectPath)
|
|
1553
1557
|
: this.projectPath;
|
|
1554
1558
|
const loc = (0, learning_context_builder_1.resolveTeamContextFile)(projectPath, body.key);
|
|
1555
|
-
if (loc.managedByOrgSync || !loc.writePath) {
|
|
1559
|
+
if (loc.managedByOrgSync || loc.managedByManagerSync || !loc.writePath) {
|
|
1556
1560
|
// Enforcement only: block editing a synced org file (it would be
|
|
1557
1561
|
// overwritten on next sync). The how-to-change procedure lives in the
|
|
1558
1562
|
// organization-onboarding job, not in this error body (issue #563 review).
|
|
1559
1563
|
return res.status(409).json({
|
|
1560
|
-
error:
|
|
1564
|
+
error: loc.managedByManagerSync
|
|
1565
|
+
? 'This manager file is managed by manager sync and is read-only here.'
|
|
1566
|
+
: 'This organization file is managed by org sync and is read-only here.'
|
|
1561
1567
|
});
|
|
1562
1568
|
}
|
|
1563
1569
|
const dest = path_1.default.resolve(loc.writePath);
|
|
@@ -1718,6 +1724,8 @@ class AiHubServer {
|
|
|
1718
1724
|
personaKey: getProtectedPersonaForHubJob(jobId),
|
|
1719
1725
|
// Issue #442: mark this as the FRAIM side of an A/B pair when applicable.
|
|
1720
1726
|
...(compareMode === 'ab' ? { runRole: 'fraim' } : {}),
|
|
1727
|
+
// #0: trigger source — defaults to 'manager' when not provided by the caller.
|
|
1728
|
+
sourceTrigger: req.body.sourceTrigger ?? 'manager',
|
|
1721
1729
|
};
|
|
1722
1730
|
this.runRegistry.create(run, {});
|
|
1723
1731
|
this.persistRunConversation(run, run.conversationId || run.id);
|
|
@@ -2111,6 +2119,21 @@ class AiHubServer {
|
|
|
2111
2119
|
res.status(400).json({ error: error instanceof Error ? error.message : 'Could not continue Direct run.' });
|
|
2112
2120
|
}
|
|
2113
2121
|
});
|
|
2122
|
+
// GET /api/ai-hub/runs — list runs for cross-host polling and the UI ledger.
|
|
2123
|
+
// Query params: status (filter), sourceTrigger (filter), limit (default 100, max 200).
|
|
2124
|
+
// Must be registered BEFORE the :runId route to avoid 'runs' being matched as a runId.
|
|
2125
|
+
this.app.get('/api/ai-hub/runs', (req, res) => {
|
|
2126
|
+
const statusFilter = typeof req.query.status === 'string' ? req.query.status : null;
|
|
2127
|
+
const triggerFilter = typeof req.query.sourceTrigger === 'string' ? req.query.sourceTrigger : null;
|
|
2128
|
+
const limit = Math.min(200, parseInt(String(req.query.limit || '100'), 10) || 100);
|
|
2129
|
+
let runs = this.runRegistry.all();
|
|
2130
|
+
if (statusFilter)
|
|
2131
|
+
runs = runs.filter((r) => r.status === statusFilter);
|
|
2132
|
+
if (triggerFilter)
|
|
2133
|
+
runs = runs.filter((r) => (r.sourceTrigger ?? 'manager') === triggerFilter);
|
|
2134
|
+
runs = runs.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt)).slice(0, limit);
|
|
2135
|
+
return res.json(runs.map((r) => this.enrichRunForResponse(r)));
|
|
2136
|
+
});
|
|
2114
2137
|
this.app.get('/api/ai-hub/runs/:runId', (req, res) => {
|
|
2115
2138
|
const run = this.runRegistry.get(req.params.runId);
|
|
2116
2139
|
if (!run) {
|
|
@@ -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
|