fraim-framework 2.0.151 → 2.0.152
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/hosts.js +16 -7
- package/dist/src/ai-hub/server.js +7 -1
- package/dist/src/cli/commands/test-mcp.js +171 -0
- package/dist/src/cli/setup/first-run.js +242 -0
- package/dist/src/core/config-writer.js +75 -0
- package/dist/src/core/utils/job-aliases.js +47 -0
- package/dist/src/core/utils/workflow-parser.js +174 -0
- package/index.js +1 -1
- package/package.json +1 -1
- package/public/ai-hub/index.html +67 -67
- package/public/ai-hub/script.js +374 -365
- package/public/ai-hub/styles.css +582 -565
- package/public/first-run/index.html +35 -35
- package/public/first-run/script.js +667 -667
- package/public/first-run/styles.css +73 -73
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WorkflowParser = void 0;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
class WorkflowParser {
|
|
7
|
+
static extractMetadataBlock(content) {
|
|
8
|
+
const frontmatterMatch = content.match(/^---\r?\n([\s\S]+?)\r?\n---/);
|
|
9
|
+
if (frontmatterMatch) {
|
|
10
|
+
try {
|
|
11
|
+
return {
|
|
12
|
+
state: 'valid',
|
|
13
|
+
metadata: JSON.parse(frontmatterMatch[1]),
|
|
14
|
+
bodyStartIndex: frontmatterMatch[0].length
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return { state: 'invalid' };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const trimmedStart = content.search(/\S/);
|
|
22
|
+
if (trimmedStart === -1 || content[trimmedStart] !== '{') {
|
|
23
|
+
return { state: 'none' };
|
|
24
|
+
}
|
|
25
|
+
let depth = 0;
|
|
26
|
+
let inString = false;
|
|
27
|
+
let escaping = false;
|
|
28
|
+
for (let i = trimmedStart; i < content.length; i++) {
|
|
29
|
+
const ch = content[i];
|
|
30
|
+
if (inString) {
|
|
31
|
+
if (escaping) {
|
|
32
|
+
escaping = false;
|
|
33
|
+
}
|
|
34
|
+
else if (ch === '\\') {
|
|
35
|
+
escaping = true;
|
|
36
|
+
}
|
|
37
|
+
else if (ch === '"') {
|
|
38
|
+
inString = false;
|
|
39
|
+
}
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (ch === '"') {
|
|
43
|
+
inString = true;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (ch === '{') {
|
|
47
|
+
depth++;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (ch === '}') {
|
|
51
|
+
depth--;
|
|
52
|
+
if (depth === 0) {
|
|
53
|
+
const bodyStartIndex = i + 1;
|
|
54
|
+
const remainder = content.slice(bodyStartIndex).trimStart();
|
|
55
|
+
// `{...}\n---` is usually malformed frontmatter, not bare JSON metadata.
|
|
56
|
+
if (remainder.startsWith('---')) {
|
|
57
|
+
return { state: 'none' };
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
return {
|
|
61
|
+
state: 'valid',
|
|
62
|
+
metadata: JSON.parse(content.slice(trimmedStart, bodyStartIndex)),
|
|
63
|
+
bodyStartIndex
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return { state: 'invalid' };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return { state: 'none' };
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Parse a workflow markdown file into a structured definition
|
|
76
|
+
* Supports three formats:
|
|
77
|
+
* 1. Phase-based workflows with JSON frontmatter
|
|
78
|
+
* 2. Phase-based workflows with bare leading JSON metadata
|
|
79
|
+
* 3. Simple workflows without metadata
|
|
80
|
+
*/
|
|
81
|
+
static parse(filePath) {
|
|
82
|
+
if (!(0, fs_1.existsSync)(filePath))
|
|
83
|
+
return null;
|
|
84
|
+
let content = (0, fs_1.readFileSync)(filePath, 'utf-8');
|
|
85
|
+
if (content.charCodeAt(0) === 0xfeff) {
|
|
86
|
+
content = content.slice(1);
|
|
87
|
+
}
|
|
88
|
+
const metadataBlock = this.extractMetadataBlock(content);
|
|
89
|
+
if (metadataBlock.state === 'invalid') {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
if (metadataBlock.state === 'valid') {
|
|
93
|
+
return this.parsePhaseBasedWorkflow(filePath, content, metadataBlock.metadata, metadataBlock.bodyStartIndex);
|
|
94
|
+
}
|
|
95
|
+
return this.parseSimpleWorkflow(filePath, content);
|
|
96
|
+
}
|
|
97
|
+
static parsePhaseBasedWorkflow(filePath, content, metadata, bodyStartIndex) {
|
|
98
|
+
const contentAfterMetadata = content.substring(bodyStartIndex).trim();
|
|
99
|
+
const firstPhaseIndex = contentAfterMetadata.search(/^##\s+Phase:/m);
|
|
100
|
+
let overview = '';
|
|
101
|
+
let restOfContent = '';
|
|
102
|
+
if (firstPhaseIndex !== -1) {
|
|
103
|
+
overview = contentAfterMetadata.substring(0, firstPhaseIndex).trim();
|
|
104
|
+
restOfContent = contentAfterMetadata.substring(firstPhaseIndex);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
overview = contentAfterMetadata;
|
|
108
|
+
}
|
|
109
|
+
const phases = new Map();
|
|
110
|
+
const phaseSections = restOfContent.split(/^##\s+Phase:\s+/m);
|
|
111
|
+
if (!metadata.phases) {
|
|
112
|
+
metadata.phases = {};
|
|
113
|
+
}
|
|
114
|
+
for (let i = 1; i < phaseSections.length; i++) {
|
|
115
|
+
const section = phaseSections[i];
|
|
116
|
+
const sectionLines = section.split('\n');
|
|
117
|
+
const firstLine = sectionLines[0].trim();
|
|
118
|
+
const id = firstLine.split(/[ (]/)[0].trim().toLowerCase();
|
|
119
|
+
phases.set(id, `## Phase: ${section.trim()}`);
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
metadata,
|
|
123
|
+
overview,
|
|
124
|
+
phases,
|
|
125
|
+
isSimple: false,
|
|
126
|
+
path: filePath
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
static parseSimpleWorkflow(filePath, content) {
|
|
130
|
+
const workflowName = (0, path_1.basename)(filePath, '.md');
|
|
131
|
+
const metadata = {
|
|
132
|
+
name: workflowName
|
|
133
|
+
};
|
|
134
|
+
return {
|
|
135
|
+
metadata,
|
|
136
|
+
overview: content.trim(),
|
|
137
|
+
phases: new Map(),
|
|
138
|
+
isSimple: true,
|
|
139
|
+
path: filePath
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
static parseContent(content, name, path) {
|
|
143
|
+
if (content.charCodeAt(0) === 0xfeff) {
|
|
144
|
+
content = content.slice(1);
|
|
145
|
+
}
|
|
146
|
+
const metadataBlock = this.extractMetadataBlock(content);
|
|
147
|
+
if (metadataBlock.state === 'invalid') {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
if (metadataBlock.state === 'valid') {
|
|
151
|
+
return this.parsePhaseBasedWorkflow(path || `content:${name}`, content, metadataBlock.metadata, metadataBlock.bodyStartIndex);
|
|
152
|
+
}
|
|
153
|
+
return this.parseSimpleWorkflow(path || `content:${name}`, content);
|
|
154
|
+
}
|
|
155
|
+
static getOverviewFromContent(content, name) {
|
|
156
|
+
const wf = this.parseContent(content, name);
|
|
157
|
+
return wf ? wf.overview : null;
|
|
158
|
+
}
|
|
159
|
+
static getOverview(filePath) {
|
|
160
|
+
const wf = this.parse(filePath);
|
|
161
|
+
return wf ? wf.overview : null;
|
|
162
|
+
}
|
|
163
|
+
static extractDescription(filePath) {
|
|
164
|
+
const wf = this.parse(filePath);
|
|
165
|
+
if (!wf)
|
|
166
|
+
return '';
|
|
167
|
+
const intentMatch = wf.overview.match(/## Intent\s+([\s\S]+?)(?:\r?\n##|$)/);
|
|
168
|
+
if (intentMatch)
|
|
169
|
+
return intentMatch[1].trim().split(/\r?\n/)[0];
|
|
170
|
+
const firstPara = wf.overview.split(/\r?\n/).find(l => l.trim() !== '' && !l.startsWith('#'));
|
|
171
|
+
return firstPara ? firstPara.trim() : '';
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
exports.WorkflowParser = WorkflowParser;
|
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fraim-framework",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.152",
|
|
4
4
|
"description": "FRAIM: AI Workforce Infrastructure — the organizational capability that turns AI agents into an accountable workforce, their operators into capable AI managers, and executives into leaders with clear optics on AI proficiency.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": "./bin/fraim.js",
|
package/public/ai-hub/index.html
CHANGED
|
@@ -11,16 +11,16 @@
|
|
|
11
11
|
|
|
12
12
|
<div class="page">
|
|
13
13
|
|
|
14
|
-
<header class="header">
|
|
15
|
-
<div class="header-copy">
|
|
16
|
-
<span class="header-eyebrow">FRAIM Hub</span>
|
|
17
|
-
<h1>AI Hub</h1>
|
|
18
|
-
</div>
|
|
19
|
-
<button class="project-button" type="button" id="project-button">
|
|
20
|
-
<span class="folder">Project</span>
|
|
21
|
-
<strong id="project-name">Choose a folder</strong>
|
|
22
|
-
</button>
|
|
23
|
-
</header>
|
|
14
|
+
<header class="header">
|
|
15
|
+
<div class="header-copy">
|
|
16
|
+
<span class="header-eyebrow">FRAIM Hub</span>
|
|
17
|
+
<h1>AI Hub</h1>
|
|
18
|
+
</div>
|
|
19
|
+
<button class="project-button" type="button" id="project-button">
|
|
20
|
+
<span class="folder">Project</span>
|
|
21
|
+
<strong id="project-name">Choose a folder</strong>
|
|
22
|
+
</button>
|
|
23
|
+
</header>
|
|
24
24
|
|
|
25
25
|
<section class="welcome">
|
|
26
26
|
Hi <strong class="you">there</strong>, remember, you are the
|
|
@@ -66,20 +66,20 @@
|
|
|
66
66
|
</section>
|
|
67
67
|
|
|
68
68
|
<div class="layout">
|
|
69
|
-
<aside class="rail">
|
|
70
|
-
<button class="new-conv" type="button" id="new-conv-btn">+ New job</button>
|
|
71
|
-
<div class="rail-note">Alpha: browser shell for directing employees across your project.</div>
|
|
72
|
-
<!-- R2.4: team roster — horizontal row of avatar chips per hired persona -->
|
|
73
|
-
<section class="rail-section rail-section--employees">
|
|
74
|
-
<div class="rail-section-label">Hired employees</div>
|
|
75
|
-
<div class="team-roster" id="team-roster" hidden></div>
|
|
76
|
-
</section>
|
|
77
|
-
|
|
78
|
-
<section class="rail-section rail-section--runs">
|
|
79
|
-
<div class="rail-section-label">Runs</div>
|
|
80
|
-
<div class="conv-list" id="conv-list"></div>
|
|
81
|
-
</section>
|
|
82
|
-
</aside>
|
|
69
|
+
<aside class="rail">
|
|
70
|
+
<button class="new-conv" type="button" id="new-conv-btn">+ New job</button>
|
|
71
|
+
<div class="rail-note">Alpha: browser shell for directing employees across your project.</div>
|
|
72
|
+
<!-- R2.4: team roster — horizontal row of avatar chips per hired persona -->
|
|
73
|
+
<section class="rail-section rail-section--employees">
|
|
74
|
+
<div class="rail-section-label">Hired employees</div>
|
|
75
|
+
<div class="team-roster" id="team-roster" hidden></div>
|
|
76
|
+
</section>
|
|
77
|
+
|
|
78
|
+
<section class="rail-section rail-section--runs">
|
|
79
|
+
<div class="rail-section-label">Runs</div>
|
|
80
|
+
<div class="conv-list" id="conv-list"></div>
|
|
81
|
+
</section>
|
|
82
|
+
</aside>
|
|
83
83
|
|
|
84
84
|
<main class="conversation" id="conversation">
|
|
85
85
|
<div class="empty-state" id="empty">
|
|
@@ -87,25 +87,25 @@
|
|
|
87
87
|
<p>Pick an existing job from the left, or click <strong>+ New job</strong> to give your employee something to work on.</p>
|
|
88
88
|
</div>
|
|
89
89
|
|
|
90
|
-
<div id="active-conv" hidden>
|
|
91
|
-
<div class="conv-topline">
|
|
92
|
-
<div class="employee-identity" id="active-identity"></div>
|
|
93
|
-
<div class="run-state-pill" id="run-state-pill"></div>
|
|
94
|
-
</div>
|
|
95
|
-
|
|
96
|
-
<div class="conv-header">
|
|
97
|
-
<div class="title-block">
|
|
98
|
-
<h2 id="active-title"></h2>
|
|
99
|
-
<div class="conv-job" id="active-job"></div>
|
|
100
|
-
</div>
|
|
101
|
-
<div id="artifact-slot"></div>
|
|
102
|
-
</div>
|
|
103
|
-
|
|
104
|
-
<div class="summary-strip" id="summary-strip"></div>
|
|
105
|
-
|
|
106
|
-
<!-- Issue #347 R1: pizza tracker. Hidden when the active job
|
|
107
|
-
declares no phases. Populated by renderTracker() in script.js. -->
|
|
108
|
-
<div class="tracker" id="tracker" aria-label="Job progress" hidden>
|
|
90
|
+
<div id="active-conv" hidden>
|
|
91
|
+
<div class="conv-topline">
|
|
92
|
+
<div class="employee-identity" id="active-identity"></div>
|
|
93
|
+
<div class="run-state-pill" id="run-state-pill"></div>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<div class="conv-header">
|
|
97
|
+
<div class="title-block">
|
|
98
|
+
<h2 id="active-title"></h2>
|
|
99
|
+
<div class="conv-job" id="active-job"></div>
|
|
100
|
+
</div>
|
|
101
|
+
<div id="artifact-slot"></div>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<div class="summary-strip" id="summary-strip"></div>
|
|
105
|
+
|
|
106
|
+
<!-- Issue #347 R1: pizza tracker. Hidden when the active job
|
|
107
|
+
declares no phases. Populated by renderTracker() in script.js. -->
|
|
108
|
+
<div class="tracker" id="tracker" aria-label="Job progress" hidden>
|
|
109
109
|
<div class="tracker-rows" id="tracker-rows"></div>
|
|
110
110
|
<div class="tracker-note" id="tracker-note" hidden></div>
|
|
111
111
|
</div>
|
|
@@ -115,32 +115,32 @@
|
|
|
115
115
|
<span class="latest" id="latest"></span>
|
|
116
116
|
</div>
|
|
117
117
|
|
|
118
|
-
<section class="thread-surface" aria-label="Manager and employee thread">
|
|
119
|
-
<div class="thread-surface-label">Manager and employee thread</div>
|
|
120
|
-
<div class="messages" id="messages"></div>
|
|
121
|
-
</section>
|
|
118
|
+
<section class="thread-surface" aria-label="Manager and employee thread">
|
|
119
|
+
<div class="thread-surface-label">Manager and employee thread</div>
|
|
120
|
+
<div class="messages" id="messages"></div>
|
|
121
|
+
</section>
|
|
122
122
|
|
|
123
123
|
<div class="coach">
|
|
124
|
-
<div class="coach-title-row">
|
|
125
|
-
<span class="section-title">Coach the employee</span>
|
|
126
|
-
<span class="active-employee-row">
|
|
127
|
-
<label for="active-employee-select" class="active-employee-label">tool</label>
|
|
128
|
-
<select id="active-employee-select" class="employee-select inline"></select>
|
|
129
|
-
</span>
|
|
130
|
-
</div>
|
|
131
|
-
<textarea id="coach-text" placeholder="Tell the employee what to do next…"></textarea>
|
|
132
|
-
<div class="coach-actions">
|
|
133
|
-
<!-- Issue #347 R2: template picker. Hidden when the project
|
|
134
|
-
has no manager-job templates. -->
|
|
135
|
-
<button class="ghost" type="button" id="template-picker-btn" aria-haspopup="menu" aria-expanded="false" hidden>Use a template ▾</button>
|
|
136
|
-
<button class="send-button" type="button" id="send" disabled>Send</button>
|
|
137
|
-
<div class="template-popover" id="template-popover" role="menu" hidden></div>
|
|
138
|
-
</div>
|
|
139
|
-
<div class="coach-note" id="coach-note"></div>
|
|
140
|
-
<!-- Issue #347 R4: run-level totals. Discoverable, not dominating.
|
|
141
|
-
Populated by renderTotals() each poll tick. -->
|
|
142
|
-
<div class="totals" id="totals" aria-label="Run totals" hidden></div>
|
|
143
|
-
</div>
|
|
124
|
+
<div class="coach-title-row">
|
|
125
|
+
<span class="section-title">Coach the employee</span>
|
|
126
|
+
<span class="active-employee-row">
|
|
127
|
+
<label for="active-employee-select" class="active-employee-label">tool</label>
|
|
128
|
+
<select id="active-employee-select" class="employee-select inline"></select>
|
|
129
|
+
</span>
|
|
130
|
+
</div>
|
|
131
|
+
<textarea id="coach-text" placeholder="Tell the employee what to do next…"></textarea>
|
|
132
|
+
<div class="coach-actions">
|
|
133
|
+
<!-- Issue #347 R2: template picker. Hidden when the project
|
|
134
|
+
has no manager-job templates. -->
|
|
135
|
+
<button class="ghost" type="button" id="template-picker-btn" aria-haspopup="menu" aria-expanded="false" hidden>Use a template ▾</button>
|
|
136
|
+
<button class="send-button" type="button" id="send" disabled>Send</button>
|
|
137
|
+
<div class="template-popover" id="template-popover" role="menu" hidden></div>
|
|
138
|
+
</div>
|
|
139
|
+
<div class="coach-note" id="coach-note"></div>
|
|
140
|
+
<!-- Issue #347 R4: run-level totals. Discoverable, not dominating.
|
|
141
|
+
Populated by renderTotals() each poll tick. -->
|
|
142
|
+
<div class="totals" id="totals" aria-label="Run totals" hidden></div>
|
|
143
|
+
</div>
|
|
144
144
|
|
|
145
145
|
<details class="micro" id="micro-manage">
|
|
146
146
|
<summary>Micro-manage — raw host details</summary>
|