@codegrammer/co-od 0.1.2 → 0.1.4
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/dist/commands/daemon.js +48 -20
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +168 -0
- package/dist/commands/run.js +5 -1
- package/dist/index.js +7 -1
- package/package.json +1 -1
package/dist/commands/daemon.js
CHANGED
|
@@ -36,6 +36,9 @@ function parseArgs(args) {
|
|
|
36
36
|
else if (args[i] === "--server" && args[i + 1]) {
|
|
37
37
|
parsed.server = args[++i];
|
|
38
38
|
}
|
|
39
|
+
else if (args[i] === "--handoff-to" && args[i + 1]) {
|
|
40
|
+
parsed.handoffTo = args[++i];
|
|
41
|
+
}
|
|
39
42
|
else if (!args[i].startsWith("--")) {
|
|
40
43
|
parsed.roomId = args[i];
|
|
41
44
|
}
|
|
@@ -128,41 +131,66 @@ export async function run(args) {
|
|
|
128
131
|
activeRuns++;
|
|
129
132
|
console.error(`[${timestamp()}] Dispatching: ${goal.slice(0, 80)}`);
|
|
130
133
|
// Create a run on the server
|
|
134
|
+
const sessionToken = api.getSessionToken();
|
|
135
|
+
const baseUrl = api.getBaseUrl();
|
|
131
136
|
api
|
|
132
137
|
.post(`/api/rooms/${parsed.roomId}/agent-runs`, { goal, provider: parsed.provider, agentId, eventId: event._id })
|
|
133
138
|
.then(async ({ runId }) => {
|
|
134
139
|
const result = await adapter.execute(goal, {
|
|
135
140
|
workDir: parsed.dir,
|
|
136
141
|
onOutput: (data) => process.stderr.write(data),
|
|
142
|
+
// Room context enables Claude Code hooks for live reporting
|
|
143
|
+
roomId: parsed.roomId,
|
|
144
|
+
runId,
|
|
145
|
+
serverUrl: baseUrl,
|
|
146
|
+
sessionToken: sessionToken || undefined,
|
|
137
147
|
});
|
|
138
|
-
|
|
148
|
+
const succeeded = result.exitCode === 0;
|
|
149
|
+
console.error(`\n[${timestamp()}] Run ${runId} ${succeeded ? "succeeded" : "failed"}`);
|
|
139
150
|
// Report completion
|
|
140
151
|
try {
|
|
141
|
-
await api.post(`/api/rooms/${parsed.roomId}/agent-runs/${runId}/
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
payload: {
|
|
152
|
-
stdout: result.stdout.slice(-4096),
|
|
153
|
-
stderr: result.stderr.slice(-2048),
|
|
154
|
-
exitCode: result.exitCode,
|
|
155
|
-
},
|
|
156
|
-
},
|
|
157
|
-
created_at: Date.now(),
|
|
158
|
-
},
|
|
159
|
-
],
|
|
152
|
+
await api.post(`/api/rooms/${parsed.roomId}/agent-runs/${runId}/steps`, {
|
|
153
|
+
kind: succeeded ? "observation" : "error",
|
|
154
|
+
input: { description: succeeded ? "Task completed" : "Task failed" },
|
|
155
|
+
output: {
|
|
156
|
+
success: succeeded,
|
|
157
|
+
payload: {
|
|
158
|
+
stdout: result.stdout.slice(-4096),
|
|
159
|
+
stderr: result.stderr.slice(-2048),
|
|
160
|
+
exitCode: result.exitCode,
|
|
161
|
+
},
|
|
160
162
|
},
|
|
161
163
|
});
|
|
162
164
|
}
|
|
163
165
|
catch {
|
|
164
166
|
console.error(`[${timestamp()}] Warning: failed to report completion`);
|
|
165
167
|
}
|
|
168
|
+
// Agent-to-agent handoff
|
|
169
|
+
const handoffTarget = parsed.handoffTo ||
|
|
170
|
+
event.payload?.handoffTo ||
|
|
171
|
+
null;
|
|
172
|
+
if (succeeded && handoffTarget && parsed.roomId) {
|
|
173
|
+
console.error(`[${timestamp()}] Handing off to "${handoffTarget}"...`);
|
|
174
|
+
try {
|
|
175
|
+
// Find target agent by name
|
|
176
|
+
const agents = await api.get(`/api/rooms/${parsed.roomId}/agents`);
|
|
177
|
+
const target = agents.agents?.find((a) => a.name.toLowerCase() === handoffTarget.toLowerCase() || a._id === handoffTarget);
|
|
178
|
+
if (target) {
|
|
179
|
+
await api.post(`/api/rooms/${parsed.roomId}/agent-runs/${runId}/handoff`, {
|
|
180
|
+
targetAgentId: target._id,
|
|
181
|
+
context: `Completed: ${goal}. Output: ${result.stdout.slice(-500)}`,
|
|
182
|
+
goal: `Continue from "${agentName}": ${goal}`,
|
|
183
|
+
});
|
|
184
|
+
console.error(`[${timestamp()}] Handed off to "${target.name}" (${target._id})`);
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
console.error(`[${timestamp()}] Handoff target "${handoffTarget}" not found in room`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
catch (err) {
|
|
191
|
+
console.error(`[${timestamp()}] Handoff failed: ${err}`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
166
194
|
})
|
|
167
195
|
.catch((err) => {
|
|
168
196
|
console.error(`[${timestamp()}] Run failed: ${err}`);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function run(args: string[]): Promise<void>;
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import * as api from "../api-client.js";
|
|
2
|
+
import { getAdapter } from "../adapters/index.js";
|
|
3
|
+
import { writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
4
|
+
import { join, basename } from "node:path";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
function parseArgs(args) {
|
|
7
|
+
const parsed = {
|
|
8
|
+
dir: process.cwd(),
|
|
9
|
+
provider: "claude",
|
|
10
|
+
};
|
|
11
|
+
for (let i = 0; i < args.length; i++) {
|
|
12
|
+
if (args[i] === "--dir" && args[i + 1]) {
|
|
13
|
+
parsed.dir = args[++i];
|
|
14
|
+
}
|
|
15
|
+
else if (args[i] === "--name" && args[i + 1]) {
|
|
16
|
+
parsed.name = args[++i];
|
|
17
|
+
}
|
|
18
|
+
else if (args[i] === "--provider" && args[i + 1]) {
|
|
19
|
+
parsed.provider = args[++i];
|
|
20
|
+
}
|
|
21
|
+
else if (args[i] === "--server" && args[i + 1]) {
|
|
22
|
+
parsed.server = args[++i];
|
|
23
|
+
}
|
|
24
|
+
else if (!args[i].startsWith("--") && !parsed.name) {
|
|
25
|
+
parsed.name = args[i];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return parsed;
|
|
29
|
+
}
|
|
30
|
+
function timestamp() {
|
|
31
|
+
return new Date().toISOString().slice(11, 19);
|
|
32
|
+
}
|
|
33
|
+
export async function run(args) {
|
|
34
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
35
|
+
console.error(`Usage: co-od init [name] [--dir <path>] [--provider claude|codex|openclaw]
|
|
36
|
+
|
|
37
|
+
Sets up a new co-ode project:
|
|
38
|
+
1. Creates a room on co-od.dev
|
|
39
|
+
2. Links it to your local project directory
|
|
40
|
+
3. Adds a default agent
|
|
41
|
+
4. Generates a .co-od config file
|
|
42
|
+
5. Ready to go — start giving tasks
|
|
43
|
+
|
|
44
|
+
Examples:
|
|
45
|
+
co-od init # Uses current dir name as room name
|
|
46
|
+
co-od init "my-saas-app" # Custom room name
|
|
47
|
+
co-od init --provider codex # Use Codex as default agent
|
|
48
|
+
co-od init --dir ~/projects/app # Different directory
|
|
49
|
+
`);
|
|
50
|
+
process.exit(0);
|
|
51
|
+
}
|
|
52
|
+
const parsed = parseArgs(args);
|
|
53
|
+
if (parsed.server) {
|
|
54
|
+
process.env.CO_ODE_SERVER = parsed.server;
|
|
55
|
+
}
|
|
56
|
+
// Ensure logged in
|
|
57
|
+
const token = api.getSessionToken();
|
|
58
|
+
if (!token) {
|
|
59
|
+
console.error(`[${timestamp()}] Not logged in. Run: co-od login`);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
const projectDir = parsed.dir;
|
|
63
|
+
const roomName = parsed.name || basename(projectDir);
|
|
64
|
+
console.error(`\n co-od init\n`);
|
|
65
|
+
console.error(` project: ${projectDir}`);
|
|
66
|
+
console.error(` room: ${roomName}`);
|
|
67
|
+
console.error(` provider: ${parsed.provider}\n`);
|
|
68
|
+
// Step 1: Check provider is available
|
|
69
|
+
console.error(` [1/5] Checking ${parsed.provider} CLI...`);
|
|
70
|
+
const adapter = getAdapter(parsed.provider);
|
|
71
|
+
const available = await adapter.available();
|
|
72
|
+
if (!available) {
|
|
73
|
+
console.error(` ✗ ${adapter.name} not found on PATH`);
|
|
74
|
+
console.error(` Install it first, then run co-od init again.`);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
console.error(` ✓ ${adapter.name} available`);
|
|
78
|
+
// Step 2: Create room
|
|
79
|
+
console.error(` [2/5] Creating room "${roomName}"...`);
|
|
80
|
+
let roomId;
|
|
81
|
+
try {
|
|
82
|
+
const res = await api.post("/api/rooms", { name: roomName });
|
|
83
|
+
roomId = res.roomId || res.room?._id || "";
|
|
84
|
+
if (!roomId)
|
|
85
|
+
throw new Error("No room ID returned");
|
|
86
|
+
console.error(` ✓ Room created: ${roomId.slice(0, 12)}...`);
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
console.error(` ✗ Failed to create room: ${err}`);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
// Step 3: Add default agent
|
|
93
|
+
console.error(` [3/5] Adding default agent...`);
|
|
94
|
+
let agentId;
|
|
95
|
+
try {
|
|
96
|
+
const agentName = parsed.provider === "codex" ? "Codex Builder"
|
|
97
|
+
: parsed.provider === "openclaw" ? "OpenClaw Agent"
|
|
98
|
+
: "Claude Builder";
|
|
99
|
+
const res = await api.post(`/api/rooms/${roomId}/agents`, {
|
|
100
|
+
name: agentName,
|
|
101
|
+
type: "builder",
|
|
102
|
+
executionMode: parsed.provider === "codex" ? "local_codex" : "local_claude_code",
|
|
103
|
+
provider: parsed.provider === "codex" ? "openai"
|
|
104
|
+
: parsed.provider === "openclaw" ? "openclaw"
|
|
105
|
+
: "anthropic",
|
|
106
|
+
permissions: {
|
|
107
|
+
"fs.read": true,
|
|
108
|
+
"fs.write": true,
|
|
109
|
+
"fs.applyPatch": true,
|
|
110
|
+
"exec.run": true,
|
|
111
|
+
"net.egress": false,
|
|
112
|
+
"ports.expose": false,
|
|
113
|
+
"secrets.read": false,
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
agentId = res.agentId || "";
|
|
117
|
+
console.error(` ✓ Agent "${agentName}" added`);
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
console.error(` ✗ Failed to add agent: ${err}`);
|
|
121
|
+
agentId = "";
|
|
122
|
+
}
|
|
123
|
+
// Step 4: Save config
|
|
124
|
+
console.error(` [4/5] Saving config...`);
|
|
125
|
+
const config = {
|
|
126
|
+
roomId,
|
|
127
|
+
roomName,
|
|
128
|
+
projectDir,
|
|
129
|
+
provider: parsed.provider,
|
|
130
|
+
agentId,
|
|
131
|
+
server: api.getBaseUrl(),
|
|
132
|
+
createdAt: new Date().toISOString(),
|
|
133
|
+
};
|
|
134
|
+
// Save to project dir
|
|
135
|
+
const configPath = join(projectDir, ".co-od.json");
|
|
136
|
+
try {
|
|
137
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
138
|
+
console.error(` ✓ ${configPath}`);
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
console.error(` ✗ Could not write ${configPath}`);
|
|
142
|
+
}
|
|
143
|
+
// Also save room mapping to global config
|
|
144
|
+
const globalConfigDir = join(homedir(), ".co-od");
|
|
145
|
+
const roomsMapPath = join(globalConfigDir, "rooms.json");
|
|
146
|
+
try {
|
|
147
|
+
mkdirSync(globalConfigDir, { recursive: true });
|
|
148
|
+
let rooms = {};
|
|
149
|
+
if (existsSync(roomsMapPath)) {
|
|
150
|
+
rooms = JSON.parse(require("node:fs").readFileSync(roomsMapPath, "utf-8"));
|
|
151
|
+
}
|
|
152
|
+
rooms[roomId] = { roomId, projectDir, name: roomName };
|
|
153
|
+
writeFileSync(roomsMapPath, JSON.stringify(rooms, null, 2) + "\n", "utf-8");
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
// Non-fatal
|
|
157
|
+
}
|
|
158
|
+
// Step 5: Done
|
|
159
|
+
console.error(` [5/5] Ready!\n`);
|
|
160
|
+
console.error(` Your room is live. Next steps:\n`);
|
|
161
|
+
console.error(` co-od run ${roomId.slice(0, 12)} "describe your task" # run a single task`);
|
|
162
|
+
console.error(` co-od daemon ${roomId.slice(0, 12)} --auto-execute # autonomous mode`);
|
|
163
|
+
console.error(` co-od share # invite teammates\n`);
|
|
164
|
+
console.error(` Or open in browser:`);
|
|
165
|
+
console.error(` ${api.getBaseUrl()}/rooms/${roomId}\n`);
|
|
166
|
+
// Output JSON for scripting
|
|
167
|
+
console.log(JSON.stringify(config, null, 2));
|
|
168
|
+
}
|
package/dist/commands/run.js
CHANGED
|
@@ -57,12 +57,16 @@ export async function run(args) {
|
|
|
57
57
|
if (!parsed.json) {
|
|
58
58
|
console.error(`[co-od] Run ${runId} created. Executing with ${adapter.name}...`);
|
|
59
59
|
}
|
|
60
|
-
// Execute locally
|
|
60
|
+
// Execute locally — with room context for live hooks reporting
|
|
61
61
|
const result = await adapter.execute(parsed.goal, {
|
|
62
62
|
workDir: parsed.dir,
|
|
63
63
|
onOutput: parsed.json
|
|
64
64
|
? undefined
|
|
65
65
|
: (data) => process.stderr.write(data),
|
|
66
|
+
roomId: parsed.roomId,
|
|
67
|
+
runId,
|
|
68
|
+
serverUrl: api.getBaseUrl(),
|
|
69
|
+
sessionToken: api.getSessionToken() || undefined,
|
|
66
70
|
});
|
|
67
71
|
// Report completion
|
|
68
72
|
if (!parsed.json) {
|
package/dist/index.js
CHANGED
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
const VERSION = "0.1.0";
|
|
4
4
|
const COMMANDS = {
|
|
5
|
+
init: { desc: "Set up a new co-ode project", usage: "co-od init [name] [--dir <path>] [--provider claude|codex|openclaw]" },
|
|
5
6
|
login: { desc: "Authenticate with co-ode", usage: "co-od login [--token <t>]" },
|
|
6
7
|
rooms: { desc: "List your rooms", usage: "co-od rooms [--json]" },
|
|
7
8
|
run: { desc: "Execute a single task in a room", usage: "co-od run <room> <goal> [--provider claude|codex|openclaw] [--dir <path>] [--json]" },
|
|
8
9
|
join: { desc: "Join a room as an interactive agent", usage: "co-od join <room> [--invite-token <tok>] [--dir <path>]" },
|
|
9
|
-
daemon: { desc: "Autonomous watch mode for a room", usage: "co-od daemon <room> [--as <name>] [--auto-execute] [--
|
|
10
|
+
daemon: { desc: "Autonomous watch mode for a room", usage: "co-od daemon <room> [--as <name>] [--auto-execute] [--handoff-to <agent>]" },
|
|
10
11
|
share: { desc: "Generate a relay code for teammates", usage: "co-od share [--provider claude|codex|openclaw]" },
|
|
11
12
|
connect: { desc: "Connect to a relay via code", usage: "co-od connect <code>" },
|
|
12
13
|
status: { desc: "Show current login and running state", usage: "co-od status [--json]" },
|
|
@@ -58,6 +59,11 @@ async function main() {
|
|
|
58
59
|
}
|
|
59
60
|
// Route to command
|
|
60
61
|
switch (command) {
|
|
62
|
+
case "init": {
|
|
63
|
+
const { run } = await import("./commands/init.js");
|
|
64
|
+
await run(commandArgs);
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
61
67
|
case "login": {
|
|
62
68
|
const { run } = await import("./commands/login.js");
|
|
63
69
|
await run(commandArgs);
|