codehere 0.7.1 → 1.0.1
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/README.md +22 -13
- package/dist/application/services/verdict-service.js +154 -0
- package/dist/application/use-cases/assess-cross-vendor.js +126 -0
- package/dist/application/use-cases/build-cross-vendor-verdict.js +118 -0
- package/dist/application/use-cases/export-aat.js +107 -0
- package/dist/application/use-cases/generate-acceptance-criteria.js +223 -0
- package/dist/application/use-cases/generate-final-report.js +140 -15
- package/dist/application/use-cases/generate-narration.js +5 -3
- package/dist/application/use-cases/generate-thread-title.js +5 -2
- package/dist/application/use-cases/narrator-prompt-template.js +1 -1
- package/dist/application/use-cases/plan-auto-review.js +57 -0
- package/dist/application/use-cases/reconcile-audit.js +100 -0
- package/dist/application/use-cases/rerun-tests.js +226 -0
- package/dist/application/use-cases/search-sessions.js +70 -0
- package/dist/application/use-cases/should-auto-review.js +71 -0
- package/dist/application/use-cases/thread-export.js +90 -0
- package/dist/application/use-cases/verify-claim.js +187 -5
- package/dist/domain/audit.js +11 -0
- package/dist/domain/services/codebase-detector.js +0 -6
- package/dist/domain/task.js +91 -0
- package/dist/error-handler.js +3 -115
- package/dist/index.js +279 -31
- package/dist/infrastructure/completion/bash-completion.js +7 -3
- package/dist/infrastructure/completion/fish-completion.js +7 -12
- package/dist/infrastructure/completion/zsh-completion.js +7 -12
- package/dist/infrastructure/config/config-manager.js +4 -3
- package/dist/infrastructure/config/product-context.js +1 -19
- package/dist/infrastructure/context/adapters/figma-source.js +37 -2
- package/dist/infrastructure/context/adapters/github-issue-source.js +3 -2
- package/dist/infrastructure/context/github-api.js +110 -0
- package/dist/infrastructure/context/github-device-flow.js +65 -0
- package/dist/infrastructure/context/oauth-store.js +5 -2
- package/dist/infrastructure/context/registry-bootstrap.js +0 -4
- package/dist/infrastructure/cost/budget-config.js +4 -3
- package/dist/infrastructure/cost/cost-router.js +16 -23
- package/dist/infrastructure/cost/spend-aggregator.js +91 -16
- package/dist/infrastructure/cost/team-ledger.js +355 -0
- package/dist/infrastructure/discovery/discovery-log.js +85 -0
- package/dist/infrastructure/hooks/audit-tip-signature.js +336 -0
- package/dist/infrastructure/hooks/claude-sdk-hook.js +139 -3
- package/dist/infrastructure/hooks/hook-mode.js +20 -4
- package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +8 -2
- package/dist/infrastructure/hooks/pre-prompt-scan.js +3 -3
- package/dist/infrastructure/observability/execution-tracer.js +1 -1
- package/dist/infrastructure/orchestration/agents-config-seed.js +23 -11
- package/dist/infrastructure/orchestration/aider-adapter.js +188 -24
- package/dist/infrastructure/orchestration/artifacts-extractor.js +60 -2
- package/dist/infrastructure/orchestration/cassette-recorder.js +1 -1
- package/dist/infrastructure/orchestration/claude-code-adapter.js +117 -3
- package/dist/infrastructure/orchestration/codex-adapter.js +52 -16
- package/dist/infrastructure/orchestration/conductor-adapter.js +46 -7
- package/dist/infrastructure/orchestration/context-handoff.js +32 -14
- package/dist/infrastructure/orchestration/detect-ollama.js +75 -0
- package/dist/infrastructure/orchestration/failover-config.js +1 -11
- package/dist/infrastructure/orchestration/gemini-adapter.js +92 -12
- package/dist/infrastructure/orchestration/generic-cli-adapter.js +134 -3
- package/dist/infrastructure/orchestration/graphify-context.js +14 -59
- package/dist/infrastructure/orchestration/memory-injector.js +4 -1
- package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +167 -9
- package/dist/infrastructure/orchestration/opencode-adapter.js +84 -14
- package/dist/infrastructure/orchestration/progress-aggregator.js +275 -23
- package/dist/infrastructure/orchestration/session-manager.js +593 -94
- package/dist/infrastructure/orchestration/session-persistence.js +177 -41
- package/dist/infrastructure/orchestration/thread-key-store.js +1 -1
- package/dist/infrastructure/orchestration/write-pass-store.js +0 -8
- package/dist/infrastructure/protocols/cloud-sync.js +2 -2
- package/dist/infrastructure/protocols/mcp-helpers.js +1 -1
- package/dist/infrastructure/protocols/mcp-server-impl.js +1498 -354
- package/dist/infrastructure/protocols/mcp-tools.js +401 -0
- package/dist/infrastructure/protocols/ui/app-script-artifacts.js +121 -0
- package/dist/infrastructure/protocols/ui/app-script-audit-footer.js +44 -0
- package/dist/infrastructure/protocols/ui/app-script-audit-verify.js +56 -0
- package/dist/infrastructure/protocols/ui/app-script-composer-cost.js +588 -0
- package/dist/infrastructure/protocols/ui/app-script-context-sources.js +168 -0
- package/dist/infrastructure/protocols/ui/app-script-conversation.js +67 -0
- package/dist/infrastructure/protocols/ui/app-script-drawer.js +115 -0
- package/dist/infrastructure/protocols/ui/app-script-failover.js +133 -0
- package/dist/infrastructure/protocols/ui/app-script-github.js +113 -0
- package/dist/infrastructure/protocols/ui/app-script-observer.js +104 -0
- package/dist/infrastructure/protocols/ui/app-script-onboarding.js +80 -0
- package/dist/infrastructure/protocols/ui/app-script-overlay.js +203 -0
- package/dist/infrastructure/protocols/ui/app-script-recovery.js +136 -0
- package/dist/infrastructure/protocols/ui/app-script-render.js +166 -0
- package/dist/infrastructure/protocols/ui/app-script-share.js +78 -0
- package/dist/infrastructure/protocols/ui/app-script-sidebar.js +194 -0
- package/dist/infrastructure/protocols/ui/app-script-tour.js +78 -0
- package/dist/infrastructure/protocols/ui/app-script-wpass.js +312 -0
- package/dist/infrastructure/protocols/ui/app-script.js +2998 -0
- package/dist/infrastructure/protocols/ui/font-faces.js +7 -0
- package/dist/infrastructure/protocols/ui/styles.js +2147 -0
- package/dist/infrastructure/protocols/ui/tokens.js +96 -0
- package/dist/infrastructure/protocols/ui-page-v2.js +116 -4126
- package/dist/infrastructure/protocols/verify-page.js +884 -148
- package/dist/infrastructure/security/fix-code.js +144 -0
- package/dist/infrastructure/security/fix-secret.js +89 -0
- package/dist/infrastructure/security/permissions-config.js +3 -2
- package/dist/infrastructure/security/scan-types.js +10 -0
- package/dist/infrastructure/security/ship-scan.js +195 -0
- package/dist/infrastructure/security/trust-scorer.js +70 -3
- package/dist/infrastructure/security/vibe-checks.js +99 -0
- package/dist/infrastructure/storage/atomic-write.js +86 -0
- package/dist/infrastructure/storage/project-thread-mirror.js +151 -4
- package/dist/infrastructure/storage/project-thread-reader.js +262 -25
- package/dist/infrastructure/storage/project-thread-title-backfill.js +1 -1
- package/dist/monitoring.js +2 -2
- package/dist/presentation/cli/commands/audit-command.js +79 -4
- package/dist/presentation/cli/commands/budget-command.js +2 -2
- package/dist/presentation/cli/commands/check-command.js +96 -0
- package/dist/presentation/cli/commands/context-source-command.js +20 -1
- package/dist/presentation/cli/commands/help-command.js +5 -5
- package/dist/presentation/cli/commands/hook-command.js +5 -5
- package/dist/presentation/cli/commands/init-command.js +89 -66
- package/dist/presentation/cli/commands/memory-command.js +28 -28
- package/dist/presentation/cli/commands/permission-command.js +1 -12
- package/dist/presentation/cli/commands/protocols-builder.js +2 -2
- package/dist/presentation/cli/commands/protocols-command.js +4 -4
- package/dist/presentation/cli/commands/repo-map-builder.js +1 -1
- package/dist/presentation/cli/commands/report-command.js +48 -32
- package/dist/presentation/cli/commands/scan-command.js +52 -0
- package/dist/presentation/cli/commands/scan-tool-command.js +24 -8
- package/dist/presentation/cli/commands/search-command.js +51 -0
- package/dist/presentation/cli/commands/setup-command.js +31 -8
- package/dist/presentation/cli/commands/status-command.js +39 -86
- package/dist/presentation/cli/commands/team-command.js +256 -0
- package/dist/presentation/cli/commands/thread-command.js +87 -5
- package/dist/presentation/cli/commands/trust-command.js +53 -31
- package/dist/presentation/cli/commands/verify-command.js +74 -9
- package/dist/presentation/cli/error-display.js +0 -8
- package/dist/presentation/cli/repl.js +15 -7
- package/dist/presentation/cli/task-deliverable.js +36 -10
- package/dist/testing/replay-adapter.js +0 -19
- package/dist/ui.js +51 -239
- package/dist/utils/version.js +0 -6
- package/package.json +30 -10
- package/dist/domain/entities/task.js +0 -61
- package/dist/infrastructure/protocols/ui-page.js +0 -2377
- package/dist/infrastructure/security/input-validator.js +0 -152
- package/dist/infrastructure/storage/embeddings-path.js +0 -37
- package/dist/infrastructure/storage/task-repository.js +0 -206
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Sources panel — the settings-drawer section that connects, tests and disconnects
|
|
3
|
+
* the external sources an agent can read from, and renders their state.
|
|
4
|
+
*
|
|
5
|
+
* Split out of app-script.ts (strangler fig, split 7). Verbatim slice, interpolated back at
|
|
6
|
+
* the position it was cut from so it keeps sharing the surrounding IIFE scope —
|
|
7
|
+
* loadContextSources is still called from the drawer code left behind. Verified by hashing
|
|
8
|
+
* the composed APP_SCRIPT before and after: same bytes.
|
|
9
|
+
*/
|
|
10
|
+
export const CONTEXT_SOURCES = `// v0.8 S2.5 · Context Sources panel · lives inside the settings drawer
|
|
11
|
+
function csIconHtml(id){
|
|
12
|
+
const initials=id==='linear'?'L':id==='figma'?'F':id.slice(0,1).toUpperCase();
|
|
13
|
+
return '<span class="cs-icon '+id+'">'+initials+'</span>';
|
|
14
|
+
}
|
|
15
|
+
function csRelativeTime(ts){
|
|
16
|
+
if(!ts) return '';
|
|
17
|
+
const delta=Date.now()-ts;
|
|
18
|
+
const m=Math.floor(delta/60000); if(m<60)return m+'m ago';
|
|
19
|
+
const h=Math.floor(m/60); if(h<24)return h+'h ago';
|
|
20
|
+
return Math.floor(h/24)+'d ago';
|
|
21
|
+
}
|
|
22
|
+
function csDescription(id){
|
|
23
|
+
// Fix D6 (2026-05-23) · {placeholder} syntax avoids browser eating
|
|
24
|
+
// <angle-bracket-tags> · the description renders correctly + agents
|
|
25
|
+
// recognize the convention from man-page conventions.
|
|
26
|
+
if(id==='linear') return 'Read Linear issues by ID. Agent can pull title, status, assignee, description, comments.';
|
|
27
|
+
if(id==='figma') return 'Read Figma files or single nodes. Agent gets the tree, dimensions, and text content.';
|
|
28
|
+
if(id==='drawio') return 'Read draw.io diagrams (XML). Drop drawio://gh/{owner}/{repo}/{path} into a prompt.';
|
|
29
|
+
if(id==='github') return 'Connect once to import issues as tasks and post results back. Also unlocks github://{owner}/{repo}/issues/{n} reads in a prompt.';
|
|
30
|
+
return '';
|
|
31
|
+
}
|
|
32
|
+
function csExampleUrl(id){
|
|
33
|
+
if(id==='linear') return 'linear://INFRA-247';
|
|
34
|
+
if(id==='figma') return 'figma://file/abc123';
|
|
35
|
+
if(id==='drawio') return 'drawio://gh/owner/repo/docs/arch.drawio';
|
|
36
|
+
if(id==='github') return 'github://owner/repo/issues/42';
|
|
37
|
+
return '';
|
|
38
|
+
}
|
|
39
|
+
async function loadContextSources(){
|
|
40
|
+
const list=document.getElementById('cs-list');
|
|
41
|
+
if(!list)return;
|
|
42
|
+
list.innerHTML='<div style="color:var(--fg-40); font-size:12px; padding:8px 0">loading\\u2026</div>';
|
|
43
|
+
try{
|
|
44
|
+
// Time-box the fetch so the drawer never sits on 'loading…' forever.
|
|
45
|
+
const ctrl=new AbortController();
|
|
46
|
+
const tmo=setTimeout(function(){ctrl.abort();},8000);
|
|
47
|
+
const r=await fetch('/api/context-sources',{signal:ctrl.signal}).then(x=>x.json());
|
|
48
|
+
clearTimeout(tmo);
|
|
49
|
+
const rawItems=(r&&r.providers)||[];
|
|
50
|
+
const items=rawItems.filter(function(p){
|
|
51
|
+
return p.authType==='public' || p.clientIdConfigured || p.status==='connected' || p.status==='manual' || p.status==='expired';
|
|
52
|
+
});
|
|
53
|
+
const hiddenN=rawItems.length-items.length;
|
|
54
|
+
const hiddenLine=hiddenN>0
|
|
55
|
+
? '<div class="cs-empty-hint">more integrations when configured: <a href="https://github.com/muhammadegaa/codehere" target="_blank" rel="noopener">docs</a></div>'
|
|
56
|
+
: '';
|
|
57
|
+
if(!items.length){list.innerHTML=hiddenLine||'<div style="color:var(--fg-40); font-size:12px; padding: 8px 0">no providers registered</div>'; return;}
|
|
58
|
+
const rows=items.map(function(p){
|
|
59
|
+
const statusChip='<span class="cs-status '+p.status+'">'+p.status+'</span>';
|
|
60
|
+
let meta='';
|
|
61
|
+
if((p.status==='connected'||p.status==='expired') && p.userLabel){
|
|
62
|
+
meta='<span class="cs-meta">· '+String(p.userLabel).replace(/</g,'<');
|
|
63
|
+
if(p.connectedAt) meta+=' · '+csRelativeTime(p.connectedAt);
|
|
64
|
+
meta+='</span>';
|
|
65
|
+
} else if(p.status==='manual'){
|
|
66
|
+
meta='<span class="cs-meta">· apiKey set</span>';
|
|
67
|
+
}
|
|
68
|
+
let actions='';
|
|
69
|
+
let belowDesc='';
|
|
70
|
+
if(p.authType==='public'){
|
|
71
|
+
belowDesc='<div style="margin-top:6px;"><span class="ctx-url-chip" data-url="'+csExampleUrl(p.id)+'" title="click to preview · or copy + paste into a prompt">'+csExampleUrl(p.id)+'</span></div>';
|
|
72
|
+
// Fix D5/D6 (2026-05-23) · for public adapters, render the example
|
|
73
|
+
// URL on its OWN line BELOW the description (via belowDesc) so the
|
|
74
|
+
// description column doesn't collapse to a narrow strip when the
|
|
75
|
+
// URL chip takes a wide actions slot. No actions slot needed —
|
|
76
|
+
// Connect doesn't apply for public adapters.
|
|
77
|
+
actions='';
|
|
78
|
+
} else if(p.authType==='github-device'){
|
|
79
|
+
// GitHub uses the device flow, not auth-code OAuth — its own Connect handler.
|
|
80
|
+
actions=(p.status==='connected'||p.status==='expired')
|
|
81
|
+
? '<button class="cs-btn danger" onclick="disconnectCS(\\''+p.id+'\\', this)">Disconnect</button>'
|
|
82
|
+
: (p.clientIdConfigured?'<button class="cs-btn primary" onclick="ghConnectCS(\\''+p.id+'\\')">Connect</button>':'');
|
|
83
|
+
} else if(p.status==='connected'){
|
|
84
|
+
actions='<button class="cs-btn" onclick="testCS(\\''+p.id+'\\')">Test</button>'
|
|
85
|
+
+'<button class="cs-btn danger" onclick="disconnectCS(\\''+p.id+'\\', this)">Disconnect</button>';
|
|
86
|
+
} else if(p.status==='expired'){
|
|
87
|
+
actions=p.clientIdConfigured
|
|
88
|
+
? '<button class="cs-btn primary" onclick="connectCS(\\''+p.id+'\\')">Reconnect</button>'
|
|
89
|
+
: '';
|
|
90
|
+
} else if(p.status==='manual'){
|
|
91
|
+
actions='<button class="cs-btn" onclick="testCS(\\''+p.id+'\\')">Test</button>'
|
|
92
|
+
+(p.clientIdConfigured?'<button class="cs-btn primary" onclick="connectCS(\\''+p.id+'\\')">Switch to OAuth</button>':'');
|
|
93
|
+
} else {
|
|
94
|
+
actions=p.clientIdConfigured
|
|
95
|
+
? '<button class="cs-btn primary" onclick="connectCS(\\''+p.id+'\\')">Connect</button>'
|
|
96
|
+
: '';
|
|
97
|
+
}
|
|
98
|
+
return '<div class="cs-row" id="cs-row-'+p.id+'">'
|
|
99
|
+
+ csIconHtml(p.id)
|
|
100
|
+
+ '<div style="display:flex; flex-direction:column; min-width:0; flex:1;">'
|
|
101
|
+
+ '<div style="display:flex; align-items:center; gap:6px;"><span class="cs-label">'+p.label+'</span>'+statusChip+meta+'</div>'
|
|
102
|
+
+ '<div style="font-size:12px; color:var(--fg-50); margin-top:3px; line-height:1.5;">'+csDescription(p.id)+'</div>'
|
|
103
|
+
+ belowDesc
|
|
104
|
+
+ '</div>'
|
|
105
|
+
+ (actions ? '<div class="cs-actions">'+actions+'</div>' : '')
|
|
106
|
+
+ '</div>'
|
|
107
|
+
+ '<div id="cs-test-'+p.id+'"></div>';
|
|
108
|
+
}).join('');
|
|
109
|
+
list.innerHTML=rows+hiddenLine;
|
|
110
|
+
}catch(e){
|
|
111
|
+
list.innerHTML='<div style="color:var(--fg-50); font-size:12px; padding:8px 0">couldn\\u2019t reach the daemon \\u00b7 <a href="#" onclick="loadContextSources();return false" style="color:var(--accent)">retry</a></div>';
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async function connectCS(provider){
|
|
115
|
+
try{
|
|
116
|
+
const r=await fetch('/api/context-sources/'+encodeURIComponent(provider)+'/connect',{method:'POST'});
|
|
117
|
+
const data=await r.json();
|
|
118
|
+
if(!r.ok){
|
|
119
|
+
if(data.error==='provider_not_registered'){
|
|
120
|
+
al(provider+' OAuth app not registered. Set '+data.envVar+' and restart the daemon.');
|
|
121
|
+
} else {
|
|
122
|
+
al('connect failed: '+(data.error||r.status));
|
|
123
|
+
}
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if(data.authorizeUrl){
|
|
127
|
+
window.open(data.authorizeUrl,'_blank');
|
|
128
|
+
let poll=setInterval(loadContextSources, 2000);
|
|
129
|
+
setTimeout(()=>clearInterval(poll), 120000);
|
|
130
|
+
}
|
|
131
|
+
}catch(e){
|
|
132
|
+
al('connect failed: '+(e&&e.message?e.message:String(e)));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async function disconnectCS(provider, btnEl){
|
|
136
|
+
// Inline confirmation · replace the button with a confirm/cancel pair
|
|
137
|
+
if(btnEl && !btnEl.dataset.confirming){
|
|
138
|
+
const orig=btnEl.outerHTML;
|
|
139
|
+
btnEl.outerHTML='<span style="font-size:10.5px; color:var(--fg-50); margin-right:6px;">disconnect?</span><button class="cs-btn danger" id="cs-confirm-'+provider+'">yes</button><button class="cs-btn" id="cs-cancel-'+provider+'">cancel</button>';
|
|
140
|
+
const yes=document.getElementById('cs-confirm-'+provider);
|
|
141
|
+
const no=document.getElementById('cs-cancel-'+provider);
|
|
142
|
+
if(yes) yes.onclick=async()=>{
|
|
143
|
+
await fetch('/api/context-sources/'+encodeURIComponent(provider)+'/disconnect',{method:'POST'});
|
|
144
|
+
loadContextSources();
|
|
145
|
+
};
|
|
146
|
+
if(no) no.onclick=()=>{loadContextSources();};
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
async function testCS(provider){
|
|
151
|
+
const slot=document.getElementById('cs-test-'+provider);
|
|
152
|
+
if(slot) slot.innerHTML='<div class="cs-test-result" style="color:var(--fg-50); background: rgba(255,255,255,0.02)">testing...</div>';
|
|
153
|
+
try{
|
|
154
|
+
const r=await fetch('/api/context-sources/'+encodeURIComponent(provider)+'/test',{method:'POST'});
|
|
155
|
+
const data=await r.json();
|
|
156
|
+
if(slot){
|
|
157
|
+
if(data.ok){
|
|
158
|
+
slot.innerHTML='<div class="cs-test-result ok">✓ reachable · '+(data.who||'authenticated')+'</div>';
|
|
159
|
+
} else {
|
|
160
|
+
slot.innerHTML='<div class="cs-test-result fail">× failed: '+(data.error||'unknown')+'</div>';
|
|
161
|
+
}
|
|
162
|
+
setTimeout(()=>{ if(slot) slot.innerHTML=''; }, 6000);
|
|
163
|
+
}
|
|
164
|
+
}catch(e){
|
|
165
|
+
if(slot) slot.innerHTML='<div class="cs-test-result fail">× '+(e&&e.message?e.message:String(e))+'</div>';
|
|
166
|
+
}
|
|
167
|
+
}`;
|
|
168
|
+
//# sourceMappingURL=app-script-context-sources.js.map
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// The conversation turn — one speaker's turn in the room thread, extracted from
|
|
2
|
+
// app-script.ts (strangler-fig split 3, 2026-07-06; same pattern as
|
|
3
|
+
// app-script-render.ts). A browser-JS string fragment interpolated into
|
|
4
|
+
// APP_SCRIPT at the same scope: function declarations hoist exactly as before,
|
|
5
|
+
// so no behavior change — the monolith just no longer owns the conversation.
|
|
6
|
+
//
|
|
7
|
+
// Owns: speaker identity (isAgentSpeaker/speakerName), escaping
|
|
8
|
+
// (convHe), the identity rail hue (railHue), and the turn itself
|
|
9
|
+
// (renderTurn/renderConversation) — Direction A "The Record", Ega-picked
|
|
10
|
+
// 2026-07-05: hue rail + dot marks the speaker, voice text full-width, no
|
|
11
|
+
// bubbles; the human is the only ember.
|
|
12
|
+
//
|
|
13
|
+
// House rules for this file (it is INSIDE a TS template literal when served):
|
|
14
|
+
// no literal backticks, unicode escapes stay double-escaped (\\uXXXX), served
|
|
15
|
+
// regexes double-escaped.
|
|
16
|
+
export const CONVERSATION_TURN = `
|
|
17
|
+
// Multi-speaker conversation · the demo v12 thread model · turns from the human
|
|
18
|
+
// (me), other team members, and agents (claude-code/codex/aider/...) — each with
|
|
19
|
+
// an avatar badge. A turn's speaker is an agent key, user/conductor, or a
|
|
20
|
+
// human name. This is the thread spine, not a collapsed afterthought.
|
|
21
|
+
function isAgentSpeaker(sp){
|
|
22
|
+
var s=String(sp||'').toLowerCase();
|
|
23
|
+
return /^(claude|codex|aider|gemini|opencode|conductor)/.test(s);
|
|
24
|
+
}
|
|
25
|
+
function speakerName(sp,name){
|
|
26
|
+
if(name) return name;
|
|
27
|
+
if(isAgentSpeaker(sp)) return agentLabel(sp);
|
|
28
|
+
return sp||'unknown';
|
|
29
|
+
}
|
|
30
|
+
function convHe(s){ return String(s==null?'':s).replace(/[&<>"]/g, c=>({'&':'&','<':'<','>':'>','"':'"'}[c])); }
|
|
31
|
+
// One conversation row · reused by the conversation panel AND the unified
|
|
32
|
+
// timeline so a turn looks identical wherever it appears.
|
|
33
|
+
// Direction A "The Record" (Ega-picked 2026-07-05) — the identity hue for a
|
|
34
|
+
// speaker's rail/dot. You are the only ember; agents get their hue; a human
|
|
35
|
+
// teammate gets a distinct mauve. Same treatment for everyone else.
|
|
36
|
+
function railHue(sp, name, human){
|
|
37
|
+
if(sp==='user') return 'var(--accent)';
|
|
38
|
+
if(human) return '#c9a3d9';
|
|
39
|
+
var k=(typeof agentKey==='function'?agentKey(name||sp):String(sp||''));
|
|
40
|
+
var m={claude:'#d9a679',codex:'#a3b8d9',aider:'#c9a3d9',gemini:'#8fb8c7',opencode:'#a8c79a',conductor:'#c8c2bb',cursor:'#d4b483'};
|
|
41
|
+
return m[k]||'#c8c2bb';
|
|
42
|
+
}
|
|
43
|
+
function renderTurn(u){
|
|
44
|
+
var sp=u.speaker||u.role||'assistant';
|
|
45
|
+
var body=convHe(u.content||u.body||'').replace(/@(claude-code|claude|codex|aider|gemini|opencode)/g,'<span class="at-route">@$1</span>');
|
|
46
|
+
var human=!isAgentSpeaker(sp);
|
|
47
|
+
// Routing / hand-off reads as a thin centered divider, not a turn.
|
|
48
|
+
if(u.routeLine){
|
|
49
|
+
return '<div class="conv-turn route-div"><span class="rl-line"></span><span class="rl-txt" data-user-content>'+body+'</span><span class="rl-line"></span></div>';
|
|
50
|
+
}
|
|
51
|
+
// Direction A · The Record — ONE consistent treatment: a hue rail + dot marks
|
|
52
|
+
// who's speaking, voice text full-width, no bubbles; you are the only ember.
|
|
53
|
+
var role=human?(sp==='user'?'you':'teammate'):'agent';
|
|
54
|
+
return '<div class="rec-row'+(sp==='user'?' you':'')+'" style="--rail:'+railHue(sp,u.name,human)+'">'
|
|
55
|
+
+'<div class="rec-rail"></div>'
|
|
56
|
+
+'<div class="rec-body">'
|
|
57
|
+
+'<div class="rec-meta"><span class="rec-dot"></span><span class="rec-who">'+convHe(speakerName(sp,u.name))+'</span><span class="rec-role">'+role+'</span>'
|
|
58
|
+
+(u.ts?'<span class="rec-time">'+convHe(u.ts)+'</span>':'')+'</div>'
|
|
59
|
+
+'<div class="rec-text" data-user-content>'+body+'</div>'
|
|
60
|
+
+'</div></div>';
|
|
61
|
+
}
|
|
62
|
+
function renderConversation(turns){
|
|
63
|
+
if(!Array.isArray(turns)||!turns.length) return '';
|
|
64
|
+
return '<div class="panel"><div class="panel-title">Conversation</div>'+turns.map(renderTurn).join('')+'</div>';
|
|
65
|
+
}
|
|
66
|
+
`;
|
|
67
|
+
//# sourceMappingURL=app-script-conversation.js.map
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings drawer and its neighbours — open/close for the drawer, reshape, and clone, plus the
|
|
3
|
+
* shape apply/reset/classify/restore helpers that share its state.
|
|
4
|
+
*
|
|
5
|
+
* Split out of app-script.ts (strangler fig). Verbatim slice, interpolated back at the
|
|
6
|
+
* position it was cut from so it keeps sharing the surrounding IIFE scope. Verified by
|
|
7
|
+
* hashing the composed APP_SCRIPT before and after: same bytes.
|
|
8
|
+
*/
|
|
9
|
+
export const SETTINGS_DRAWER = `function openDrawer(){
|
|
10
|
+
document.getElementById('settings-drawer')?.classList.add('open');
|
|
11
|
+
document.getElementById('drawer-overlay')?.classList.add('open');
|
|
12
|
+
loadContextSources();
|
|
13
|
+
}
|
|
14
|
+
function closeDrawer(){
|
|
15
|
+
document.getElementById('settings-drawer')?.classList.remove('open');
|
|
16
|
+
document.getElementById('drawer-overlay')?.classList.remove('open');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// v0.8 S3 · Reshape view · port of demo v12 lines 2486-2534.
|
|
20
|
+
// Body-class-driven view modifier. Three preset shapes + a free-text
|
|
21
|
+
// classifier that maps to the closest preset. Persists the active shape
|
|
22
|
+
// to localStorage so the user's choice survives a reload.
|
|
23
|
+
const SHAPE_CLASSES=['shape-ship-only','shape-velocity','shape-security'];
|
|
24
|
+
const SHAPE_CONFIGS={
|
|
25
|
+
'ship-only': 'density: standard · hide: tc, audit, code · expand: final-report, file-chips',
|
|
26
|
+
'velocity': 'density: compact · hide: tc-outputs, audit, code · expand: outcome, duration',
|
|
27
|
+
'security': 'density: standard · amplify: audit, hook-block, trust · hide: prose, repl-beats',
|
|
28
|
+
};
|
|
29
|
+
function openReshape(){
|
|
30
|
+
const m=document.getElementById('reshape-modal');
|
|
31
|
+
const ac=document.getElementById('agent-compose');
|
|
32
|
+
const inp=document.getElementById('reshape-input');
|
|
33
|
+
m?.classList.add('open');
|
|
34
|
+
ac?.classList.remove('show');
|
|
35
|
+
if(inp){ inp.value=''; setTimeout(()=>inp.focus(), 100); }
|
|
36
|
+
}
|
|
37
|
+
function closeReshape(){
|
|
38
|
+
document.getElementById('reshape-modal')?.classList.remove('open');
|
|
39
|
+
}
|
|
40
|
+
function openClone(){
|
|
41
|
+
const m=document.getElementById('clone-modal');
|
|
42
|
+
const inp=document.getElementById('clone-url');
|
|
43
|
+
const st=document.getElementById('clone-status');
|
|
44
|
+
if(st){ st.style.display='none'; st.textContent=''; }
|
|
45
|
+
m?.classList.add('open');
|
|
46
|
+
if(inp){ inp.value=''; inp.disabled=false; setTimeout(()=>inp.focus(), 100); }
|
|
47
|
+
const b=document.getElementById('clone-submit'); if(b) b.disabled=false;
|
|
48
|
+
}
|
|
49
|
+
function closeClone(){
|
|
50
|
+
document.getElementById('clone-modal')?.classList.remove('open');
|
|
51
|
+
}
|
|
52
|
+
async function doClone(){
|
|
53
|
+
const inp=document.getElementById('clone-url');
|
|
54
|
+
const btn=document.getElementById('clone-submit');
|
|
55
|
+
const st=document.getElementById('clone-status');
|
|
56
|
+
const url=(inp&&inp.value||'').trim();
|
|
57
|
+
if(!url){ if(st){ st.style.display='block'; st.className='clone-status warn'; st.textContent='Paste a repo URL first.'; } return; }
|
|
58
|
+
if(btn) btn.disabled=true; if(inp) inp.disabled=true;
|
|
59
|
+
if(st){ st.style.display='block'; st.className='clone-status'; st.textContent='Cloning \\u2026 this can take a moment for a big repo.'; }
|
|
60
|
+
try{
|
|
61
|
+
const r=await fetch('/api/projects/clone',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({url:url})});
|
|
62
|
+
const j=await r.json().catch(function(){return {};});
|
|
63
|
+
if(r.ok && j.cwd){
|
|
64
|
+
if(st){ st.className='clone-status ok'; st.textContent=(j.alreadyHere?'Already here. Opening\\u2026':'Cloned. Opening\\u2026'); }
|
|
65
|
+
closeClone();
|
|
66
|
+
openRoom(encodeURIComponent(j.cwd));
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
// honest, plain failure — never a vanishing toast, never a fake check
|
|
70
|
+
const map={url_required:'Paste a repo URL first.',invalid_url:'That doesn\\u2019t look like a git or GitHub URL.',invalid_json:'Something went wrong reading that. Try again.',invalid_body:'Something went wrong reading that. Try again.',dir_exists:'A different folder already sits at '+(j.detail||'the target path')+'. Move or rename it, then retry.'};
|
|
71
|
+
let msg=map[j.error]||('Couldn\\u2019t clone'+(j.detail?' \\u00b7 '+String(j.detail):''));
|
|
72
|
+
if(j.error==='clone_failed') msg='Couldn\\u2019t clone \\u00b7 '+String(j.detail||'check the URL and your git access');
|
|
73
|
+
if(st){ st.className='clone-status warn'; st.textContent=msg; }
|
|
74
|
+
}catch(e){
|
|
75
|
+
if(st){ st.className='clone-status warn'; st.textContent='Network error \\u00b7 '+(e&&e.message||'try again'); }
|
|
76
|
+
}
|
|
77
|
+
if(btn) btn.disabled=false; if(inp) inp.disabled=false;
|
|
78
|
+
}
|
|
79
|
+
function applyShape(shape, promptText, configText){
|
|
80
|
+
// Clear existing
|
|
81
|
+
SHAPE_CLASSES.forEach(s=>document.body.classList.remove(s));
|
|
82
|
+
document.body.classList.add('shape-'+shape);
|
|
83
|
+
document.body.classList.add('custom-view');
|
|
84
|
+
// Update banner with prompt
|
|
85
|
+
const bp=document.getElementById('custom-view-prompt');
|
|
86
|
+
if(bp) bp.textContent='"'+promptText+'"';
|
|
87
|
+
// Animate the compose preview
|
|
88
|
+
const echo=document.getElementById('compose-prompt-echo');
|
|
89
|
+
const cfg=document.getElementById('compose-config');
|
|
90
|
+
if(echo) echo.textContent=promptText;
|
|
91
|
+
if(cfg) cfg.textContent=configText;
|
|
92
|
+
document.getElementById('agent-compose')?.classList.add('show');
|
|
93
|
+
// Persist
|
|
94
|
+
try{ localStorage.setItem('codehere_shape', JSON.stringify({shape, promptText, configText})); }catch{}
|
|
95
|
+
// Close modal after compose animation finishes
|
|
96
|
+
setTimeout(()=>closeReshape(), 2600);
|
|
97
|
+
}
|
|
98
|
+
function resetShape(){
|
|
99
|
+
SHAPE_CLASSES.forEach(s=>document.body.classList.remove(s));
|
|
100
|
+
document.body.classList.remove('custom-view');
|
|
101
|
+
try{ localStorage.removeItem('codehere_shape'); }catch{}
|
|
102
|
+
}
|
|
103
|
+
function classifyShape(text){
|
|
104
|
+
const lower=text.toLowerCase();
|
|
105
|
+
if(/shipped|final|cost|outcome/.test(lower)) return 'ship-only';
|
|
106
|
+
if(/security|audit|trust|block|hook/.test(lower)) return 'security';
|
|
107
|
+
if(/velocity|duration|fast|quick|time/.test(lower)) return 'velocity';
|
|
108
|
+
return 'velocity';
|
|
109
|
+
}
|
|
110
|
+
function restoreShape(){
|
|
111
|
+
// Reshape removed (2026-06-20) — one consistent view. Never re-apply a saved
|
|
112
|
+
// shape; clear any leftover so it can't resurface.
|
|
113
|
+
try{ SHAPE_CLASSES.forEach(s=>document.body.classList.remove(s)); document.body.classList.remove('custom-view'); localStorage.removeItem('codehere_shape'); }catch{}
|
|
114
|
+
}`;
|
|
115
|
+
//# sourceMappingURL=app-script-drawer.js.map
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Failover banner — surfaces that a vendor call fell back to another vendor, so a result that
|
|
3
|
+
* came from somewhere other than the agent you picked never arrives unlabelled.
|
|
4
|
+
*
|
|
5
|
+
* Split out of app-script.ts (strangler fig). Verbatim slice, interpolated back at the
|
|
6
|
+
* position it was cut from so it keeps sharing the surrounding IIFE scope — its functions
|
|
7
|
+
* are still called from the code left behind. Verified by hashing the composed APP_SCRIPT
|
|
8
|
+
* before and after: same bytes.
|
|
9
|
+
*/
|
|
10
|
+
export const FAILOVER_BANNER = ` /* Failover banner · ported from v1 on 2026-05-23 /v1 kill.
|
|
11
|
+
Polls /api/failover-offers · when a rate-limited agent has a viable
|
|
12
|
+
fallback, an amber strip appears with Switch + Dismiss. */
|
|
13
|
+
(function wireFailover(){
|
|
14
|
+
const fbEl = document.getElementById('failover-banner');
|
|
15
|
+
if(!fbEl) return;
|
|
16
|
+
let fbDismissed = false;
|
|
17
|
+
function escFb(s){ return String(s||'').replace(/[&<>"]/g, c => ({'&':'&','<':'<','>':'>','"':'"'}[c])); }
|
|
18
|
+
function render(offers){
|
|
19
|
+
if(!offers || offers.length === 0 || fbDismissed){ fbEl.classList.remove('show'); return; }
|
|
20
|
+
fbEl.classList.add('show');
|
|
21
|
+
fbEl.innerHTML = offers.map(o => {
|
|
22
|
+
const msg = '<b>'+escFb(o.signal && o.signal.fromAgent)+'</b> hit '+escFb(o.signal && o.signal.errorCode)+
|
|
23
|
+
' · switch to <b>'+escFb(o.target)+'</b>?';
|
|
24
|
+
const actions = '<div class="fb-actions">'+
|
|
25
|
+
'<button class="fb-accept" data-session="'+escFb(o.sessionId)+'" data-target="'+escFb(o.target)+'">Switch</button>'+
|
|
26
|
+
'<button class="fb-dismiss" data-session="'+escFb(o.sessionId)+'">Dismiss</button>'+
|
|
27
|
+
'</div>';
|
|
28
|
+
return '<div class="fb-msg">'+msg+'</div>'+actions;
|
|
29
|
+
}).join('');
|
|
30
|
+
fbEl.querySelectorAll('.fb-accept').forEach(btn => {
|
|
31
|
+
btn.addEventListener('click', async ()=>{
|
|
32
|
+
btn.disabled = true; btn.textContent = 'Switching…';
|
|
33
|
+
try{
|
|
34
|
+
const r = await fetch('/api/failover-accept', {
|
|
35
|
+
method: 'POST',
|
|
36
|
+
headers: { 'Content-Type': 'application/json' },
|
|
37
|
+
body: JSON.stringify({ sessionId: btn.dataset.session, targetAgent: btn.dataset.target }),
|
|
38
|
+
});
|
|
39
|
+
const d = await r.json();
|
|
40
|
+
if(d.error || !d.ok || !d.newSessionId){
|
|
41
|
+
fbEl.innerHTML = '<div class="fb-msg"><b>Failover failed:</b> '+escFb(d.error || d.detail || 'Target agent not ready')+'</div>';
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
fbEl.innerHTML = '<div class="fb-msg">Switched to <b>'+escFb(btn.dataset.target)+'</b> · new session <b>'+escFb(d.newSessionId.slice(0,8))+'</b></div>'+
|
|
45
|
+
'<div class="fb-actions"><button class="fb-dismiss" id="fb-ok">OK</button></div>';
|
|
46
|
+
document.getElementById('fb-ok').addEventListener('click', ()=>{ fbDismissed = true; fbEl.classList.remove('show'); });
|
|
47
|
+
}catch(err){
|
|
48
|
+
fbEl.innerHTML = '<div class="fb-msg"><b>Failover failed:</b> network error · check daemon</div>';
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
fbEl.querySelectorAll('.fb-dismiss').forEach(btn => {
|
|
53
|
+
btn.addEventListener('click', ()=>{ fbDismissed = true; fbEl.classList.remove('show'); });
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
// Expose the REAL render so /verify coverage can observe the banner's own
|
|
57
|
+
// render path with a real offer shape (no fake DOM injection). The 5s poll
|
|
58
|
+
// still drives it live from /api/failover-offers in normal use.
|
|
59
|
+
window.__fbRender = function(offers){ fbDismissed = false; render(offers); };
|
|
60
|
+
setInterval(async ()=>{
|
|
61
|
+
try{
|
|
62
|
+
const r = await fetch('/api/failover-offers');
|
|
63
|
+
const d = await r.json();
|
|
64
|
+
render(d.offers);
|
|
65
|
+
}catch{ /* fine */ }
|
|
66
|
+
}, 5000);
|
|
67
|
+
})();
|
|
68
|
+
|
|
69
|
+
document.addEventListener('keydown',e=>{
|
|
70
|
+
// Gate on the VISIBLE state, not just _detail: the not-found screen nulls
|
|
71
|
+
// _detail while advertising "· Esc" — a dead advertised key is the
|
|
72
|
+
// recovery-buttons-must-not-lie class (verify-distill loop-5 catch).
|
|
73
|
+
// Spend joins the gate (loop-6): Esc is the way out of every non-home
|
|
74
|
+
// surface, and hideD already clears the #/spend hash correctly.
|
|
75
|
+
if(e.key==='Escape'&&(_detail||document.body.classList.contains('detail-open')||_spendVisible())&&!e.defaultPrevented)hideD();
|
|
76
|
+
// S14 · the Your-call block's filled button shows a ⏎ hint — make it
|
|
77
|
+
// true: Enter on an open detail (focus NOT in an input/summary, so the
|
|
78
|
+
// composer + the conversation row keep their own Enter) clicks the one
|
|
79
|
+
// enabled primary action. No primary rendered -> Enter does nothing.
|
|
80
|
+
if(e.key==='Enter'&&_detail&&!e.defaultPrevented){
|
|
81
|
+
const _ae=document.activeElement;
|
|
82
|
+
if(!_ae||_ae===document.body){
|
|
83
|
+
const _yc=document.querySelector('#task-detail-view .recovery-card button.primary:not([disabled])');
|
|
84
|
+
if(_yc){ e.preventDefault(); _yc.click(); }
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// S13 · ⌘K / Ctrl-K opens (or toggles) the command palette — every
|
|
88
|
+
// control that left the header lives there.
|
|
89
|
+
if((e.metaKey||e.ctrlKey)&&(e.key==='k'||e.key==='K')){
|
|
90
|
+
e.preventDefault();
|
|
91
|
+
const sc=document.getElementById('palette-scrim');
|
|
92
|
+
if(sc&&sc.classList.contains('open')) closePalette(); else openPalette();
|
|
93
|
+
}
|
|
94
|
+
// S12 AX · full keyboard path: Tab to a card, Enter opens it (Esc above
|
|
95
|
+
// already goes back). Only when the card ITSELF is focused — buttons
|
|
96
|
+
// inside the card keep their own Enter behavior.
|
|
97
|
+
if(e.key==='Enter'&&e.target instanceof Element&&e.target.classList.contains('task-card')){
|
|
98
|
+
e.preventDefault(); e.target.click();
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Composer .typing toggle · brand border the moment the input has chars.
|
|
103
|
+
// The focus-within border handles "input is focused"; the .typing class
|
|
104
|
+
// handles "input has content" so the affordance stays even if the user
|
|
105
|
+
// tabs away mid-prompt.
|
|
106
|
+
function bindTypingState(taEl){
|
|
107
|
+
if(!taEl) return;
|
|
108
|
+
const bar = taEl.closest('.input-bar');
|
|
109
|
+
if(!bar) return;
|
|
110
|
+
taEl.addEventListener('input', ()=>{
|
|
111
|
+
if(taEl.value.length > 0) bar.classList.add('typing');
|
|
112
|
+
else bar.classList.remove('typing');
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const ta=document.getElementById('main-ta'), btn=document.getElementById('main-btn');
|
|
117
|
+
if(btn)btn.addEventListener('click',()=>submit(ta,btn));
|
|
118
|
+
if(ta)ta.addEventListener('keydown',e=>{if(e.key==='Enter'&&!e.shiftKey&&!ta.disabled&&btn&&!btn.disabled){e.preventDefault();submit(ta,btn);}});
|
|
119
|
+
bindTypingState(ta);
|
|
120
|
+
|
|
121
|
+
const dta=document.getElementById('detail-ta'), dbtn=document.getElementById('detail-btn');
|
|
122
|
+
// S7 · the estimate tracks what it claims to track: prompt length + the
|
|
123
|
+
// picked agent. Recompute on type and on picker change.
|
|
124
|
+
const _mta=document.getElementById('main-ta'), _pick=document.getElementById('agent-pick');
|
|
125
|
+
if(_mta)_mta.addEventListener('input',updateCostEst);
|
|
126
|
+
if(_pick)_pick.addEventListener('change',updateCostEst);
|
|
127
|
+
if(dta)dta.addEventListener('input',updateCostEst);
|
|
128
|
+
updateCostEst();
|
|
129
|
+
if(dbtn)dbtn.addEventListener('click',()=>submit(dta,dbtn,_detail));
|
|
130
|
+
if(dta)dta.addEventListener('keydown',e=>{if(e.key==='Enter'&&!e.shiftKey&&!dta.disabled&&dbtn&&!dbtn.disabled){e.preventDefault();submit(dta,dbtn,_detail);}});
|
|
131
|
+
bindTypingState(dta);
|
|
132
|
+
});`;
|
|
133
|
+
//# sourceMappingURL=app-script-failover.js.map
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub integration — one device-flow connect shared by the welcome card, the context-sources
|
|
3
|
+
* panel and the composer import row, plus the issue picker and posting a finished result back
|
|
4
|
+
* as a comment.
|
|
5
|
+
*
|
|
6
|
+
* Split out of app-script.ts (strangler fig, split 11 — the first one that needed reading
|
|
7
|
+
* rather than a banner, since every marked region was already gone). Verbatim slice,
|
|
8
|
+
* interpolated back where it was cut so it keeps sharing the surrounding IIFE scope: the
|
|
9
|
+
* onboarding card calls welcomeConnectTool, render() calls initGhImport on every SSE tick, and
|
|
10
|
+
* the context-sources panel calls ghConnectCS. Verified by hashing the composed APP_SCRIPT
|
|
11
|
+
* before and after: same bytes.
|
|
12
|
+
*/
|
|
13
|
+
export const GITHUB_INTEGRATION = `// ONE GitHub device-flow connect, reused by the welcome card, the context-sources
|
|
14
|
+
// panel, and the composer import row. noteEl is where progress/code renders;
|
|
15
|
+
// onConnected fires once the token lands so each surface can refresh itself.
|
|
16
|
+
async function _ghDeviceConnect(noteEl, onConnected){
|
|
17
|
+
function set(html){ if(noteEl) noteEl.innerHTML=html; }
|
|
18
|
+
set('Connecting GitHub\\u2026');
|
|
19
|
+
var r; try{ r=await fetch('/api/github/connect/start',{method:'POST'}).then(function(x){return x.json();}); }catch(e){ set('Could not reach the daemon.'); return; }
|
|
20
|
+
if(r&&r.error==='github_not_configured'){
|
|
21
|
+
set('GitHub needs a one-time setup: create an OAuth App with <b>Device Flow</b> enabled at <a href="https://github.com/settings/developers" target="_blank" rel="noopener">github.com/settings/developers</a>, set <code>CODEHERE_GITHUB_CLIENT_ID</code>, and restart codehere.');
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if(!r||!r.deviceCode){ set('GitHub error: '+((r&&r.error)||'unknown')); return; }
|
|
25
|
+
set('Open <a href="'+r.verificationUri+'" target="_blank" rel="noopener"><b>'+r.verificationUri+'</b></a> and enter code <b>'+r.userCode+'</b>. Waiting for approval\\u2026');
|
|
26
|
+
(function poll(intervalSec){
|
|
27
|
+
setTimeout(async function(){
|
|
28
|
+
var o; try{ o=await fetch('/api/github/connect/poll',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({deviceCode:r.deviceCode})}).then(function(x){return x.json();}); }catch(e){ return; }
|
|
29
|
+
if(o.status==='connected'){ set('GitHub connected.'); if(onConnected)onConnected(); return; }
|
|
30
|
+
if(o.status==='error'){ set('GitHub connect failed: '+o.error); return; }
|
|
31
|
+
poll((o.status==='slow_down'&&o.intervalSec)?o.intervalSec:intervalSec);
|
|
32
|
+
}, Math.max(2,intervalSec)*1000);
|
|
33
|
+
})(r.intervalSec||5);
|
|
34
|
+
}
|
|
35
|
+
function welcomeConnectTool(){
|
|
36
|
+
var n=document.getElementById('hw-note'); if(!n) return;
|
|
37
|
+
n.style.display='block';
|
|
38
|
+
_ghDeviceConnect(n, function(){ if(typeof initGhImport==='function') initGhImport(); });
|
|
39
|
+
}
|
|
40
|
+
// Composer import row · gated on connection. Disconnected -> a Connect button;
|
|
41
|
+
// connected -> repo field + an issue PICKER (no more typing owner/repo#123 blind).
|
|
42
|
+
// Snapshot-guarded (same reason as upHero's _heroSnap): render() calls this on
|
|
43
|
+
// every SSE tick, and an unconditional innerHTML rewrite wipes the user's typed
|
|
44
|
+
// repo, the open select, and focus mid-interaction.
|
|
45
|
+
var _ghiSnap=null;
|
|
46
|
+
async function initGhImport(){
|
|
47
|
+
var box=document.getElementById('gh-import'); if(!box) return;
|
|
48
|
+
var s; try{ s=await fetch('/api/github/status').then(function(x){return x.json();}); }catch(e){ s={connected:false}; }
|
|
49
|
+
var snap=String(!!s.connected)+'|'+String(!!s.clientIdConfigured);
|
|
50
|
+
if(snap===_ghiSnap && box.firstChild) return;
|
|
51
|
+
_ghiSnap=snap;
|
|
52
|
+
if(!s.connected){
|
|
53
|
+
box.innerHTML='<span>from GitHub:</span>'
|
|
54
|
+
+(s.clientIdConfigured
|
|
55
|
+
? '<button type="button" class="ghi-btn" onclick="ghConnectComposer()">Connect GitHub</button><span class="ghi-hint">import an issue as a task</span>'
|
|
56
|
+
: '<span class="ghi-hint">connect a GitHub OAuth App to import issues</span>')
|
|
57
|
+
+'<span id="gh-import-msg" class="ghi-msg"></span>';
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
box.innerHTML='<span>from GitHub:</span>'
|
|
61
|
+
+'<input id="gh-repo" type="text" placeholder="owner/repo" class="ghi-in">'
|
|
62
|
+
+'<button type="button" class="ghi-btn" onclick="ghLoadIssues()">load issues</button>'
|
|
63
|
+
+'<select id="gh-issue-sel" class="ghi-sel" style="display:none" onchange="ghPickIssue()"></select>'
|
|
64
|
+
+'<span id="gh-import-msg" class="ghi-msg"></span>';
|
|
65
|
+
}
|
|
66
|
+
function ghConnectComposer(){
|
|
67
|
+
var box=document.getElementById('gh-import'); if(!box) return;
|
|
68
|
+
var note=document.getElementById('gh-import-msg')||box;
|
|
69
|
+
_ghDeviceConnect(note, function(){ initGhImport(); });
|
|
70
|
+
}
|
|
71
|
+
async function ghLoadIssues(){
|
|
72
|
+
var repoEl=document.getElementById('gh-repo'); var msg=document.getElementById('gh-import-msg');
|
|
73
|
+
var repo=repoEl?repoEl.value.trim():'';
|
|
74
|
+
if(!repo){ if(msg)msg.textContent='enter owner/repo'; return; }
|
|
75
|
+
if(msg)msg.textContent='loading issues\\u2026';
|
|
76
|
+
var o; try{ o=await fetch('/api/github/issues?repo='+encodeURIComponent(repo)).then(function(x){return x.json();}); }catch(e){ if(msg)msg.textContent='daemon unreachable'; return; }
|
|
77
|
+
if(!o||o.error){ if(msg)msg.textContent=((o&&(o.message||o.error))||'could not list issues'); return; }
|
|
78
|
+
var sel=document.getElementById('gh-issue-sel'); if(!sel) return;
|
|
79
|
+
var issues=Array.isArray(o.issues)?o.issues:[];
|
|
80
|
+
if(!issues.length){ if(msg)msg.textContent='no open issues'; sel.style.display='none'; return; }
|
|
81
|
+
sel.innerHTML='<option value="">pick an issue ('+issues.length+')</option>'
|
|
82
|
+
+issues.map(function(i){ return '<option value="'+Number(i.number)+'">#'+Number(i.number)+' '+convHe(i.title)+'</option>'; }).join('');
|
|
83
|
+
sel.setAttribute('data-repo', o.repo); sel.style.display=''; if(msg)msg.textContent='';
|
|
84
|
+
}
|
|
85
|
+
async function ghPickIssue(){
|
|
86
|
+
var sel=document.getElementById('gh-issue-sel'); var msg=document.getElementById('gh-import-msg');
|
|
87
|
+
if(!sel||!sel.value){ return; }
|
|
88
|
+
var ref=sel.getAttribute('data-repo')+'#'+sel.value;
|
|
89
|
+
if(msg)msg.textContent='fetching '+ref+'\\u2026';
|
|
90
|
+
var o; try{ o=await fetch('/api/github/issue-to-task',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({ref:ref})}).then(function(x){return x.json();}); }catch(e){ if(msg)msg.textContent='daemon unreachable'; return; }
|
|
91
|
+
if(!o||!o.prompt){ if(msg)msg.textContent=((o&&(o.message||o.error))||'could not fetch that issue'); return; }
|
|
92
|
+
var ta=document.getElementById('main-ta'); if(ta){ ta.value=o.prompt; ta.focus(); try{ ta.dispatchEvent(new Event('input',{bubbles:true})); }catch(e){} }
|
|
93
|
+
if(msg)msg.textContent='loaded #'+o.issue.number+' \\u00b7 review and send';
|
|
94
|
+
}
|
|
95
|
+
// Connect GitHub from the context-sources panel (device flow) — refreshes the
|
|
96
|
+
// panel + the composer import row on success.
|
|
97
|
+
function ghConnectCS(id){
|
|
98
|
+
var note=document.getElementById('cs-test-'+id); if(!note){ return; }
|
|
99
|
+
_ghDeviceConnect(note, function(){ loadContextSources(); if(typeof initGhImport==='function') initGhImport(); });
|
|
100
|
+
}
|
|
101
|
+
// results -> GitHub: post a finished task's result back as a comment on its source issue.
|
|
102
|
+
async function _ghPostResult(sid){
|
|
103
|
+
var m=document.getElementById('gh-back-msg');
|
|
104
|
+
var t=findS(sid); if(!t){ if(m)m.textContent='task not found'; return; }
|
|
105
|
+
var bodyTxt=(t.finalReport&&t.finalReport.summary)?String(t.finalReport.summary):'';
|
|
106
|
+
if(!bodyTxt){ if(m)m.textContent='no result to post yet'; return; }
|
|
107
|
+
var comment='codehere \\u00b7 '+(t.agent||'agent')+' result:\\n\\n'+bodyTxt;
|
|
108
|
+
if(m)m.textContent='posting\\u2026';
|
|
109
|
+
var o; try{ o=await fetch('/api/github/post-result',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({text:t.prompt,body:comment})}).then(function(x){return x.json();}); }catch(e){ if(m)m.textContent='daemon unreachable'; return; }
|
|
110
|
+
if(o&&o.htmlUrl){ if(m)m.innerHTML='posted to <a href="'+o.htmlUrl+'" target="_blank" rel="noopener">'+(o.issue||'the issue')+'</a>'; return; }
|
|
111
|
+
if(m)m.textContent='failed: '+((o&&(o.message||o.error))||'unknown');
|
|
112
|
+
}`;
|
|
113
|
+
//# sourceMappingURL=app-script-github.js.map
|