Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,8 @@ results.*.json
codegen-examples/examples/swebench_agent_run/results/*
codegen-examples/examples/swebench_agent_run/predictions/*
codegen-examples/examples/swebench_agent_run/logs/*

# CodegenRestDashboard secrets
CodegenRestDashboard/.env
CodegenRestDashboard/mock/*.json-local

11 changes: 11 additions & 0 deletions CodegenRestDashboard/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Copy to .env and fill your values. Do NOT commit the .env file.
CODEGEN_API_BASE=https://api.codegen.com
CODEGEN_ORG_ID=323
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Replace the hard-coded organization ID with a placeholder so users don’t accidentally use or leak a real org identifier.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At CodegenRestDashboard/.env.example, line 3:

<comment>Replace the hard-coded organization ID with a placeholder so users don’t accidentally use or leak a real org identifier.</comment>

<file context>
@@ -0,0 +1,11 @@
+# Copy to .env and fill your values. Do NOT commit the .env file.
+CODEGEN_API_BASE=https://api.codegen.com
+CODEGEN_ORG_ID=323
+CODEGEN_TOKEN=sk-REPLACE_ME
+# Optional: run in mock/offline mode (server serves fixtures; no network calls)
</file context>
Fix with Cubic

CODEGEN_TOKEN=sk-REPLACE_ME
# Optional: run in mock/offline mode (server serves fixtures; no network calls)
CODEGEN_OFFLINE=0
# Optional: Webhook HMAC secret (if you enable signature verification)
CODEGEN_WEBHOOK_SECRET=
# Server port
PORT=8787

69 changes: 69 additions & 0 deletions CodegenRestDashboard/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# CodegenRestDashboard (No-deps REST UI + Commands)

This package adds a dependency-free dashboard and Node.js command scripts to interact with the Codegen REST API.

Important:
- Do NOT commit real secrets. Create CodegenRestDashboard/.env locally (see .env.example)
- The browser UI never receives your API token; a tiny local proxy server injects auth headers
- Cloudflare Worker webhook is provided separately for production webhooks

## Quick start

1) Copy .env.example to .env and fill in your values
```
cp CodegenRestDashboard/.env.example CodegenRestDashboard/.env
```

2) Start the local server (serves UI and proxies API)
```
node CodegenRestDashboard/server.js
```
Then open http://localhost:8787

3) Use commands (examples)
```
node CodegenRestDashboard/commands/create_agent_run.js --prompt "Hello" --model "Sonnet 4.5"
node CodegenRestDashboard/commands/list_agent_runs.js --state active --limit 20
node CodegenRestDashboard/commands/get_agent_run.js --id 123
node CodegenRestDashboard/commands/resume_agent_run.js --id 123 --prompt "Continue"
node CodegenRestDashboard/commands/generate_setup_commands.js --repo_id 999
```

4) Mock mode (no network)
```
CODEGEN_OFFLINE=1 node CodegenRestDashboard/server.js
```

## Files
- commands/: Node CLI scripts, no external deps
- dashboard/: Vanilla HTML/CSS/JS UI
- utils/env.js: safe .env loader (Node-only)
- utils/apiClient.js: shared REST client for Node context
- server.js: local static server + API proxy (injects Authorization)
- webhook_server.js: Cloudflare Worker handler for /webhook
- mock/: local fixtures for offline development

## Security
- .env is in .gitignore. Do not commit it.
- The token is used only in Node (server/commands). The browser never sees it.

## Webhook (Cloudflare)
- Deploy CodegenRestDashboard/webhook_server.js as a Worker (route /webhook)
- Configure your DNS so https://www.pixelium.uk/webhook points to the Worker
- Optionally set CODEGEN_WEBHOOK_SECRET and verify HMAC in the worker

## New features
- Auto-refresh only UI (no manual refresh button)
- Header shows only Active count (hover reveals top active runs)
- Compact run cards with status dots; click a completed run to open logs/resume dialog; click an active run’s “Chain” to configure multi-template chaining
- Per-run template selection and chaining (Templates tab manages templates)
- Follow-up automation sends templates in sequence on each completion cycle
- Desktop notifications (optional, via browser permission) + in-app toasts

## Extra commands
```
node CodegenRestDashboard/commands/get_agent_run_logs.js --id 123 --skip 0 --limit 100
node CodegenRestDashboard/commands/ban_agent_run.js --id 123 [--before <order>] [--after <order>]
node CodegenRestDashboard/commands/unban_agent_run.js --id 123 [--before <order>] [--after <order>]
```

28 changes: 28 additions & 0 deletions CodegenRestDashboard/commands/ban_agent_run.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/usr/bin/env node
const { apiPost } = require('../utils/apiClient');
const { loadEnv } = require('../utils/env');
const path = require('path');
loadEnv(path.join(__dirname, '..', '.env'));

function pathBan(org){ return `/v1/organizations/${org}/agent/run/ban`; }

async function main(){
const args = process.argv.slice(2);
let id; let before=null; let after=null;
for (let i=0;i<args.length;i++){
if (args[i]==='--id') id = Number(args[++i]);
else if (args[i]==='--before') before = args[++i];
else if (args[i]==='--after') after = args[++i];
}
if (!id) { console.error('Usage: ban_agent_run.js --id <runId> [--before <order>] [--after <order>]'); process.exit(2); }
const org = process.env.CODEGEN_ORG_ID;
const body = { agent_run_id: id };
if (before!==null) body.before_card_order_id = before;
if (after!==null) body.after_card_order_id = after;
const data = await apiPost(pathBan(org), body);
console.log(JSON.stringify(data, null, 2));
}

if (require.main===module){ main().catch(e=>{ console.error(e.message||e); process.exit(1); }); }
module.exports = main;

48 changes: 48 additions & 0 deletions CodegenRestDashboard/commands/create_agent_run.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/usr/bin/env node
const { apiPost, pathCreate } = require('../utils/apiClient');

const MODELS = [
'Sonnet 4.5',
'GPT-5',
'GPT 5 Codex',
'Claude opus 4.5',
'Grok 4',
'Grok 4 Fast reasoning',
'Grok Code Fast 1',
];

async function main() {
const args = process.argv.slice(2);
let prompt = '';
let model = '';
let repo_id = undefined;

for (let i = 0; i < args.length; i++) {
if (args[i] === '--prompt') prompt = args[++i] || '';
else if (args[i] === '--model') model = args[++i] || '';
else if (args[i] === '--repo_id') repo_id = Number(args[++i]);
}

if (!prompt) {
console.error('Usage: create_agent_run.js --prompt "..." [--model "Sonnet 4.5"|...] [--repo_id 123]');
process.exit(2);
}
if (model && !MODELS.includes(model)) {
console.error(`Model must be one of: ${MODELS.join(', ')}`);
process.exit(2);
}

const body = { prompt };
if (model) body.model = model;
if (repo_id) body.repo_id = repo_id;

const data = await apiPost(pathCreate(), body);
console.log(JSON.stringify(data, null, 2));
}

if (require.main === module) {
main().catch((e) => { console.error(e.message || e); process.exit(1); });
}

module.exports = main;

31 changes: 31 additions & 0 deletions CodegenRestDashboard/commands/generate_setup_commands.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/env node
const { apiPost, pathGenerateSetup } = require('../utils/apiClient');

async function main() {
const args = process.argv.slice(2);
let repo_id = undefined;
let language = undefined; // optional

for (let i = 0; i < args.length; i++) {
if (args[i] === '--repo_id') repo_id = Number(args[++i]);
else if (args[i] === '--language') language = args[++i] || undefined;
}

if (!repo_id) {
console.error('Usage: generate_setup_commands.js --repo_id 123 [--language node|python|...]');
process.exit(2);
}

const body = { repo_id };
if (language) body.language = language;

const data = await apiPost(pathGenerateSetup(), body);
console.log(JSON.stringify(data, null, 2));
}

if (require.main === module) {
main().catch((e) => { console.error(e.message || e); process.exit(1); });
}

module.exports = main;

35 changes: 35 additions & 0 deletions CodegenRestDashboard/commands/get_agent_run.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/usr/bin/env node
const { apiGet, pathGet, pathLogs } = require('../utils/apiClient');

async function main() {
const args = process.argv.slice(2);
let id = undefined;
let withLogs = false;
let skip = 0;
let limit = 50;

for (let i = 0; i < args.length; i++) {
if (args[i] === '--id') id = Number(args[++i]);
else if (args[i] === '--logs') withLogs = true;
else if (args[i] === '--skip') skip = Number(args[++i]);
else if (args[i] === '--limit') limit = Number(args[++i]);
}

if (!id) {
console.error('Usage: get_agent_run.js --id 123 [--logs] [--skip 0] [--limit 50]');
process.exit(2);
}

const res = await apiGet(pathGet(id));
if (!withLogs) return console.log(JSON.stringify(res, null, 2));

const logs = await apiGet(pathLogs(id), { skip, limit });
console.log(JSON.stringify({ run: res, logs }, null, 2));
}

if (require.main === module) {
main().catch((e) => { console.error(e.message || e); process.exit(1); });
}

module.exports = main;

19 changes: 19 additions & 0 deletions CodegenRestDashboard/commands/get_agent_run_logs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/usr/bin/env node
const { apiGet, pathLogs } = require('../utils/apiClient');

async function main(){
const args = process.argv.slice(2);
let id; let skip=0; let limit=100;
for (let i=0;i<args.length;i++){
if (args[i]==='--id') id = Number(args[++i]);
else if (args[i]==='--skip') skip = Number(args[++i]);
else if (args[i]==='--limit') limit = Number(args[++i]);
}
if (!id) { console.error('Usage: get_agent_run_logs.js --id <runId> [--skip 0] [--limit 100]'); process.exit(2); }
const data = await apiGet(pathLogs(id), { skip, limit });
console.log(JSON.stringify(data, null, 2));
}

if (require.main===module){ main().catch(e=>{ console.error(e.message||e); process.exit(1); }); }
module.exports = main;

15 changes: 15 additions & 0 deletions CodegenRestDashboard/commands/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env node
const path = require('path');
const { loadEnv } = require('../utils/env');
loadEnv(path.join(__dirname, '..', '.env'));

module.exports = {
create: require('./create_agent_run'),
resume: require('./resume_agent_run'),
list: require('./list_agent_runs'),
get: require('./get_agent_run'),
logs: require('./get_agent_run_logs'),
genSetup: require('./generate_setup_commands'),
ban: require('./ban_agent_run'),
unban: require('./unban_agent_run'),
};
28 changes: 28 additions & 0 deletions CodegenRestDashboard/commands/list_agent_runs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/usr/bin/env node
const { apiGet, pathList } = require('../utils/apiClient');

async function main() {
const args = process.argv.slice(2);
let state = '';
let page = 1;
let limit = 50;

for (let i = 0; i < args.length; i++) {
if (args[i] === '--state') state = args[++i] || '';
else if (args[i] === '--page') page = Number(args[++i]);
else if (args[i] === '--limit') limit = Number(args[++i]);
}

const params = { page, limit };
if (state) params.state = state; // server may ignore if unsupported

const data = await apiGet(pathList(), params);
console.log(JSON.stringify(data, null, 2));
}

if (require.main === module) {
main().catch((e) => { console.error(e.message || e); process.exit(1); });
}

module.exports = main;

30 changes: 30 additions & 0 deletions CodegenRestDashboard/commands/render_template.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/usr/bin/env node
const path = require('path');
const { loadEnv } = require('../utils/env');
loadEnv(path.join(__dirname, '..', '.env'));

// Minimal inlined renderer to avoid depending on dashboard file
function getPath(obj, p){ try { return p.split('.').reduce((a,k)=>(a&&a[k]!=null?a[k]:undefined), obj);} catch(_){ return undefined; } }
function renderTemplate(tpl, vars){ return String(tpl||'').replace(/\{\{\s*([a-zA-Z0-9_.]+)\s*\}\}/g,(_,k)=>{ const v=getPath(vars||{},k); return v==null? '': String(v); }); }

async function main(){
const args = process.argv.slice(2);
let template = '';
let varsJson = '{}';
for (let i=0;i<args.length;i++){
if (args[i]==='--template') template = args[++i]||'';
else if (args[i]==='--vars') varsJson = args[++i]||'{}';
}
if (!template){
console.error('Usage: render_template.js --template "Hello {{run_id}}" --vars "{\"run_id\":123,\"result\":\"OK\"}"');
process.exit(2);
}
let vars={};
try { vars = JSON.parse(varsJson); } catch(e){ console.error('Invalid JSON for --vars'); process.exit(2); }
const out = renderTemplate(template, vars);
console.log(out);
}

if (require.main===module){ main().catch(e=>{ console.error(e.message||e); process.exit(1); }); }
module.exports = main;

31 changes: 31 additions & 0 deletions CodegenRestDashboard/commands/resume_agent_run.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/env node
const { apiPost, pathResume } = require('../utils/apiClient');

async function main() {
const args = process.argv.slice(2);
let id = undefined;
let prompt = '';

for (let i = 0; i < args.length; i++) {
if (args[i] === '--id') id = Number(args[++i]);
else if (args[i] === '--prompt') prompt = args[++i] || '';
}

if (!id) {
console.error('Usage: resume_agent_run.js --id 123 [--prompt "..."]');
process.exit(2);
}

const body = { agent_run_id: id };
if (prompt) body.prompt = prompt;

const data = await apiPost(pathResume(), body);
console.log(JSON.stringify(data, null, 2));
}

if (require.main === module) {
main().catch((e) => { console.error(e.message || e); process.exit(1); });
}

module.exports = main;

15 changes: 15 additions & 0 deletions CodegenRestDashboard/commands/runCommand.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env node
const cmds = require('./index');

async function main() {
const [,, name, ...rest] = process.argv;
if (!name || !cmds[name]) {
console.error('Usage: runCommand.js <create|resume|list|get|genSetup> [args...]');
process.exit(2);
}
// Re-dispatch by spawning module main
await cmds[name]();
}

main().catch((e)=>{ console.error(e.message || e); process.exit(1); });

Loading