fraim-framework 2.0.163 → 2.0.165
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/desktop-main.js +4 -1
- package/dist/src/ai-hub/hosts.js +4 -11
- package/dist/src/ai-hub/server.js +48 -123
- package/dist/src/cli/commands/first-run.js +37 -2
- package/dist/src/cli/commands/hub.js +41 -5
- package/dist/src/cli/commands/init-project.js +15 -14
- package/dist/src/cli/commands/sync.js +38 -0
- package/dist/src/cli/utils/git-org-sync.js +56 -0
- package/dist/src/cli/utils/org-migration.js +50 -0
- package/dist/src/cli/utils/org-pack-sync.js +208 -0
- package/dist/src/cli/utils/project-bootstrap.js +20 -7
- package/dist/src/cli/utils/user-config.js +68 -0
- package/dist/src/core/fraim-config-schema.generated.js +10 -0
- package/dist/src/local-mcp-server/learning-context-builder.js +78 -29
- package/dist/src/local-mcp-server/stdio-server.js +30 -0
- package/index.js +1 -1
- package/package.json +1 -2
- package/public/ai-hub/index.html +2 -2
- 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 +15 -15
- package/public/ai-hub/script.js +70 -78
- package/public/ai-hub/styles.css +173 -16
- package/public/first-run/styles.css +73 -73
- 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
|
@@ -273,7 +273,10 @@ async function bootstrap() {
|
|
|
273
273
|
const options = parseArgs(process.argv.slice(2));
|
|
274
274
|
// Single-instance lock — if another instance is already running, focus it
|
|
275
275
|
// and exit rather than spawning a second server + window.
|
|
276
|
-
|
|
276
|
+
// Skip when FRAIM_AI_HUB_FAKE_HOST=1 (test mode) so Playwright can launch
|
|
277
|
+
// a test instance alongside the real desktop app without the lock killing it.
|
|
278
|
+
const skipSingleInstance = process.env.FRAIM_AI_HUB_FAKE_HOST === '1';
|
|
279
|
+
const gotLock = skipSingleInstance || electron_1.app.requestSingleInstanceLock();
|
|
277
280
|
if (!gotLock) {
|
|
278
281
|
electron_1.app.quit();
|
|
279
282
|
return;
|
package/dist/src/ai-hub/hosts.js
CHANGED
|
@@ -436,17 +436,10 @@ function machineLevelStorageGuard(jobId) {
|
|
|
436
436
|
'- If the exact machine-level paths cannot be written, fail the phase and report the concrete filesystem error.',
|
|
437
437
|
].join('\n');
|
|
438
438
|
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
'Storage scope guardrail:',
|
|
444
|
-
'- Organization onboarding artifacts are machine-level, not repo-level.',
|
|
445
|
-
`- Required write targets: ${orgContext} and ${orgRules}.`,
|
|
446
|
-
'- Do not write, validate, call canonical, commit, or open a PR for repo-local fraim/personalized-employee/context/org_context.md or fraim/personalized-employee/rules/org_rules.md as substitutes.',
|
|
447
|
-
'- If the exact machine-level paths cannot be written, fail the phase and report the concrete filesystem error.',
|
|
448
|
-
].join('\n');
|
|
449
|
-
}
|
|
439
|
+
// organization-onboarding intentionally has no guardrail here: the job's
|
|
440
|
+
// submit phase is backend-aware and owns the write-path procedure (git PR /
|
|
441
|
+
// cloud publish / machine-level fallback), so duplicating it in runtime
|
|
442
|
+
// prompt text would be redundant (issue #563 review).
|
|
450
443
|
return null;
|
|
451
444
|
}
|
|
452
445
|
// If ~/.gemini/settings.json has a wrong/test FRAIM_API_KEY, patch it with the
|
|
@@ -50,30 +50,7 @@ const https_1 = __importDefault(require("https"));
|
|
|
50
50
|
const types_1 = require("../first-run/types");
|
|
51
51
|
const learning_context_builder_1 = require("../local-mcp-server/learning-context-builder");
|
|
52
52
|
const project_fraim_paths_1 = require("../core/utils/project-fraim-paths");
|
|
53
|
-
const
|
|
54
|
-
maestro: { seed: 'MAESTRO-founder-mode', bg: 'fde68a' },
|
|
55
|
-
beza: { seed: 'BEZA-strategist', bg: 'c7d2fe' },
|
|
56
|
-
pam: { seed: 'PAM-product', bg: 'ddd6fe' },
|
|
57
|
-
swen: { seed: 'SWEN-engineer', bg: 'bfdbfe' },
|
|
58
|
-
qasm: { seed: 'QASM-quality', bg: 'a7f3d0' },
|
|
59
|
-
huxley: { seed: 'HUXLEY-design', bg: 'fbcfe8' },
|
|
60
|
-
gautam: { seed: 'Gautam-marketing', bg: 'fed7aa' },
|
|
61
|
-
cela: { seed: 'CELA-legal', bg: 'cbd5e1' },
|
|
62
|
-
sekhar: { seed: 'SEKHAR-security', bg: 'fecaca' },
|
|
63
|
-
ashley: { seed: 'Ashley-assistant', bg: 'fde68a' },
|
|
64
|
-
mandy: { seed: 'MANDY-manager', bg: 'ede9fe' },
|
|
65
|
-
hari: { seed: 'HARI-hr', bg: 'ccfbf1' },
|
|
66
|
-
careena: { seed: 'CAREENA-career-coach', bg: 'e0f2fe' },
|
|
67
|
-
ricardo: { seed: 'RICARDO-recruiter', bg: 'ede9fe' },
|
|
68
|
-
sade: { seed: 'SADE-salesforce-dev', bg: 'bae6fd' },
|
|
69
|
-
sam: { seed: 'SAM-sales-manager', bg: 'bbf7d0' },
|
|
70
|
-
casey: { seed: 'CASEY-customer-cx', bg: 'fecdd3' },
|
|
71
|
-
};
|
|
72
|
-
function buildPersonaAvatarUrl(personaKey) {
|
|
73
|
-
const data = PERSONA_AVATAR_SEEDS[personaKey] ?? { seed: personaKey, bg: 'f1f5f9' };
|
|
74
|
-
const params = new URLSearchParams({ seed: data.seed, backgroundColor: data.bg, radius: '50' });
|
|
75
|
-
return `https://api.dicebear.com/9.x/notionists/svg?${params.toString()}`;
|
|
76
|
-
}
|
|
53
|
+
const persona_hiring_1 = require("../config/persona-hiring");
|
|
77
54
|
const catalog_1 = require("./catalog");
|
|
78
55
|
const agent_token_prices_1 = require("../local-mcp-server/agent-token-prices");
|
|
79
56
|
const hosts_1 = require("./hosts");
|
|
@@ -602,19 +579,41 @@ function resolveSafeArtifactPath(rawPath, projectPath) {
|
|
|
602
579
|
return safeRoots.some((root) => pathWithin(root, resolved)) ? resolved : null;
|
|
603
580
|
}
|
|
604
581
|
function hubOpenFile(filePath) {
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
582
|
+
return new Promise((resolve, reject) => {
|
|
583
|
+
const args = process.platform === 'win32'
|
|
584
|
+
? [
|
|
585
|
+
'-NoProfile',
|
|
586
|
+
'-ExecutionPolicy',
|
|
587
|
+
'Bypass',
|
|
588
|
+
'-Command',
|
|
589
|
+
'& { param($p) try { Invoke-Item -LiteralPath $p; exit 0 } catch { Write-Error $_; exit 1 } }',
|
|
590
|
+
filePath,
|
|
591
|
+
]
|
|
592
|
+
: process.platform === 'darwin'
|
|
593
|
+
? [filePath]
|
|
594
|
+
: [filePath];
|
|
595
|
+
const command = process.platform === 'win32'
|
|
596
|
+
? 'powershell.exe'
|
|
597
|
+
: process.platform === 'darwin'
|
|
598
|
+
? 'open'
|
|
599
|
+
: 'xdg-open';
|
|
600
|
+
const child = (0, child_process_1.spawn)(command, args, {
|
|
601
|
+
stdio: ['ignore', 'ignore', 'pipe'],
|
|
602
|
+
windowsHide: process.platform === 'win32',
|
|
603
|
+
});
|
|
604
|
+
let stderr = '';
|
|
605
|
+
child.stderr?.on('data', (chunk) => {
|
|
606
|
+
stderr += String(chunk);
|
|
607
|
+
});
|
|
608
|
+
child.on('error', reject);
|
|
609
|
+
child.on('close', (code) => {
|
|
610
|
+
if (code === 0) {
|
|
611
|
+
resolve();
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
reject(new Error(stderr.trim() || `Open command failed with exit code ${code ?? 'unknown'}.`));
|
|
615
|
+
});
|
|
616
|
+
});
|
|
618
617
|
}
|
|
619
618
|
function buildManagedLoginCommand(command) {
|
|
620
619
|
const managedPath = (0, managed_agent_paths_1.buildPathWithManagedAgentBins)(process.env.PATH);
|
|
@@ -1114,36 +1113,6 @@ class AiHubServer {
|
|
|
1114
1113
|
// Lightweight markdown → .docx. Shared by the GET (file path) and POST (inline
|
|
1115
1114
|
// content) export routes so a conversational deliverable with no on-disk file
|
|
1116
1115
|
// can still be downloaded for Word annotation.
|
|
1117
|
-
async markdownToDocxBuffer(md) {
|
|
1118
|
-
const html = md
|
|
1119
|
-
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
1120
|
-
// headings
|
|
1121
|
-
.replace(/^#### (.+)$/gm, '<h4>$1</h4>')
|
|
1122
|
-
.replace(/^### (.+)$/gm, '<h3>$1</h3>')
|
|
1123
|
-
.replace(/^## (.+)$/gm, '<h2>$1</h2>')
|
|
1124
|
-
.replace(/^# (.+)$/gm, '<h1>$1</h1>')
|
|
1125
|
-
// bold, italic, code
|
|
1126
|
-
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
|
|
1127
|
-
.replace(/\*(.+?)\*/g, '<em>$1</em>')
|
|
1128
|
-
.replace(/`(.+?)`/g, '<code>$1</code>')
|
|
1129
|
-
// unordered lists
|
|
1130
|
-
.replace(/^[-*] (.+)$/gm, '<li>$1</li>')
|
|
1131
|
-
// horizontal rules
|
|
1132
|
-
.replace(/^---+$/gm, '<hr/>')
|
|
1133
|
-
// paragraphs (double newlines)
|
|
1134
|
-
.replace(/\n{2,}/g, '</p><p>')
|
|
1135
|
-
.replace(/^/, '<p>')
|
|
1136
|
-
.replace(/$/, '</p>')
|
|
1137
|
-
// clean up list items into a ul
|
|
1138
|
-
.replace(/(<li>.+<\/li>\n?)+/g, (m) => `<ul>${m}</ul>`);
|
|
1139
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
1140
|
-
const htmlToDocx = require('html-to-docx');
|
|
1141
|
-
return await htmlToDocx(`<html><body>${html}</body></html>`, null, {
|
|
1142
|
-
table: { row: { cantSplit: true } },
|
|
1143
|
-
footer: false,
|
|
1144
|
-
pageNumber: false,
|
|
1145
|
-
});
|
|
1146
|
-
}
|
|
1147
1116
|
prepareStartPayload(projectPath, hostId, selectedJobId, instructions) {
|
|
1148
1117
|
const explicit = (0, manager_turns_1.extractExplicitFraimInvocation)(instructions);
|
|
1149
1118
|
const resolvedJobId = explicit?.jobId || selectedJobId;
|
|
@@ -1202,7 +1171,7 @@ class AiHubServer {
|
|
|
1202
1171
|
key: bundle.personaKey,
|
|
1203
1172
|
displayName: bundle.catalogMetadata.displayName,
|
|
1204
1173
|
role: bundle.catalogMetadata.role,
|
|
1205
|
-
avatarUrl: buildPersonaAvatarUrl(bundle.personaKey),
|
|
1174
|
+
avatarUrl: (0, persona_hiring_1.buildPersonaAvatarUrl)(bundle.personaKey),
|
|
1206
1175
|
pricingLabel: bundle.catalogMetadata.pricingLabel,
|
|
1207
1176
|
status: 'locked',
|
|
1208
1177
|
hireUrl: buildHubPersonaHireUrl(bundle.personaKey, bundle.defaultHireMode),
|
|
@@ -1240,7 +1209,7 @@ class AiHubServer {
|
|
|
1240
1209
|
key: bundle.personaKey,
|
|
1241
1210
|
displayName: bundle.catalogMetadata.displayName,
|
|
1242
1211
|
role: bundle.catalogMetadata.role,
|
|
1243
|
-
avatarUrl: buildPersonaAvatarUrl(bundle.personaKey),
|
|
1212
|
+
avatarUrl: (0, persona_hiring_1.buildPersonaAvatarUrl)(bundle.personaKey),
|
|
1244
1213
|
pricingLabel: hiredKeys.has(bundle.personaKey) ? '' : bundle.catalogMetadata.pricingLabel,
|
|
1245
1214
|
status: (hiredKeys.has(bundle.personaKey) ? 'hired' : 'locked'),
|
|
1246
1215
|
hireUrl: buildHubPersonaHireUrl(bundle.personaKey, bundle.defaultHireMode),
|
|
@@ -1583,6 +1552,14 @@ class AiHubServer {
|
|
|
1583
1552
|
? path_1.default.resolve(body.projectPath)
|
|
1584
1553
|
: this.projectPath;
|
|
1585
1554
|
const loc = (0, learning_context_builder_1.resolveTeamContextFile)(projectPath, body.key);
|
|
1555
|
+
if (loc.managedByOrgSync || !loc.writePath) {
|
|
1556
|
+
// Enforcement only: block editing a synced org file (it would be
|
|
1557
|
+
// overwritten on next sync). The how-to-change procedure lives in the
|
|
1558
|
+
// organization-onboarding job, not in this error body (issue #563 review).
|
|
1559
|
+
return res.status(409).json({
|
|
1560
|
+
error: 'This organization file is managed by org sync and is read-only here.'
|
|
1561
|
+
});
|
|
1562
|
+
}
|
|
1586
1563
|
const dest = path_1.default.resolve(loc.writePath);
|
|
1587
1564
|
// Path-traversal guard: the resolved destination must live under a
|
|
1588
1565
|
// personalized-employee directory (covers both ~/.fraim/... and repo-local).
|
|
@@ -1600,59 +1577,7 @@ class AiHubServer {
|
|
|
1600
1577
|
// Re-read so the client gets the canonical post-write state (present flips).
|
|
1601
1578
|
return res.json(readContextFile(projectPath, body.key));
|
|
1602
1579
|
});
|
|
1603
|
-
|
|
1604
|
-
// GET /api/ai-hub/artifact/export-docx?path=<abs-path-to-md>
|
|
1605
|
-
// Converts a markdown file to .docx using html-to-docx and streams the result.
|
|
1606
|
-
// The manager downloads it, annotates in Word, and saves it in place on disk
|
|
1607
|
-
// (no upload). The agent then reads the comments + tracked changes via the
|
|
1608
|
-
// `apply-docx-changes-to-md` skill (registry script extract-docx-edits.js)
|
|
1609
|
-
// during the address-feedback phase.
|
|
1610
|
-
this.app.get('/api/ai-hub/artifact/export-docx', async (req, res) => {
|
|
1611
|
-
const rawPath = typeof req.query.path === 'string' ? req.query.path : '';
|
|
1612
|
-
if (!rawPath)
|
|
1613
|
-
return res.status(400).json({ error: 'path is required.' });
|
|
1614
|
-
// Safety: path must be under the current workspace or a known safe root.
|
|
1615
|
-
const resolved = resolveSafeArtifactPath(rawPath, this.projectPath);
|
|
1616
|
-
if (!resolved)
|
|
1617
|
-
return res.status(403).json({ error: 'Path outside allowed roots.' });
|
|
1618
|
-
if (!fs_1.default.existsSync(resolved))
|
|
1619
|
-
return res.status(404).json({ error: 'File not found.' });
|
|
1620
|
-
try {
|
|
1621
|
-
const md = fs_1.default.readFileSync(resolved, 'utf8');
|
|
1622
|
-
const docxBuf = await this.markdownToDocxBuffer(md);
|
|
1623
|
-
const basename = path_1.default.basename(resolved, path_1.default.extname(resolved));
|
|
1624
|
-
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document');
|
|
1625
|
-
res.setHeader('Content-Disposition', `attachment; filename="${basename}.docx"`);
|
|
1626
|
-
res.send(docxBuf);
|
|
1627
|
-
}
|
|
1628
|
-
catch (err) {
|
|
1629
|
-
res.status(500).json({ error: err instanceof Error ? err.message : 'Export failed.' });
|
|
1630
|
-
}
|
|
1631
|
-
});
|
|
1632
|
-
// POST /api/ai-hub/artifact/export-docx { content, filename? }
|
|
1633
|
-
// Converts inline markdown (the employee's conversational deliverable) to .docx
|
|
1634
|
-
// when the run produced no on-disk file — e.g. an onboarding answer to an empty
|
|
1635
|
-
// repo. Keeps the "annotate in Word" review flow working instead of erroring
|
|
1636
|
-
// with "no local artifact path available".
|
|
1637
|
-
this.app.post('/api/ai-hub/artifact/export-docx', async (req, res) => {
|
|
1638
|
-
const content = typeof req.body?.content === 'string' ? req.body.content : '';
|
|
1639
|
-
if (!content.trim())
|
|
1640
|
-
return res.status(400).json({ error: 'content is required.' });
|
|
1641
|
-
const rawName = typeof req.body?.filename === 'string' && req.body.filename.trim()
|
|
1642
|
-
? req.body.filename.trim()
|
|
1643
|
-
: 'deliverable';
|
|
1644
|
-
const safeName = rawName.replace(/[^a-zA-Z0-9._ -]/g, '').replace(/\.docx?$/i, '').slice(0, 80) || 'deliverable';
|
|
1645
|
-
try {
|
|
1646
|
-
const docxBuf = await this.markdownToDocxBuffer(content);
|
|
1647
|
-
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document');
|
|
1648
|
-
res.setHeader('Content-Disposition', `attachment; filename="${safeName}.docx"`);
|
|
1649
|
-
res.send(docxBuf);
|
|
1650
|
-
}
|
|
1651
|
-
catch (err) {
|
|
1652
|
-
res.status(500).json({ error: err instanceof Error ? err.message : 'Export failed.' });
|
|
1653
|
-
}
|
|
1654
|
-
});
|
|
1655
|
-
this.app.post('/api/ai-hub/artifact/open', (req, res) => {
|
|
1580
|
+
this.app.post('/api/ai-hub/artifact/open', async (req, res) => {
|
|
1656
1581
|
const rawPath = typeof req.body?.path === 'string' ? req.body.path : '';
|
|
1657
1582
|
const projectPath = typeof req.body?.projectPath === 'string' && req.body.projectPath.length > 0
|
|
1658
1583
|
? path_1.default.resolve(req.body.projectPath)
|
|
@@ -1665,7 +1590,7 @@ class AiHubServer {
|
|
|
1665
1590
|
if (!fs_1.default.existsSync(resolved))
|
|
1666
1591
|
return res.status(404).json({ error: 'File not found.' });
|
|
1667
1592
|
try {
|
|
1668
|
-
hubOpenFile(resolved);
|
|
1593
|
+
await hubOpenFile(resolved);
|
|
1669
1594
|
return res.json({ ok: true, path: resolved });
|
|
1670
1595
|
}
|
|
1671
1596
|
catch (err) {
|
|
@@ -1,4 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
37
|
};
|
|
@@ -9,7 +42,6 @@ const chalk_1 = __importDefault(require("chalk"));
|
|
|
9
42
|
const child_process_1 = require("child_process");
|
|
10
43
|
const server_1 = require("../../first-run/server");
|
|
11
44
|
const session_service_1 = require("../../first-run/session-service");
|
|
12
|
-
const server_2 = require("../../ai-hub/server");
|
|
13
45
|
function openBrowser(url) {
|
|
14
46
|
try {
|
|
15
47
|
if (process.platform === 'win32') {
|
|
@@ -45,7 +77,10 @@ const runFirstRun = async (options) => {
|
|
|
45
77
|
projectRoot: options.projectRoot,
|
|
46
78
|
});
|
|
47
79
|
const server = new server_1.FirstRunServer({ sessionService });
|
|
48
|
-
|
|
80
|
+
// Lazy import: ai-hub/server pulls server-only code not shipped in the client
|
|
81
|
+
// package; keep it out of the CLI startup graph so the packed client loads (#422).
|
|
82
|
+
const { findAvailablePort } = await Promise.resolve().then(() => __importStar(require('../../ai-hub/server')));
|
|
83
|
+
const port = await findAvailablePort(43120);
|
|
49
84
|
const url = `http://127.0.0.1:${port}/first-run/`;
|
|
50
85
|
await server.start(port);
|
|
51
86
|
console.log(chalk_1.default.blue('Starting FRAIM first-run...'));
|
|
@@ -1,11 +1,46 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
37
|
};
|
|
5
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
39
|
exports.hubCommand = void 0;
|
|
7
40
|
const commander_1 = require("commander");
|
|
8
|
-
|
|
41
|
+
// ai-hub/server pulls in server-only code (persona-hiring etc.) that is not
|
|
42
|
+
// shipped in the client package; it is imported lazily inside the action below
|
|
43
|
+
// to keep it out of the CLI startup graph so the packed client loads (issue #422).
|
|
9
44
|
const git_utils_1 = require("../../core/utils/git-utils");
|
|
10
45
|
const path_1 = __importDefault(require("path"));
|
|
11
46
|
const child_process_1 = require("child_process");
|
|
@@ -69,13 +104,14 @@ exports.hubCommand = new commander_1.Command('hub')
|
|
|
69
104
|
.option('--no-open', 'Do not open the hub after startup')
|
|
70
105
|
.option('--browser', 'Open in the default browser instead of the desktop shell')
|
|
71
106
|
.action(async (options) => {
|
|
107
|
+
const { AiHubServer, findAvailablePort } = await Promise.resolve().then(() => __importStar(require('../../ai-hub/server')));
|
|
72
108
|
const preferredPort = options.port || (0, git_utils_1.getPort)() + 100;
|
|
73
109
|
const projectPath = path_1.default.resolve(options.projectPath || process.cwd());
|
|
74
110
|
if (options.open) {
|
|
75
111
|
const openedDesktop = !options.browser && openDesktopWindow(projectPath, preferredPort);
|
|
76
112
|
if (!openedDesktop) {
|
|
77
|
-
const port = await
|
|
78
|
-
const server = new
|
|
113
|
+
const port = await findAvailablePort(preferredPort);
|
|
114
|
+
const server = new AiHubServer({ projectPath });
|
|
79
115
|
await server.start(port);
|
|
80
116
|
const url = `http://127.0.0.1:${port}/ai-hub/`;
|
|
81
117
|
console.log(`AI Hub running at ${url}`);
|
|
@@ -87,8 +123,8 @@ exports.hubCommand = new commander_1.Command('hub')
|
|
|
87
123
|
console.log(`Project path: ${projectPath}`);
|
|
88
124
|
return;
|
|
89
125
|
}
|
|
90
|
-
const port = await
|
|
91
|
-
const server = new
|
|
126
|
+
const port = await findAvailablePort(preferredPort);
|
|
127
|
+
const server = new AiHubServer({ projectPath });
|
|
92
128
|
await server.start(port);
|
|
93
129
|
const url = `http://127.0.0.1:${port}/ai-hub/`;
|
|
94
130
|
console.log(`AI Hub running at ${url}`);
|
|
@@ -210,22 +210,23 @@ const runInitProject = async (options = {}) => {
|
|
|
210
210
|
if (!isMinimalConversationMode(preferredMode)) {
|
|
211
211
|
console.log(chalk_1.default.blue(` Platform: ${formatPlatformLabel(detection.provider)}`));
|
|
212
212
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
const repo = detection.repository;
|
|
216
|
-
if (repo.owner && repo.name) {
|
|
217
|
-
console.log(chalk_1.default.gray(` Repository: ${repo.owner}/${repo.name}`));
|
|
218
|
-
}
|
|
219
|
-
else if (repo.organization && repo.project && repo.name) {
|
|
220
|
-
console.log(chalk_1.default.gray(` Organization: ${repo.organization}`));
|
|
221
|
-
console.log(chalk_1.default.gray(` Project: ${repo.project}`));
|
|
222
|
-
console.log(chalk_1.default.gray(` Repository: ${repo.name}`));
|
|
223
|
-
}
|
|
224
|
-
else if (repo.namespace && repo.name) {
|
|
225
|
-
console.log(chalk_1.default.gray(` Namespace: ${repo.namespace || '(none)'}`));
|
|
226
|
-
console.log(chalk_1.default.gray(` Repository: ${repo.name}`));
|
|
213
|
+
else {
|
|
214
|
+
console.log(chalk_1.default.blue(` Repository-backed project: ${formatPlatformLabel(detection.provider)}`));
|
|
227
215
|
}
|
|
228
216
|
}
|
|
217
|
+
const repo = detection.repository;
|
|
218
|
+
if (repo.owner && repo.name) {
|
|
219
|
+
console.log(chalk_1.default.gray(` Repository: ${repo.owner}/${repo.name}`));
|
|
220
|
+
}
|
|
221
|
+
else if (repo.organization && repo.project && repo.name) {
|
|
222
|
+
console.log(chalk_1.default.gray(` Organization: ${repo.organization}`));
|
|
223
|
+
console.log(chalk_1.default.gray(` Project: ${repo.project}`));
|
|
224
|
+
console.log(chalk_1.default.gray(` Repository: ${repo.name}`));
|
|
225
|
+
}
|
|
226
|
+
else if (repo.namespace && repo.name) {
|
|
227
|
+
console.log(chalk_1.default.gray(` Namespace: ${repo.namespace || '(none)'}`));
|
|
228
|
+
console.log(chalk_1.default.gray(` Repository: ${repo.name}`));
|
|
229
|
+
}
|
|
229
230
|
}
|
|
230
231
|
else {
|
|
231
232
|
result.mode = 'conversational';
|
|
@@ -144,6 +144,42 @@ const runSync = async (options) => {
|
|
|
144
144
|
console.log(chalk_1.default.green('Removed legacy FRAIM sync block from .gitignore'));
|
|
145
145
|
}
|
|
146
146
|
};
|
|
147
|
+
// Issue #563: refresh the machine-level org cache (~/.fraim/org/) from the
|
|
148
|
+
// configured org backend. Failures never fail the sync: an existing cache is
|
|
149
|
+
// served stale with its age (R2.3, R4.3).
|
|
150
|
+
const refreshOrgCache = async (remoteUrl, apiKey) => {
|
|
151
|
+
// Org sync is a network round-trip to the org backend. In automated
|
|
152
|
+
// tests there is no org configured and no server to reach, so skip it
|
|
153
|
+
// entirely rather than emit warnings or attempt a real request. Local
|
|
154
|
+
// dev (--local / FRAIM_LOCAL_SYNC) passes the loopback URL + 'local-dev'
|
|
155
|
+
// key below, which syncOrgCache treats as "no cloud org" unless a git
|
|
156
|
+
// backend is configured, so it degrades cleanly without this guard.
|
|
157
|
+
if (process.env.TEST_MODE === 'true')
|
|
158
|
+
return;
|
|
159
|
+
const { syncOrgCache } = await Promise.resolve().then(() => __importStar(require('../utils/org-pack-sync')));
|
|
160
|
+
const outcome = await syncOrgCache({ remoteUrl, apiKey });
|
|
161
|
+
if (outcome.status === 'synced') {
|
|
162
|
+
console.log(chalk_1.default.green(`Org context synced (${outcome.metadata.backend}, version ${outcome.metadata.version.slice(0, 12)})`));
|
|
163
|
+
}
|
|
164
|
+
else if (outcome.status === 'stale') {
|
|
165
|
+
console.log(chalk_1.default.yellow(`Org source unreachable. Using cached org context from ${Math.round(outcome.ageHours)}h ago. Will refresh when reachable.`));
|
|
166
|
+
}
|
|
167
|
+
else if (outcome.status === 'absent') {
|
|
168
|
+
console.log(chalk_1.default.yellow(`Org context not synced: ${outcome.error}`));
|
|
169
|
+
}
|
|
170
|
+
// 'disabled' (no org configured) stays silent.
|
|
171
|
+
// R8.1: one-time publish offer for legacy machine-local org files. The
|
|
172
|
+
// publish itself runs through organization-onboarding (propose-and-approve);
|
|
173
|
+
// sync only surfaces the offer and never moves files on its own (R8.2).
|
|
174
|
+
if (outcome.status !== 'disabled') {
|
|
175
|
+
const { detectLegacyOrgArtifacts } = await Promise.resolve().then(() => __importStar(require('../utils/org-migration')));
|
|
176
|
+
const legacy = detectLegacyOrgArtifacts();
|
|
177
|
+
if (legacy.length > 0) {
|
|
178
|
+
console.log(chalk_1.default.yellow(`Found ${legacy.length} legacy machine-local org file(s) at ~/.fraim/personalized-employee/.`));
|
|
179
|
+
console.log(chalk_1.default.yellow('Run the organization-onboarding job to publish them to your shared org backend; the originals are archived to ~/.fraim/backups/.'));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
};
|
|
147
183
|
const isNpx = process.env.npm_config_prefix === undefined || process.env.npm_lifecycle_event === 'npx';
|
|
148
184
|
const isGlobal = !isNpx && (process.env.npm_config_global === 'true' || process.env.npm_config_prefix);
|
|
149
185
|
if (isGlobal && !options.skipUpdates) {
|
|
@@ -188,6 +224,7 @@ const runSync = async (options) => {
|
|
|
188
224
|
console.log(chalk_1.default.green(line));
|
|
189
225
|
}
|
|
190
226
|
}
|
|
227
|
+
await refreshOrgCache(localUrl, 'local-dev');
|
|
191
228
|
return;
|
|
192
229
|
}
|
|
193
230
|
console.error(chalk_1.default.red(`Local sync failed: ${result.error}`));
|
|
@@ -241,6 +278,7 @@ const runSync = async (options) => {
|
|
|
241
278
|
if (adapterUpdates.length > 0) {
|
|
242
279
|
console.log(chalk_1.default.green(`Updated FRAIM agent adapter files: ${adapterUpdates.join(', ')}`));
|
|
243
280
|
}
|
|
281
|
+
await refreshOrgCache(config.remoteUrl || process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me', apiKey);
|
|
244
282
|
};
|
|
245
283
|
exports.runSync = runSync;
|
|
246
284
|
exports.syncCommand = new commander_1.Command('sync')
|
|
@@ -0,0 +1,56 @@
|
|
|
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.fetchOrgRepoSnapshot = fetchOrgRepoSnapshot;
|
|
7
|
+
/**
|
|
8
|
+
* Shallow snapshot of a customer-owned org repo (issue #563, git backend).
|
|
9
|
+
*
|
|
10
|
+
* Uses execSync git like the rest of the CLI (see core/utils/git-utils.ts);
|
|
11
|
+
* no new git dependency. The snapshot is a depth-1 clone of the default
|
|
12
|
+
* branch into a temp directory the caller copies from and then cleans up.
|
|
13
|
+
*/
|
|
14
|
+
const child_process_1 = require("child_process");
|
|
15
|
+
const fs_1 = __importDefault(require("fs"));
|
|
16
|
+
const os_1 = __importDefault(require("os"));
|
|
17
|
+
const path_1 = __importDefault(require("path"));
|
|
18
|
+
/**
|
|
19
|
+
* Org repo URLs are restricted to real transport schemes plus scp-style
|
|
20
|
+
* SSH shorthand. This blocks git's command-executing transports
|
|
21
|
+
* (`ext::`, `fd::`) and option injection via URLs starting with `-`.
|
|
22
|
+
*/
|
|
23
|
+
const ALLOWED_GIT_URL = /^(https?:\/\/|ssh:\/\/|git:\/\/|file:\/\/|[\w.-]+@[\w.-]+:)/;
|
|
24
|
+
/**
|
|
25
|
+
* Shallow-clone the org repo's default branch (R7.4). Throws on any git
|
|
26
|
+
* failure; callers translate failures into stale/absent outcomes.
|
|
27
|
+
*/
|
|
28
|
+
function fetchOrgRepoSnapshot(gitUrl) {
|
|
29
|
+
if (!ALLOWED_GIT_URL.test(gitUrl)) {
|
|
30
|
+
throw new Error(`Org repo URL has an unsupported scheme: ${gitUrl}`);
|
|
31
|
+
}
|
|
32
|
+
const dir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'fraim-org-snap-'));
|
|
33
|
+
try {
|
|
34
|
+
// Arg-array exec (no shell) and `--` so the URL can never be parsed
|
|
35
|
+
// as a git option or shell metacharacters.
|
|
36
|
+
(0, child_process_1.execFileSync)('git', ['clone', '--depth=1', '--quiet', '--', gitUrl, '.'], {
|
|
37
|
+
cwd: dir,
|
|
38
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
39
|
+
timeout: 60_000
|
|
40
|
+
});
|
|
41
|
+
const sha = (0, child_process_1.execFileSync)('git', ['rev-parse', 'HEAD'], {
|
|
42
|
+
cwd: dir,
|
|
43
|
+
encoding: 'utf8',
|
|
44
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
45
|
+
}).trim();
|
|
46
|
+
return {
|
|
47
|
+
dir,
|
|
48
|
+
sha,
|
|
49
|
+
cleanup: () => fs_1.default.rmSync(dir, { recursive: true, force: true })
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
fs_1.default.rmSync(dir, { recursive: true, force: true });
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
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.detectLegacyOrgArtifacts = detectLegacyOrgArtifacts;
|
|
7
|
+
exports.archiveLegacyOrgArtifacts = archiveLegacyOrgArtifacts;
|
|
8
|
+
/**
|
|
9
|
+
* Legacy org artifact migration (issue #563, R8).
|
|
10
|
+
*
|
|
11
|
+
* Before #563, organization-onboarding wrote org_context.md and org_rules.md
|
|
12
|
+
* machine-locally under ~/.fraim/personalized-employee/. These helpers detect
|
|
13
|
+
* those files for a one-time publish offer and archive them (never delete)
|
|
14
|
+
* once the user accepts. Declining is safe: detection is read-only and the
|
|
15
|
+
* legacy resolution tier keeps working (AC8).
|
|
16
|
+
*
|
|
17
|
+
* Manager files are personal by design and are never migrated (R3.3).
|
|
18
|
+
*/
|
|
19
|
+
const fs_1 = __importDefault(require("fs"));
|
|
20
|
+
const path_1 = __importDefault(require("path"));
|
|
21
|
+
const project_fraim_paths_1 = require("../../core/utils/project-fraim-paths");
|
|
22
|
+
const LEGACY_ORG_RELATIVE_PATHS = [
|
|
23
|
+
'context/org_context.md',
|
|
24
|
+
'rules/org_rules.md'
|
|
25
|
+
];
|
|
26
|
+
function detectLegacyOrgArtifacts() {
|
|
27
|
+
const root = path_1.default.join((0, project_fraim_paths_1.getUserFraimDirPath)(), 'personalized-employee');
|
|
28
|
+
const detected = [];
|
|
29
|
+
for (const relativePath of LEGACY_ORG_RELATIVE_PATHS) {
|
|
30
|
+
const absolutePath = path_1.default.join(root, relativePath);
|
|
31
|
+
if (fs_1.default.existsSync(absolutePath)) {
|
|
32
|
+
detected.push({ absolutePath, relativePath });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return detected;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Move legacy org artifacts into a timestamped directory under
|
|
39
|
+
* ~/.fraim/backups/ (R8.1). Returns the backup directory path.
|
|
40
|
+
*/
|
|
41
|
+
function archiveLegacyOrgArtifacts(artifacts) {
|
|
42
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
43
|
+
const backupDir = path_1.default.join((0, project_fraim_paths_1.getUserFraimDirPath)(), 'backups', `org-migration-${stamp}`);
|
|
44
|
+
for (const artifact of artifacts) {
|
|
45
|
+
const destination = path_1.default.join(backupDir, artifact.relativePath);
|
|
46
|
+
fs_1.default.mkdirSync(path_1.default.dirname(destination), { recursive: true });
|
|
47
|
+
fs_1.default.renameSync(artifact.absolutePath, destination);
|
|
48
|
+
}
|
|
49
|
+
return backupDir;
|
|
50
|
+
}
|