thepopebot 1.2.75-beta.17 → 1.2.75-beta.19

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 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
- | POST | `/api/set-agent-job-secret` | `x-api-key` | Set/update an agent job secret (for agents to persist rotated credentials) |
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 handleSetAgentSecret(request) {
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
- const body = await request.json();
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': return handleGetAgentSecret(request);
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
- chat.chatMode === "code" ? /* @__PURE__ */ jsxs("span", { className: "relative", children: [
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
- ] }) : /* @__PURE__ */ jsx(AgentIcon, { size: 16 }),
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
- {chat.chatMode === 'code' ? (
316
- <span className="relative">
317
- <CodeIcon size={16} />
318
- {chat.hasChanges ? <span className="absolute -bottom-0.5 -right-0.5 w-2 h-2 rounded-full bg-destructive" /> : null}
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
- chat.chatMode === "code" ? /* @__PURE__ */ jsxs("span", { className: "relative", children: [
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
- ] }) : /* @__PURE__ */ jsx(AgentIcon, { size: 14 }),
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
- {chat.chatMode === 'code' ? (
44
- <span className="relative">
45
- <CodeIcon size={14} />
46
- {chat.hasChanges ? <span className="absolute -bottom-0.5 -right-0.5 w-2 h-2 rounded-full bg-destructive" /> : null}
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>
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thepopebot",
3
- "version": "1.2.75-beta.17",
3
+ "version": "1.2.75-beta.19",
4
4
  "type": "module",
5
5
  "description": "Create autonomous AI agents with a two-layer architecture: Next.js Event Handler + Docker Agent.",
6
6
  "bin": {
@@ -0,0 +1,5 @@
1
+ # .tmp/ — Scratch Directory
2
+
3
+ Temporary working files — downloads, screenshots, snapshots, intermediate data, generated files. This directory is gitignored and nothing here gets committed.
4
+
5
+ Playwright MCP saves screenshots and snapshots here. Other tools and scripts should use this directory for any transient output that doesn't belong in the repo.
@@ -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. Use `/tmp` for working files — downloads, intermediate data, scripts, generated files. `/tmp` is outside the repo and nothing there gets committed. If a tool downloads a file to `/tmp`, leave it there and reference it directly.
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
- ## SKILL.md Format
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
- ## Skill Structure
58
+ ### Skill Structure
36
59
 
37
60
  - **`SKILL.md`** (required) — YAML frontmatter + markdown documentation
38
- - **Scripts** (optional) prefer bash (`.sh`) for simplicity
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 this pattern only when bash + curl isn't sufficient (e.g., HTML parsing, complex data processing). Add a `package.json` with dependencies — they're installed automatically in Docker.
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
 
File without changes
@@ -1,25 +1,23 @@
1
1
  ---
2
2
  name: agent-job-secrets
3
- description: List, get, or update agent secrets. Use get for OAuth credentials (auto-refreshed on every call). Use set to persist updated credentials back to the event handler.
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 secrets (null = must fetch, plain = already in env)
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 credentials show as `null` in the list and must be fetched via `get`
24
- - `get` on an OAuth credential refreshes it and persists the updated token immediately
25
- - Plain secrets are available directly as env vars (e.g. `echo $MY_KEY`)
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, inlineValue] = process.argv.slice(2);
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
- const secretsJson = process.env.AGENT_JOB_SECRETS;
9
- if (!secretsJson) {
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 secrets = JSON.parse(secretsJson);
14
- const keys = Object.keys(secrets);
15
- if (keys.length === 0) {
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
- keys.forEach(k => {
20
- const fetchRequired = secrets[k] === null;
21
- console.log(` - ${k}${fetchRequired ? ' (use agent-job-secrets skill to fetch)' : ''}`);
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);