fraim-framework 2.0.124 → 2.0.127

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 (46) hide show
  1. package/bin/fraim.js +1 -1
  2. package/dist/src/ai-hub/catalog.js +280 -44
  3. package/dist/src/ai-hub/desktop-main.js +2 -2
  4. package/dist/src/ai-hub/hosts.js +384 -10
  5. package/dist/src/ai-hub/server.js +255 -9
  6. package/dist/src/cli/commands/add-ide.js +4 -3
  7. package/dist/src/cli/commands/first-run.js +61 -0
  8. package/dist/src/cli/commands/hub.js +4 -4
  9. package/dist/src/cli/commands/init-project.js +4 -4
  10. package/dist/src/cli/commands/setup.js +4 -3
  11. package/dist/src/cli/commands/sync.js +21 -2
  12. package/dist/src/cli/doctor/checks/ide-config-checks.js +20 -2
  13. package/dist/src/cli/fraim.js +2 -0
  14. package/dist/src/cli/mcp/ide-formats.js +29 -1
  15. package/dist/src/cli/mcp/mcp-server-registry.js +1 -0
  16. package/dist/src/cli/setup/auto-mcp-setup.js +14 -8
  17. package/dist/src/cli/setup/ide-detector.js +32 -1
  18. package/dist/src/cli/setup/ide-global-integration.js +5 -1
  19. package/dist/src/cli/setup/ide-invocation-surfaces.js +70 -17
  20. package/dist/src/cli/setup/mcp-config-generator.js +12 -1
  21. package/dist/src/cli/utils/agent-adapters.js +12 -2
  22. package/dist/src/cli/utils/project-bootstrap.js +4 -3
  23. package/dist/src/core/quality-evidence.js +81 -8
  24. package/dist/src/core/utils/git-utils.js +32 -7
  25. package/dist/src/core/utils/job-aliases.js +47 -0
  26. package/dist/src/core/utils/workflow-parser.js +3 -5
  27. package/dist/src/first-run/install-state.js +68 -0
  28. package/dist/src/first-run/server.js +153 -0
  29. package/dist/src/first-run/session-service.js +302 -0
  30. package/dist/src/first-run/types.js +40 -0
  31. package/dist/src/local-mcp-server/agent-token-prices.js +114 -0
  32. package/dist/src/local-mcp-server/codex-token-adapter.js +232 -0
  33. package/dist/src/local-mcp-server/learning-context-builder.js +21 -8
  34. package/dist/src/local-mcp-server/otlp-metrics-receiver.js +7 -1
  35. package/dist/src/local-mcp-server/stdio-server.js +70 -17
  36. package/dist/src/local-mcp-server/token-adapter-registry.js +64 -0
  37. package/dist/src/local-mcp-server/usage-collector.js +25 -0
  38. package/index.js +83 -83
  39. package/package.json +7 -1
  40. package/public/ai-hub/index.html +149 -102
  41. package/public/ai-hub/script.js +1154 -271
  42. package/public/ai-hub/styles.css +753 -450
  43. package/public/first-run/index.html +221 -0
  44. package/public/first-run/script.js +361 -0
  45. package/dist/src/cli/services/device-flow-service.js +0 -83
  46. package/dist/src/local-mcp-server/prometheus-scraper.js +0 -152
@@ -14,6 +14,9 @@ class UsageCollector {
14
14
  this.events = [];
15
15
  this.userId = null;
16
16
  this.repoIdentifier = null;
17
+ // Issue #330: per-session agent attribution captured at fraim_connect.
18
+ this.agentName = null;
19
+ this.agentModel = null;
17
20
  }
