@digitalpresence/cliclaw 0.1.2 → 0.2.1
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/agent/crud.d.ts.map +1 -1
- package/dist/agent/crud.js +3 -4
- package/dist/agent/crud.js.map +1 -1
- package/dist/agent/memory.js +4 -4
- package/dist/agent/memory.js.map +1 -1
- package/dist/agent/permissions.d.ts +2 -2
- package/dist/agent/permissions.d.ts.map +1 -1
- package/dist/agent/permissions.js +16 -13
- package/dist/agent/permissions.js.map +1 -1
- package/dist/cli.js +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/agent.d.ts.map +1 -1
- package/dist/commands/agent.js +4 -6
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/cron.js +4 -4
- package/dist/commands/cron.js.map +1 -1
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +19 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/cron/daemon.js +2 -2
- package/dist/cron/daemon.js.map +1 -1
- package/dist/cron/handlers.d.ts +1 -1
- package/dist/cron/handlers.d.ts.map +1 -1
- package/dist/cron/handlers.js +14 -5
- package/dist/cron/handlers.js.map +1 -1
- package/dist/cron/ralph-wiggum.d.ts.map +1 -1
- package/dist/cron/ralph-wiggum.js +109 -54
- package/dist/cron/ralph-wiggum.js.map +1 -1
- package/package.json +2 -2
- package/src/agent/crud.ts +3 -4
- package/src/agent/memory.ts +4 -4
- package/src/agent/permissions.ts +14 -17
- package/src/cli.ts +2 -0
- package/src/commands/agent.ts +4 -6
- package/src/commands/cron.ts +4 -4
- package/src/commands/init.ts +22 -0
- package/src/cron/daemon.ts +2 -2
- package/src/cron/handlers.ts +17 -6
- package/src/cron/ralph-wiggum.ts +131 -63
|
@@ -1,35 +1,95 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
|
+
import { createInterface } from "readline";
|
|
3
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync } from "fs";
|
|
4
|
+
import { join } from "path";
|
|
5
5
|
import { getProgressFilePath, ensureCronDirs, writeRunningMarker, clearRunningMarker } from "./progress.js";
|
|
6
6
|
import { cronLog } from "./logger.js";
|
|
7
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
-
const __dirname = dirname(__filename);
|
|
9
7
|
const CONTINUE_MARKER = "NEEDS_MORE_ITERATIONS";
|
|
8
|
+
const CONTAINER_IMAGE = "cliclaw-agent";
|
|
9
|
+
const CONTAINER_TIMEOUT_MS = 300_000; // 5 minutes per iteration
|
|
10
|
+
function loadTaskContent(store, agentName, job) {
|
|
11
|
+
const taskFilePath = join(store.workspacePath(agentName), "cron-tasks", job.taskFile);
|
|
12
|
+
if (existsSync(taskFilePath)) {
|
|
13
|
+
return readFileSync(taskFilePath, "utf-8");
|
|
14
|
+
}
|
|
15
|
+
return `Task file not found: ${job.taskFile}`;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Run a single iteration inside a Docker container.
|
|
19
|
+
* Returns parsed NDJSON events.
|
|
20
|
+
*/
|
|
21
|
+
async function runContainerIteration(instancePath, prompt, sessionId) {
|
|
22
|
+
// Write session.json
|
|
23
|
+
writeFileSync(join(instancePath, "session.json"), JSON.stringify({ prompt, ...(sessionId ? { sessionId } : {}) }), "utf-8");
|
|
24
|
+
const args = [
|
|
25
|
+
"run", "--rm", "-i",
|
|
26
|
+
"-v", `${instancePath}:/instance`,
|
|
27
|
+
"--network=host",
|
|
28
|
+
"--cpus=2", "--memory=2g",
|
|
29
|
+
];
|
|
30
|
+
// Pass API key
|
|
31
|
+
if (process.env.ANTHROPIC_API_KEY) {
|
|
32
|
+
args.push("-e", `ANTHROPIC_API_KEY=${process.env.ANTHROPIC_API_KEY}`);
|
|
33
|
+
}
|
|
34
|
+
args.push(CONTAINER_IMAGE);
|
|
35
|
+
return new Promise((resolve) => {
|
|
36
|
+
const child = spawn("docker", args, {
|
|
37
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
38
|
+
});
|
|
39
|
+
const timeout = setTimeout(() => {
|
|
40
|
+
child.kill("SIGTERM");
|
|
41
|
+
}, CONTAINER_TIMEOUT_MS);
|
|
42
|
+
const events = [];
|
|
43
|
+
const rl = createInterface({ input: child.stdout });
|
|
44
|
+
rl.on("line", (line) => {
|
|
45
|
+
if (!line.trim())
|
|
46
|
+
return;
|
|
47
|
+
try {
|
|
48
|
+
events.push(JSON.parse(line));
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// skip malformed lines
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
let stderr = "";
|
|
55
|
+
child.stderr.on("data", (chunk) => {
|
|
56
|
+
stderr += chunk.toString();
|
|
57
|
+
});
|
|
58
|
+
child.on("close", (code) => {
|
|
59
|
+
clearTimeout(timeout);
|
|
60
|
+
rl.close();
|
|
61
|
+
if (stderr.trim()) {
|
|
62
|
+
console.error(`[ralph-wiggum] container stderr: ${stderr.trim()}`);
|
|
63
|
+
}
|
|
64
|
+
resolve({ events, exitCode: code ?? 1 });
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
}
|
|
10
68
|
export async function executeRalphWiggumLoop(store, agentName, job, startedAt) {
|
|
11
69
|
ensureCronDirs(agentName, job.id);
|
|
12
70
|
writeRunningMarker(agentName, job.id, startedAt ?? new Date().toISOString());
|
|
13
|
-
|
|
71
|
+
// Use the cron instance path
|
|
72
|
+
const instancesDir = join(store.workspacePath(agentName), "..", "..", "instances");
|
|
73
|
+
const cronInstancePath = join(instancesDir, agentName, "_cron");
|
|
74
|
+
// Ensure cron instance exists with necessary files
|
|
75
|
+
if (!existsSync(cronInstancePath)) {
|
|
76
|
+
mkdirSync(join(cronInstancePath, "workspace"), { recursive: true });
|
|
77
|
+
mkdirSync(join(cronInstancePath, "memory"), { recursive: true });
|
|
78
|
+
// Copy template files
|
|
79
|
+
const agentDir = store.workspacePath(agentName);
|
|
80
|
+
for (const file of ["SOUL.md", "ROLE.md"]) {
|
|
81
|
+
const src = join(agentDir, file);
|
|
82
|
+
if (existsSync(src)) {
|
|
83
|
+
writeFileSync(join(cronInstancePath, file), readFileSync(src, "utf-8"));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
14
87
|
const progressFile = getProgressFilePath(agentName, job.id);
|
|
88
|
+
const taskContent = loadTaskContent(store, agentName, job);
|
|
89
|
+
let totalCostUsd = 0;
|
|
90
|
+
let sessionId;
|
|
91
|
+
const transcript = [];
|
|
15
92
|
try {
|
|
16
|
-
// Ensure cliclaw CLI is in PATH
|
|
17
|
-
const cleanEnv = { ...process.env };
|
|
18
|
-
delete cleanEnv.CLAUDECODE;
|
|
19
|
-
const monorepoRoot = join(__dirname, "..", "..", "..");
|
|
20
|
-
const binScript = join(monorepoRoot, "packages", "cliclaw", "dist", "cli.js");
|
|
21
|
-
if (existsSync(binScript)) {
|
|
22
|
-
const resolvedBin = realpathSync(binScript);
|
|
23
|
-
const localBin = join(workspacePath, ".bin");
|
|
24
|
-
if (!existsSync(localBin))
|
|
25
|
-
mkdirSync(localBin, { recursive: true });
|
|
26
|
-
const wrapper = join(localBin, "cliclaw");
|
|
27
|
-
writeFileSync(wrapper, `#!/bin/sh\nexec node "${resolvedBin}" "$@"\n`, { mode: 0o755 });
|
|
28
|
-
cleanEnv.PATH = `${localBin}:${cleanEnv.PATH ?? ""}`;
|
|
29
|
-
}
|
|
30
|
-
let totalCostUsd = 0;
|
|
31
|
-
let sessionId;
|
|
32
|
-
const transcript = [];
|
|
33
93
|
for (let iteration = 1; iteration <= job.maxIterations; iteration++) {
|
|
34
94
|
cronLog("info", `Iteration ${iteration}/${job.maxIterations}`, agentName, job.id);
|
|
35
95
|
// Clear the continue marker before each iteration
|
|
@@ -44,40 +104,30 @@ export async function executeRalphWiggumLoop(store, agentName, job, startedAt) {
|
|
|
44
104
|
prompt = [
|
|
45
105
|
`You are executing a scheduled task. Here are your instructions:`,
|
|
46
106
|
``,
|
|
47
|
-
|
|
107
|
+
taskContent,
|
|
48
108
|
``,
|
|
49
|
-
`Write your output/progress to:
|
|
109
|
+
`Write your output/progress to: /instance/workspace/progress.md`,
|
|
50
110
|
``,
|
|
51
|
-
`If you CANNOT finish the task in this iteration and need to be re-invoked, write "${CONTINUE_MARKER}" anywhere in
|
|
111
|
+
`If you CANNOT finish the task in this iteration and need to be re-invoked, write "${CONTINUE_MARKER}" anywhere in the progress file. Otherwise, just complete the task normally — no special signal is needed.`,
|
|
52
112
|
].join("\n");
|
|
53
113
|
}
|
|
54
114
|
else {
|
|
55
115
|
prompt = [
|
|
56
|
-
`You are continuing a scheduled task. Read your progress file at:
|
|
116
|
+
`You are continuing a scheduled task. Read your progress file at: /instance/workspace/progress.md`,
|
|
57
117
|
`Continue from where you left off.`,
|
|
58
118
|
``,
|
|
59
|
-
`Original task
|
|
119
|
+
`Original task:`,
|
|
120
|
+
taskContent,
|
|
60
121
|
``,
|
|
61
|
-
`If you CANNOT finish the task in this iteration and need to be re-invoked, write "${CONTINUE_MARKER}" anywhere in
|
|
122
|
+
`If you CANNOT finish the task in this iteration and need to be re-invoked, write "${CONTINUE_MARKER}" anywhere in the progress file. Otherwise, just complete the task normally — no special signal is needed.`,
|
|
62
123
|
].join("\n");
|
|
63
124
|
}
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
options: {
|
|
67
|
-
cwd: workspacePath,
|
|
68
|
-
env: cleanEnv,
|
|
69
|
-
systemPrompt: { type: "preset", preset: "claude_code" },
|
|
70
|
-
settingSources: ["project"],
|
|
71
|
-
includePartialMessages: true,
|
|
72
|
-
allowedTools: ["Read", "Write", "Edit", "Bash", "Glob", "Grep"],
|
|
73
|
-
...(sessionId ? { resume: sessionId } : {}),
|
|
74
|
-
},
|
|
75
|
-
});
|
|
125
|
+
const { events } = await runContainerIteration(cronInstancePath, prompt, sessionId);
|
|
126
|
+
// Process events into transcript
|
|
76
127
|
let currentToolInput = "";
|
|
77
|
-
for
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const streamEvent = msg.event;
|
|
128
|
+
for (const event of events) {
|
|
129
|
+
if (event.type === "stream_event" && event.event) {
|
|
130
|
+
const streamEvent = event.event;
|
|
81
131
|
if (streamEvent.type === "content_block_start" && streamEvent.content_block?.type === "tool_use") {
|
|
82
132
|
transcript.push({ type: "tool", name: streamEvent.content_block.name ?? "unknown", done: false });
|
|
83
133
|
currentToolInput = "";
|
|
@@ -109,25 +159,30 @@ export async function executeRalphWiggumLoop(store, agentName, job, startedAt) {
|
|
|
109
159
|
currentToolInput = "";
|
|
110
160
|
}
|
|
111
161
|
}
|
|
112
|
-
else if (
|
|
162
|
+
else if (event.type === "tool_result") {
|
|
113
163
|
const lastTool = [...transcript].reverse().find((b) => b.type === "tool" && !b.done);
|
|
114
164
|
if (lastTool && lastTool.type === "tool") {
|
|
115
165
|
lastTool.done = true;
|
|
116
166
|
}
|
|
117
167
|
}
|
|
118
|
-
else if (
|
|
119
|
-
if (
|
|
120
|
-
totalCostUsd +=
|
|
168
|
+
else if (event.type === "result") {
|
|
169
|
+
if (event.total_cost_usd) {
|
|
170
|
+
totalCostUsd += event.total_cost_usd;
|
|
121
171
|
}
|
|
122
|
-
if (
|
|
123
|
-
sessionId =
|
|
172
|
+
if (event.session_id) {
|
|
173
|
+
sessionId = event.session_id;
|
|
124
174
|
}
|
|
125
175
|
}
|
|
126
176
|
}
|
|
127
177
|
// Check if the agent needs more iterations by reading the progress file
|
|
128
|
-
|
|
178
|
+
// In container mode, progress is written to workspace/progress.md
|
|
179
|
+
const containerProgressFile = join(cronInstancePath, "workspace", "progress.md");
|
|
180
|
+
const needsMore = existsSync(containerProgressFile) &&
|
|
181
|
+
readFileSync(containerProgressFile, "utf-8").includes(CONTINUE_MARKER);
|
|
182
|
+
// Also check the original progress file location
|
|
183
|
+
const needsMoreOriginal = existsSync(progressFile) &&
|
|
129
184
|
readFileSync(progressFile, "utf-8").includes(CONTINUE_MARKER);
|
|
130
|
-
if (!needsMore) {
|
|
185
|
+
if (!needsMore && !needsMoreOriginal) {
|
|
131
186
|
cronLog("info", `Task completed at iteration ${iteration}`, agentName, job.id);
|
|
132
187
|
return { completed: true, iterations: iteration, totalCostUsd, transcript };
|
|
133
188
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ralph-wiggum.js","sourceRoot":"","sources":["../../src/cron/ralph-wiggum.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,
|
|
1
|
+
{"version":3,"file":"ralph-wiggum.js","sourceRoot":"","sources":["../../src/cron/ralph-wiggum.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@digitalpresence/cliclaw",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"cliclaw": "./dist/cli.js"
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"mime-types": "^3.0.2",
|
|
14
14
|
"node-cron": "^4.2.1",
|
|
15
15
|
"open": "^10.1.0",
|
|
16
|
-
"@digitalpresence/cliclaw-auth": "0.
|
|
16
|
+
"@digitalpresence/cliclaw-auth": "0.2.0"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@types/mime-types": "^3.0.1",
|
package/src/agent/crud.ts
CHANGED
|
@@ -16,8 +16,7 @@ export async function handleAgentCreate(
|
|
|
16
16
|
name,
|
|
17
17
|
displayName,
|
|
18
18
|
role,
|
|
19
|
-
|
|
20
|
-
memory: [],
|
|
19
|
+
integrations: [],
|
|
21
20
|
cronJobs: [],
|
|
22
21
|
createdAt: now,
|
|
23
22
|
updatedAt: now,
|
|
@@ -32,8 +31,8 @@ export async function handleAgentList(store: AgentStore): Promise<void> {
|
|
|
32
31
|
agents.map((a) => ({
|
|
33
32
|
name: a.name,
|
|
34
33
|
displayName: a.displayName,
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
integrations: a.integrations.length,
|
|
35
|
+
cronJobs: a.cronJobs.length,
|
|
37
36
|
})),
|
|
38
37
|
);
|
|
39
38
|
}
|
package/src/agent/memory.ts
CHANGED
|
@@ -32,7 +32,7 @@ export async function handleAgentMemoryAdd(
|
|
|
32
32
|
const importance = opts?.importance ? (parseInt(opts.importance, 10) as 1 | 2 | 3) : 2;
|
|
33
33
|
|
|
34
34
|
const entry = memoryStore.add(fact, { tags, source, importance });
|
|
35
|
-
store.
|
|
35
|
+
store.regenerateContextMd(name);
|
|
36
36
|
|
|
37
37
|
outputJson({ status: "added", name, entry });
|
|
38
38
|
}
|
|
@@ -52,14 +52,14 @@ export async function handleAgentMemoryRemove(
|
|
|
52
52
|
|
|
53
53
|
if (tag) {
|
|
54
54
|
const removed = memoryStore.removeByTag(tag);
|
|
55
|
-
store.
|
|
55
|
+
store.regenerateContextMd(name);
|
|
56
56
|
outputJson({ status: "removed", name, removedByTag: tag, count: removed });
|
|
57
57
|
} else if (id) {
|
|
58
58
|
const removed = memoryStore.remove(id);
|
|
59
59
|
if (!removed) {
|
|
60
60
|
outputError("memory_not_found", `Memory "${id}" not found`);
|
|
61
61
|
}
|
|
62
|
-
store.
|
|
62
|
+
store.regenerateContextMd(name);
|
|
63
63
|
outputJson({ status: "removed", name, id });
|
|
64
64
|
} else {
|
|
65
65
|
outputError("invalid_args", "Provide an ID or --tag to remove");
|
|
@@ -89,7 +89,7 @@ export async function handleAgentMemoryClear(store: AgentStore, name: string): P
|
|
|
89
89
|
|
|
90
90
|
const memoryStore = store.getMemoryStore(name);
|
|
91
91
|
const cleared = memoryStore.clear();
|
|
92
|
-
store.
|
|
92
|
+
store.regenerateContextMd(name);
|
|
93
93
|
|
|
94
94
|
outputJson({ status: "cleared", name, cleared });
|
|
95
95
|
}
|
package/src/agent/permissions.ts
CHANGED
|
@@ -5,50 +5,47 @@ export async function handleAgentGrant(
|
|
|
5
5
|
store: AgentStore,
|
|
6
6
|
name: string,
|
|
7
7
|
integration: string,
|
|
8
|
-
account: string,
|
|
9
8
|
): Promise<void> {
|
|
10
9
|
const agent = store.get(name);
|
|
11
10
|
if (!agent) {
|
|
12
11
|
outputError("agent_not_found", `Agent "${name}" not found`);
|
|
12
|
+
return;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
(
|
|
17
|
-
|
|
18
|
-
if (exists) {
|
|
19
|
-
outputError("permission_exists", `Agent "${name}" already has ${integration}:${account}`);
|
|
15
|
+
if (agent.integrations.includes(integration)) {
|
|
16
|
+
outputError("integration_exists", `Agent "${name}" already has ${integration}`);
|
|
17
|
+
return;
|
|
20
18
|
}
|
|
21
19
|
|
|
22
|
-
agent.
|
|
20
|
+
agent.integrations.push(integration);
|
|
23
21
|
agent.updatedAt = new Date().toISOString();
|
|
24
22
|
store.save(agent);
|
|
25
|
-
store.
|
|
23
|
+
store.regenerateContextMd(name);
|
|
26
24
|
|
|
27
|
-
outputJson({ status: "granted", name, integration
|
|
25
|
+
outputJson({ status: "granted", name, integration });
|
|
28
26
|
}
|
|
29
27
|
|
|
30
28
|
export async function handleAgentRevoke(
|
|
31
29
|
store: AgentStore,
|
|
32
30
|
name: string,
|
|
33
31
|
integration: string,
|
|
34
|
-
account: string,
|
|
35
32
|
): Promise<void> {
|
|
36
33
|
const agent = store.get(name);
|
|
37
34
|
if (!agent) {
|
|
38
35
|
outputError("agent_not_found", `Agent "${name}" not found`);
|
|
36
|
+
return;
|
|
39
37
|
}
|
|
40
38
|
|
|
41
|
-
const idx = agent.
|
|
42
|
-
(p) => p.integration === integration && p.account === account,
|
|
43
|
-
);
|
|
39
|
+
const idx = agent.integrations.indexOf(integration);
|
|
44
40
|
if (idx === -1) {
|
|
45
|
-
outputError("
|
|
41
|
+
outputError("integration_not_found", `Agent "${name}" does not have ${integration}`);
|
|
42
|
+
return;
|
|
46
43
|
}
|
|
47
44
|
|
|
48
|
-
agent.
|
|
45
|
+
agent.integrations.splice(idx, 1);
|
|
49
46
|
agent.updatedAt = new Date().toISOString();
|
|
50
47
|
store.save(agent);
|
|
51
|
-
store.
|
|
48
|
+
store.regenerateContextMd(name);
|
|
52
49
|
|
|
53
|
-
outputJson({ status: "revoked", name, integration
|
|
50
|
+
outputJson({ status: "revoked", name, integration });
|
|
54
51
|
}
|
package/src/cli.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { registerCalendarCommands } from "./commands/calendar.js";
|
|
|
10
10
|
import { registerFormsCommands } from "./commands/forms.js";
|
|
11
11
|
import { registerAgentCommands } from "./commands/agent.js";
|
|
12
12
|
import { registerCronCommands } from "./commands/cron.js";
|
|
13
|
+
import { registerInitCommand } from "./commands/init.js";
|
|
13
14
|
import { outputError } from "./lib/output.js";
|
|
14
15
|
|
|
15
16
|
function getClientManager(): { clientManager: OAuthClientManager; port: number } {
|
|
@@ -35,6 +36,7 @@ registerCalendarCommands(program, getClientManager);
|
|
|
35
36
|
registerFormsCommands(program, getClientManager);
|
|
36
37
|
registerAgentCommands(program, () => new AgentStore(getAgentsDir()));
|
|
37
38
|
registerCronCommands(program, () => new AgentStore(getAgentsDir()));
|
|
39
|
+
registerInitCommand(program);
|
|
38
40
|
|
|
39
41
|
program.parseAsync().catch((err) => {
|
|
40
42
|
outputError("cli_error", err instanceof Error ? err.message : String(err));
|
package/src/commands/agent.ts
CHANGED
|
@@ -50,22 +50,20 @@ export function registerAgentCommands(program: Command, getAgentStore: AgentStor
|
|
|
50
50
|
|
|
51
51
|
agent
|
|
52
52
|
.command("grant")
|
|
53
|
-
.description("Grant
|
|
53
|
+
.description("Grant an integration to an agent")
|
|
54
54
|
.argument("<name>", "Agent name")
|
|
55
55
|
.requiredOption("--integration <integration>", "Integration name (gmail, gdrive)")
|
|
56
|
-
.requiredOption("--account <account>", "Account name")
|
|
57
56
|
.action(async (name, opts) => {
|
|
58
|
-
await handleAgentGrant(getAgentStore(), name, opts.integration
|
|
57
|
+
await handleAgentGrant(getAgentStore(), name, opts.integration);
|
|
59
58
|
});
|
|
60
59
|
|
|
61
60
|
agent
|
|
62
61
|
.command("revoke")
|
|
63
|
-
.description("Revoke
|
|
62
|
+
.description("Revoke an integration from an agent")
|
|
64
63
|
.argument("<name>", "Agent name")
|
|
65
64
|
.requiredOption("--integration <integration>", "Integration name (gmail, gdrive)")
|
|
66
|
-
.requiredOption("--account <account>", "Account name")
|
|
67
65
|
.action(async (name, opts) => {
|
|
68
|
-
await handleAgentRevoke(getAgentStore(), name, opts.integration
|
|
66
|
+
await handleAgentRevoke(getAgentStore(), name, opts.integration);
|
|
69
67
|
});
|
|
70
68
|
|
|
71
69
|
const memory = agent
|
package/src/commands/cron.ts
CHANGED
|
@@ -22,17 +22,17 @@ export function registerCronCommands(program: Command, getAgentStore: AgentStore
|
|
|
22
22
|
.description("Add a cron job to an agent")
|
|
23
23
|
.argument("<agent>", "Agent name")
|
|
24
24
|
.requiredOption("--schedule <schedule>", "Cron expression (e.g. \"0 9 * * *\")")
|
|
25
|
-
.requiredOption("--task <task>", "
|
|
25
|
+
.requiredOption("--task <task>", "Task description (written to markdown file)")
|
|
26
|
+
.option("--task-file <taskFile>", "Custom task file name (default: {jobId}.md)")
|
|
26
27
|
.option("--max-iterations <n>", "Maximum loop iterations", "10")
|
|
27
|
-
.option("--completion-promise <word>", "Completion word", "TASK_COMPLETE")
|
|
28
28
|
.action(async (agent, opts) => {
|
|
29
29
|
await handleCronAdd(
|
|
30
30
|
getAgentStore(),
|
|
31
31
|
agent,
|
|
32
32
|
opts.schedule,
|
|
33
|
+
opts.taskFile || "",
|
|
33
34
|
opts.task,
|
|
34
35
|
parseInt(opts.maxIterations, 10),
|
|
35
|
-
opts.completionPromise,
|
|
36
36
|
);
|
|
37
37
|
});
|
|
38
38
|
|
|
@@ -91,7 +91,7 @@ export function registerCronCommands(program: Command, getAgentStore: AgentStore
|
|
|
91
91
|
const job = config!.cronJobs.find((j) => j.id === jobId);
|
|
92
92
|
if (!job) outputError("job_not_found", `Job "${jobId}" not found`);
|
|
93
93
|
|
|
94
|
-
cronLog("info", `Manual trigger: ${job!.
|
|
94
|
+
cronLog("info", `Manual trigger: ${job!.taskFile}`, agent, jobId);
|
|
95
95
|
|
|
96
96
|
const startedAt = new Date().toISOString();
|
|
97
97
|
try {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { writeFileSync, existsSync, mkdirSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { generateUniversalClaudeMd, getConfigDir } from "@digitalpresence/cliclaw-auth";
|
|
5
|
+
import { outputJson } from "../lib/output.js";
|
|
6
|
+
|
|
7
|
+
export function registerInitCommand(program: Command): void {
|
|
8
|
+
program
|
|
9
|
+
.command("init")
|
|
10
|
+
.description("Initialize/update the universal CLAUDE.md for all agents")
|
|
11
|
+
.action(async () => {
|
|
12
|
+
const configDir = getConfigDir();
|
|
13
|
+
if (!existsSync(configDir)) {
|
|
14
|
+
mkdirSync(configDir, { recursive: true });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const claudeMdPath = join(configDir, "CLAUDE.md");
|
|
18
|
+
writeFileSync(claudeMdPath, generateUniversalClaudeMd(), "utf-8");
|
|
19
|
+
|
|
20
|
+
outputJson({ status: "initialized", path: claudeMdPath });
|
|
21
|
+
});
|
|
22
|
+
}
|
package/src/cron/daemon.ts
CHANGED
|
@@ -51,7 +51,7 @@ export async function startDaemon(store: AgentStore): Promise<void> {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
running.add(key);
|
|
54
|
-
cronLog("info", `Executing: ${job.
|
|
54
|
+
cronLog("info", `Executing: ${job.taskFile}`, agentName, job.id);
|
|
55
55
|
const startedAt = new Date().toISOString();
|
|
56
56
|
|
|
57
57
|
try {
|
|
@@ -87,7 +87,7 @@ export async function startDaemon(store: AgentStore): Promise<void> {
|
|
|
87
87
|
});
|
|
88
88
|
|
|
89
89
|
schedules.set(key, task);
|
|
90
|
-
cronLog("info", `Scheduled: "${job.
|
|
90
|
+
cronLog("info", `Scheduled: "${job.taskFile}" at ${job.schedule}`, agentName, job.id);
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
function syncSchedules(): void {
|
package/src/cron/handlers.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { randomBytes } from "crypto";
|
|
2
|
+
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
2
4
|
import type { AgentStore, CronJobConfig } from "@digitalpresence/cliclaw-auth";
|
|
3
5
|
import { outputJson, outputError } from "../lib/output.js";
|
|
4
6
|
|
|
@@ -6,25 +8,34 @@ export async function handleCronAdd(
|
|
|
6
8
|
store: AgentStore,
|
|
7
9
|
agentName: string,
|
|
8
10
|
schedule: string,
|
|
9
|
-
|
|
11
|
+
taskFile: string,
|
|
12
|
+
taskContent: string,
|
|
10
13
|
maxIterations: number,
|
|
11
|
-
completionPromise: string,
|
|
12
14
|
): Promise<void> {
|
|
13
15
|
const agent = store.get(agentName);
|
|
14
16
|
if (!agent) outputError("agent_not_found", `Agent "${agentName}" not found`);
|
|
15
17
|
|
|
18
|
+
const jobId = randomBytes(6).toString("hex");
|
|
19
|
+
|
|
20
|
+
// Write task markdown file
|
|
21
|
+
const cronTasksDir = join(store.workspacePath(agentName), "cron-tasks");
|
|
22
|
+
if (!existsSync(cronTasksDir)) {
|
|
23
|
+
mkdirSync(cronTasksDir, { recursive: true });
|
|
24
|
+
}
|
|
25
|
+
const taskFilePath = taskFile || `${jobId}.md`;
|
|
26
|
+
writeFileSync(join(cronTasksDir, taskFilePath), taskContent, "utf-8");
|
|
27
|
+
|
|
16
28
|
const job: CronJobConfig = {
|
|
17
|
-
id:
|
|
29
|
+
id: jobId,
|
|
18
30
|
schedule,
|
|
19
|
-
|
|
31
|
+
taskFile: taskFilePath,
|
|
20
32
|
maxIterations,
|
|
21
|
-
completionPromise,
|
|
22
33
|
enabled: true,
|
|
23
34
|
createdAt: new Date().toISOString(),
|
|
24
35
|
};
|
|
25
36
|
|
|
26
37
|
store.addCronJob(agentName, job);
|
|
27
|
-
outputJson({ status: "added", jobId: job.id, schedule,
|
|
38
|
+
outputJson({ status: "added", jobId: job.id, schedule, taskFile: taskFilePath });
|
|
28
39
|
}
|
|
29
40
|
|
|
30
41
|
export async function handleCronRemove(
|