codiedev 0.9.79 → 0.9.81

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.
Files changed (39) hide show
  1. package/assets/opencode-plugin.mjs +6 -0
  2. package/assets/session-key.mjs +48 -0
  3. package/dist/cli.js +113 -9
  4. package/dist/cliSession.d.ts +108 -0
  5. package/dist/cliSession.js +513 -0
  6. package/dist/codie.js +21 -1
  7. package/dist/commands/agentTemplates.generated.d.ts +11 -0
  8. package/dist/commands/agentTemplates.generated.js +139 -0
  9. package/dist/commands/agents.d.ts +7 -0
  10. package/dist/commands/agents.js +1130 -0
  11. package/dist/commands/agentsSdk.generated.d.ts +4 -0
  12. package/dist/commands/agentsSdk.generated.js +10 -0
  13. package/dist/commands/scrub.d.ts +11 -0
  14. package/dist/commands/scrub.js +102 -0
  15. package/dist/configDir.d.ts +9 -0
  16. package/dist/configDir.js +25 -1
  17. package/dist/connect.js +45 -2
  18. package/dist/connectFlow.d.ts +21 -0
  19. package/dist/connectFlow.js +41 -0
  20. package/dist/hook.js +25 -0
  21. package/dist/knownSecrets.d.ts +43 -0
  22. package/dist/knownSecrets.js +241 -0
  23. package/dist/launcher.d.ts +7 -2
  24. package/dist/launcher.js +36 -2
  25. package/dist/localScrub.d.ts +172 -0
  26. package/dist/localScrub.js +820 -0
  27. package/dist/localScrubReport.d.ts +83 -0
  28. package/dist/localScrubReport.js +268 -0
  29. package/dist/managedPi.js +18 -2
  30. package/dist/mcp.js +3 -0
  31. package/dist/scrub/extraRules.d.ts +4 -0
  32. package/dist/scrub/extraRules.js +17 -0
  33. package/dist/scrub/rules.d.ts +86 -0
  34. package/dist/scrub/rules.js +645 -0
  35. package/dist/scrub/scrubProviderRules.d.ts +14 -0
  36. package/dist/scrub/scrubProviderRules.js +149 -0
  37. package/dist/utils.d.ts +2 -0
  38. package/dist/utils.js +9 -7
  39. package/package.json +5 -4