18
21
  static resolveMentoringJobName(args) {
19
22
  if (!args || typeof args !== 'object') {
@@ -40,6 +43,16 @@ class UsageCollector {
40
43
  setRepoIdentifier(repoIdentifier) {
41
44
  this.repoIdentifier = repoIdentifier;
42
45
  }
46
+ /**
47
+ * Capture the agent identity at fraim_connect time so subsequent events
48
+ * can be attributed to the right agent without re-reading args.
49
+ */
50
+ setAgent(name, model) {
51
+ this.agentName = name ? name.toLowerCase() : null;
52
+ this.agentModel = model || null;
53
+ }
54
+ getAgentName() { return this.agentName; }
55
+ getAgentModel() { return this.agentModel; }
43
56
  /**
44
57
  * Collect MCP tool call event
45
58
  */
@@ -67,6 +80,8 @@ class UsageCollector {
67
80
  success,
68
81
  category: parsed.category,
69
82
  repoIdentifier: this.repoIdentifier || undefined,
83
+ agentName: this.agentName || undefined,
84
+ agentModel: this.agentModel || undefined,
70
85
  args: Object.keys(analyticsArgs).length > 0 ? analyticsArgs : undefined
71
86
  };
72
87
  this.events.push(event);
@@ -178,6 +193,16 @@ class UsageCollector {
178
193
  this.events[this.events.length - 1].tokenSnapshot = snapshot;
179
194
  }
180
195
  }
196
+ /**
197
+ * Issue #330: when a token capture adapter returns null, record the
198
+ * typed reason on the most recently queued event so the dashboard
199
+ * can explain coverage gaps to the user.
200
+ */
201
+ attachCaptureUnavailable(reason) {
202
+ if (this.events.length > 0) {
203
+ this.events[this.events.length - 1].tokenCaptureUnavailableReason = reason;
204
+ }
205
+ }
181
206
  /**
182
207
  * Get current event count
183
208
  */
package/index.js CHANGED
@@ -1,85 +1,85 @@
1
1
  #!/usr/bin/env node
2
-
3
- /**
4
- * FRAIM Framework - Smart Entry Point
5
- * This file handles both production (dist/) and development (src/) environments.
6
- */
7
-
8
- const path = require('path');
9
- const fs = require('fs');
10
- const { spawnSync } = require('child_process');
11
-
12
- /**
13
- * Runs the CLI using either the compiled JS or the source TS via tsx
14
- */
15
- function runCLI() {
16
- const distPath = path.join(__dirname, 'dist', 'src', 'cli', 'fraim.js');
17
- const srcPath = path.join(__dirname, 'src', 'cli', 'fraim.ts');
18
-
19
- // 1. Check if we have a compiled version (Production / CI)
20
- if (fs.existsSync(distPath)) {
21
- require(distPath);
22
- return;
23
- }
24
-
25
- // 2. Explicitly fail in production if dist/ is missing
26
- if (process.env.NODE_ENV === 'production') {
27
- console.error('❌ FRAIM Error: Production build (dist/) not found.');
28
- console.error('In production environments, you must run the compiled version.');
29
- console.error(`Expected: ${distPath}`);
30
- process.exit(1);
31
- }
32
-
33
- // 3. Fallback to source version using tsx (Development)
34
- if (fs.existsSync(srcPath)) {
35
- // We use spawnSync to run tsx so we don't have to require it in memory
36
- // if it's not needed, and it handles the process arguments correctly.
37
- //
38
- // IMPORTANT FIX: Directory names with spaces and dashes (e.g., "FRAIM - Issue 166")
39
- // cause argument parsing issues on Windows when shell: true is used.
40
- // Without quoting, a path like "C:\...\FRAIM - Issue 166\src\cli\fraim.ts" gets
41
- // split into multiple arguments, with the dash interpreted as a command flag,
42
- // resulting in "error: unknown command '-'".
43
- //
44
- // Solution: On Windows with shell: true, quote paths containing spaces.
45
- // On Unix with shell: false, pass the path unquoted (spawnSync handles it correctly).
46
- const isWindows = process.platform === 'win32';
47
-
48
- // On Windows with shell, quote paths with spaces to prevent shell misinterpretation
49
- // On Unix without shell, pass path as-is (spawnSync handles spaces correctly)
50
- const processedSrcPath = (isWindows && srcPath.includes(' '))
51
- ? `"${srcPath}"`
52
- : srcPath;
53
-
54
- const result = spawnSync(
55
- 'npx',
56
- ['tsx', processedSrcPath, ...process.argv.slice(2)],
57
- {
58
- stdio: 'inherit',
59
- shell: isWindows, // Windows needs shell for npx, Unix doesn't
60
- windowsHide: true
61
- }
62
- );
63
- process.exit(result.status || 0);
64
- }
65
-
66
- console.error('❌ FRAIM Error: Could not find CLI entry point.');
67
- console.error('Expected one of:');
68
- console.error(` - ${distPath}`);
69
- console.error(` - ${srcPath}`);
70
- process.exit(1);
71
- }
72
-
73
- // Global programmatic exports
74
- module.exports = {
75
- FRAIM_INFO: {
76
- name: 'FRAIM',
77
- version: '2.0.98',
78
- repository: 'https://github.com/mathursrus/FRAIM'
79
- }
80
- };
81
-
82
- // If this file is run directly (via npx or global link), run the CLI
83
- if (require.main === module) {
84
- runCLI();
2
+
3
+ /**
4
+ * FRAIM Framework - Smart Entry Point
5
+ * This file handles both production (dist/) and development (src/) environments.
6
+ */
7
+
8
+ const path = require('path');
9
+ const fs = require('fs');
10
+ const { spawnSync } = require('child_process');
11
+
12
+ /**
13
+ * Runs the CLI using either the compiled JS or the source TS via tsx
14
+ */
15
+ function runCLI() {
16
+ const distPath = path.join(__dirname, 'dist', 'src', 'cli', 'fraim.js');
17
+ const srcPath = path.join(__dirname, 'src', 'cli', 'fraim.ts');
18
+
19
+ // 1. Check if we have a compiled version (Production / CI)
20
+ if (fs.existsSync(distPath)) {
21
+ require(distPath);
22
+ return;
23
+ }
24
+
25
+ // 2. Explicitly fail in production if dist/ is missing
26
+ if (process.env.NODE_ENV === 'production') {
27
+ console.error('❌ FRAIM Error: Production build (dist/) not found.');
28
+ console.error('In production environments, you must run the compiled version.');
29
+ console.error(`Expected: ${distPath}`);
30
+ process.exit(1);
31
+ }
32
+
33
+ // 3. Fallback to source version using tsx (Development)
34
+ if (fs.existsSync(srcPath)) {
35
+ // We use spawnSync to run tsx so we don't have to require it in memory
36
+ // if it's not needed, and it handles the process arguments correctly.
37
+ //
38
+ // IMPORTANT FIX: Directory names with spaces and dashes (e.g., "FRAIM - Issue 166")
39
+ // cause argument parsing issues on Windows when shell: true is used.
40
+ // Without quoting, a path like "C:\...\FRAIM - Issue 166\src\cli\fraim.ts" gets
41
+ // split into multiple arguments, with the dash interpreted as a command flag,
42
+ // resulting in "error: unknown command '-'".
43
+ //
44
+ // Solution: On Windows with shell: true, quote paths containing spaces.
45
+ // On Unix with shell: false, pass the path unquoted (spawnSync handles it correctly).
46
+ const isWindows = process.platform === 'win32';
47
+
48
+ // On Windows with shell, quote paths with spaces to prevent shell misinterpretation
49
+ // On Unix without shell, pass path as-is (spawnSync handles spaces correctly)
50
+ const processedSrcPath = (isWindows && srcPath.includes(' '))
51
+ ? `"${srcPath}"`
52
+ : srcPath;
53
+
54
+ const result = spawnSync(
55
+ 'npx',
56
+ ['tsx', processedSrcPath, ...process.argv.slice(2)],
57
+ {
58
+ stdio: 'inherit',
59
+ shell: isWindows, // Windows needs shell for npx, Unix doesn't
60
+ windowsHide: true
61
+ }
62
+ );
63
+ process.exit(result.status || 0);
64
+ }
65
+
66
+ console.error('❌ FRAIM Error: Could not find CLI entry point.');
67
+ console.error('Expected one of:');
68
+ console.error(` - ${distPath}`);
69
+ console.error(` - ${srcPath}`);
70
+ process.exit(1);
71
+ }
72
+
73
+ // Global programmatic exports
74
+ module.exports = {
75
+ FRAIM_INFO: {
76
+ name: 'FRAIM',
77
+ version: '2.0.98',
78
+ repository: 'https://github.com/mathursrus/FRAIM'
79
+ }
80
+ };
81
+
82
+ // If this file is run directly (via npx or global link), run the CLI
83
+ if (require.main === module) {
84
+ runCLI();
85
85
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fraim-framework",
3
- "version": "2.0.124",
3
+ "version": "2.0.127",
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": {
@@ -32,6 +32,7 @@
32
32
  "manage-teams": "tsx scripts/fraim/manage-teams.ts",
33
33
  "partner-discounts": "tsx scripts/fraim/manage-partner-discounts.ts",
34
34
  "fix-key": "tsx scripts/fraim/fix-expired-key.ts",
35
+ "backfill:persona-entitlements": "tsx scripts/backfill-persona-entitlements.ts",
35
36
  "setup-stripe-webhook": "tsx scripts/fraim/setup-stripe-webhook.ts",
36
37
  "view-signups": "tsx scripts/view-signups.ts",
37
38
  "fraim:init": "npm run build && node index.js init",
@@ -113,10 +114,12 @@
113
114
  "dist/src/local-mcp-server/",
114
115
  "dist/src/cli/",
115
116
  "dist/src/ai-hub/",
117
+ "dist/src/first-run/",
116
118
  "dist/src/core/",
117
119
  "bin/fraim.js",
118
120
  "bin/fraim-mcp.js",
119
121
  "public/ai-hub/",
122
+ "public/first-run/",
120
123
  "index.js",
121
124
  "README.md",
122
125
  "CHANGELOG.md",
@@ -126,6 +129,9 @@
126
129
  "publishConfig": {
127
130
  "access": "public"
128
131
  },
132
+ "overrides": {
133
+ "uuid": "^14.0.0"
134
+ },
129
135
  "dependencies": {
130
136
  "@octokit/rest": "^22.0.1",
131
137
  "adm-zip": "^0.5.16",
@@ -3,127 +3,174 @@
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Visa AI Hub</title>
7
- <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Crect width='48' height='48' rx='10' fill='%230d3f8a'/%3E%3Cpath d='M13 31h6l3-14h-6zm10 0h6l3-14h-6zm11-14h-6l-3 14h6z' fill='%23f7b600'/%3E%3C/svg%3E">
6
+ <title>AI Hub</title>
7
+ <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Crect width='48' height='48' rx='10' fill='%233d8a6e'/%3E%3Ctext x='24' y='32' text-anchor='middle' font-family='Helvetica,Arial,sans-serif' font-size='22' font-weight='700' fill='white'%3EH%3C/text%3E%3C/svg%3E">
8
8
  <link rel="stylesheet" href="./styles.css">
9
9
  </head>
10
10
  <body>
11
- <div class="app-shell">
12
- <header class="app-header">
13
- <div class="brand-lockup">
14
- <p class="brand-title">Visa AI Hub</p>
15
- <p class="brand-subtitle">Powered by <a href="https://fraimworks.ai" target="_blank" rel="noreferrer">FRAIM</a></p>
16
- </div>
17
- <div class="topbar-card workspace-card">
18
- <div class="workspace-section workspace-section-inline">
19
- <span class="section-label">Employee</span>
20
- <div class="employee-picker" id="employee-picker"></div>
21
- </div>
22
- <details class="project-path workspace-project">
23
- <summary>
24
- <span>Project Path</span>
25
- <span class="project-path-preview" id="project-path-preview"></span>
26
- </summary>
27
- <div class="project-path-body">
28
- <input id="project-path-input" class="text-input" type="text" />
29
- <div class="project-actions">
30
- <button id="browse-project" class="secondary-button" type="button">Choose Folder</button>
31
- <button id="reload-project" class="secondary-button" type="button">Reload</button>
32
- </div>
33
- <p class="helper-text" id="project-status"></p>
34
- </div>
35
- </details>
36
- </div>
11
+
12
+ <div class="page">
13
+
14
+ <header class="header">
15
+ <h1>AI Hub</h1>
16
+ <button class="project-button" type="button" id="project-button">
17
+ <span class="folder">Project</span>
18
+ <strong id="project-name">Choose a folder</strong>
19
+ </button>
37
20
  </header>
38
21
 
39
- <main class="workspace">
40
- <section class="workspace-intro card">
41
- <div class="workspace-intro-copy">
42
- <p class="section-label">Pick A Job</p>
43
- <p class="panel-intro">Manage your AI employees just like you manage your teams. Tell them what jobs they need to complete, review their results, coach them, expect them to learn from you, and expect to learn from your employees.</p>
44
- </div>
45
- <div class="workspace-intro-actions">
46
- <p class="section-label">Job Category</p>
47
- <div class="category-picker" id="category-picker"></div>
48
- </div>
49
- </section>
50
-
51
- <section class="job-panel">
52
- <div class="panel-head card">
53
- <div class="panel-copy">
54
- <p class="section-label">Job Category</p>
55
- <h2 class="panel-title">Pick a job</h2>
56
- <p class="muted">Choose the next assignment for your employee.</p>
57
- </div>
58
- </div>
22
+ <section class="welcome">
23
+ Hi <strong class="you">there</strong>, remember, you are the
24
+ <span class="concept">AI Manager<button class="info" data-concept="manager" aria-label="What is AI Manager?">i</button>
25
+ <span class="popover" id="pop-manager">
26
+ <span class="pop-title">AI Manager</span>
27
+ That's you. You decide what outcome matters, pick the right job, and hold the bar on quality. You're not writing the work — you're directing it.
28
+ <a class="pop-link" href="#" data-target="jobs-manager">See manager jobs →</a>
29
+ <span class="pop-jobs" id="jobs-manager"></span>
30
+ </span></span>.
31
+ Delegate jobs to your
32
+ <span class="concept">AI Employees<button class="info" data-concept="employee" aria-label="What is AI Employee?">i</button>
33
+ <span class="popover" id="pop-employee">
34
+ <span class="pop-title">AI Employee</span>
35
+ The coding agent — Codex, Claude, or another — that actually does the work you assign. AI Hub is where you direct them; the agent itself is the employee.
36
+ <a class="pop-link" href="#" data-target="jobs-employee">See employee jobs →</a>
37
+ <span class="pop-jobs" id="jobs-employee"></span>
38
+ </span></span>,
39
+ <span class="concept">Coach<button class="info" data-concept="coach" aria-label="What is Coach?">i</button>
40
+ <span class="popover" id="pop-coach">
41
+ <span class="pop-title">Coach</span>
42
+ Add context, raise the bar, or correct course mid-flight without abandoning the conversation. Same job, sharper guidance.
43
+ <a class="pop-link" href="#" data-target="jobs-coach">See coaching jobs →</a>
44
+ <span class="pop-jobs" id="jobs-coach"></span>
45
+ </span></span>
46
+ them,
47
+ <span class="concept">Verify<button class="info" data-concept="verify" aria-label="What is Verify?">i</button>
48
+ <span class="popover" id="pop-verify">
49
+ <span class="pop-title">Verify</span>
50
+ Check that the artifact actually solved the problem before you trust completion. Read the result, not just the logs.
51
+ <a class="pop-link" href="#" data-target="jobs-verify">See verification jobs →</a>
52
+ <span class="pop-jobs" id="jobs-verify"></span>
53
+ </span></span>
54
+ their work, and expect them to
55
+ <span class="concept">Learn<button class="info" data-concept="learn" aria-label="What is Learn?">i</button>
56
+ <span class="popover" id="pop-learn">
57
+ <span class="pop-title">Learn</span>
58
+ Each run should make the next one better. Your coaching becomes their lasting habit.
59
+ <a class="pop-link" href="#" data-target="jobs-learn">See learning jobs →</a>
60
+ <span class="pop-jobs" id="jobs-learn"></span>
61
+ </span></span>
62
+ and get better. Let your employees make you shine.
63
+ </section>
59
64
 
60
- <div class="job-list-card card">
61
- <div class="job-list" id="job-list"></div>
65
+ <div class="layout">
66
+ <aside class="rail">
67
+ <button class="new-conv" type="button" id="new-conv-btn">+ New job</button>
68
+ <div class="conv-list" id="conv-list"></div>
69
+ </aside>
70
+
71
+ <main class="conversation" id="conversation">
72
+ <div class="empty-state" id="empty">
73
+ <h3>No job selected</h3>
74
+ <p>Pick an existing job from the left, or click <strong>+ New job</strong> to give your employee something to work on.</p>
62
75
  </div>
63
- </section>
64
-
65
- <section class="interaction-panel">
66
- <div class="card interaction-card">
67
- <div class="run-toolbar">
68
- <div class="selected-job-summary">
69
- <div>
70
- <p class="section-label">Selected Job</p>
71
- <h2 id="selected-job-title">Select a job</h2>
72
- <p id="selected-job-intent" class="muted"></p>
73
- </div>
74
- <ul class="job-outcomes" id="selected-job-outcomes"></ul>
75
- </div>
76
- <div class="run-toolbar-actions">
77
- <button id="start-job" class="primary-button" type="button" disabled>Start Job</button>
78
- </div>
76
+
77
+ <div id="active-conv" hidden>
78
+ <div class="conv-header">
79
+ <h2 id="active-title"></h2>
80
+ <div class="conv-job" id="active-job"></div>
81
+ <div id="artifact-slot"></div>
79
82
  </div>
80
83
 
81
- <div class="interaction-body">
82
- <div class="conversation-column">
83
- <div class="interaction-head">
84
- <div>
85
- <p class="section-label">Manager / Employee Interactions</p>
86
- <h2 id="conversation-title">No active job</h2>
87
- </div>
88
- <p class="muted" id="conversation-state">Select a job and coach your employee toward your desired outcome.</p>
89
- </div>
90
-
91
- <div class="timeline" id="timeline"></div>
92
- </div>
84
+ <!-- Issue #347 R1: pizza tracker. Hidden when the active job
85
+ declares no phases. Populated by renderTracker() in script.js. -->
86
+ <div class="tracker" id="tracker" aria-label="Job progress" hidden>
87
+ <div class="tracker-rows" id="tracker-rows"></div>
88
+ <div class="tracker-note" id="tracker-note" hidden></div>
89
+ </div>
93
90
 
94
- <div class="manager-tools">
95
- <div class="template-row">
96
- <span class="section-label">Manager Templates</span>
97
- <div class="template-chips" id="manager-templates"></div>
98
- </div>
91
+ <div class="progress" id="progress">
92
+ <span class="stage" id="stage">Getting started…</span>
93
+ <span class="latest" id="latest"></span>
94
+ </div>
99
95
 
100
- <label class="composer-label" for="manager-message">Coach your employee toward your desired outcome.</label>
101
- <textarea id="manager-message" class="composer" placeholder='Use FRAIM job "marketing-content-creation" and include the audience, desired outcome, and any constraints.'></textarea>
96
+ <div class="messages" id="messages"></div>
102
97
 
103
- <div class="composer-actions">
104
- <button id="send-coaching" class="secondary-button" type="button" disabled>Send Coaching</button>
105
- </div>
98
+ <div class="coach">
99
+ <div class="section-title">Coach the employee</div>
100
+ <textarea id="coach-text" placeholder="Tell the employee what to do next…"></textarea>
101
+ <div class="coach-actions">
102
+ <!-- Issue #347 R2: template picker. Hidden when the project
103
+ has no manager-job templates. -->
104
+ <button class="ghost" type="button" id="template-picker-btn" aria-haspopup="menu" aria-expanded="false" hidden>Use a template ▾</button>
105
+ <button class="send-button" type="button" id="send" disabled>Send</button>
106
+ <div class="template-popover" id="template-popover" role="menu" hidden></div>
106
107
  </div>
108
+ <!-- Issue #347 R4: run-level totals. Discoverable, not dominating.
109
+ Populated by renderTotals() each poll tick. -->
110
+ <div class="totals" id="totals" aria-label="Run totals" hidden></div>
107
111
  </div>
112
+
113
+ <details class="micro" id="micro-manage">
114
+ <summary>Micro-manage — raw host details</summary>
115
+ <pre class="micro-log" id="micro-log"></pre>
116
+ </details>
108
117
  </div>
118
+ </main>
119
+ </div>
109
120
 
110
- <details class="card micro-manage" id="micro-manage">
111
- <summary>Micro-manage</summary>
112
- <div class="micro-manage-body" id="raw-history"></div>
113
- </details>
114
- </section>
115
- </main>
121
+ <p class="status-line" id="status-line" role="status" aria-live="polite"></p>
116
122
  </div>
117
123
 
118
- <template id="timeline-message-template">
119
- <article class="message">
120
- <div class="message-meta">
121
- <span class="message-role"></span>
122
- <time class="message-time"></time>
124
+ <!-- Modal: New Job -->
125
+ <div class="modal-backdrop" id="modal" role="dialog" aria-modal="true" hidden>
126
+ <div class="modal">
127
+
128
+ <div id="step1">
129
+ <div class="modal-header">
130
+ <h2>What should the employee work on?</h2>
131
+ <p>Pick one job. You can always start a new job for different work.</p>
132
+ </div>
133
+ <div class="modal-body">
134
+ <input class="search" type="text" placeholder="Search jobs…" id="job-search">
135
+ <div id="job-catalog"></div>
136
+ </div>
137
+ <div class="modal-footer">
138
+ <span class="left" id="job-pick-status">Choose a job to continue</span>
139
+ <div class="right">
140
+ <button class="ghost" type="button" id="cancel1">Cancel</button>
141
+ <button class="send-button" type="button" id="next1" disabled>Next →</button>
142
+ </div>
143
+ </div>
123
144
  </div>
124
- <p class="message-text"></p>
125
- </article>
126
- </template>
145
+
146
+ <div id="step2" class="step2" hidden>
147
+ <div class="modal-header">
148
+ <h2>Tell the employee what you need</h2>
149
+ <p>A few sentences is enough. The employee will ask if anything is unclear.</p>
150
+ </div>
151
+ <div class="modal-body">
152
+ <div class="assigned-job">
153
+ <div class="label">Assigned job</div>
154
+ <div class="name" id="picked-name"></div>
155
+ <div class="desc" id="picked-desc"></div>
156
+ </div>
157
+ <textarea id="instructions" placeholder="What outcome do you want? Any context the employee should know?"></textarea>
158
+ <div class="employee-line">
159
+ <span class="employee-label">Employee:</span>
160
+ <select id="employee-select" class="employee-select"></select>
161
+ </div>
162
+ </div>
163
+ <div class="modal-footer">
164
+ <span class="left">You can coach the employee with more detail after they start.</span>
165
+ <div class="right">
166
+ <button class="ghost" type="button" id="back2">← Back</button>
167
+ <button class="send-button" type="button" id="start" disabled>Start</button>
168
+ </div>
169
+ </div>
170
+ </div>
171
+
172
+ </div>
173
+ </div>
127
174
 
128
175
  <script src="./script.js"></script>
129
176
  </body>