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