@walwal-harness/cli 7.1.39 → 7.1.41
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/HR-Resource/cdo/SKILL.md +9 -1
- package/HR-Resource/ceo/SKILL.md +33 -2
- package/HR-Resource/coo/SKILL.md +9 -1
- package/HR-Resource/cqo/SKILL.md +9 -1
- package/HR-Resource/cto/SKILL.md +10 -2
- package/HR-Resource/ops/SKILL.md +20 -2
- package/HR-Resource/ops-prompt-inspector/SKILL.md +105 -0
- package/HR-Resource/ops-prompt-inspector/agents/installation-agent.json +7 -0
- package/HR-Resource/ops-prompt-inspector/assets/PromptInspector.tsx +599 -0
- package/HR-Resource/ops-prompt-inspector/assets/prompt-inspector-static.js +180 -0
- package/HR-Resource/ops-prompt-inspector/references/handler-templates.md +255 -0
- package/HR-Resource/ops-prompt-inspector/scripts/discover_apis.py +509 -0
- package/HR-Resource/ops-prompt-inspector/scripts/install.sh +32 -0
- package/HR-Resource/ops-prompt-inspector/scripts/setup.py +381 -0
- package/apps/harness-dashboard/app/api/wake/route.ts +41 -0
- package/apps/harness-dashboard/components/Scene.tsx +568 -35
- package/apps/harness-dashboard/lib/__tests__/harness-state.test.ts +1 -202
- package/apps/harness-dashboard/lib/__tests__/sandbox-e2e.test.ts +490 -0
- package/apps/harness-dashboard/lib/harness-state.ts +107 -796
- package/apps/harness-dashboard/lib/types.ts +22 -222
- package/apps/harness-dashboard/playwright.config.ts +26 -7
- package/assets/templates/AGENTS-ko.md.template +1 -1
- package/assets/templates/AGENTS.md.template +12 -2
- package/assets/templates/HARNESS.md +2 -0
- package/assets/templates/company-pipeline-manifest.json +5 -14
- package/assets/templates/worker-report.md.template +38 -0
- package/bin/init.js +4 -3
- package/commands/goal.md +4 -2
- package/commands/hot-fix.md +1 -1
- package/commands/submission.md +1 -1
- package/package.json +1 -1
- package/scripts/conductor-tick.sh +12 -0
- package/scripts/harness-agenda.sh +105 -0
- package/scripts/harness-company-block.sh +89 -0
- package/scripts/harness-company-complete.sh +17 -8
- package/scripts/harness-company-cycle.sh +54 -0
- package/scripts/harness-progress-set.sh +8 -2
- package/scripts/harness-stop.sh +76 -0
- package/scripts/harness-wake-control.sh +151 -0
- package/scripts/harness-wake.sh +5 -0
- package/apps/harness-dashboard/components/Drawer.tsx +0 -146
- package/apps/harness-dashboard/components/Header.tsx +0 -40
- package/apps/harness-dashboard/components/MissionTimeline.tsx +0 -214
- package/apps/harness-dashboard/components/OrgTree.tsx +0 -459
- package/apps/harness-dashboard/components/drawer/AgentLogTab.tsx +0 -56
- package/apps/harness-dashboard/components/drawer/DesignPreviewTab.tsx +0 -160
- package/apps/harness-dashboard/components/drawer/GotchasTab.tsx +0 -89
- package/apps/harness-dashboard/components/drawer/HypothesisTab.tsx +0 -42
- package/apps/harness-dashboard/components/drawer/IncidentsTab.tsx +0 -50
- package/apps/harness-dashboard/components/drawer/MissionDocTab.tsx +0 -96
- package/apps/harness-dashboard/components/drawer/MissionFlowTab.tsx +0 -576
- package/apps/harness-dashboard/components/drawer/OwnerHistoryTab.tsx +0 -160
- package/apps/harness-dashboard/components/drawer/RoomMetricsTab.tsx +0 -90
- package/apps/harness-dashboard/components/drawer/TracksTab.tsx +0 -47
- package/apps/harness-dashboard/components/three/ArchiveBoxes.tsx +0 -95
- package/apps/harness-dashboard/components/three/DeptBoard.tsx +0 -175
- package/apps/harness-dashboard/components/three/Floor3D.tsx +0 -404
- package/apps/harness-dashboard/components/three/GoalCard3D.tsx +0 -123
- package/apps/harness-dashboard/components/three/HypothesisCards.tsx +0 -71
- package/apps/harness-dashboard/components/three/MeetingWhiteboard.tsx +0 -98
- package/apps/harness-dashboard/components/three/Minifig3D.tsx +0 -391
- package/apps/harness-dashboard/components/three/OpsSiren.tsx +0 -99
- package/apps/harness-dashboard/components/three/Stage3D.tsx +0 -104
- package/apps/harness-dashboard/components/three/TrackRibbons.tsx +0 -81
- package/apps/harness-dashboard/lib/__tests__/i18n.test.ts +0 -16
- package/apps/harness-dashboard/lib/__tests__/iso.test.ts +0 -42
- package/apps/harness-dashboard/lib/__tests__/state-mapping.test.ts +0 -183
- package/apps/harness-dashboard/lib/agent-roster.ts +0 -42
- package/apps/harness-dashboard/lib/furniture.ts +0 -165
- package/apps/harness-dashboard/lib/i18n.ts +0 -26
- package/apps/harness-dashboard/lib/iso.ts +0 -150
- package/apps/harness-dashboard/lib/markdown.tsx +0 -306
- package/apps/harness-dashboard/lib/path-planning.ts +0 -72
- package/apps/harness-dashboard/lib/state-mapping.ts +0 -146
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Auto-setup script for Prompt Inspector
|
|
4
|
+
|
|
5
|
+
Automatically detects project type and injects the component.
|
|
6
|
+
Supports: Next.js (App Router / Pages Router)
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
python setup.py <project_path>
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
import re
|
|
15
|
+
import shutil
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
COMPONENT_IMPORT = "import { PromptInspector } from '@/components/PromptInspector';"
|
|
19
|
+
|
|
20
|
+
def get_version_from_file(file_path: Path) -> str | None:
|
|
21
|
+
"""Extract version from PromptInspector.tsx file."""
|
|
22
|
+
if not file_path.exists():
|
|
23
|
+
return None
|
|
24
|
+
content = file_path.read_text(encoding='utf-8')
|
|
25
|
+
match = re.search(r"PROMPT_INSPECTOR_VERSION\s*=\s*['\"]([^'\"]+)['\"]", content)
|
|
26
|
+
return match.group(1) if match else '1.0.0' # Default to 1.0.0 for old versions
|
|
27
|
+
COMPONENT_JSX = """{process.env.NODE_ENV === 'development' && <PromptInspector />}"""
|
|
28
|
+
|
|
29
|
+
def find_project_type(project_path: Path) -> dict:
|
|
30
|
+
"""Detect project type and find the appropriate layout file."""
|
|
31
|
+
result = {
|
|
32
|
+
'type': None,
|
|
33
|
+
'layout_file': None,
|
|
34
|
+
'components_dir': None,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# Check for static/vanilla apps
|
|
38
|
+
static_index = project_path / 'index.html'
|
|
39
|
+
|
|
40
|
+
# Check for Next.js App Router
|
|
41
|
+
app_layout = project_path / 'app' / 'layout.tsx'
|
|
42
|
+
app_layout_js = project_path / 'app' / 'layout.js'
|
|
43
|
+
|
|
44
|
+
# Check for src directory structure
|
|
45
|
+
src_app_layout = project_path / 'src' / 'app' / 'layout.tsx'
|
|
46
|
+
src_app_layout_js = project_path / 'src' / 'app' / 'layout.js'
|
|
47
|
+
|
|
48
|
+
# Check for Pages Router
|
|
49
|
+
pages_app = project_path / 'pages' / '_app.tsx'
|
|
50
|
+
pages_app_js = project_path / 'pages' / '_app.js'
|
|
51
|
+
src_pages_app = project_path / 'src' / 'pages' / '_app.tsx'
|
|
52
|
+
src_pages_app_js = project_path / 'src' / 'pages' / '_app.js'
|
|
53
|
+
|
|
54
|
+
# Determine layout file
|
|
55
|
+
if app_layout.exists():
|
|
56
|
+
result['type'] = 'nextjs-app'
|
|
57
|
+
result['layout_file'] = app_layout
|
|
58
|
+
result['components_dir'] = project_path / 'components'
|
|
59
|
+
elif app_layout_js.exists():
|
|
60
|
+
result['type'] = 'nextjs-app'
|
|
61
|
+
result['layout_file'] = app_layout_js
|
|
62
|
+
result['components_dir'] = project_path / 'components'
|
|
63
|
+
elif src_app_layout.exists():
|
|
64
|
+
result['type'] = 'nextjs-app'
|
|
65
|
+
result['layout_file'] = src_app_layout
|
|
66
|
+
result['components_dir'] = project_path / 'src' / 'components'
|
|
67
|
+
elif src_app_layout_js.exists():
|
|
68
|
+
result['type'] = 'nextjs-app'
|
|
69
|
+
result['layout_file'] = src_app_layout_js
|
|
70
|
+
result['components_dir'] = project_path / 'src' / 'components'
|
|
71
|
+
elif pages_app.exists():
|
|
72
|
+
result['type'] = 'nextjs-pages'
|
|
73
|
+
result['layout_file'] = pages_app
|
|
74
|
+
result['components_dir'] = project_path / 'components'
|
|
75
|
+
elif pages_app_js.exists():
|
|
76
|
+
result['type'] = 'nextjs-pages'
|
|
77
|
+
result['layout_file'] = pages_app_js
|
|
78
|
+
result['components_dir'] = project_path / 'components'
|
|
79
|
+
elif src_pages_app.exists():
|
|
80
|
+
result['type'] = 'nextjs-pages'
|
|
81
|
+
result['layout_file'] = src_pages_app
|
|
82
|
+
result['components_dir'] = project_path / 'src' / 'components'
|
|
83
|
+
elif src_pages_app_js.exists():
|
|
84
|
+
result['type'] = 'nextjs-pages'
|
|
85
|
+
result['layout_file'] = src_pages_app_js
|
|
86
|
+
result['components_dir'] = project_path / 'src' / 'components'
|
|
87
|
+
elif static_index.exists():
|
|
88
|
+
result['type'] = 'static-html'
|
|
89
|
+
result['layout_file'] = static_index
|
|
90
|
+
result['components_dir'] = project_path / 'public'
|
|
91
|
+
|
|
92
|
+
return result
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def compare_versions(v1: str, v2: str) -> int:
|
|
96
|
+
"""Compare two version strings. Returns: -1 if v1 < v2, 0 if equal, 1 if v1 > v2."""
|
|
97
|
+
def parse(v):
|
|
98
|
+
return [int(x) for x in v.split('.')]
|
|
99
|
+
p1, p2 = parse(v1), parse(v2)
|
|
100
|
+
for a, b in zip(p1, p2):
|
|
101
|
+
if a < b:
|
|
102
|
+
return -1
|
|
103
|
+
if a > b:
|
|
104
|
+
return 1
|
|
105
|
+
return 0
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def copy_component(components_dir: Path, script_dir: Path, force_update: bool = False) -> bool:
|
|
109
|
+
"""Copy PromptInspector component to project."""
|
|
110
|
+
components_dir.mkdir(parents=True, exist_ok=True)
|
|
111
|
+
|
|
112
|
+
source = script_dir.parent / 'assets' / 'PromptInspector.tsx'
|
|
113
|
+
dest = components_dir / 'PromptInspector.tsx'
|
|
114
|
+
|
|
115
|
+
if not source.exists():
|
|
116
|
+
print(f" Error: Source component not found at {source}")
|
|
117
|
+
return False
|
|
118
|
+
|
|
119
|
+
source_version = get_version_from_file(source)
|
|
120
|
+
dest_version = get_version_from_file(dest)
|
|
121
|
+
|
|
122
|
+
if dest.exists():
|
|
123
|
+
if dest_version and source_version:
|
|
124
|
+
comparison = compare_versions(source_version, dest_version)
|
|
125
|
+
if comparison > 0:
|
|
126
|
+
# Newer version available
|
|
127
|
+
print(f"\n ╔════════════════════════════════════════════════════════════╗")
|
|
128
|
+
print(f" ║ 🆕 UPDATE AVAILABLE ║")
|
|
129
|
+
print(f" ╠════════════════════════════════════════════════════════════╣")
|
|
130
|
+
print(f" ║ Current version: {dest_version:<10} ║")
|
|
131
|
+
print(f" ║ Latest version: {source_version:<10} ║")
|
|
132
|
+
print(f" ╚════════════════════════════════════════════════════════════╝")
|
|
133
|
+
|
|
134
|
+
if force_update:
|
|
135
|
+
response = 'y'
|
|
136
|
+
else:
|
|
137
|
+
response = input("\n Update to latest version? [Y/n]: ").strip().lower()
|
|
138
|
+
|
|
139
|
+
if response in ('', 'y', 'yes'):
|
|
140
|
+
# Backup existing
|
|
141
|
+
backup = dest.with_suffix('.tsx.backup')
|
|
142
|
+
shutil.copy(dest, backup)
|
|
143
|
+
print(f" 📦 Backup saved to {backup.name}")
|
|
144
|
+
|
|
145
|
+
shutil.copy(source, dest)
|
|
146
|
+
print(f" ✅ Updated to v{source_version}")
|
|
147
|
+
return True
|
|
148
|
+
else:
|
|
149
|
+
print(f" ⏭️ Skipped update (keeping v{dest_version})")
|
|
150
|
+
return True
|
|
151
|
+
elif comparison == 0:
|
|
152
|
+
print(f" ✅ Component is up to date (v{dest_version})")
|
|
153
|
+
return True
|
|
154
|
+
else:
|
|
155
|
+
print(f" ⚠️ Local version (v{dest_version}) is newer than source (v{source_version})")
|
|
156
|
+
return True
|
|
157
|
+
else:
|
|
158
|
+
print(f" Component already exists at {dest}")
|
|
159
|
+
return True
|
|
160
|
+
|
|
161
|
+
shutil.copy(source, dest)
|
|
162
|
+
print(f" ✅ Installed PromptInspector v{source_version}")
|
|
163
|
+
return True
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def inject_into_layout(layout_file: Path, project_type: str) -> bool:
|
|
167
|
+
"""Inject the component into the layout file."""
|
|
168
|
+
content = layout_file.read_text(encoding='utf-8')
|
|
169
|
+
|
|
170
|
+
# Check if already injected
|
|
171
|
+
if 'PromptInspector' in content:
|
|
172
|
+
print(f" Component already injected in {layout_file}")
|
|
173
|
+
return True
|
|
174
|
+
|
|
175
|
+
lines = content.split('\n')
|
|
176
|
+
new_lines = []
|
|
177
|
+
import_added = False
|
|
178
|
+
component_added = False
|
|
179
|
+
|
|
180
|
+
for i, line in enumerate(lines):
|
|
181
|
+
# Add import after the last import statement
|
|
182
|
+
if not import_added and (line.startswith('import ') or line.startswith('from ')):
|
|
183
|
+
new_lines.append(line)
|
|
184
|
+
# Check if next line is not an import
|
|
185
|
+
if i + 1 < len(lines) and not lines[i + 1].startswith('import ') and not lines[i + 1].startswith('from ') and lines[i + 1].strip() != '':
|
|
186
|
+
new_lines.append(COMPONENT_IMPORT)
|
|
187
|
+
import_added = True
|
|
188
|
+
continue
|
|
189
|
+
|
|
190
|
+
new_lines.append(line)
|
|
191
|
+
|
|
192
|
+
# For App Router: inject before closing </body> or </html>
|
|
193
|
+
if project_type == 'nextjs-app':
|
|
194
|
+
if '</body>' in line and not component_added:
|
|
195
|
+
indent = len(line) - len(line.lstrip())
|
|
196
|
+
new_lines.insert(-1, ' ' * (indent + 2) + COMPONENT_JSX)
|
|
197
|
+
component_added = True
|
|
198
|
+
|
|
199
|
+
# For Pages Router: inject inside Component wrapper
|
|
200
|
+
elif project_type == 'nextjs-pages':
|
|
201
|
+
if '<Component' in line and not component_added:
|
|
202
|
+
# Find the closing of the return statement and inject before it
|
|
203
|
+
pass
|
|
204
|
+
|
|
205
|
+
# If import wasn't added (no imports found), add at top
|
|
206
|
+
if not import_added:
|
|
207
|
+
new_lines.insert(0, COMPONENT_IMPORT)
|
|
208
|
+
new_lines.insert(1, '')
|
|
209
|
+
|
|
210
|
+
# For Pages Router, try different injection point
|
|
211
|
+
if project_type == 'nextjs-pages' and not component_added:
|
|
212
|
+
new_content = '\n'.join(new_lines)
|
|
213
|
+
# Find return statement and inject component
|
|
214
|
+
if 'return (' in new_content or 'return(' in new_content:
|
|
215
|
+
# Add before the closing of the main wrapper
|
|
216
|
+
new_content = new_content.replace(
|
|
217
|
+
'</Component>',
|
|
218
|
+
f'</Component>\n {COMPONENT_JSX}'
|
|
219
|
+
)
|
|
220
|
+
new_lines = new_content.split('\n')
|
|
221
|
+
component_added = True
|
|
222
|
+
|
|
223
|
+
new_content = '\n'.join(new_lines)
|
|
224
|
+
|
|
225
|
+
# Backup original
|
|
226
|
+
backup_file = layout_file.with_suffix(layout_file.suffix + '.backup')
|
|
227
|
+
if not backup_file.exists():
|
|
228
|
+
shutil.copy(layout_file, backup_file)
|
|
229
|
+
print(f" Backup created at {backup_file}")
|
|
230
|
+
|
|
231
|
+
layout_file.write_text(new_content, encoding='utf-8')
|
|
232
|
+
print(f" Injected component into {layout_file}")
|
|
233
|
+
|
|
234
|
+
return True
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def copy_static_inspector(public_dir: Path, script_dir: Path, force_update: bool = False) -> bool:
|
|
238
|
+
"""Copy the static Prompt Inspector browser script."""
|
|
239
|
+
public_dir.mkdir(parents=True, exist_ok=True)
|
|
240
|
+
source = script_dir.parent / 'assets' / 'prompt-inspector-static.js'
|
|
241
|
+
dest = public_dir / 'prompt-inspector-static.js'
|
|
242
|
+
if not source.exists():
|
|
243
|
+
print(f" Error: Source static inspector not found at {source}")
|
|
244
|
+
return False
|
|
245
|
+
if dest.exists() and not force_update:
|
|
246
|
+
print(f" Static inspector already exists at {dest}")
|
|
247
|
+
return True
|
|
248
|
+
shutil.copy(source, dest)
|
|
249
|
+
print(f" Installed static Prompt Inspector at {dest}")
|
|
250
|
+
return True
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def inject_into_static_html(index_file: Path) -> bool:
|
|
254
|
+
"""Inject the static inspector script into index.html."""
|
|
255
|
+
content = index_file.read_text(encoding='utf-8')
|
|
256
|
+
marker = 'data-harness-ops-prompt-inspector'
|
|
257
|
+
if marker in content:
|
|
258
|
+
print(f" Static inspector already injected in {index_file}")
|
|
259
|
+
return True
|
|
260
|
+
|
|
261
|
+
script_tag = ' <script src="/public/prompt-inspector-static.js" data-harness-ops-prompt-inspector></script>'
|
|
262
|
+
if '</body>' in content:
|
|
263
|
+
new_content = content.replace('</body>', f'{script_tag}\n</body>')
|
|
264
|
+
else:
|
|
265
|
+
new_content = content + f'\n{script_tag}\n'
|
|
266
|
+
|
|
267
|
+
backup_file = index_file.with_suffix(index_file.suffix + '.backup')
|
|
268
|
+
if not backup_file.exists():
|
|
269
|
+
shutil.copy(index_file, backup_file)
|
|
270
|
+
print(f" Backup created at {backup_file}")
|
|
271
|
+
|
|
272
|
+
index_file.write_text(new_content, encoding='utf-8')
|
|
273
|
+
print(f" Injected static inspector into {index_file}")
|
|
274
|
+
return True
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def main():
|
|
278
|
+
if len(sys.argv) < 2:
|
|
279
|
+
print("Usage: python setup.py <project_path> [options]")
|
|
280
|
+
print("\nAutomatically sets up Prompt Inspector in your project.")
|
|
281
|
+
print("\nOptions:")
|
|
282
|
+
print(" --check, -c Check for updates only (no installation)")
|
|
283
|
+
print(" --force, -y Auto-accept updates without prompting")
|
|
284
|
+
return 1
|
|
285
|
+
|
|
286
|
+
# Parse arguments
|
|
287
|
+
args = sys.argv[1:]
|
|
288
|
+
project_path = None
|
|
289
|
+
force_update = False
|
|
290
|
+
check_only = False
|
|
291
|
+
|
|
292
|
+
for arg in args:
|
|
293
|
+
if arg in ('--force', '-y'):
|
|
294
|
+
force_update = True
|
|
295
|
+
elif arg in ('--check', '-c'):
|
|
296
|
+
check_only = True
|
|
297
|
+
elif not project_path:
|
|
298
|
+
project_path = Path(arg).resolve()
|
|
299
|
+
|
|
300
|
+
if not project_path:
|
|
301
|
+
print("Error: Project path is required")
|
|
302
|
+
return 1
|
|
303
|
+
|
|
304
|
+
script_dir = Path(__file__).parent
|
|
305
|
+
|
|
306
|
+
if not project_path.exists():
|
|
307
|
+
print(f"Error: Path '{project_path}' does not exist")
|
|
308
|
+
return 1
|
|
309
|
+
|
|
310
|
+
print(f"Detecting project type in {project_path}...")
|
|
311
|
+
|
|
312
|
+
project_info = find_project_type(project_path)
|
|
313
|
+
|
|
314
|
+
if not project_info['type']:
|
|
315
|
+
print("Could not detect project type.")
|
|
316
|
+
print("Supported: Next.js (App Router / Pages Router), static index.html")
|
|
317
|
+
print("\nManual setup required. Copy PromptInspector.tsx or prompt-inspector-static.js")
|
|
318
|
+
print("and import it in your root layout/app file or index.html.")
|
|
319
|
+
return 1
|
|
320
|
+
|
|
321
|
+
print(f"Detected: {project_info['type']}")
|
|
322
|
+
print(f"Layout file: {project_info['layout_file']}")
|
|
323
|
+
print(f"Components dir: {project_info['components_dir']}")
|
|
324
|
+
|
|
325
|
+
# Check-only mode
|
|
326
|
+
if check_only:
|
|
327
|
+
if project_info['type'] == 'static-html':
|
|
328
|
+
dest = project_info['components_dir'] / 'prompt-inspector-static.js'
|
|
329
|
+
print(f"\n Static install check:")
|
|
330
|
+
print(f" └─ Installed: {'yes' if dest.exists() else 'no'}")
|
|
331
|
+
return 0 if dest.exists() else 1
|
|
332
|
+
|
|
333
|
+
source = script_dir.parent / 'assets' / 'PromptInspector.tsx'
|
|
334
|
+
dest = project_info['components_dir'] / 'PromptInspector.tsx'
|
|
335
|
+
|
|
336
|
+
source_version = get_version_from_file(source)
|
|
337
|
+
dest_version = get_version_from_file(dest)
|
|
338
|
+
|
|
339
|
+
print(f"\n Version Check:")
|
|
340
|
+
print(f" ├─ Installed: {dest_version or 'Not installed'}")
|
|
341
|
+
print(f" └─ Latest: {source_version}")
|
|
342
|
+
|
|
343
|
+
if not dest.exists():
|
|
344
|
+
print("\n ⚠️ PromptInspector is not installed. Run without --check to install.")
|
|
345
|
+
return 1
|
|
346
|
+
elif dest_version and source_version and compare_versions(source_version, dest_version) > 0:
|
|
347
|
+
print("\n 🆕 Update available! Run without --check to update.")
|
|
348
|
+
return 1
|
|
349
|
+
else:
|
|
350
|
+
print("\n ✅ Up to date!")
|
|
351
|
+
return 0
|
|
352
|
+
|
|
353
|
+
print("\nSetting up Prompt Inspector...")
|
|
354
|
+
|
|
355
|
+
if project_info['type'] == 'static-html':
|
|
356
|
+
if not copy_static_inspector(project_info['components_dir'], script_dir, force_update):
|
|
357
|
+
return 1
|
|
358
|
+
if not inject_into_static_html(project_info['layout_file']):
|
|
359
|
+
return 1
|
|
360
|
+
print("\nSetup complete!")
|
|
361
|
+
print("Run your dev server and look for the toolbar in the bottom-right corner.")
|
|
362
|
+
print("The static inspector is intended for development servers only.")
|
|
363
|
+
return 0
|
|
364
|
+
|
|
365
|
+
# Step 1: Copy component
|
|
366
|
+
if not copy_component(project_info['components_dir'], script_dir, force_update):
|
|
367
|
+
return 1
|
|
368
|
+
|
|
369
|
+
# Step 2: Inject into layout
|
|
370
|
+
if not inject_into_layout(project_info['layout_file'], project_info['type']):
|
|
371
|
+
return 1
|
|
372
|
+
|
|
373
|
+
print("\nSetup complete!")
|
|
374
|
+
print("Run your dev server and look for the toolbar in the bottom-right corner.")
|
|
375
|
+
print("The component only loads in development mode.")
|
|
376
|
+
|
|
377
|
+
return 0
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
if __name__ == '__main__':
|
|
381
|
+
sys.exit(main())
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { resolveHarnessRoot } from "@/lib/harness-root";
|
|
6
|
+
|
|
7
|
+
export const dynamic = "force-dynamic";
|
|
8
|
+
|
|
9
|
+
// Per-project hourly-wake launchd control, keyed by HARNESS_BASE_PORT. The
|
|
10
|
+
// dashboard runs locally for the Owner, so it may toggle the Owner's own
|
|
11
|
+
// launchd job. Only the fixed subcommands status|on|off are ever invoked.
|
|
12
|
+
function runControl(action: "status" | "on" | "off") {
|
|
13
|
+
const root = resolveHarnessRoot();
|
|
14
|
+
const script = path.join(root, "scripts", "harness-wake-control.sh");
|
|
15
|
+
if (!existsSync(script)) {
|
|
16
|
+
return { supported: false, enabled: false, error: "harness-wake-control.sh not installed in this project" };
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const out = execFileSync("bash", [script, action, root], {
|
|
20
|
+
encoding: "utf8",
|
|
21
|
+
timeout: 20000,
|
|
22
|
+
});
|
|
23
|
+
const lastLine = out.trim().split("\n").filter(Boolean).pop() ?? "{}";
|
|
24
|
+
return JSON.parse(lastLine);
|
|
25
|
+
} catch (e) {
|
|
26
|
+
return { supported: false, enabled: false, error: (e as Error).message };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function GET() {
|
|
31
|
+
return NextResponse.json(runControl("status"));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function POST(req: Request) {
|
|
35
|
+
const body = (await req.json().catch(() => ({}))) as { action?: string };
|
|
36
|
+
const action = body.action === "on" ? "on" : body.action === "off" ? "off" : null;
|
|
37
|
+
if (!action) {
|
|
38
|
+
return NextResponse.json({ error: "action must be 'on' or 'off'" }, { status: 400 });
|
|
39
|
+
}
|
|
40
|
+
return NextResponse.json(runControl(action));
|
|
41
|
+
}
|