@@ -0,0 +1,1130 @@
1
+ "use strict";
2
+ // `codiedev agents` — deploy and run agents on Codiedev.
3
+ //
4
+ // One primitive: run an agent. A run starts a fresh sandbox with the agent's
5
+ // bundle, its gateway key (model calls land in Traces under the agent's
6
+ // name) and its secrets, runs its entrypoint (Python, JS/TS or a shell
7
+ // script) once with an input, records the output, and destroys the sandbox. Chains, schedules and webhooks are all
8
+ // just ways of starting a run (see AGENT_GUIDE below, also written by `init`).
9
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ var desc = Object.getOwnPropertyDescriptor(m, k);
12
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
13
+ desc = { enumerable: true, get: function() { return m[k]; } };
14
+ }
15
+ Object.defineProperty(o, k2, desc);
16
+ }) : (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ o[k2] = m[k];
19
+ }));
20
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
21
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
22
+ }) : function(o, v) {
23
+ o["default"] = v;
24
+ });
25
+ var __importStar = (this && this.__importStar) || (function () {
26
+ var ownKeys = function(o) {
27
+ ownKeys = Object.getOwnPropertyNames || function (o) {
28
+ var ar = [];
29
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
30
+ return ar;
31
+ };
32
+ return ownKeys(o);
33
+ };
34
+ return function (mod) {
35
+ if (mod && mod.__esModule) return mod;
36
+ var result = {};
37
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
38
+ __setModuleDefault(result, mod);
39
+ return result;
40
+ };
41
+ })();
42
+ Object.defineProperty(exports, "__esModule", { value: true });
43
+ exports.AGENT_GUIDE = exports.AGENTS_HELP = void 0;
44
+ exports.parseEvery = parseEvery;
45
+ exports.parseDuration = parseDuration;
46
+ exports.runAgents = runAgents;
47
+ const fs = __importStar(require("fs"));
48
+ const path = __importStar(require("path"));
49
+ const child_process_1 = require("child_process");
50
+ const shared_1 = require("./shared");
51
+ const sdk = __importStar(require("./agentsSdk.generated"));
52
+ const agentTemplates_generated_1 = require("./agentTemplates.generated");
53
+ exports.AGENTS_HELP = `
54
+ codiedev agents — deploy agents and run them in fresh sandboxes
55
+
56
+ codiedev agents init <name> [dir] [--lang python|ts|js] [--template <id>]
57
+ Scaffold an agent folder (entrypoint, agent.json, AGENTS.md),
58
+ blank or from a starter / your team's template
59
+ codiedev agents templates [--json] Starter templates and the ones your team and company shared
60
+ codiedev agents publish <agent> [--company] [--summary "…"] [--title "…"] [--as <slug>]
61
+ Share an agent as a template (your team, or --company)
62
+ codiedev agents install <template> <name>
63
+ Deploy a copy of a shared template as your own agent
64
+ codiedev agents unpublish <template> Take a template you shared off the marketplace
65
+ codiedev agents connect <name> [--description "…"] [--budget <usd>]
66
+ An agent you already run on your own servers: get its key.
67
+ Its model calls (via the gateway) show in Traces with spend,
68
+ budget and a kill switch; the key also reaches approvals and state
69
+ codiedev agents deploy [dir] Package the folder and deploy it (new version each time)
70
+ codiedev agents run <name> [--input '<json>' | --input-file f] [--no-wait] [--json] [--quiet]
71
+ [--idempotency-key k] [--delay 30m] [--retries n]
72
+ Start a run; streams its log, then prints the output
73
+ codiedev agents run --local [dir] [--input …] [--env-file .env] [--remote-state]
74
+ Run the agent on this machine with the same SDK, no deploy
75
+ codiedev agents runs [name] [--failed] [--limit n] [--json]
76
+ Recent runs, newest first
77
+ codiedev agents show <run-id> [--json] One run: input, output, error, log, cost, child runs
78
+ codiedev agents logs <run-id> [-f] A run's log; -f follows it until the run ends
79
+ codiedev agents cancel <run-id> Stop a run and its sandbox
80
+ codiedev agents list [--json] Your company's agents, schedules and last runs
81
+ codiedev agents secrets <name> [set KEY=value | unset KEY] [--mine]
82
+ Environment variables the agent gets on every run
83
+ codiedev agents webhook <name> [--off] A URL that starts a run with the request body as input
84
+ codiedev agents stop <name> | --all [--reason "…"] Kill switch: cancel its runs, cut model access, block every start
85
+ codiedev agents resume <name> | --all Let it run again (owner or admin; --all is admins)
86
+ codiedev agents pull <name> [dir] [--version n]
87
+ Download a deployed agent's folder to change and redeploy
88
+ codiedev agents versions <name> Its last 10 deploys
89
+ codiedev agents rollback <name> [--to n]
90
+ Ship an earlier version again (the previous one by default)
91
+ codiedev agents state <name> [list [prefix] | get <key> | set <key> '<json>' | delete <key>]
92
+ The agent's stored state (owner and admins)
93
+ codiedev agents approvals [--all] [--json]
94
+ Decisions agents are waiting on
95
+ codiedev agents decide <approval-id> <option> [--comment "…"]
96
+ Decide one; the agent's run starts with the decision
97
+ codiedev agents delete <name> Remove an agent (its run history stays)
98
+
99
+ agent.json: { "name": "ticket-triage", "entrypoint": "agent.py", "timeoutSec": 600,
100
+ "every": "15m", "retries": 2, "cpu": 1, "memoryMb": 2048, "budgetUsd": 25 }
101
+ .codiedevignore lists extra files to leave out of the bundle (.env*, .git,
102
+ node_modules and virtualenvs are always left out).
103
+ entrypoint: a .py, .js, .mjs, .ts or .sh file. "every" (5m–7d) runs it on a
104
+ schedule; set null for none. retries (0–5) tries a failed run again on the
105
+ same run id, after 15s, 30s, 60s… cpu 0.25–8 cores, memoryMb 512–32768.
106
+ Leave a setting out to keep what it is now (it can be changed on the agent's
107
+ Setup tab in the portal); a new agent gets the defaults.
108
+ `.trim();
109
+ exports.AGENT_GUIDE = `# Building a Codiedev agent
110
+
111
+ This folder is one agent. Each time it's started, Codiedev runs it once in a
112
+ fresh sandbox, then destroys the sandbox. Model access, tracing and secrets
113
+ are already wired in. You write the entrypoint (\`agent.py\`, \`agent.ts\`,
114
+ \`agent.mjs\` or a \`.sh\` script, named in agent.json).
115
+
116
+ ## The primitive
117
+
118
+ Python:
119
+
120
+ \`\`\`python
121
+ import codiedev_agents as agents
122
+
123
+ task = agents.input() # this run's input (any JSON), or None
124
+ llm = agents.openai() # OpenAI SDK client on the Codiedev gateway, traced
125
+ reply = llm.chat.completions.create(model=MODEL, messages=[...])
126
+ agents.output({"answer": "..."}) # this run's result (any JSON)
127
+ \`\`\`
128
+
129
+ TypeScript / JavaScript (\`codiedev-agents\` and \`openai\` are preinstalled):
130
+
131
+ \`\`\`ts
132
+ import * as agents from "codiedev-agents";
133
+
134
+ const task = agents.input();
135
+ const llm = await agents.openai();
136
+ const reply = await llm.chat.completions.create({ model: MODEL, messages: [...] });
137
+ agents.output({ answer: reply.choices[0].message.content });
138
+ \`\`\`
139
+
140
+ From a shell script: the input is \`/agent/.codiedev/input.json\`; write the
141
+ result to \`/agent/.codiedev/output.json\`.
142
+
143
+ - **Chain agents:** \`agents.run("other-agent", {...})\` starts another deployed
144
+ agent as a child run, waits, and returns it; its result is \`["output"]\`
145
+ (\`.output\` in JS). Use \`agents.start(...)\` + \`agents.get(id)\` to fan out in
146
+ parallel. Children show under their parent in Codiedev.
147
+ - **Tools / MCP:** call APIs directly (\`httpx\`, \`fetch\`), or start any MCP
148
+ server over stdio: Python \`mcp\` and Node \`@modelcontextprotocol/sdk\` are
149
+ installed, and \`npx -y <server>\` works. For a remote MCP server, put its
150
+ URL and token in secrets.
151
+ - **Personal secrets:** \`"personalSecrets": ["SALESFORCE_TOKEN"]\` in agent.json
152
+ means each person sets their own (\`secrets <name> set KEY=… --mine\`), and a
153
+ run someone starts uses theirs, never yours. Schedules and webhooks use the
154
+ shared value. Teammates "Use" your agent instead of copying it, so they get
155
+ your updates.
156
+ - **Secrets:** \`codiedev agents secrets <name> set ZENDESK_TOKEN=…\`; read them
157
+ from the environment (\`os.environ["ZENDESK_TOKEN"]\`, \`process.env.ZENDESK_TOKEN\`).
158
+ Never put tokens in code.
159
+ - **Dependencies:** \`requirements.txt\` and/or \`package.json\` (+ lock file). They
160
+ install once into a cached image when they change, not on every run.
161
+ Preinstalled: Python 3.12 with openai, mcp, httpx, pydantic; Node 22 with
162
+ openai, @modelcontextprotocol/sdk, tsx; git, curl.
163
+ - **Exit code:** 0 = succeeded. An exception or non-zero exit = failed, with
164
+ the exception (or the last log line) as the error. Everything printed goes to the run's log.
165
+
166
+ ## The sandbox
167
+
168
+ Linux x64 (Debian, glibc) with internet access. Your code is in \`/agent\` (the
169
+ working directory); \`/tmp\` is writable. Every run gets these environment
170
+ variables:
171
+
172
+ | Variable | What it is |
173
+ |---|---|
174
+ | \`OPENAI_BASE_URL\`, \`OPENAI_API_KEY\` | The Codiedev model gateway and this agent's key. Any OpenAI-compatible tool picks them up as is. |
175
+ | \`CODIEDEV_GATEWAY_URL\`, \`CODIEDEV_API_KEY\` | The same two, under Codiedev names |
176
+ | \`CODIEDEV_AGENT\`, \`CODIEDEV_RUN_ID\` | This agent's name and this run's id |
177
+ | \`CODIEDEV_ATTEMPT\`, \`CODIEDEV_MAX_ATTEMPTS\` | Which try this is, out of how many |
178
+ | \`CODIEDEV_MODEL\` | The agent's default model: the first one its key allows (unset when it allows every model) |
179
+ | your secrets | Each by its own name, e.g. \`GITHUB_TOKEN\` |
180
+
181
+ **Models:** use a model id from your company's **Models** page, like
182
+ \`openai/gpt-5.1-codex-mini\`. A model your company hasn't approved fails with
183
+ \`model_not_allowed\`. Calls are traced under the agent's name and count
184
+ toward its \`budgetUsd\` (a monthly budget for the agent, not per run).
185
+ A call counts toward a run's cost when it sends the header
186
+ \`X-Codiedev-Run-Id: $CODIEDEV_RUN_ID\`. \`agents.openai()\` does; for any other
187
+ client or tool, add the header yourself (see opencode below).
188
+
189
+ **Dependencies** from \`package.json\` install into \`/agent/node_modules\`, so
190
+ their binaries are in \`/agent/node_modules/.bin\`. Python packages are importable
191
+ as usual. A Python agent can have a \`package.json\` too (both install).
192
+
193
+ ## Outside tools (opencode, aider, Codex CLI…)
194
+
195
+ Any tool that speaks the OpenAI API works: point it at \`OPENAI_BASE_URL\` with
196
+ \`OPENAI_API_KEY\`. For example, opencode as a coding agent inside a run
197
+ (\`"opencode-ai"\` in package.json, \`"timeoutSec": 3600\` and \`"cpu": 2,
198
+ "memoryMb": 4096\` in agent.json):
199
+
200
+ \`\`\`python
201
+ import json, os, subprocess
202
+ config = {
203
+ "$schema": "https://opencode.ai/config.json",
204
+ "autoupdate": False, "share": "disabled", "permission": "allow",
205
+ "provider": {"codiedev": {
206
+ "npm": "@ai-sdk/openai-compatible",
207
+ "options": {"baseURL": os.environ["OPENAI_BASE_URL"], "apiKey": "{env:OPENAI_API_KEY}",
208
+ # Counts its model calls toward this run's cost.
209
+ "headers": {"X-Codiedev-Run-Id": os.environ["CODIEDEV_RUN_ID"]}},
210
+ "models": {MODEL: {}},
211
+ }},
212
+ }
213
+ json.dump(config, open(f"{repo_dir}/opencode.json", "w"))
214
+ subprocess.run(["/agent/node_modules/.bin/opencode", "run", "--model", f"codiedev/{MODEL}", prompt],
215
+ cwd=repo_dir, check=True, timeout=45 * 60)
216
+ \`\`\`
217
+
218
+ Keep the tool's own time limit under the run's, so the agent can still write
219
+ its output. Git is installed; clone with a token from secrets
220
+ (\`https://x-access-token:$GITHUB_TOKEN@github.com/owner/repo.git\`).
221
+
222
+ ## Building blocks
223
+
224
+ All in the SDK (Python names shown; JS uses camelCase and returns promises).
225
+
226
+ - **State that outlives runs:** \`agents.state.get(key, default)\`,
227
+ \`.set(key, value, ttl=None)\`, \`.delete(key)\`, \`.list(prefix)\`. Values are any
228
+ JSON up to 64 KB, 1,000 keys per agent, shared by all its runs. For a
229
+ counter or a cursor that parallel runs touch, use
230
+ \`agents.state.update(key, lambda old: ...)\`, or \`get_versioned\` +
231
+ \`set(..., if_version=n)\`, which raises \`StateConflict\` instead of overwriting.
232
+ - **Retries:** \`"retries": 2\` in agent.json (or \`--retries\` / \`retries=\` per run)
233
+ tries a failed run again on the same run id after 15s, 30s, 60s… Callers
234
+ waiting on it keep waiting. \`agents.attempt()\` says which try this is.
235
+ - **Steps (durable across retries):** wrap side effects in
236
+ \`agents.step("post-reply", lambda: post_reply())\`. A finished step returns
237
+ its saved result on a retry instead of running again, so a retry never
238
+ posts twice. A multi-step job is a list of steps.
239
+ - **Answering a webhook:** a caller that POSTs to \`<webhook url>?wait=30\`
240
+ gets the run's result as the HTTP response (Slack slash commands, form
241
+ handlers). \`agents.respond(body, status=200, headers=None)\` sets exactly
242
+ what it gets; otherwise it's the output. Runs take seconds to start, so
243
+ for senders with a 3s limit, acknowledge and post back instead.
244
+ - **Later:** \`agents.start("name", input, delay=3600)\` starts a run in an hour
245
+ (up to 7 days). An agent can schedule its own next check this way.
246
+ - **Human approval:** \`agents.request_approval("Refund $120 to Acme?",
247
+ details=..., context={...})\` puts a decision in Codiedev (Agents →
248
+ Approvals, or \`codiedev agents decide\`) and returns a \`url\` to post anywhere.
249
+ End the run there; when someone decides, a new run of this agent starts
250
+ with input \`{"approval": {"decision", "comment", "decidedBy"}, "context": ...}\`.
251
+ Check \`agents.input().get("approval")\` at the top to tell the two apart.
252
+ Unanswered by \`expires_in\` (default 7 days): decision \`"expired"\`.
253
+ \`options=[...]\` for other choices; \`approvers=[emails]\` to limit who decides.
254
+
255
+ ## Ship, watch, iterate
256
+
257
+ \`\`\`bash
258
+ codiedev agents run --local --input '{"ticket": 42}' # run it here first, no deploy
259
+ codiedev agents deploy # from this folder
260
+ codiedev agents run <name> --input '{"ticket": 42}' # streams the log, prints output
261
+ codiedev agents runs <name> --failed # what broke
262
+ codiedev agents show <run-id> --json # input, output, log, cost, children
263
+ codiedev agents logs <run-id> -f # follow a run that's still going
264
+ \`\`\`
265
+
266
+ To fix a failing agent: \`codiedev agents runs <name> --failed\`, read the run
267
+ with \`show --json\`, change the code, try it with \`run --local\` and the same
268
+ input, \`deploy\`, then rerun it with \`codiedev agents run <name> --input-file <file>\`.
269
+ A deploy that made things worse: \`codiedev agents rollback <name>\` (the last
270
+ 10 versions are kept; \`codiedev agents versions <name>\` lists them).
271
+
272
+ \`run --local\` runs the entrypoint on your machine with the same SDK: secrets
273
+ from \`.env\` (never bundled), state in \`.codiedev/local/state.json\`
274
+ (\`--remote-state\` uses the deployed agent's), model calls on your own CLI
275
+ token. Python agents need \`openai\` installed locally; TS runs through \`npx tsx\`. \`show\` also prints the run's
276
+ Traces id, so \`codiedev traces\` (test cases, evals) works on it.
277
+
278
+ ## Starting runs
279
+
280
+ - **Schedule:** set \`"every": "15m"\` in agent.json and redeploy. A scheduled run
281
+ is skipped while the previous one is still going.
282
+ - **Webhook:** \`codiedev agents webhook <name>\` returns a URL. A POST to it
283
+ starts a run with the body as input (JSON parsed, anything else as a
284
+ string). \`agents.request()\` gives the exact request (method, query,
285
+ headers, raw body) so the agent can verify the sender's signature (Slack
286
+ \`X-Slack-Signature\`, GitHub \`X-Hub-Signature-256\`); keep the signing
287
+ secret in secrets. Slack's URL check is answered for you.
288
+ - **From other code:** POST \`/api/agents/v1/run\` with \`{"agent", "input"}\` and
289
+ your CLI token, or \`codiedev_agents.run()\` with \`CODIEDEV_TOKEN\` and
290
+ \`CODIEDEV_AGENTS_URL\` set.
291
+ - **No duplicates:** send \`Idempotency-Key\` (header, or \`idempotencyKey\` in the
292
+ body, or \`--idempotency-key\`). A repeat within a day returns the first run.
293
+
294
+ ## Sharing it
295
+
296
+ \`codiedev agents publish <name> --summary "…"\` shares it as a template with
297
+ your team (\`--company\` for everyone), like a skill. Teammates find it under
298
+ Agents → Marketplace or \`codiedev agents templates\`; they can run yours as
299
+ is, or install their own copy (\`codiedev agents install <template> <name>\`)
300
+ with their own secrets. Publish again to update it. Write a README.md in the
301
+ folder: it's what people read before installing.
302
+
303
+ ## Who can do what
304
+
305
+ Anyone in the company can run, pull and watch any agent. Redeploying,
306
+ deleting, rolling back, secrets, webhooks and reading its state from outside
307
+ a run are for the agent's owner (who first deployed it) and company admins,
308
+ since whoever can redeploy an agent can read its secrets. Anyone can decide
309
+ an approval unless it names approvers.
310
+
311
+ ## Cost and limits
312
+
313
+ Each run shows its sandbox time and cost (passed through at the provider's
314
+ rate, about $0.19 an hour at 1 core / 2 GB) and its model spend. The agent's
315
+ gateway key has a monthly budget (\`budgetUsd\` in agent.json, $25 by default).
316
+
317
+ Input 64 KB, output 256 KB, bundle 10 MB, runs up to 1 hour, chains up to 5
318
+ deep, 25 runs at once per company, retries up to 5, delays up to 7 days,
319
+ state 1,000 keys × 64 KB per agent.
320
+ `;
321
+ const STARTER_AGENT = `"""%NAME% — a Codiedev agent. See AGENTS.md for the primitive."""
322
+
323
+ import json
324
+ import os
325
+
326
+ import codiedev_agents as agents
327
+
328
+ MODEL = os.environ.get("MODEL", "%MODEL%")
329
+
330
+
331
+ def main() -> None:
332
+ task = agents.input() or {"question": "Say hello and tell me what you can do."}
333
+ llm = agents.openai()
334
+ reply = llm.chat.completions.create(
335
+ model=MODEL,
336
+ messages=[
337
+ {"role": "system", "content": "You are a concise, helpful assistant."},
338
+ {"role": "user", "content": json.dumps(task)},
339
+ ],
340
+ )
341
+ answer = reply.choices[0].message.content
342
+ print(answer)
343
+ agents.output({"answer": answer})
344
+
345
+
346
+ if __name__ == "__main__":
347
+ main()
348
+ `;
349
+ const STARTER_TS = `// %NAME% — a Codiedev agent. See AGENTS.md for the primitive.
350
+ import * as agents from "codiedev-agents";
351
+
352
+ const MODEL = process.env.MODEL ?? "%MODEL%";
353
+
354
+ const task = agents.input({ question: "Say hello and tell me what you can do." });
355
+ const llm = await agents.openai();
356
+ const reply = await llm.chat.completions.create({
357
+ model: MODEL,
358
+ messages: [
359
+ { role: "system", content: "You are a concise, helpful assistant." },
360
+ { role: "user", content: JSON.stringify(task) },
361
+ ],
362
+ });
363
+ const answer = reply.choices[0].message.content;
364
+ console.log(answer);
365
+ agents.output({ answer });
366
+ `;
367
+ const ENTRYPOINTS = ["agent.py", "agent.ts", "agent.mjs", "agent.js", "agent.sh"];
368
+ const BOOLEAN_FLAGS = ["--mine", "--json", "--no-wait", "--failed", "--off", "--quiet", "--follow", "--force", "--local", "--remote-state", "--all"];
369
+ function flag(args, name) {
370
+ const i = args.indexOf(name);
371
+ return i >= 0 ? args[i + 1] : undefined;
372
+ }
373
+ function positional(args) {
374
+ const out = [];
375
+ for (let i = 0; i < args.length; i++) {
376
+ if (args[i] === "-f")
377
+ continue;
378
+ if (args[i].startsWith("--")) {
379
+ if (!BOOLEAN_FLAGS.includes(args[i]))
380
+ i++;
381
+ continue;
382
+ }
383
+ out.push(args[i]);
384
+ }
385
+ return out;
386
+ }
387
+ function parseEvery(value) {
388
+ if (value === undefined)
389
+ return undefined;
390
+ if (value === null || value === "" || value === "off")
391
+ return null;
392
+ const m = /^(\d+)\s*(m|min|h|d)$/.exec(String(value).trim());
393
+ if (!m)
394
+ throw new Error(`"every" looks like 15m, 2h or 1d (got ${value})`);
395
+ const n = Number(m[1]);
396
+ return m[2] === "d" ? n * 1440 : m[2] === "h" ? n * 60 : n;
397
+ }
398
+ function fail(message) {
399
+ console.error(message);
400
+ process.exit(1);
401
+ }
402
+ function readAgentJson(dir) {
403
+ const file = path.join(dir, "agent.json");
404
+ if (!fs.existsSync(file))
405
+ return {};
406
+ try {
407
+ return JSON.parse(fs.readFileSync(file, "utf8"));
408
+ }
409
+ catch (err) {
410
+ return fail(`agent.json isn't valid JSON: ${err.message}`);
411
+ }
412
+ }
413
+ const EXCLUDES = [".git", "node_modules", ".venv", "venv", "__pycache__", ".codiedev", ".env", ".env.*", "*.pyc", ".DS_Store"];
414
+ /** Patterns from .codiedevignore (one per line, # comments), on top of EXCLUDES. */
415
+ function ignorePatterns(dir) {
416
+ const file = path.join(dir, ".codiedevignore");
417
+ if (!fs.existsSync(file))
418
+ return [];
419
+ return fs.readFileSync(file, "utf8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
420
+ }
421
+ function bundle(dir) {
422
+ // macOS bsdtar: leave out extended attributes, which GNU tar warns about.
423
+ const mac = process.platform === "darwin" ? ["--no-mac-metadata", "--no-xattrs"] : [];
424
+ const excludes = [...EXCLUDES, ...ignorePatterns(dir)];
425
+ const args = ["-czf", "-", ...mac, ...excludes.map((e) => `--exclude=${e}`), "-C", dir, "."];
426
+ return (0, child_process_1.execFileSync)("tar", args, { maxBuffer: 64 * 1024 * 1024, env: { ...process.env, COPYFILE_DISABLE: "1" } });
427
+ }
428
+ function ago(at) {
429
+ if (!at)
430
+ return "—";
431
+ const s = Math.round((Date.now() - at) / 1000);
432
+ if (s < 60)
433
+ return `${s}s ago`;
434
+ if (s < 3600)
435
+ return `${Math.round(s / 60)}m ago`;
436
+ if (s < 86400)
437
+ return `${Math.round(s / 3600)}h ago`;
438
+ return `${Math.round(s / 86400)}d ago`;
439
+ }
440
+ const secs = (ms) => (ms == null ? "—" : `${(ms / 1000).toFixed(1)}s`);
441
+ const DONE = ["succeeded", "failed", "cancelled"];
442
+ const usd = (n) => (n == null ? "—" : n < 0.01 ? `$${n.toFixed(4)}` : `$${n.toFixed(2)}`);
443
+ /** Prints the part of a run's log not printed yet. The run keeps the last
444
+ * 16 KB of its log and a count of everything logged, so the new part is the
445
+ * tail's last (logChars - printed) characters; a gap means lines scrolled off. */
446
+ function makeLogPrinter(write) {
447
+ let printed = 0;
448
+ return (run) => {
449
+ const tail = run.logTail ?? "";
450
+ const total = run.logChars ?? tail.length;
451
+ if (total <= printed)
452
+ return;
453
+ const fresh = total - printed;
454
+ if (fresh > tail.length)
455
+ write(`… ${fresh - tail.length} characters not shown …\n`);
456
+ write(tail.slice(Math.max(0, tail.length - fresh)));
457
+ printed = total;
458
+ };
459
+ }
460
+ function pretty(text) {
461
+ if (text == null)
462
+ return "(none)";
463
+ try {
464
+ return JSON.stringify(JSON.parse(text), null, 2);
465
+ }
466
+ catch {
467
+ return text;
468
+ }
469
+ }
470
+ function writeGuides(dir) {
471
+ if (!fs.existsSync(path.join(dir, "AGENTS.md")))
472
+ fs.writeFileSync(path.join(dir, "AGENTS.md"), exports.AGENT_GUIDE);
473
+ if (!fs.existsSync(path.join(dir, "CLAUDE.md")))
474
+ fs.writeFileSync(path.join(dir, "CLAUDE.md"), "@AGENTS.md\n");
475
+ }
476
+ /** Fills %NAME% and %MODEL%. Without --model, a run uses its agent's default
477
+ * model (CODIEDEV_MODEL, the first one its key allows). */
478
+ function fillTemplate(text, file, name, model) {
479
+ let out = text.replace(/%NAME%/g, name);
480
+ if (!model) {
481
+ const fallback = file.endsWith(".py") ? 'os.environ.get("CODIEDEV_MODEL", "codiedev/fast")' : '(process.env.CODIEDEV_MODEL ?? "codiedev/fast")';
482
+ out = out.replace(/"%MODEL%"/g, fallback);
483
+ }
484
+ return out.replace(/%MODEL%/g, model ?? "codiedev/fast");
485
+ }
486
+ /** init --template: a starter (built in) or a template your team or company shared. */
487
+ async function initFromTemplate(name, dir, id, args) {
488
+ const model = flag(args, "--model");
489
+ const starter = agentTemplates_generated_1.TEMPLATES.find((t) => t.id === id);
490
+ if (starter) {
491
+ for (const [rel, text] of Object.entries(starter.files)) {
492
+ fs.mkdirSync(path.dirname(path.join(dir, rel)), { recursive: true });
493
+ fs.writeFileSync(path.join(dir, rel), fillTemplate(text, rel, name, model));
494
+ }
495
+ writeGuides(dir);
496
+ console.log(`Created ${path.relative(process.cwd(), dir) || "."}/ from the ${starter.title} template.`);
497
+ console.log(`Read its README.md, then try it: cd ${path.relative(process.cwd(), dir) || "."} && codiedev agents run --local`);
498
+ return;
499
+ }
500
+ const config = (0, shared_1.requireConfig)();
501
+ const res = await fetch(new URL(`/api/agents/v1/templates/bundle?slug=${encodeURIComponent(id)}`, config.backendUrl), { headers: { Authorization: `Bearer ${config.token}` } });
502
+ if (!res.ok) {
503
+ const body = (await res.json().catch(() => ({})));
504
+ fail(`${body.error ?? `HTTP ${res.status}`}. Starter templates: ${agentTemplates_generated_1.TEMPLATES.map((t) => t.id).join(", ")} (codiedev agents templates)`);
505
+ }
506
+ const meta = JSON.parse(res.headers.get("x-codiedev-template") ?? "{}");
507
+ (0, child_process_1.execFileSync)("tar", ["-xzf", "-", "-C", dir], { input: Buffer.from(await res.arrayBuffer()) });
508
+ const agentJson = readAgentJson(dir);
509
+ fs.writeFileSync(path.join(dir, "agent.json"), JSON.stringify({ ...agentJson, name }, null, 2) + "\n");
510
+ writeGuides(dir);
511
+ console.log(`Created ${path.relative(process.cwd(), dir) || "."}/ from the ${id} template.`);
512
+ if (meta.secretNames?.length)
513
+ console.log(`It uses secrets: ${meta.secretNames.join(", ")}. Set them after deploying: codiedev agents secrets ${name} set NAME=value`);
514
+ }
515
+ async function cmdInit(args) {
516
+ const [name, dirArg] = positional(args);
517
+ if (!name || !/^[a-z][a-z0-9-]{1,47}$/.test(name))
518
+ fail("Usage: codiedev agents init <name> [dir] [--template <id>] (lowercase letters, digits, dashes)");
519
+ const dir = path.resolve(dirArg ?? name);
520
+ const template = flag(args, "--template");
521
+ if (template) {
522
+ if (fs.existsSync(dir) && fs.readdirSync(dir).length)
523
+ fail(`${dir} isn't empty. Pick another folder.`);
524
+ fs.mkdirSync(dir, { recursive: true });
525
+ return initFromTemplate(name, dir, template, args);
526
+ }
527
+ const lang = flag(args, "--lang") ?? "python";
528
+ if (!["python", "py", "ts", "typescript", "js", "javascript"].includes(lang))
529
+ fail("--lang is python, ts or js");
530
+ const existing = ENTRYPOINTS.find((f) => fs.existsSync(path.join(dir, f)));
531
+ if (existing)
532
+ fail(`${dir} already has an ${existing}`);
533
+ fs.mkdirSync(dir, { recursive: true });
534
+ const model = flag(args, "--model");
535
+ let entrypoint = "agent.py";
536
+ if (lang.startsWith("py")) {
537
+ fs.writeFileSync(path.join(dir, "agent.py"), fillTemplate(STARTER_AGENT, "agent.py", name, model));
538
+ fs.writeFileSync(path.join(dir, "requirements.txt"), "# Extra packages for this agent, one per line. openai, mcp, httpx and pydantic are preinstalled.\n");
539
+ }
540
+ else {
541
+ entrypoint = lang.startsWith("t") ? "agent.ts" : "agent.mjs";
542
+ fs.writeFileSync(path.join(dir, entrypoint), fillTemplate(STARTER_TS, entrypoint, name, model));
543
+ // No dependencies needed to start (openai and codiedev-agents are
544
+ // preinstalled); add them here and they install into a cached image.
545
+ fs.writeFileSync(path.join(dir, "package.json"), JSON.stringify({ name, private: true, type: "module", dependencies: {} }, null, 2) + "\n");
546
+ }
547
+ fs.writeFileSync(path.join(dir, "agent.json"), JSON.stringify({ name, entrypoint, timeoutSec: 600 }, null, 2) + "\n");
548
+ writeGuides(dir);
549
+ console.log(`Created ${path.relative(process.cwd(), dir) || "."}/ with ${entrypoint}, agent.json and AGENTS.md.`);
550
+ console.log(`Want a head start? codiedev agents templates`);
551
+ console.log(`Next: edit ${entrypoint} (or ask your coding agent to, it reads AGENTS.md), then \`codiedev agents deploy ${dirArg ?? name}\`.`);
552
+ }
553
+ async function cmdDeploy(args, config) {
554
+ const dir = path.resolve(positional(args)[0] ?? ".");
555
+ const meta = readAgentJson(dir);
556
+ const name = flag(args, "--name") ?? meta.name;
557
+ if (!name)
558
+ fail("Name the agent in agent.json (\"name\") or with --name");
559
+ const entrypoint = meta.entrypoint ?? ENTRYPOINTS.find((f) => fs.existsSync(path.join(dir, f))) ?? "agent.py";
560
+ if (!fs.existsSync(path.join(dir, entrypoint)))
561
+ fail(`${entrypoint} not found in ${dir}. Set "entrypoint" in agent.json.`);
562
+ let everyMinutes;
563
+ try {
564
+ everyMinutes = parseEvery(flag(args, "--every") ?? (meta.every === undefined ? null : meta.every));
565
+ }
566
+ catch (err) {
567
+ fail(err.message);
568
+ }
569
+ const tgz = bundle(dir);
570
+ const res = await (0, shared_1.apiRequest)("POST", "/api/agents/v1/deploy", {
571
+ config,
572
+ body: {
573
+ name,
574
+ bundle: tgz.toString("base64"),
575
+ entrypoint,
576
+ timeoutSec: Number(flag(args, "--timeout") ?? meta.timeoutSec ?? 600),
577
+ everyMinutes: everyMinutes ?? null,
578
+ ...(meta.cpu !== undefined ? { cpu: meta.cpu } : {}),
579
+ ...(meta.memoryMb !== undefined ? { memoryMb: meta.memoryMb } : {}),
580
+ ...(meta.budgetUsd !== undefined ? { monthlyBudgetUsd: meta.budgetUsd } : {}),
581
+ ...(meta.retries !== undefined ? { retries: meta.retries } : {}),
582
+ ...(meta.personalSecrets !== undefined ? { personalSecrets: meta.personalSecrets } : {}),
583
+ },
584
+ });
585
+ console.log(`${res.created ? "Deployed" : "Updated"} ${res.agent} → version ${res.version} (${(tgz.length / 1024).toFixed(1)} KB)${everyMinutes ? `, runs every ${everyMinutes}m` : ""}${meta.retries ? `, ${meta.retries} retries` : ""}`);
586
+ if (!res.created)
587
+ console.log(`Undo: codiedev agents rollback ${res.agent}`);
588
+ const deps = ["requirements.txt", "package.json"].filter((f) => fs.existsSync(path.join(dir, f)));
589
+ if (deps.length)
590
+ console.log(`Dependencies (${deps.join(", ")}) install on the first run after they change, then come from cache.`);
591
+ console.log(`Run it: codiedev agents run ${res.agent}`);
592
+ }
593
+ async function waitFor(config, id, timeoutMs, onUpdate) {
594
+ const deadline = Date.now() + timeoutMs;
595
+ for (;;) {
596
+ const run = await (0, shared_1.apiRequest)("GET", `/api/agents/v1/run?id=${encodeURIComponent(id)}`, { config });
597
+ onUpdate?.(run);
598
+ if (DONE.includes(run.status))
599
+ return run;
600
+ if (Date.now() > deadline)
601
+ fail(`Still ${run.status} after ${timeoutMs / 1000}s. Check later: codiedev agents show ${id}`);
602
+ await new Promise((r) => setTimeout(r, 2000));
603
+ }
604
+ }
605
+ function readInput(args) {
606
+ const raw = flag(args, "--input") ?? (flag(args, "--input-file") ? fs.readFileSync(flag(args, "--input-file"), "utf8") : undefined);
607
+ if (raw === undefined)
608
+ return undefined;
609
+ try {
610
+ return JSON.parse(raw);
611
+ }
612
+ catch {
613
+ return raw;
614
+ }
615
+ }
616
+ /** "90", "90s", "30m", "2h", "1d" → seconds. */
617
+ function parseDuration(value) {
618
+ const m = /^(\d+)\s*(s|m|min|h|d)?$/.exec(value.trim());
619
+ if (!m)
620
+ throw new Error(`durations look like 90s, 30m, 2h or 1d (got ${value})`);
621
+ const n = Number(m[1]);
622
+ return m[2] === "d" ? n * 86400 : m[2] === "h" ? n * 3600 : m[2] === "m" || m[2] === "min" ? n * 60 : n;
623
+ }
624
+ async function cmdRun(args, config) {
625
+ const [name] = positional(args);
626
+ if (!name)
627
+ fail("Usage: codiedev agents run <name> [--input '<json>'] [--no-wait] (or --local [dir])");
628
+ const input = readInput(args);
629
+ const idempotencyKey = flag(args, "--idempotency-key");
630
+ let delaySec;
631
+ try {
632
+ delaySec = flag(args, "--delay") ? parseDuration(flag(args, "--delay")) : undefined;
633
+ }
634
+ catch (err) {
635
+ fail(err.message);
636
+ }
637
+ const retries = flag(args, "--retries") !== undefined ? Number(flag(args, "--retries")) : undefined;
638
+ const started = await (0, shared_1.apiRequest)("POST", "/api/agents/v1/run", {
639
+ config,
640
+ body: { agent: name, input, source: "cli", ...(idempotencyKey ? { idempotencyKey } : {}), ...(delaySec ? { delaySec } : {}), ...(retries !== undefined ? { retries } : {}) },
641
+ });
642
+ if (delaySec) {
643
+ console.log(args.includes("--json") ? JSON.stringify(started) : `Scheduled ${name} run ${started.runId} to start in ${flag(args, "--delay")}. Watch it: codiedev agents logs ${started.runId} -f`);
644
+ return;
645
+ }
646
+ if (args.includes("--no-wait")) {
647
+ console.log(args.includes("--json") ? JSON.stringify(started) : `${started.status === "existing" ? "Already started" : "Started"} ${name} run ${started.runId}`);
648
+ return;
649
+ }
650
+ // The log streams to stderr, so stdout stays just the output (pipe-friendly).
651
+ const quiet = args.includes("--quiet") || args.includes("--json");
652
+ process.stderr.write(`Run ${started.runId}${started.status === "existing" ? " (already started with this idempotency key)" : ""}\n`);
653
+ const printLog = makeLogPrinter((t) => process.stderr.write(t));
654
+ let lastStatus = "";
655
+ const run = await waitFor(config, started.runId, Number(flag(args, "--timeout") ?? 3900) * 1000, (r) => {
656
+ const label = r.attempt && r.attempt > 1 ? `${r.status} · attempt ${r.attempt}/${r.maxAttempts}` : r.status;
657
+ if (label !== lastStatus && !DONE.includes(r.status))
658
+ process.stderr.write(`[${label}]\n`);
659
+ lastStatus = label;
660
+ if (!quiet)
661
+ printLog(r);
662
+ });
663
+ const cost = `${secs(run.computeMs ?? run.durationMs)} · compute ${usd(run.computeUsd)} · models ${usd(run.modelUsd)}`;
664
+ if (args.includes("--json")) {
665
+ console.log(JSON.stringify(run, null, 2));
666
+ }
667
+ else if (run.status === "succeeded") {
668
+ process.stderr.write(`[succeeded · ${cost}]\n`);
669
+ console.log(pretty(run.output));
670
+ }
671
+ else {
672
+ process.stderr.write(`[${run.status} · ${cost}]\n`);
673
+ console.error(`${run.status}: ${run.error ?? "no error message"}`);
674
+ if (quiet && run.logTail)
675
+ console.error(`\nLog (tail):\n${run.logTail.split("\n").slice(-25).join("\n")}`);
676
+ console.error(`\nInspect: codiedev agents show ${run.id}`);
677
+ }
678
+ if (run.status !== "succeeded")
679
+ process.exit(1);
680
+ }
681
+ async function cmdRuns(args, config) {
682
+ const [name] = positional(args);
683
+ const q = new URLSearchParams();
684
+ if (name)
685
+ q.set("agent", name);
686
+ if (args.includes("--failed"))
687
+ q.set("status", "failed");
688
+ q.set("limit", flag(args, "--limit") ?? "20");
689
+ const { runs } = await (0, shared_1.apiRequest)("GET", `/api/agents/v1/runs?${q}`, { config });
690
+ if (args.includes("--json"))
691
+ return void console.log(JSON.stringify(runs, null, 2));
692
+ if (!runs.length)
693
+ return void console.log("No runs.");
694
+ for (const r of runs) {
695
+ const chain = r.parentRunId ? ` ↳ child of ${r.parentRunId}` : "";
696
+ console.log(`${r.id} ${r.agent.padEnd(20)} ${r.status.padEnd(10)} ${r.trigger.padEnd(9)} ${secs(r.durationMs).padStart(7)} ${usd(r.computeUsd).padStart(8)} ${ago(r.createdAt)}${chain}`);
697
+ if (r.error)
698
+ console.log(` ${r.error}`);
699
+ }
700
+ }
701
+ async function cmdShow(args, config) {
702
+ const [id] = positional(args);
703
+ if (!id)
704
+ fail("Usage: codiedev agents show <run-id>");
705
+ const r = await (0, shared_1.apiRequest)("GET", `/api/agents/v1/run?id=${encodeURIComponent(id)}`, { config });
706
+ if (args.includes("--json"))
707
+ return void console.log(JSON.stringify(r, null, 2));
708
+ const attempts = r.maxAttempts && r.maxAttempts > 1 ? ` · attempt ${r.attempt}/${r.maxAttempts}` : "";
709
+ const scheduled = r.scheduledFor ? ` · starts ${new Date(r.scheduledFor).toLocaleString()}` : "";
710
+ console.log(`${r.agent} v${r.version} · ${r.status}${attempts}${scheduled} · ${r.trigger} · ${secs(r.durationMs)} · ${ago(r.createdAt)}`);
711
+ console.log(`Cost: compute ${usd(r.computeUsd)}${r.computeMs != null ? ` (${secs(r.computeMs)} sandbox)` : ""} · models ${usd(r.modelUsd)} over ${r.modelCalls ?? 0} calls`);
712
+ if (r.traceRunId)
713
+ console.log(`Trace: ${portalUrl()}/portal/traces?view=runs&run=${encodeURIComponent(r.traceRunId)}`);
714
+ if (r.parentRunId)
715
+ console.log(`Parent: ${r.parentRunId}`);
716
+ if (r.error)
717
+ console.log(`Error: ${r.error}`);
718
+ console.log(`\nInput:\n${pretty(r.input)}\n\nOutput:\n${pretty(r.output)}`);
719
+ if (r.children?.length) {
720
+ console.log("\nChild runs:");
721
+ for (const c of r.children)
722
+ console.log(` ${c.id} ${c.agent} ${c.status} ${secs(c.durationMs)}`);
723
+ }
724
+ if (r.logTail)
725
+ console.log(`\nLog${(r.logChars ?? 0) > r.logTail.length ? " (last 16 KB)" : ""}:\n${r.logTail}`);
726
+ }
727
+ function portalUrl() {
728
+ return (process.env.CODIEDEV_PORTAL_URL || "https://codiedev.com").replace(/\/$/, "");
729
+ }
730
+ async function cmdLogs(args, config) {
731
+ const [id] = positional(args);
732
+ if (!id)
733
+ fail("Usage: codiedev agents logs <run-id> [-f]");
734
+ const follow = args.includes("-f") || args.includes("--follow");
735
+ const print = makeLogPrinter((t) => process.stdout.write(t));
736
+ const run = follow
737
+ ? await waitFor(config, id, 3900 * 1000, print)
738
+ : await (0, shared_1.apiRequest)("GET", `/api/agents/v1/run?id=${encodeURIComponent(id)}`, { config });
739
+ if (!follow)
740
+ print(run);
741
+ if (follow || !DONE.includes(run.status))
742
+ process.stderr.write(`[${run.status}]\n`);
743
+ }
744
+ async function cmdList(args, config) {
745
+ const { agents } = await (0, shared_1.apiRequest)("GET", "/api/agents/v1/agents", { config });
746
+ if (args.includes("--json"))
747
+ return void console.log(JSON.stringify(agents, null, 2));
748
+ if (!agents.length)
749
+ return void console.log("No agents yet. Start one: codiedev agents init <name>");
750
+ for (const a of agents) {
751
+ const how = [a.everyMinutes ? `every ${a.everyMinutes}m` : null, a.hasWebhook ? "webhook" : null].filter(Boolean).join(", ") || "on demand";
752
+ const last = a.lastRun ? `${a.lastRun.status} ${ago(a.lastRun.createdAt)}` : "never run";
753
+ console.log(`${a.name.padEnd(24)} v${String(a.version).padEnd(4)} ${how.padEnd(20)} last: ${last.padEnd(20)} 24h: ${a.runs24h} runs, ${a.failed24h} failed, ${usd(a.computeUsd24h ?? 0)} compute (${a.owner})`);
754
+ }
755
+ }
756
+ async function cmdSecrets(args, config) {
757
+ const [name, action, pair] = positional(args);
758
+ const mine = args.includes("--mine");
759
+ const usage = "Usage: codiedev agents secrets <name> [set KEY=value | unset KEY] [--mine]";
760
+ if (!name)
761
+ fail(usage);
762
+ if (!action) {
763
+ const { secrets } = await (0, shared_1.apiRequest)("GET", `/api/agents/v1/secrets?agent=${encodeURIComponent(name)}`, { config });
764
+ if (!secrets.length)
765
+ return void console.log(`${name} has no secrets. Add one: codiedev agents secrets ${name} set KEY=value`);
766
+ for (const s of secrets) {
767
+ const shared = s.updatedAt ? `shared, updated ${ago(s.updatedAt)}` : "no shared value";
768
+ console.log(s.personal ? `${s.name} (personal: ${s.yours ? `yours set ${ago(s.yours)}` : `yours not set; codiedev agents secrets ${name} set ${s.name}=… --mine`}; ${shared})` : `${s.name} (${shared})`);
769
+ }
770
+ return;
771
+ }
772
+ if (action === "set") {
773
+ const eq = pair?.indexOf("=") ?? -1;
774
+ if (!pair || eq < 1)
775
+ fail(usage);
776
+ await (0, shared_1.apiRequest)("POST", "/api/agents/v1/secrets", { config, body: { agent: name, name: pair.slice(0, eq), value: pair.slice(eq + 1), mine } });
777
+ return void console.log(mine ? `Set your own ${pair.slice(0, eq)} on ${name}. Runs you start use it.` : `Set ${pair.slice(0, eq)} on ${name}. It's in the environment from the next run.`);
778
+ }
779
+ if (action === "unset") {
780
+ if (!pair)
781
+ fail(usage);
782
+ await (0, shared_1.apiRequest)("POST", "/api/agents/v1/secrets", { config, body: { agent: name, name: pair, value: null, mine } });
783
+ return void console.log(mine ? `Removed your own ${pair} from ${name}.` : `Removed ${pair} from ${name}.`);
784
+ }
785
+ fail(usage);
786
+ }
787
+ /** Kill switch: stop (or resume) one agent, or every agent with --all (admins). */
788
+ async function cmdControl(args, config, stop) {
789
+ const [name] = positional(args);
790
+ const all = args.includes("--all");
791
+ if (!name && !all)
792
+ fail(`Usage: codiedev agents ${stop ? "stop" : "resume"} <name> | --all${stop ? " [--reason \"…\"]" : ""}`);
793
+ const res = await (0, shared_1.apiRequest)("POST", `/api/agents/v1/${stop ? "stop" : "resume"}`, { config, body: { agent: name, all, reason: flag(args, "--reason") } });
794
+ const what = all ? "Every agent" : name;
795
+ if (stop)
796
+ console.log(res.already ? `${what} was already stopped.` : `${what} stopped. Cancelled ${res.cancelledRuns ?? 0} run(s) and revoked model access. Nothing starts it until someone resumes it.`);
797
+ else
798
+ console.log(res.companyStopped ? `${what} resumed, but every agent is still stopped company-wide; an admin runs codiedev agents resume --all.` : `${what} resumed with a fresh model key.`);
799
+ }
800
+ async function cmdWebhook(args, config) {
801
+ const [name] = positional(args);
802
+ if (!name)
803
+ fail("Usage: codiedev agents webhook <name> [--off]");
804
+ const res = await (0, shared_1.apiRequest)("POST", "/api/agents/v1/webhook", { config, body: { agent: name, off: args.includes("--off") } });
805
+ if (!res.url)
806
+ return void console.log(`Removed ${name}'s webhook.`);
807
+ console.log(res.url);
808
+ if (process.stderr.isTTY)
809
+ process.stderr.write("POST JSON to this URL to start a run; the body is the input. It's shown once, and running this again replaces it.\n");
810
+ }
811
+ async function cmdPull(args, config) {
812
+ const [name, dirArg] = positional(args);
813
+ if (!name)
814
+ fail("Usage: codiedev agents pull <name> [dir]");
815
+ const dir = path.resolve(dirArg ?? name);
816
+ if (fs.existsSync(dir) && fs.readdirSync(dir).length && !args.includes("--force"))
817
+ fail(`${dir} isn't empty. Pick another folder or pass --force to unpack over it.`);
818
+ const version = flag(args, "--version");
819
+ const res = await fetch(new URL(`/api/agents/v1/bundle?agent=${encodeURIComponent(name)}${version ? `&version=${encodeURIComponent(version)}` : ""}`, config.backendUrl), {
820
+ headers: { Authorization: `Bearer ${config.token}` },
821
+ });
822
+ if (!res.ok) {
823
+ const body = (await res.json().catch(() => ({})));
824
+ fail(`codiedev agents pull: ${body.error ?? `HTTP ${res.status}`}`);
825
+ }
826
+ fs.mkdirSync(dir, { recursive: true });
827
+ (0, child_process_1.execFileSync)("tar", ["-xzf", "-", "-C", dir], { input: Buffer.from(await res.arrayBuffer()) });
828
+ console.log(`Pulled ${name} v${res.headers.get("x-codiedev-agent-version") ?? "?"} into ${path.relative(process.cwd(), dir) || "."}/. Secrets stay in Codiedev; deploy from here to ship a new version.`);
829
+ }
830
+ async function cmdVersions(args, config) {
831
+ const [name] = positional(args);
832
+ if (!name)
833
+ fail("Usage: codiedev agents versions <name>");
834
+ const { versions } = await (0, shared_1.apiRequest)("GET", `/api/agents/v1/versions?agent=${encodeURIComponent(name)}`, { config });
835
+ if (args.includes("--json"))
836
+ return void console.log(JSON.stringify(versions, null, 2));
837
+ for (const r of versions) {
838
+ const note = [r.current ? "current" : null, r.rollbackOf ? `rollback to v${r.rollbackOf}` : null, r.everyMinutes ? `every ${r.everyMinutes}m` : null, r.retries ? `${r.retries} retries` : null].filter(Boolean).join(", ");
839
+ console.log(`v${String(r.version).padEnd(4)} ${ago(r.createdAt).padEnd(9)} ${r.deployedBy.padEnd(22)} ${(r.bundleBytes / 1024).toFixed(1).padStart(7)} KB ${note}`);
840
+ }
841
+ if (versions.length > 1)
842
+ console.log(`\nRoll back: codiedev agents rollback ${name} [--to <n>]`);
843
+ }
844
+ async function cmdRollback(args, config) {
845
+ const [name] = positional(args);
846
+ if (!name)
847
+ fail("Usage: codiedev agents rollback <name> [--to <version>]");
848
+ const to = flag(args, "--to");
849
+ const res = await (0, shared_1.apiRequest)("POST", "/api/agents/v1/rollback", { config, body: { agent: name, ...(to ? { to: Number(to.replace(/^v/, "")) } : {}) } });
850
+ console.log(`Rolled ${name} back to v${res.from}'s code and settings, as v${res.version}. New runs use it now.`);
851
+ }
852
+ async function cmdState(args, config) {
853
+ const [name, action = "list", key, value] = positional(args);
854
+ if (!name)
855
+ fail("Usage: codiedev agents state <name> [list [prefix] | get <key> | set <key> '<json>' | delete <key>]");
856
+ const agent = encodeURIComponent(name);
857
+ if (action === "list") {
858
+ const keys = [];
859
+ let after = null;
860
+ do {
861
+ const q = new URLSearchParams({ agent: name, prefix: key ?? "", values: args.includes("--json") ? "1" : "0", limit: "200", ...(after ? { after } : {}) });
862
+ const page = await (0, shared_1.apiRequest)("GET", `/api/agents/v1/state?${q}`, { config });
863
+ keys.push(...page.keys);
864
+ after = page.next;
865
+ } while (after);
866
+ if (args.includes("--json"))
867
+ return void console.log(JSON.stringify(keys.map((k) => ({ ...k, value: k.value ? JSON.parse(k.value) : undefined })), null, 2));
868
+ if (!keys.length)
869
+ return void console.log(`${name} has no state${key ? ` under ${key}` : ""}.`);
870
+ for (const k of keys)
871
+ console.log(`${k.key.padEnd(40)} v${String(k.version).padEnd(5)} ${String(k.bytes).padStart(7)} B ${ago(k.updatedAt)}${k.expiresAt ? ` expires ${new Date(k.expiresAt).toLocaleString()}` : ""}`);
872
+ return;
873
+ }
874
+ if (!key)
875
+ fail(`Usage: codiedev agents state ${name} ${action} <key>`);
876
+ if (action === "get") {
877
+ const row = await (0, shared_1.apiRequest)("GET", `/api/agents/v1/state?agent=${agent}&key=${encodeURIComponent(key)}`, { config });
878
+ return void console.log(pretty(row.value));
879
+ }
880
+ if (action === "set" || action === "delete") {
881
+ let parsed = null;
882
+ if (action === "set") {
883
+ if (value === undefined)
884
+ fail(`Usage: codiedev agents state ${name} set <key> '<json>'`);
885
+ try {
886
+ parsed = JSON.parse(value);
887
+ }
888
+ catch {
889
+ parsed = value;
890
+ }
891
+ }
892
+ const res = await (0, shared_1.apiRequest)("POST", "/api/agents/v1/state", { config, body: { agent: name, key, value: parsed } });
893
+ return void console.log(action === "set" ? `Set ${key} (v${res.version}).` : `Deleted ${key}.`);
894
+ }
895
+ fail("Usage: codiedev agents state <name> [list [prefix] | get <key> | set <key> '<json>' | delete <key>]");
896
+ }
897
+ async function cmdApprovals(args, config) {
898
+ const status = args.includes("--all") ? "decided" : "pending";
899
+ const { approvals } = await (0, shared_1.apiRequest)("GET", `/api/agents/v1/approvals?status=${status}`, { config });
900
+ if (args.includes("--json"))
901
+ return void console.log(JSON.stringify(approvals, null, 2));
902
+ if (!approvals.length)
903
+ return void console.log(status === "pending" ? "No approvals waiting." : "No decided approvals.");
904
+ for (const a of approvals) {
905
+ console.log(`${a.id} ${a.agent.padEnd(20)} ${a.title}`);
906
+ if (a.status === "pending")
907
+ console.log(` ${a.options.join(" | ")} · asked ${ago(a.createdAt)} · expires ${new Date(a.expiresAt).toLocaleString()}`);
908
+ else
909
+ console.log(` ${a.decision} by ${a.decidedBy ?? "—"}${a.resumeRunId ? ` → run ${a.resumeRunId}` : ""}`);
910
+ if (a.details)
911
+ console.log(a.details.split("\n").slice(0, 6).map((l) => ` ${l}`).join("\n"));
912
+ }
913
+ if (status === "pending")
914
+ console.log(`\nDecide: codiedev agents decide <id> <option> [--comment "…"]`);
915
+ }
916
+ async function cmdDecide(args, config) {
917
+ const [id, decision] = positional(args);
918
+ if (!id || !decision)
919
+ fail('Usage: codiedev agents decide <approval-id> <option> [--comment "…"]');
920
+ const res = await (0, shared_1.apiRequest)("POST", "/api/agents/v1/approvals/decide", { config, body: { id, decision, comment: flag(args, "--comment") } });
921
+ console.log(`Decided: ${res.decision}.${res.runId ? ` Started run ${res.runId} (codiedev agents logs ${res.runId} -f).` : ""}`);
922
+ }
923
+ function readEnvFile(file) {
924
+ const out = {};
925
+ for (const line of fs.readFileSync(file, "utf8").split("\n")) {
926
+ const m = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)\s*$/.exec(line);
927
+ if (!m)
928
+ continue;
929
+ out[m[1]] = m[2].replace(/^(["'])(.*)\1$/, "$2");
930
+ }
931
+ return out;
932
+ }
933
+ /** Runs the agent in this folder on this machine: same SDK, same input and
934
+ * output files, secrets from .env, state in .codiedev/local/state.json (or
935
+ * the deployed agent's with --remote-state), model calls on your own CLI
936
+ * token. Child runs (agents.run) start for real in Codiedev. */
937
+ async function cmdRunLocal(args) {
938
+ const dir = path.resolve(positional(args)[0] ?? ".");
939
+ const meta = readAgentJson(dir);
940
+ const name = meta.name ?? path.basename(dir);
941
+ const entrypoint = meta.entrypoint ?? ENTRYPOINTS.find((f) => fs.existsSync(path.join(dir, f)));
942
+ if (!entrypoint || !fs.existsSync(path.join(dir, entrypoint)))
943
+ fail(`No entrypoint in ${dir}. Set "entrypoint" in agent.json, or run from the agent's folder.`);
944
+ let config = null;
945
+ try {
946
+ config = (0, shared_1.requireConfig)();
947
+ }
948
+ catch {
949
+ // Not connected: still runs, but model calls, child runs and --remote-state need a token.
950
+ }
951
+ const work = path.join(dir, ".codiedev", "local");
952
+ fs.mkdirSync(path.join(work, "py"), { recursive: true });
953
+ fs.writeFileSync(path.join(work, "py", "codiedev_agents.py"), sdk.python);
954
+ // ESM resolves bare imports from node_modules next to the code.
955
+ const jsSdk = path.join(dir, "node_modules", "codiedev-agents");
956
+ if (/\.(m?[jt]s|cjs|mts)$/.test(entrypoint)) {
957
+ fs.mkdirSync(jsSdk, { recursive: true });
958
+ fs.writeFileSync(path.join(jsSdk, "index.mjs"), sdk.jsIndex);
959
+ fs.writeFileSync(path.join(jsSdk, "index.d.ts"), sdk.jsTypes);
960
+ fs.writeFileSync(path.join(jsSdk, "package.json"), sdk.jsPackage);
961
+ }
962
+ const files = { input: path.join(work, "input.json"), output: path.join(work, "output.json"), request: path.join(work, "request.json"), response: path.join(work, "response.json") };
963
+ fs.writeFileSync(files.input, JSON.stringify(readInput(args) ?? null));
964
+ for (const f of [files.output, files.response])
965
+ fs.rmSync(f, { force: true });
966
+ const requestFile = flag(args, "--request-file");
967
+ if (requestFile)
968
+ fs.copyFileSync(path.resolve(requestFile), files.request);
969
+ else
970
+ fs.rmSync(files.request, { force: true });
971
+ const envFile = flag(args, "--env-file") ?? (fs.existsSync(path.join(dir, ".env")) ? path.join(dir, ".env") : undefined);
972
+ const secrets = envFile ? readEnvFile(envFile) : {};
973
+ const remoteState = args.includes("--remote-state");
974
+ if (remoteState && !config)
975
+ fail("--remote-state needs `codiedev connect` first.");
976
+ // The deployed agent whose state this uses must be named, not guessed from the folder.
977
+ if (remoteState && !meta.name)
978
+ fail('--remote-state needs "name" in agent.json, so it uses the right agent\'s state.');
979
+ const runId = `local-${Date.now()}`;
980
+ const env = {
981
+ ...process.env,
982
+ ...secrets,
983
+ CODIEDEV_LOCAL: "1",
984
+ CODIEDEV_AGENT: name,
985
+ CODIEDEV_RUN_ID: runId,
986
+ CODIEDEV_ATTEMPT: "1",
987
+ CODIEDEV_MAX_ATTEMPTS: "1",
988
+ CODIEDEV_INPUT_PATH: files.input,
989
+ CODIEDEV_OUTPUT_PATH: files.output,
990
+ CODIEDEV_REQUEST_PATH: files.request,
991
+ CODIEDEV_RESPONSE_PATH: files.response,
992
+ ...(remoteState ? {} : { CODIEDEV_STATE_PATH: path.join(work, "state.json") }),
993
+ PYTHONPATH: [path.join(work, "py"), process.env.PYTHONPATH].filter(Boolean).join(path.delimiter),
994
+ ...(config ? { CODIEDEV_AGENTS_URL: config.backendUrl, CODIEDEV_TOKEN: config.token } : {}),
995
+ };
996
+ const ext = path.extname(entrypoint);
997
+ const [cmd, ...cmdArgs] = ext === ".py" ? [process.env.PYTHON ?? "python3", "-u", entrypoint]
998
+ : ext === ".sh" ? ["bash", entrypoint]
999
+ : ext === ".ts" || ext === ".mts" ? ["npx", "--yes", "tsx", entrypoint]
1000
+ : [process.execPath, entrypoint];
1001
+ process.stderr.write(`[local run of ${name} · ${entrypoint}${envFile ? ` · secrets from ${path.relative(process.cwd(), envFile) || envFile}` : ""} · state ${remoteState ? "from Codiedev" : "in .codiedev/local/state.json"}]\n`);
1002
+ const started = Date.now();
1003
+ // The agent's own output goes to stderr, so stdout stays just the result.
1004
+ const res = (0, child_process_1.spawnSync)(cmd, cmdArgs, { cwd: dir, env, stdio: ["inherit", process.stderr, process.stderr] });
1005
+ const took = `${((Date.now() - started) / 1000).toFixed(1)}s`;
1006
+ if (res.error)
1007
+ fail(`Couldn't start ${cmd}: ${res.error.message}`);
1008
+ const output = fs.existsSync(files.output) ? fs.readFileSync(files.output, "utf8") : null;
1009
+ const response = fs.existsSync(files.response) ? fs.readFileSync(files.response, "utf8") : null;
1010
+ if (res.status === 0) {
1011
+ process.stderr.write(`[succeeded · ${took} · local]\n`);
1012
+ if (response)
1013
+ process.stderr.write(`Webhook response: ${response}\n`);
1014
+ console.log(pretty(output));
1015
+ }
1016
+ else {
1017
+ process.stderr.write(`[failed · exit ${res.status ?? res.signal} · ${took} · local]\n`);
1018
+ process.exit(1);
1019
+ }
1020
+ }
1021
+ async function cmdTemplates(args) {
1022
+ let shared = [];
1023
+ try {
1024
+ shared = (await (0, shared_1.apiRequest)("GET", "/api/agents/v1/templates", { config: (0, shared_1.requireConfig)() })).templates;
1025
+ }
1026
+ catch {
1027
+ // Not connected (or agents off): starters only.
1028
+ }
1029
+ if (args.includes("--json"))
1030
+ return void console.log(JSON.stringify({ starters: agentTemplates_generated_1.TEMPLATES.map(({ files: _f, ...t }) => t), shared }, null, 2));
1031
+ console.log("Starter templates\n");
1032
+ for (const t of agentTemplates_generated_1.TEMPLATES)
1033
+ console.log(` ${t.id.padEnd(18)} ${t.title} · ${t.trigger}\n ${"".padEnd(18)} ${t.summary}\n`);
1034
+ if (shared.length) {
1035
+ console.log("Shared in your company\n");
1036
+ for (const t of shared)
1037
+ console.log(` ${t.slug.padEnd(18)} ${t.title} · by ${t.publishedBy} · ${t.visibility === "team" ? `team${t.team ? ` ${t.team}` : ""}` : "company"} · ${t.installs} installs\n ${"".padEnd(18)} ${t.summary}\n`);
1038
+ }
1039
+ console.log("Start one: codiedev agents init <name> --template <id>");
1040
+ if (shared.length)
1041
+ console.log("Or deploy a shared one as is: codiedev agents install <template> <name>");
1042
+ }
1043
+ async function cmdPublish(args, config) {
1044
+ const [agent] = positional(args);
1045
+ if (!agent)
1046
+ fail('Usage: codiedev agents publish <agent> --summary "what it does" [--company] [--title "…"] [--as <slug>]');
1047
+ const summary = flag(args, "--summary");
1048
+ if (!summary)
1049
+ fail('Add --summary "one line on what it does", so people know when to use it.');
1050
+ const res = await (0, shared_1.apiRequest)("POST", "/api/agents/v1/templates/publish", {
1051
+ config,
1052
+ body: { agent, summary, title: flag(args, "--title"), slug: flag(args, "--as"), visibility: args.includes("--company") ? "company" : "team" },
1053
+ });
1054
+ console.log(`${res.updated ? "Updated" : "Published"} ${res.slug} for ${args.includes("--company") ? "your whole company" : "your team"}. Teammates: codiedev agents install ${res.slug} <name>, or Agents → Marketplace.`);
1055
+ }
1056
+ async function cmdInstall(args, config) {
1057
+ const [template, name] = positional(args);
1058
+ if (!template || !name)
1059
+ fail("Usage: codiedev agents install <template> <name>");
1060
+ const res = await (0, shared_1.apiRequest)("POST", "/api/agents/v1/templates/install", { config, body: { template, name } });
1061
+ console.log(`Deployed ${res.name} (v${res.version}) from ${template}. You own it: run it with codiedev agents run ${res.name}.`);
1062
+ if (res.secretNames.length)
1063
+ console.log(`Set its secrets first: ${res.secretNames.map((s) => `codiedev agents secrets ${res.name} set ${s}=…`).join("\n ")}`);
1064
+ }
1065
+ async function cmdUnpublish(args, config) {
1066
+ const [template] = positional(args);
1067
+ if (!template)
1068
+ fail("Usage: codiedev agents unpublish <template>");
1069
+ await (0, shared_1.apiRequest)("POST", "/api/agents/v1/templates/unpublish", { config, body: { template } });
1070
+ console.log(`Removed ${template} from the marketplace. Agents installed from it keep working.`);
1071
+ }
1072
+ async function cmdCancel(args, config) {
1073
+ const [id] = positional(args);
1074
+ if (!id)
1075
+ fail("Usage: codiedev agents cancel <run-id>");
1076
+ const res = await (0, shared_1.apiRequest)("POST", "/api/agents/v1/cancel", { config, body: { id } });
1077
+ console.log(res.status && res.status !== "cancelled" ? `Run already ${res.status}.` : "Cancelled.");
1078
+ }
1079
+ async function cmdConnect(args, config) {
1080
+ const [name] = positional(args);
1081
+ if (!name)
1082
+ fail('Usage: codiedev agents connect <name> [--description "…"] [--budget <usd>]');
1083
+ const budget = flag(args, "--budget");
1084
+ const out = await (0, shared_1.apiRequest)("POST", "/api/agents/v1/connect", {
1085
+ config,
1086
+ body: { name, description: flag(args, "--description"), ...(budget ? { monthlyBudgetUsd: Number(budget) } : {}) },
1087
+ });
1088
+ console.log(`Connected ${out.agent}. Its key is shown once; put it in your secret manager.\n`);
1089
+ console.log(`CODIEDEV_API_KEY=${out.key}`);
1090
+ console.log(`CODIEDEV_GATEWAY_URL=${out.gatewayUrl}`);
1091
+ console.log(`CODIEDEV_AGENTS_URL=${out.agentsUrl}\n`);
1092
+ console.log("Point its OpenAI-compatible client at CODIEDEV_GATEWAY_URL with that key (one X-Codiedev-Run-Id per job groups its calls into a trace).");
1093
+ console.log(`The same key calls ${out.agentsUrl}/api/agents/v1/approvals and /state. See it: Agents → ${out.agent}.`);
1094
+ }
1095
+ async function cmdDelete(args, config) {
1096
+ const [name] = positional(args);
1097
+ if (!name)
1098
+ fail("Usage: codiedev agents delete <name>");
1099
+ await (0, shared_1.apiRequest)("POST", "/api/agents/v1/delete", { config, body: { agent: name } });
1100
+ console.log(`Deleted ${name}. Its run history stays in Codiedev.`);
1101
+ }
1102
+ async function runAgents(args, configOverride) {
1103
+ const [sub, ...rest] = args;
1104
+ if (sub === "init")
1105
+ return cmdInit(rest).catch((err) => fail(`codiedev agents init: ${err.message}`));
1106
+ if (sub === "templates")
1107
+ return cmdTemplates(rest);
1108
+ if (sub === "run" && rest.includes("--local"))
1109
+ return cmdRunLocal(rest);
1110
+ const commands = {
1111
+ connect: cmdConnect, deploy: cmdDeploy, run: cmdRun, runs: cmdRuns, show: cmdShow, logs: cmdLogs, list: cmdList, ls: cmdList,
1112
+ secrets: cmdSecrets, webhook: cmdWebhook, cancel: cmdCancel, stop: (a, c) => cmdControl(a, c, true), resume: (a, c) => cmdControl(a, c, false), delete: cmdDelete, pull: cmdPull,
1113
+ versions: cmdVersions, rollback: cmdRollback, state: cmdState, approvals: cmdApprovals, decide: cmdDecide,
1114
+ publish: cmdPublish, install: cmdInstall, unpublish: cmdUnpublish,
1115
+ };
1116
+ const fn = sub ? commands[sub] : undefined;
1117
+ if (!fn) {
1118
+ console.log(exports.AGENTS_HELP);
1119
+ if (sub && sub !== "help")
1120
+ process.exit(1);
1121
+ return;
1122
+ }
1123
+ try {
1124
+ const config = configOverride ?? (0, shared_1.requireConfig)();
1125
+ await (0, shared_1.withTelemetry)(config, `codiedev_agents_${sub}`, () => fn(rest, config));
1126
+ }
1127
+ catch (err) {
1128
+ fail(`codiedev agents ${sub}: ${err.message}`);
1129
+ }
1130
+ }