thepopebot 1.2.75-beta.17 → 1.2.75-beta.18
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/api/CLAUDE.md +1 -1
- package/api/index.js +5 -12
- package/bin/cli.js +96 -0
- package/lib/chat/components/chats-page.js +3 -3
- package/lib/chat/components/chats-page.jsx +4 -6
- package/lib/chat/components/sidebar-history-item.js +3 -3
- package/lib/chat/components/sidebar-history-item.jsx +4 -6
- package/lib/tools/docker.js +0 -9
- package/package.json +1 -1
- package/templates/agent-job/SYSTEM.md +6 -1
- package/templates/skills/CLAUDE.md.template +47 -22
- package/templates/skills/agent-job-secrets/SKILL.md +7 -9
- package/templates/skills/agent-job-secrets/agent-job-secrets.js +26 -39
package/api/CLAUDE.md
CHANGED
|
@@ -26,7 +26,7 @@ Browser-facing data fetching uses **fetch route handlers** colocated with pages
|
|
|
26
26
|
| GET | `/api/ping` | None | Health check |
|
|
27
27
|
| POST | `/api/create-agent-job` | `x-api-key` | Create agent job |
|
|
28
28
|
| GET | `/api/get-agent-job-secret` | `x-api-key` | Get an agent job secret; oauth2 credentials return only the access_token (auto-refreshed) |
|
|
29
|
-
|
|
|
29
|
+
| GET | `/api/agent-job-list-secrets` | `x-api-key` | List agent job secret keys (no values); returns `{secrets: [{key, isSet, updatedAt, secretType}]}` |
|
|
30
30
|
| GET | `/api/agent-jobs/status` | `x-api-key` | Agent job status (query: `?agent_job_id=`) |
|
|
31
31
|
| POST | `/api/telegram/webhook` | Telegram webhook secret | Telegram message handler |
|
|
32
32
|
| POST | `/api/telegram/register` | `x-api-key` | Register bot token + webhook URL |
|
package/api/index.js
CHANGED
|
@@ -166,20 +166,13 @@ async function handleGetAgentSecret(request) {
|
|
|
166
166
|
return Response.json({ value: raw });
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
-
async function
|
|
169
|
+
async function handleListAgentSecrets(request) {
|
|
170
170
|
const record = verifyApiKey(request.headers.get('x-api-key'));
|
|
171
171
|
if (record.type !== 'agent_job_api_key') {
|
|
172
172
|
return Response.json({ error: 'Forbidden' }, { status: 403 });
|
|
173
173
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const { key, value } = body;
|
|
177
|
-
if (!key || typeof value !== 'string') {
|
|
178
|
-
return Response.json({ error: 'Missing key or value' }, { status: 400 });
|
|
179
|
-
}
|
|
180
|
-
const { setAgentJobSecret } = await import('../lib/db/config.js');
|
|
181
|
-
setAgentJobSecret(key, value, 'agent');
|
|
182
|
-
return Response.json({ success: true });
|
|
174
|
+
const { listAgentJobSecrets } = await import('../lib/db/config.js');
|
|
175
|
+
return Response.json({ secrets: listAgentJobSecrets() });
|
|
183
176
|
}
|
|
184
177
|
|
|
185
178
|
async function handleTelegramRegister(request) {
|
|
@@ -405,7 +398,6 @@ async function POST(request) {
|
|
|
405
398
|
// Route to handler
|
|
406
399
|
switch (routePath) {
|
|
407
400
|
case '/create-agent-job': return handleCreateAgentJob(request);
|
|
408
|
-
case '/set-agent-job-secret': return handleSetAgentSecret(request);
|
|
409
401
|
case '/telegram/webhook': return handleTelegramWebhook(request);
|
|
410
402
|
case '/telegram/register': return handleTelegramRegister(request);
|
|
411
403
|
case '/github/webhook': return handleGithubWebhook(request);
|
|
@@ -424,7 +416,8 @@ async function GET(request) {
|
|
|
424
416
|
switch (routePath) {
|
|
425
417
|
case '/ping': return Response.json({ message: 'Pong!' });
|
|
426
418
|
case '/agent-jobs/status': return handleAgentJobStatus(request);
|
|
427
|
-
case '/get-agent-job-secret':
|
|
419
|
+
case '/get-agent-job-secret': return handleGetAgentSecret(request);
|
|
420
|
+
case '/agent-job-list-secrets': return handleListAgentSecrets(request);
|
|
428
421
|
case '/oauth/callback': return handleOAuthCallback(request);
|
|
429
422
|
default: return Response.json({ error: 'Not found' }, { status: 404 });
|
|
430
423
|
}
|
package/bin/cli.js
CHANGED
|
@@ -57,6 +57,7 @@ Commands:
|
|
|
57
57
|
reset-auth Regenerate AUTH_SECRET (invalidates all sessions)
|
|
58
58
|
reset [file] Restore a template file (or list available templates)
|
|
59
59
|
reset-all Nuclear reset — restore entire project to fresh init state
|
|
60
|
+
audit Show project state vs. package templates (modified/missing/unknown)
|
|
60
61
|
diff [file] Show differences between project files and package templates
|
|
61
62
|
sync <path> Sync local package to a test install (build, pack, Docker)
|
|
62
63
|
sync --fast <path> Fast sync — copy source into running container, rebuild .next
|
|
@@ -530,6 +531,98 @@ function diff(filePath) {
|
|
|
530
531
|
}
|
|
531
532
|
}
|
|
532
533
|
|
|
534
|
+
/**
|
|
535
|
+
* Audit project state against package templates.
|
|
536
|
+
* Groups all non-protected files into: matching, modified, missing, unknown.
|
|
537
|
+
*/
|
|
538
|
+
function audit() {
|
|
539
|
+
const packageDir = path.join(__dirname, '..');
|
|
540
|
+
const templatesDir = path.join(packageDir, 'templates');
|
|
541
|
+
const cwd = process.cwd();
|
|
542
|
+
|
|
543
|
+
const templateFiles = getTemplateFiles(templatesDir);
|
|
544
|
+
const matching = [];
|
|
545
|
+
const modified = [];
|
|
546
|
+
const missing = [];
|
|
547
|
+
|
|
548
|
+
// Check every template file against the project
|
|
549
|
+
for (const relPath of templateFiles) {
|
|
550
|
+
const src = path.join(templatesDir, relPath);
|
|
551
|
+
const outPath = destPath(relPath);
|
|
552
|
+
const dest = path.join(cwd, outPath);
|
|
553
|
+
|
|
554
|
+
if (!fs.existsSync(dest)) {
|
|
555
|
+
missing.push(outPath);
|
|
556
|
+
} else {
|
|
557
|
+
const srcContent = fs.readFileSync(src);
|
|
558
|
+
const destContent = fs.readFileSync(dest);
|
|
559
|
+
if (srcContent.equals(destContent)) {
|
|
560
|
+
matching.push(outPath);
|
|
561
|
+
} else {
|
|
562
|
+
modified.push(outPath);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// Build a set of known template dest paths for lookup
|
|
568
|
+
const templateDestPaths = new Set(templateFiles.map(f => destPath(f)));
|
|
569
|
+
|
|
570
|
+
// Walk the project for unknown files (not in templates, not protected)
|
|
571
|
+
const unknown = [];
|
|
572
|
+
function walkProject(dir) {
|
|
573
|
+
const items = fs.readdirSync(dir, { withFileTypes: true });
|
|
574
|
+
for (const item of items) {
|
|
575
|
+
const fullPath = path.join(dir, item.name);
|
|
576
|
+
const relPath = path.relative(cwd, fullPath);
|
|
577
|
+
if (isProtected(relPath)) continue;
|
|
578
|
+
if (item.isDirectory() && !item.isSymbolicLink()) {
|
|
579
|
+
walkProject(fullPath);
|
|
580
|
+
} else if (!templateDestPaths.has(relPath)) {
|
|
581
|
+
unknown.push(relPath);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
walkProject(cwd);
|
|
586
|
+
|
|
587
|
+
// Report
|
|
588
|
+
console.log('\n Project audit\n');
|
|
589
|
+
|
|
590
|
+
if (modified.length > 0) {
|
|
591
|
+
console.log(` Modified (${modified.length}) — template exists, your version differs:`);
|
|
592
|
+
for (const f of modified) {
|
|
593
|
+
console.log(` ${f}`);
|
|
594
|
+
}
|
|
595
|
+
console.log('');
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
if (missing.length > 0) {
|
|
599
|
+
console.log(` Missing (${missing.length}) — template exists, not in your project:`);
|
|
600
|
+
for (const f of missing) {
|
|
601
|
+
console.log(` ${f}`);
|
|
602
|
+
}
|
|
603
|
+
console.log('');
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
if (unknown.length > 0) {
|
|
607
|
+
console.log(` Unknown (${unknown.length}) — in your project, no template (reset-all would remove):`);
|
|
608
|
+
for (const f of unknown) {
|
|
609
|
+
console.log(` ${f}`);
|
|
610
|
+
}
|
|
611
|
+
console.log('');
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
console.log(` ${matching.length} file(s) match package templates.`);
|
|
615
|
+
|
|
616
|
+
if (modified.length > 0 || missing.length > 0) {
|
|
617
|
+
console.log('\n To reset a file: thepopebot reset <file>');
|
|
618
|
+
console.log(' To view a diff: thepopebot diff <file>');
|
|
619
|
+
}
|
|
620
|
+
if (unknown.length > 0 || modified.length > 0 || missing.length > 0) {
|
|
621
|
+
console.log(' To reset everything: thepopebot reset-all');
|
|
622
|
+
}
|
|
623
|
+
console.log('');
|
|
624
|
+
}
|
|
625
|
+
|
|
533
626
|
function copyDirSyncForce(src, dest, templateRelBase = '') {
|
|
534
627
|
fs.mkdirSync(dest, { recursive: true });
|
|
535
628
|
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
@@ -991,6 +1084,9 @@ switch (command) {
|
|
|
991
1084
|
case 'reset-all':
|
|
992
1085
|
await resetAll();
|
|
993
1086
|
break;
|
|
1087
|
+
case 'audit':
|
|
1088
|
+
audit();
|
|
1089
|
+
break;
|
|
994
1090
|
case 'diff':
|
|
995
1091
|
diff(args[0]);
|
|
996
1092
|
break;
|
|
@@ -263,10 +263,10 @@ function ChatRow({ chat, onNavigate, onDelete, onStar, onRename }) {
|
|
|
263
263
|
}
|
|
264
264
|
},
|
|
265
265
|
children: [
|
|
266
|
-
|
|
267
|
-
/* @__PURE__ */ jsx(CodeIcon, { size: 16 }),
|
|
266
|
+
/* @__PURE__ */ jsxs("span", { className: "relative", children: [
|
|
267
|
+
chat.chatMode === "code" ? /* @__PURE__ */ jsx(CodeIcon, { size: 16 }) : /* @__PURE__ */ jsx(AgentIcon, { size: 16 }),
|
|
268
268
|
chat.hasChanges ? /* @__PURE__ */ jsx("span", { className: "absolute -bottom-0.5 -right-0.5 w-2 h-2 rounded-full bg-destructive" }) : null
|
|
269
|
-
] })
|
|
269
|
+
] }),
|
|
270
270
|
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
271
271
|
editing ? /* @__PURE__ */ jsx(
|
|
272
272
|
"input",
|
|
@@ -312,12 +312,10 @@ function ChatRow({ chat, onNavigate, onDelete, onStar, onRename }) {
|
|
|
312
312
|
}
|
|
313
313
|
}}
|
|
314
314
|
>
|
|
315
|
-
|
|
316
|
-
<
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
</span>
|
|
320
|
-
) : <AgentIcon size={16} />}
|
|
315
|
+
<span className="relative">
|
|
316
|
+
{chat.chatMode === 'code' ? <CodeIcon size={16} /> : <AgentIcon size={16} />}
|
|
317
|
+
{chat.hasChanges ? <span className="absolute -bottom-0.5 -right-0.5 w-2 h-2 rounded-full bg-destructive" /> : null}
|
|
318
|
+
</span>
|
|
321
319
|
<div className="flex-1 min-w-0">
|
|
322
320
|
{editing ? (
|
|
323
321
|
<input
|
|
@@ -40,10 +40,10 @@ function SidebarHistoryItem({ chat, isActive, onDelete, onStar, onRename }) {
|
|
|
40
40
|
setOpenMobile(false);
|
|
41
41
|
},
|
|
42
42
|
children: [
|
|
43
|
-
|
|
44
|
-
/* @__PURE__ */ jsx(CodeIcon, { size: 14 }),
|
|
43
|
+
/* @__PURE__ */ jsxs("span", { className: "relative", children: [
|
|
44
|
+
chat.chatMode === "code" ? /* @__PURE__ */ jsx(CodeIcon, { size: 14 }) : /* @__PURE__ */ jsx(AgentIcon, { size: 14 }),
|
|
45
45
|
chat.hasChanges ? /* @__PURE__ */ jsx("span", { className: "absolute -bottom-0.5 -right-0.5 w-2 h-2 rounded-full bg-destructive" }) : null
|
|
46
|
-
] })
|
|
46
|
+
] }),
|
|
47
47
|
/* @__PURE__ */ jsx("span", { className: "truncate flex-1", children: chat.title })
|
|
48
48
|
]
|
|
49
49
|
}
|
|
@@ -40,12 +40,10 @@ export function SidebarHistoryItem({ chat, isActive, onDelete, onStar, onRename
|
|
|
40
40
|
setOpenMobile(false);
|
|
41
41
|
}}
|
|
42
42
|
>
|
|
43
|
-
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
</span>
|
|
48
|
-
) : <AgentIcon size={14} />}
|
|
43
|
+
<span className="relative">
|
|
44
|
+
{chat.chatMode === 'code' ? <CodeIcon size={14} /> : <AgentIcon size={14} />}
|
|
45
|
+
{chat.hasChanges ? <span className="absolute -bottom-0.5 -right-0.5 w-2 h-2 rounded-full bg-destructive" /> : null}
|
|
46
|
+
</span>
|
|
49
47
|
<span className="truncate flex-1">
|
|
50
48
|
{chat.title}
|
|
51
49
|
</span>
|
package/lib/tools/docker.js
CHANGED
|
@@ -233,9 +233,6 @@ async function runInteractiveContainer({ containerName, repo, branch, codingAgen
|
|
|
233
233
|
env.push(`${key}=${value}`);
|
|
234
234
|
}
|
|
235
235
|
}
|
|
236
|
-
if (jobSecrets.length > 0) {
|
|
237
|
-
env.push(`AGENT_JOB_SECRETS=${JSON.stringify(Object.fromEntries(jobSecrets.map(s => [s.key, s.value])))}`);
|
|
238
|
-
}
|
|
239
236
|
// Create per-container API key for agent-secrets access
|
|
240
237
|
const { createAgentJobApiKey } = await import('../db/api-keys.js');
|
|
241
238
|
const { key: agentJobToken } = createAgentJobApiKey(containerName);
|
|
@@ -446,9 +443,6 @@ async function runHeadlessContainer({ containerName, repo, branch, featureBranch
|
|
|
446
443
|
env.push(`${key}=${value}`);
|
|
447
444
|
}
|
|
448
445
|
}
|
|
449
|
-
if (jobSecrets.length > 0) {
|
|
450
|
-
env.push(`AGENT_JOB_SECRETS=${JSON.stringify(Object.fromEntries(jobSecrets.map(s => [s.key, s.value])))}`);
|
|
451
|
-
}
|
|
452
446
|
// Create per-container API key for agent-secrets access
|
|
453
447
|
const { createAgentJobApiKey } = await import('../db/api-keys.js');
|
|
454
448
|
const { key: agentJobToken } = createAgentJobApiKey(containerName);
|
|
@@ -916,9 +910,6 @@ async function runAgentJobContainer({ agentJobId, repo, branch, title, descripti
|
|
|
916
910
|
env.push(`${key}=${value}`);
|
|
917
911
|
}
|
|
918
912
|
}
|
|
919
|
-
if (jobSecrets.length > 0) {
|
|
920
|
-
env.push(`AGENT_JOB_SECRETS=${JSON.stringify(Object.fromEntries(jobSecrets.map(s => [s.key, s.value])))}`);
|
|
921
|
-
}
|
|
922
913
|
|
|
923
914
|
console.log(`[agent-job] id=${shortId} agent=${agent} image=${image} backendApi=${backendApi}`);
|
|
924
915
|
|
package/package.json
CHANGED
|
@@ -4,7 +4,12 @@ You are an autonomous AI agent running inside a Docker container on thepopebot.
|
|
|
4
4
|
|
|
5
5
|
## Runtime Environment
|
|
6
6
|
|
|
7
|
-
Your workspace is `/home/coding-agent/workspace` — a live git repository.
|
|
7
|
+
Your workspace is `/home/coding-agent/workspace` — a live git repository.
|
|
8
|
+
|
|
9
|
+
## Temporary Files
|
|
10
|
+
Use `/home/coding-agent/workspace/.tmp/` for working files — downloads, screenshots, intermediate data, scripts, generated files. `/home/coding-agent/workspace/.tmp/` is gitignored and nothing there gets committed. If a tool downloads a file, save it to `/home/coding-agent/workspace/.tmp/` and reference it directly.
|
|
11
|
+
|
|
12
|
+
**DO NOT USE** `/tmp` because that will continue waste disk space writing extra layers to the container.
|
|
8
13
|
|
|
9
14
|
Everything in the workspace is automatically committed and pushed when your job finishes. You do not control this. Be intentional about what you put here — **any file you create, move, or download into the workspace WILL be committed.**
|
|
10
15
|
|
|
@@ -10,7 +10,30 @@ Skills are lightweight plugins that extend agent abilities. Each skill lives in
|
|
|
10
10
|
|
|
11
11
|
Both Pi and Claude Code discover skills from the same `skills/active/` directory (via `.pi/skills` and `.claude/skills` symlink bridges).
|
|
12
12
|
|
|
13
|
-
##
|
|
13
|
+
## Conventions
|
|
14
|
+
|
|
15
|
+
### Language Preference
|
|
16
|
+
|
|
17
|
+
**Bash first.** Skills are glue code — API calls, data piping, file manipulation. Bash + curl + python3 (for JSON) handles nearly everything. No module systems, no dependency management, no surprises.
|
|
18
|
+
|
|
19
|
+
Use Node.js **only** when a required library has no alternative (e.g., `youtube-transcript-plus`). Never for new skills where bash + curl would work.
|
|
20
|
+
|
|
21
|
+
### Bash Script Standards
|
|
22
|
+
|
|
23
|
+
- Include `#!/bin/bash` and `set -euo pipefail` at the top
|
|
24
|
+
- `chmod +x` after creating
|
|
25
|
+
|
|
26
|
+
### Node.js Module Rules
|
|
27
|
+
|
|
28
|
+
The root `package.json` has `"type": "module"`, which forces **all** `.js` files in the project tree to be treated as ESM. This silently breaks any script using `require()`.
|
|
29
|
+
|
|
30
|
+
- **`.cjs`** — for CommonJS scripts (uses `require()`)
|
|
31
|
+
- **`.mjs`** — for ESM scripts (uses `import`)
|
|
32
|
+
- **Never use plain `.js`** for skill scripts. The behavior depends on the nearest `package.json` and will break unpredictably.
|
|
33
|
+
|
|
34
|
+
If you encounter a broken `.js` script in a skill, rename it to `.cjs` or `.mjs` as appropriate and update SKILL.md references.
|
|
35
|
+
|
|
36
|
+
### SKILL.md Format
|
|
14
37
|
|
|
15
38
|
Every skill must have a `SKILL.md` with YAML frontmatter:
|
|
16
39
|
|
|
@@ -32,12 +55,30 @@ skills/skill-name/script.sh <args>
|
|
|
32
55
|
- The `description` field appears in the system prompt — keep it concise and action-oriented.
|
|
33
56
|
- Use project-root-relative paths in documentation (e.g., `skills/skill-name/script.sh`).
|
|
34
57
|
|
|
35
|
-
|
|
58
|
+
### Skill Structure
|
|
36
59
|
|
|
37
60
|
- **`SKILL.md`** (required) — YAML frontmatter + markdown documentation
|
|
38
|
-
- **Scripts**
|
|
61
|
+
- **Scripts** — bash (`.sh`) by default, `.cjs`/`.mjs` only when necessary
|
|
39
62
|
- **`package.json`** (optional) — only if Node.js dependencies are truly needed
|
|
40
63
|
|
|
64
|
+
### Credential Setup
|
|
65
|
+
|
|
66
|
+
If a skill needs an API key, add it via the admin UI (Settings > Agent Jobs > Secrets). The secret will be injected as an env var into Docker containers. The agent can discover available secrets via the `get-secret` skill.
|
|
67
|
+
|
|
68
|
+
### Activation & Deactivation
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# Activate
|
|
72
|
+
ln -s ../skill-name skills/active/skill-name
|
|
73
|
+
|
|
74
|
+
# Deactivate
|
|
75
|
+
rm skills/active/skill-name
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The `skills/active/` directory is shared by both agent backends via symlink bridges:
|
|
79
|
+
- `.claude/skills → skills/active`
|
|
80
|
+
- `.pi/skills → skills/active`
|
|
81
|
+
|
|
41
82
|
## Creating a Skill
|
|
42
83
|
|
|
43
84
|
### Simple bash skill (most common)
|
|
@@ -67,6 +108,8 @@ skills/my-skill/run.sh <args>
|
|
|
67
108
|
**skills/my-skill/run.sh:**
|
|
68
109
|
```bash
|
|
69
110
|
#!/bin/bash
|
|
111
|
+
set -euo pipefail
|
|
112
|
+
|
|
70
113
|
if [ -z "$1" ]; then echo "Usage: run.sh <args>"; exit 1; fi
|
|
71
114
|
if [ -z "$MY_API_KEY" ]; then echo "Error: MY_API_KEY not set"; exit 1; fi
|
|
72
115
|
# ... skill logic
|
|
@@ -80,25 +123,7 @@ ln -s ../my-skill skills/active/my-skill
|
|
|
80
123
|
|
|
81
124
|
### Node.js skill
|
|
82
125
|
|
|
83
|
-
Use
|
|
84
|
-
|
|
85
|
-
## Activation & Deactivation
|
|
86
|
-
|
|
87
|
-
```bash
|
|
88
|
-
# Activate
|
|
89
|
-
ln -s ../skill-name skills/active/skill-name
|
|
90
|
-
|
|
91
|
-
# Deactivate
|
|
92
|
-
rm skills/active/skill-name
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
The `skills/active/` directory is shared by both agent backends via symlink bridges:
|
|
96
|
-
- `.claude/skills → skills/active`
|
|
97
|
-
- `.pi/skills → skills/active`
|
|
98
|
-
|
|
99
|
-
## Credential Setup
|
|
100
|
-
|
|
101
|
-
If a skill needs an API key, add it via the admin UI (Settings > Agent Jobs > Secrets). The secret will be injected as an env var into Docker containers. The agent can discover available secrets via the `get-secret` skill.
|
|
126
|
+
Use only when a required library has no bash/curl alternative. Add a `package.json` with dependencies — they're installed automatically in Docker. Use `.cjs` for CommonJS or `.mjs` for ESM — never plain `.js`.
|
|
102
127
|
|
|
103
128
|
## Testing
|
|
104
129
|
|
|
@@ -1,25 +1,23 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: agent-job-secrets
|
|
3
|
-
description: List
|
|
3
|
+
description: List and retrieve agent secrets. Plain secrets are also available as env vars. OAuth credentials are auto-refreshed on every get call.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
## Usage
|
|
7
7
|
|
|
8
8
|
```bash
|
|
9
|
-
# List available
|
|
9
|
+
# List available secret keys (fetches current list from server)
|
|
10
10
|
node skills/agent-job-secrets/agent-job-secrets.js
|
|
11
11
|
|
|
12
12
|
# Get a secret value (OAuth credentials are auto-refreshed)
|
|
13
13
|
node skills/agent-job-secrets/agent-job-secrets.js get MY_CREDENTIALS
|
|
14
|
-
|
|
15
|
-
# Set/update a secret (plain string or piped value)
|
|
16
|
-
node skills/agent-job-secrets/agent-job-secrets.js set MY_KEY "value"
|
|
17
|
-
echo "$UPDATED_CREDENTIALS" | node skills/agent-job-secrets/agent-job-secrets.js set MY_KEY
|
|
18
14
|
```
|
|
19
15
|
|
|
20
16
|
## Notes
|
|
21
17
|
|
|
22
18
|
- `AGENT_JOB_TOKEN` and `APP_URL` are injected automatically — no setup required
|
|
23
|
-
- OAuth
|
|
24
|
-
-
|
|
25
|
-
-
|
|
19
|
+
- Plain (non-OAuth) secrets are also available directly as env vars (e.g. `echo $MY_KEY`)
|
|
20
|
+
- OAuth credentials must be fetched via `get` — they are not available as env vars
|
|
21
|
+
- `get` on an OAuth credential refreshes it server-side and returns a fresh access token
|
|
22
|
+
- If a fetched credential stops working (expired token, 401 error), call `get` again to obtain a fresh one
|
|
23
|
+
- `list` always fetches from the server, so it reflects secrets added after the container started
|
|
@@ -1,31 +1,43 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { readFileSync } from 'fs';
|
|
3
2
|
|
|
4
|
-
const [cmd, key
|
|
3
|
+
const [cmd, key] = process.argv.slice(2);
|
|
4
|
+
|
|
5
|
+
const apiKey = process.env.AGENT_JOB_TOKEN;
|
|
6
|
+
const appUrl = process.env.APP_URL;
|
|
5
7
|
|
|
6
8
|
// Default to list
|
|
7
9
|
if (!cmd || cmd === 'list') {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
console.log('No agent secrets configured.');
|
|
10
|
+
if (!apiKey || !appUrl) {
|
|
11
|
+
console.log('No agent secrets available (missing AGENT_JOB_TOKEN or APP_URL).');
|
|
11
12
|
process.exit(0);
|
|
12
13
|
}
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
|
|
14
|
+
const url = `${appUrl}/api/agent-job-list-secrets`;
|
|
15
|
+
const res = await fetch(url, {
|
|
16
|
+
headers: { 'x-api-key': apiKey },
|
|
17
|
+
});
|
|
18
|
+
if (!res.ok) {
|
|
19
|
+
const body = await res.text();
|
|
20
|
+
console.error(`GET ${url} → ${res.status} ${body}`);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
const json = await res.json();
|
|
24
|
+
const secrets = json.secrets;
|
|
25
|
+
if (!secrets || secrets.length === 0) {
|
|
16
26
|
console.log('No agent secrets configured.');
|
|
17
27
|
} else {
|
|
18
28
|
console.log('Available secrets:');
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
29
|
+
secrets.forEach(s => {
|
|
30
|
+
const hint = s.secretType === 'oauth2' ? ' (OAuth — use get to fetch access token)'
|
|
31
|
+
: s.secretType === 'oauth_token' ? ' (OAuth token — use get to fetch)'
|
|
32
|
+
: '';
|
|
33
|
+
console.log(` - ${s.key}${hint}`);
|
|
22
34
|
});
|
|
35
|
+
console.log('\nUse: agent-job-secrets get KEY_NAME');
|
|
36
|
+
console.log('If a fetched value stops working, call get again for a fresh one.');
|
|
23
37
|
}
|
|
24
38
|
process.exit(0);
|
|
25
39
|
}
|
|
26
40
|
|
|
27
|
-
const apiKey = process.env.AGENT_JOB_TOKEN;
|
|
28
|
-
const appUrl = process.env.APP_URL;
|
|
29
41
|
if (!apiKey) { console.error('AGENT_JOB_TOKEN not available'); process.exit(1); }
|
|
30
42
|
if (!appUrl) { console.error('APP_URL not available'); process.exit(1); }
|
|
31
43
|
|
|
@@ -45,31 +57,6 @@ if (cmd === 'get') {
|
|
|
45
57
|
process.exit(0);
|
|
46
58
|
}
|
|
47
59
|
|
|
48
|
-
if (cmd === 'set') {
|
|
49
|
-
if (!key) {
|
|
50
|
-
console.error('Usage: agent-job-secrets set KEY_NAME [value]');
|
|
51
|
-
console.error(' echo "value" | agent-job-secrets set KEY_NAME');
|
|
52
|
-
process.exit(1);
|
|
53
|
-
}
|
|
54
|
-
let value = inlineValue;
|
|
55
|
-
if (value === undefined) {
|
|
56
|
-
value = readFileSync('/dev/stdin', 'utf8').trim();
|
|
57
|
-
}
|
|
58
|
-
const url = `${appUrl}/api/set-agent-job-secret`;
|
|
59
|
-
const res = await fetch(url, {
|
|
60
|
-
method: 'POST',
|
|
61
|
-
headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey },
|
|
62
|
-
body: JSON.stringify({ key, value }),
|
|
63
|
-
});
|
|
64
|
-
if (!res.ok) {
|
|
65
|
-
const body = await res.text();
|
|
66
|
-
console.error(`POST ${url} → ${res.status} ${body}`);
|
|
67
|
-
process.exit(1);
|
|
68
|
-
}
|
|
69
|
-
const json = await res.json();
|
|
70
|
-
console.log(`Secret "${key}" updated.`);
|
|
71
|
-
process.exit(0);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
60
|
console.error(`Unknown command: ${cmd}`);
|
|
61
|
+
console.error('Available commands: list, get');
|
|
75
62
|
process.exit(1);
|