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.
Files changed (60) hide show
  1. package/dist/src/ai-hub/catalog.js +43 -36
  2. package/dist/src/ai-hub/server.js +28 -5
  3. package/dist/src/cli/commands/init-project.js +1 -98
  4. package/dist/src/cli/commands/manager.js +40 -0
  5. package/dist/src/cli/commands/sync.js +17 -21
  6. package/dist/src/cli/fraim.js +2 -0
  7. package/dist/src/cli/utils/github-workflow-sync.js +12 -146
  8. package/dist/src/cli/utils/manager-pack-sync.js +188 -0
  9. package/dist/src/cli/utils/manager-publish.js +76 -0
  10. package/dist/src/cli/utils/user-config.js +20 -0
  11. package/dist/src/core/config-loader.js +9 -5
  12. package/dist/src/core/fraim-config-schema.generated.js +85 -31
  13. package/dist/src/core/manager-pack.js +26 -0
  14. package/dist/src/core/utils/local-registry-resolver.js +8 -1
  15. package/dist/src/first-run/install-state.js +1 -0
  16. package/dist/src/first-run/server.js +9 -0
  17. package/dist/src/first-run/session-service.js +117 -23
  18. package/dist/src/first-run/types.js +2 -5
  19. package/dist/src/local-mcp-server/learning-context-builder.js +45 -8
  20. package/dist/src/local-mcp-server/stdio-server.js +28 -0
  21. package/index.js +1 -1
  22. package/package.json +4 -1
  23. package/public/ai-hub/powerpoint-taskpane/index.html +236 -236
  24. package/public/ai-hub/powerpoint-taskpane/manifest.xml +29 -29
  25. package/public/ai-hub/review.css +13 -0
  26. package/public/ai-hub/script.js +199 -5
  27. package/public/ai-hub/styles.css +28 -0
  28. package/public/first-run/index.html +1 -1
  29. package/public/first-run/script.js +459 -530
  30. package/public/first-run/styles.css +288 -73
  31. package/public/portfolio/ashley.html +523 -0
  32. package/public/portfolio/auditya.html +83 -0
  33. package/public/portfolio/banke.html +83 -0
  34. package/public/portfolio/beza.html +659 -0
  35. package/public/portfolio/careena.html +632 -0
  36. package/public/portfolio/casey.html +568 -0
  37. package/public/portfolio/celia.html +490 -0
  38. package/public/portfolio/deidre.html +642 -0
  39. package/public/portfolio/gautam.html +597 -0
  40. package/public/portfolio/hari.html +469 -0
  41. package/public/portfolio/huxley.html +1354 -0
  42. package/public/portfolio/index.html +741 -0
  43. package/public/portfolio/maestro.html +518 -0
  44. package/public/portfolio/mandy.html +590 -0
  45. package/public/portfolio/mona.html +597 -0
  46. package/public/portfolio/pam.html +887 -0
  47. package/public/portfolio/procella.html +107 -0
  48. package/public/portfolio/qasm.html +569 -0
  49. package/public/portfolio/ricardo.html +489 -0
  50. package/public/portfolio/sade.html +560 -0
  51. package/public/portfolio/sam.html +654 -0
  52. package/public/portfolio/sechar.html +580 -0
  53. package/public/portfolio/sreya.html +599 -0
  54. package/public/portfolio/swen.html +601 -0
  55. package/dist/src/ai-hub/word-sideload.js +0 -95
  56. package/dist/src/cli/commands/test-mcp.js +0 -171
  57. package/dist/src/cli/setup/first-run.js +0 -242
  58. package/dist/src/core/config-writer.js +0 -75
  59. package/dist/src/core/utils/job-aliases.js +0 -47
  60. 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 precedence
17
- // order. Later entries win on {categoryId, jobId} collision. All present
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
- // Each entry is `[base, ...segments]` resolved relative to the project root:
21
- // - 'registry' → <project>/registry/jobs/ai-employee/<category>/
22
- // (present in the FRAIM source repo and in dist/)
23
- // - 'fraim' → <project>/fraim/ai-employee/jobs/<category>/
24
- // (the baseline synced into a regular project via
25
- // `fraim setup` / `fraim sync`)
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
- { base: 'registry', segments: ['jobs', 'ai-employee'] },
31
- { base: 'fraim', segments: ['ai-employee', 'jobs'] },
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 two-layer model.
29
+ // Manager templates use the matching layer model.
35
30
  const MANAGER_JOB_LAYERS = [
36
- { base: 'registry', segments: ['jobs', 'ai-manager'] },
37
- { base: 'fraim', segments: ['ai-manager', 'jobs'] },
38
- { base: 'fraim', segments: ['personalized-employee', 'manager-jobs'] },
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 registryJobsDir = path_1.default.join(projectPath, 'registry', 'jobs');
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 === 'registry') {
161
- return path_1.default.join(projectPath, 'registry', ...layer.segments);
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, EMPLOYEE_JOB_LAYERS);
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, MANAGER_JOB_LAYERS);
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, EMPLOYEE_JOB_LAYERS);
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 rawJobs = (0, catalog_1.discoverEmployeeJobs)(normalizedProjectPath);
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: 'This organization file is managed by org sync and is read-only here.'
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 = exports.findProjectFile = void 0;
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')
@@ -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