@jx0/agency 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/README.md +272 -0
  2. package/bin/agency.js +2 -0
  3. package/dashboard/out/404.html +1 -0
  4. package/dashboard/out/_next/static/chunks/255-67e8754147461423.js +1 -0
  5. package/dashboard/out/_next/static/chunks/4bd1b696-c023c6e3521b1417.js +1 -0
  6. package/dashboard/out/_next/static/chunks/app/_not-found/page-ad40673d821037f6.js +1 -0
  7. package/dashboard/out/_next/static/chunks/app/layout-056f12675e691d12.js +1 -0
  8. package/dashboard/out/_next/static/chunks/app/page-80f01fdbb09b43c8.js +1 -0
  9. package/dashboard/out/_next/static/chunks/framework-de98b93a850cfc71.js +1 -0
  10. package/dashboard/out/_next/static/chunks/main-1a0dcce460eb61ce.js +1 -0
  11. package/dashboard/out/_next/static/chunks/main-app-1d848b791b823fa6.js +1 -0
  12. package/dashboard/out/_next/static/chunks/pages/_app-7d307437aca18ad4.js +1 -0
  13. package/dashboard/out/_next/static/chunks/pages/_error-cb2a52f75f2162e2.js +1 -0
  14. package/dashboard/out/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
  15. package/dashboard/out/_next/static/chunks/webpack-4e6bf084ac60582b.js +1 -0
  16. package/dashboard/out/_next/static/css/27d1ea794f04e96a.css +1 -0
  17. package/dashboard/out/_next/static/pU1nwWH_dNUOCI8y4nl3C/_buildManifest.js +1 -0
  18. package/dashboard/out/_next/static/pU1nwWH_dNUOCI8y4nl3C/_ssgManifest.js +1 -0
  19. package/dashboard/out/index.html +1 -0
  20. package/dashboard/out/index.txt +19 -0
  21. package/docs/images/agency_cli_ps.png +0 -0
  22. package/docs/images/agency_ui_ai_prodivder_settings.png +0 -0
  23. package/docs/images/agency_ui_aws_settings.png +0 -0
  24. package/docs/images/agency_ui_identity_settings.png +0 -0
  25. package/docs/images/agency_ui_mission_control.png +0 -0
  26. package/docs/images/agent_ui_agent_config.png +0 -0
  27. package/package.json +31 -0
  28. package/src/api/db/client.ts +16 -0
  29. package/src/api/db/migrate.ts +37 -0
  30. package/src/api/db/migrations/001_initial.ts +193 -0
  31. package/src/api/db/migrations/002_configs.ts +76 -0
  32. package/src/api/db/migrations/003_settings_columns.ts +13 -0
  33. package/src/api/db/seed.ts +142 -0
  34. package/src/api/db/types.ts +126 -0
  35. package/src/api/index.ts +73 -0
  36. package/src/api/lib/activity.ts +13 -0
  37. package/src/api/lib/fleet-sync.ts +156 -0
  38. package/src/api/lib/mentions.ts +59 -0
  39. package/src/api/lib/processes.ts +45 -0
  40. package/src/api/lib/resolve-agent.ts +5 -0
  41. package/src/api/lib/tunnels.ts +99 -0
  42. package/src/api/routes/activities.ts +27 -0
  43. package/src/api/routes/agents.ts +311 -0
  44. package/src/api/routes/documents.ts +41 -0
  45. package/src/api/routes/knowledge.ts +60 -0
  46. package/src/api/routes/messages.ts +54 -0
  47. package/src/api/routes/notifications.ts +40 -0
  48. package/src/api/routes/oauth.ts +171 -0
  49. package/src/api/routes/role-configs.ts +71 -0
  50. package/src/api/routes/settings.ts +94 -0
  51. package/src/api/routes/skills.ts +76 -0
  52. package/src/api/routes/tasks.ts +154 -0
  53. package/src/cli/commands/config.ts +42 -0
  54. package/src/cli/commands/daemon.ts +173 -0
  55. package/src/cli/commands/doc.ts +47 -0
  56. package/src/cli/commands/init.ts +105 -0
  57. package/src/cli/commands/learn.ts +51 -0
  58. package/src/cli/commands/logs.ts +31 -0
  59. package/src/cli/commands/msg.ts +18 -0
  60. package/src/cli/commands/ps.ts +19 -0
  61. package/src/cli/commands/recall.ts +18 -0
  62. package/src/cli/commands/skills.ts +66 -0
  63. package/src/cli/commands/ssh.ts +68 -0
  64. package/src/cli/commands/start.ts +14 -0
  65. package/src/cli/commands/status.ts +33 -0
  66. package/src/cli/commands/stop.ts +11 -0
  67. package/src/cli/commands/tasks.ts +150 -0
  68. package/src/cli/index.ts +70 -0
  69. package/src/cli/lib/api.ts +16 -0
  70. package/src/cli/lib/config.ts +5 -0
  71. package/src/cli/lib/find-root.ts +32 -0
  72. package/src/cli/lib/prompt.ts +20 -0
  73. package/src/daemon.ts +83 -0
  74. package/src/templates/implementer/agents-config.md +44 -0
  75. package/src/templates/implementer/agents.md +32 -0
  76. package/src/templates/implementer/heartbeat.md +47 -0
  77. package/src/templates/implementer/tools.md +33 -0
  78. package/src/templates/orchestrator/agents-config.md +44 -0
  79. package/src/templates/orchestrator/agents.md +27 -0
  80. package/src/templates/orchestrator/heartbeat.md +40 -0
  81. package/src/templates/orchestrator/tools.md +40 -0
  82. package/src/templates/shared/environment.md +20 -0
  83. package/src/templates/shared/memory.md +20 -0
  84. package/src/templates/shared/soul.md +26 -0
  85. package/src/templates/shared/user.md +12 -0
@@ -0,0 +1,66 @@
1
+ import { api } from "../lib/api.js";
2
+
3
+ function parseFlag(args: string[], flag: string): string | undefined {
4
+ const idx = args.indexOf(flag);
5
+ if (idx === -1 || idx + 1 >= args.length) return undefined;
6
+ return args[idx + 1];
7
+ }
8
+
9
+ export default async function skills(args: string[]) {
10
+ const sub = args[0];
11
+
12
+ if (sub === "list" || !sub) {
13
+ const category = parseFlag(args, "--category");
14
+ const search = parseFlag(args, "--search");
15
+ const params = new URLSearchParams();
16
+ if (category) params.set("category", category);
17
+ if (search) params.set("search", search);
18
+ const qs = params.toString() ? `?${params}` : "";
19
+ const rows = await api(`/skills${qs}`);
20
+ if (rows.length === 0) {
21
+ console.log("No skills found.");
22
+ } else {
23
+ for (const s of rows) {
24
+ const tags = s.tags?.length ? ` [${s.tags.join(", ")}]` : "";
25
+ console.log(` ${s.id.slice(0, 8)} ${s.name}${tags}`);
26
+ }
27
+ }
28
+ return;
29
+ }
30
+
31
+ if (sub === "show") {
32
+ const id = args[1];
33
+ if (!id) { console.error("Usage: agency skills show <id>"); process.exit(1); }
34
+ const s = await api(`/skills/${id}`);
35
+ console.log(`Skill: ${s.name}`);
36
+ console.log(` Category: ${s.category}`);
37
+ console.log(` Tags: ${s.tags?.join(", ") || "none"}`);
38
+ console.log(` Body:\n${s.body}`);
39
+ return;
40
+ }
41
+
42
+ if (sub === "create") {
43
+ const name = args[1];
44
+ if (!name) { console.error("Usage: agency skills create <name> [--category C]"); process.exit(1); }
45
+ const category = parseFlag(args, "--category");
46
+ const body = await Bun.stdin.text();
47
+ const s = await api("/skills", {
48
+ method: "POST",
49
+ body: JSON.stringify({ name, body: body || `# ${name}\n`, category }),
50
+ });
51
+ console.log(`Created skill ${s.id}: ${s.name}`);
52
+ return;
53
+ }
54
+
55
+ if (sub === "delete") {
56
+ const id = args[1];
57
+ if (!id) { console.error("Usage: agency skills delete <id>"); process.exit(1); }
58
+ await api(`/skills/${id}`, { method: "DELETE" });
59
+ console.log(`Deleted skill ${id}`);
60
+ return;
61
+ }
62
+
63
+ console.error("Unknown skills subcommand:", sub);
64
+ console.log("Subcommands: list, show, create, delete");
65
+ process.exit(1);
66
+ }
@@ -0,0 +1,68 @@
1
+ import { api } from "../lib/api.js";
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+ import * as os from "os";
5
+
6
+ export default async function ssh(args: string[]) {
7
+ const name = args[0];
8
+ if (!name) {
9
+ console.error("Usage: agency ssh <agent-name>");
10
+ process.exit(1);
11
+ }
12
+
13
+ const agent = await api(`/agents/${name}`);
14
+ if (!agent) {
15
+ console.error(`Agent "${name}" not found.`);
16
+ process.exit(1);
17
+ }
18
+
19
+ if (agent.location !== "ec2") {
20
+ console.error(`SSH is only supported for EC2 agents. "${name}" is ${agent.location ?? "local"}.`);
21
+ process.exit(1);
22
+ }
23
+
24
+ // Get SSH settings
25
+ const settings = await api("/settings?category=ssh");
26
+ const sshConfig: Record<string, string> = {};
27
+ for (const s of settings) {
28
+ sshConfig[s.key] = s.value;
29
+ }
30
+
31
+ const privateKey = sshConfig["ssh.private_key"];
32
+ const user = sshConfig["ssh.user"] || "ubuntu";
33
+
34
+ if (!privateKey) {
35
+ console.error("SSH private key not configured. Set it in Settings → SSH.");
36
+ process.exit(1);
37
+ }
38
+
39
+ // Get host from fleet.json via agent detail or settings
40
+ // The agent's host should be in fleet.json
41
+ const configRes = await api("/settings?category=aws");
42
+ // We need the fleet host — fetch it from the API or read fleet.json directly
43
+ let host = "";
44
+ try {
45
+ const fleetPath = path.resolve(process.cwd(), ".agency", "fleet.json");
46
+ const fleet = JSON.parse(fs.readFileSync(fleetPath, "utf-8"));
47
+ host = fleet.agents?.[name]?.host ?? "";
48
+ } catch {}
49
+
50
+ if (!host) {
51
+ console.error(`No host configured for "${name}". Add "host" to fleet.json for this agent.`);
52
+ process.exit(1);
53
+ }
54
+
55
+ // Write key to temp file
56
+ const keyDir = path.join(os.tmpdir(), "agency-ssh");
57
+ fs.mkdirSync(keyDir, { recursive: true, mode: 0o700 });
58
+ const keyPath = path.join(keyDir, "agent_key");
59
+ fs.writeFileSync(keyPath, privateKey + "\n", { mode: 0o600 });
60
+
61
+ // Exec ssh
62
+ const proc = Bun.spawn(
63
+ ["ssh", "-i", keyPath, "-o", "StrictHostKeyChecking=no", `${user}@${host}`, ...args.slice(1)],
64
+ { stdout: "inherit", stderr: "inherit", stdin: "inherit" }
65
+ );
66
+ const code = await proc.exited;
67
+ process.exit(code);
68
+ }
@@ -0,0 +1,14 @@
1
+ import { api } from "../lib/api.js";
2
+
3
+ export default async function start(args: string[]) {
4
+ const name = args[0];
5
+ if (!name) {
6
+ console.error("Usage: agency start <agent-name>");
7
+ process.exit(1);
8
+ }
9
+ const result = await api(`/agents/${name}/deploy`, { method: "POST" });
10
+ console.log(`Started ${name}: ${result.status} (${result.method})`);
11
+ if (result.instructions) {
12
+ console.log(` ${result.instructions}`);
13
+ }
14
+ }
@@ -0,0 +1,33 @@
1
+ import { api } from "../lib/api.js";
2
+
3
+ export default async function status(args: string[]) {
4
+ const name = args[0];
5
+
6
+ if (name) {
7
+ const agent = await api(`/agents/${name}`);
8
+ console.log(`Agent: ${agent.name}`);
9
+ console.log(` Role: ${agent.role ?? "n/a"}`);
10
+ console.log(` Status: ${agent.status}`);
11
+ console.log(` Current task: ${agent.current_task ?? "none"}`);
12
+ console.log(` Updated: ${agent.updated_at}`);
13
+ return;
14
+ }
15
+
16
+ // General health check
17
+ try {
18
+ const data = await api("/health");
19
+ console.log(`API: ${data.status}`);
20
+ } catch (err: any) {
21
+ console.log(`API: unreachable (${err.message})`);
22
+ return;
23
+ }
24
+
25
+ const agents = await api("/agents");
26
+ const active = agents.filter((a: any) => a.status === "active" && a.name !== "human");
27
+ const total = agents.filter((a: any) => a.name !== "human");
28
+ console.log(`Agents: ${active.length}/${total.length} active`);
29
+
30
+ for (const a of total) {
31
+ console.log(` ${a.name.padEnd(15)} ${(a.status ?? "").padEnd(10)} ${a.role ?? ""}`);
32
+ }
33
+ }
@@ -0,0 +1,11 @@
1
+ import { api } from "../lib/api.js";
2
+
3
+ export default async function stop(args: string[]) {
4
+ const name = args[0];
5
+ if (!name) {
6
+ console.error("Usage: agency stop <agent-name>");
7
+ process.exit(1);
8
+ }
9
+ const result = await api(`/agents/${name}/stop`, { method: "POST" });
10
+ console.log(`Stopped ${name}: ${result.status}`);
11
+ }
@@ -0,0 +1,150 @@
1
+ import { api } from "../lib/api.js";
2
+ import { getConfig } from "../lib/config.js";
3
+
4
+ function parseFlag(args: string[], flag: string): string | undefined {
5
+ const idx = args.indexOf(flag);
6
+ if (idx === -1 || idx + 1 >= args.length) return undefined;
7
+ return args[idx + 1];
8
+ }
9
+
10
+ export default async function tasks(args: string[]) {
11
+ const { agentName } = getConfig();
12
+ const sub = args[0];
13
+ const jsonMode = args.includes("--json");
14
+
15
+ if (sub === "create") {
16
+ const title = args[1];
17
+ if (!title) { console.error("Usage: agency tasks create <title> [flags]"); process.exit(1); }
18
+ const taskType = parseFlag(args, "--type") ?? "task";
19
+ const priority = parseFlag(args, "--priority");
20
+ const assign = parseFlag(args, "--assign");
21
+ const design = parseFlag(args, "--design");
22
+ const acceptance = parseFlag(args, "--acceptance");
23
+ const description = parseFlag(args, "--description") ?? title;
24
+ const from = parseFlag(args, "--from") ?? (agentName || "human");
25
+ const parentId = parseFlag(args, "--parent");
26
+
27
+ const task = await api("/tasks", {
28
+ method: "POST",
29
+ body: JSON.stringify({
30
+ title, description, from,
31
+ task_type: taskType,
32
+ priority: priority ? Number(priority) : undefined,
33
+ assign: assign ?? undefined,
34
+ design: design ?? undefined,
35
+ acceptance: acceptance ?? undefined,
36
+ parent_id: parentId ?? undefined,
37
+ }),
38
+ });
39
+ console.log(`Created task ${task.id}: ${task.title} [${task.status}]`);
40
+ return;
41
+ }
42
+
43
+ if (sub === "list") {
44
+ const status = parseFlag(args, "--status");
45
+ const assignee = parseFlag(args, "--assignee");
46
+ const type = parseFlag(args, "--type");
47
+ const parent = parseFlag(args, "--parent");
48
+ const params = new URLSearchParams();
49
+ if (status) params.set("status", status);
50
+ if (assignee) params.set("assignee", assignee);
51
+ if (type) params.set("type", type);
52
+ if (parent) params.set("parent_id", parent);
53
+ const qs = params.toString() ? `?${params}` : "";
54
+ const rows = await api(`/tasks${qs}`);
55
+ if (jsonMode) { console.log(JSON.stringify(rows)); return; }
56
+ if (rows.length === 0) {
57
+ console.log("No tasks found.");
58
+ } else {
59
+ for (const t of rows) {
60
+ console.log(` ${t.id} [${t.status.padEnd(10)}] P${t.priority} ${t.title}`);
61
+ }
62
+ }
63
+ return;
64
+ }
65
+
66
+ if (sub === "ready") {
67
+ const assignee = parseFlag(args, "--assignee") ?? agentName;
68
+ if (!assignee) { console.error("Provide --assignee or set AGENCY_AGENT_NAME"); process.exit(1); }
69
+ const rows = await api(`/tasks?status=assigned&assignee=${encodeURIComponent(assignee)}`);
70
+ if (jsonMode) { console.log(JSON.stringify(rows)); return; }
71
+ if (rows.length === 0) {
72
+ console.log("No ready tasks.");
73
+ } else {
74
+ for (const t of rows) {
75
+ console.log(` ${t.id} P${t.priority} ${t.title}`);
76
+ }
77
+ }
78
+ return;
79
+ }
80
+
81
+ if (sub === "show") {
82
+ const id = args[1];
83
+ if (!id) { console.error("Usage: agency tasks show <id>"); process.exit(1); }
84
+ const t = await api(`/tasks/${id}`);
85
+ if (jsonMode) { console.log(JSON.stringify(t)); return; }
86
+ console.log(`Task: ${t.id}`);
87
+ console.log(` Title: ${t.title}`);
88
+ console.log(` Status: ${t.status}`);
89
+ console.log(` Priority: ${t.priority}`);
90
+ console.log(` Type: ${t.task_type}`);
91
+ console.log(` Description: ${t.description}`);
92
+ if (t.design) console.log(` Design: ${t.design}`);
93
+ if (t.acceptance) console.log(` Acceptance: ${t.acceptance}`);
94
+ if (t.assignees?.length) {
95
+ console.log(` Assignees: ${t.assignees.map((a: any) => a.name).join(", ")}`);
96
+ }
97
+ if (t.messages?.length) {
98
+ console.log(` Messages:`);
99
+ for (const m of t.messages) {
100
+ console.log(` [${m.from_name ?? "?"}] ${m.content}`);
101
+ }
102
+ }
103
+ return;
104
+ }
105
+
106
+ if (sub === "update") {
107
+ const id = args[1];
108
+ if (!id) { console.error("Usage: agency tasks update <id> [flags]"); process.exit(1); }
109
+ const status = parseFlag(args, "--status");
110
+ const priority = parseFlag(args, "--priority");
111
+ const design = parseFlag(args, "--design");
112
+ const acceptance = parseFlag(args, "--acceptance");
113
+ const assign = parseFlag(args, "--assign");
114
+ const from = parseFlag(args, "--from") ?? (agentName || "human");
115
+ const body: any = { from };
116
+ if (status) body.status = status;
117
+ if (priority) body.priority = Number(priority);
118
+ if (design) body.design = design;
119
+ if (acceptance) body.acceptance = acceptance;
120
+
121
+ const t = await api(`/tasks/${id}`, { method: "PATCH", body: JSON.stringify(body) });
122
+
123
+ if (assign) {
124
+ await api(`/tasks/${id}/assign`, {
125
+ method: "POST",
126
+ body: JSON.stringify({ agentName: assign }),
127
+ });
128
+ console.log(`Updated task ${t.id}: [${t.status}] ${t.title} (assigned to ${assign})`);
129
+ } else {
130
+ console.log(`Updated task ${t.id}: [${t.status}] ${t.title}`);
131
+ }
132
+ return;
133
+ }
134
+
135
+ if (sub === "close") {
136
+ const id = args[1];
137
+ if (!id) { console.error("Usage: agency tasks close <id>"); process.exit(1); }
138
+ const from = agentName || "human";
139
+ const t = await api(`/tasks/${id}`, {
140
+ method: "PATCH",
141
+ body: JSON.stringify({ status: "done", from }),
142
+ });
143
+ console.log(`Closed task ${t.id}: ${t.title}`);
144
+ return;
145
+ }
146
+
147
+ console.error("Unknown tasks subcommand:", sub);
148
+ console.log("Subcommands: create, list, ready, show, update, close");
149
+ process.exit(1);
150
+ }
@@ -0,0 +1,70 @@
1
+ const args = process.argv.slice(2);
2
+ const command = args[0];
3
+
4
+ const COMMANDS: Record<string, () => Promise<any>> = {
5
+ init: () => import("./commands/init.js"),
6
+ ps: () => import("./commands/ps.js"),
7
+ start: () => import("./commands/start.js"),
8
+ stop: () => import("./commands/stop.js"),
9
+ logs: () => import("./commands/logs.js"),
10
+ ssh: () => import("./commands/ssh.js"),
11
+ tasks: () => import("./commands/tasks.js"),
12
+ msg: () => import("./commands/msg.js"),
13
+ learn: () => import("./commands/learn.js"),
14
+ recall: () => import("./commands/recall.js"),
15
+ doc: () => import("./commands/doc.js"),
16
+ daemon: () => import("./commands/daemon.js"),
17
+ status: () => import("./commands/status.js"),
18
+ config: () => import("./commands/config.js"),
19
+ skills: () => import("./commands/skills.js"),
20
+ ping: () => import("./commands/status.js"), // alias
21
+ };
22
+
23
+ async function main() {
24
+ if (command === "--version" || command === "-v" || command === "-V") {
25
+ const pkg = await import("../../package.json");
26
+ console.log(pkg.default?.version ?? pkg.version ?? "0.1.0");
27
+ process.exit(0);
28
+ }
29
+
30
+ if (!command || command === "--help" || command === "-h") {
31
+ console.log(`Agency — Multi-Agent AI Development Platform
32
+
33
+ Usage: agency <command> [args]
34
+
35
+ Commands:
36
+ init Set up .agency/ in current directory
37
+ ps List agents
38
+ start <name> Start an agent
39
+ stop <name> Stop an agent
40
+ logs <name> Tail agent logs
41
+ ssh <name> SSH into agent
42
+ tasks <subcommand> Task management (create/list/ready/show/update/close)
43
+ msg <task-id> <message> Post a task comment
44
+ learn <content> [--tags t,t] Store knowledge
45
+ recall <search> Search knowledge
46
+ doc <subcommand> Document management
47
+ daemon <subcommand> Daemon management (install/uninstall/start/stop/status/logs/run)
48
+ status Health check
49
+ config [key] [value] View/edit settings
50
+ skills <subcommand> Skills management (list/show/create/update/delete)`);
51
+ process.exit(0);
52
+ }
53
+
54
+ const loader = COMMANDS[command];
55
+ if (!loader) {
56
+ console.error(`Unknown command: ${command}`);
57
+ console.error("Run 'agency --help' for usage.");
58
+ process.exit(1);
59
+ }
60
+
61
+ const mod = await loader();
62
+ if (mod.default) {
63
+ await mod.default(args.slice(1));
64
+ }
65
+ }
66
+
67
+ main().catch((err) => {
68
+ console.error(err.message ?? err);
69
+ process.exit(1);
70
+ });
@@ -0,0 +1,16 @@
1
+ import { getConfig } from "./config.js";
2
+
3
+ const { apiUrl } = getConfig();
4
+
5
+ export async function api(path: string, options?: RequestInit): Promise<any> {
6
+ const url = `${apiUrl}${path}`;
7
+ const res = await fetch(url, {
8
+ ...options,
9
+ headers: { "Content-Type": "application/json", ...options?.headers },
10
+ });
11
+ if (!res.ok) {
12
+ const body = await res.text();
13
+ throw new Error(`API error ${res.status}: ${body}`);
14
+ }
15
+ return res.json();
16
+ }
@@ -0,0 +1,5 @@
1
+ export function getConfig() {
2
+ const apiUrl = process.env.AGENCY_API_URL ?? "http://localhost:3100";
3
+ const agentName = process.env.AGENCY_AGENT_NAME ?? "";
4
+ return { apiUrl, agentName };
5
+ }
@@ -0,0 +1,32 @@
1
+ import * as path from "path";
2
+ import * as fs from "fs";
3
+
4
+ /**
5
+ * Walk up from `startDir` (default CWD) looking for `.agency/` directory.
6
+ * Returns the absolute path to `.agency/`, or null if not found.
7
+ */
8
+ export function findAgencyRoot(startDir?: string): string | null {
9
+ let dir = path.resolve(startDir ?? process.cwd());
10
+ const root = path.parse(dir).root;
11
+
12
+ while (true) {
13
+ const candidate = path.join(dir, ".agency");
14
+ if (fs.existsSync(candidate) && fs.statSync(candidate).isDirectory()) {
15
+ return candidate;
16
+ }
17
+ if (dir === root) return null;
18
+ dir = path.dirname(dir);
19
+ }
20
+ }
21
+
22
+ /**
23
+ * Like findAgencyRoot but throws if not found.
24
+ */
25
+ export function requireAgencyRoot(startDir?: string): string {
26
+ const root = findAgencyRoot(startDir);
27
+ if (!root) {
28
+ console.error("No .agency/ directory found. Run `agency init` first.");
29
+ process.exit(1);
30
+ }
31
+ return root;
32
+ }
@@ -0,0 +1,20 @@
1
+ import * as readline from "readline";
2
+
3
+ export function ask(question: string, defaultValue?: string): Promise<string> {
4
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
5
+ const suffix = defaultValue ? ` (${defaultValue})` : "";
6
+ return new Promise((resolve) => {
7
+ rl.question(`${question}${suffix}: `, (answer) => {
8
+ rl.close();
9
+ resolve(answer.trim() || defaultValue || "");
10
+ });
11
+ });
12
+ }
13
+
14
+ export function confirm(question: string, defaultYes = true): Promise<boolean> {
15
+ const hint = defaultYes ? "Y/n" : "y/N";
16
+ return ask(`${question} (${hint})`).then((a) => {
17
+ if (!a) return defaultYes;
18
+ return a.toLowerCase().startsWith("y");
19
+ });
20
+ }
package/src/daemon.ts ADDED
@@ -0,0 +1,83 @@
1
+ import * as path from "path";
2
+ import { findAgencyRoot } from "./cli/lib/find-root.js";
3
+
4
+ // Resolve .agency/ and set environment
5
+ const agencyRoot = findAgencyRoot();
6
+ if (agencyRoot) {
7
+ process.env.DATABASE_PATH ??= path.join(agencyRoot, "agency.db");
8
+ process.env.FLEET_PATH ??= path.join(agencyRoot, "fleet.json");
9
+ }
10
+
11
+ // Start API server explicitly (Bun auto-serve only works for main entry)
12
+ console.log("[daemon] starting API server...");
13
+ const apiModule = await import("./api/index.js");
14
+ const server = Bun.serve({
15
+ port: apiModule.default.port,
16
+ hostname: apiModule.default.hostname,
17
+ fetch: apiModule.default.fetch,
18
+ });
19
+ console.log(`[daemon] API listening on http://${server.hostname}:${server.port}`);
20
+
21
+ // Run migrations on startup
22
+ const { runMigrations } = await import("./api/db/migrate.js");
23
+ await runMigrations();
24
+
25
+ function spawnChild(name: string, cmd: string[], cwd: string) {
26
+ let proc: ReturnType<typeof Bun.spawn> | null = null;
27
+ let stopped = false;
28
+
29
+ function start() {
30
+ console.log(`[daemon] starting ${name}...`);
31
+ proc = Bun.spawn(cmd, {
32
+ cwd,
33
+ stdout: "inherit",
34
+ stderr: "inherit",
35
+ env: { ...process.env },
36
+ });
37
+ console.log(`[daemon] ${name} pid=${proc.pid}`);
38
+
39
+ proc.exited.then((code) => {
40
+ if (stopped) return;
41
+ console.error(`[daemon] ${name} exited with code ${code}, restarting in 1s...`);
42
+ setTimeout(start, 1000);
43
+ });
44
+ }
45
+
46
+ start();
47
+
48
+ return {
49
+ kill() {
50
+ stopped = true;
51
+ proc?.kill();
52
+ },
53
+ };
54
+ }
55
+
56
+ const children: { kill(): void }[] = [];
57
+
58
+ // Dashboard is now served as static files from the API process — no child needed.
59
+
60
+ // Only spawn notify if it exists
61
+ const notifyEntry = path.resolve(import.meta.dir, "../packages/notify/src/index.ts");
62
+ if (await Bun.file(notifyEntry).exists()) {
63
+ children.push(
64
+ spawnChild(
65
+ "notify",
66
+ ["bun", "run", "src/index.ts"],
67
+ path.resolve(import.meta.dir, "../packages/notify")
68
+ )
69
+ );
70
+ }
71
+
72
+ async function shutdown() {
73
+ console.log("[daemon] shutting down...");
74
+ const { stopAllTunnels } = await import("./api/lib/tunnels.js");
75
+ stopAllTunnels();
76
+ for (const child of children) {
77
+ child.kill();
78
+ }
79
+ process.exit(0);
80
+ }
81
+
82
+ process.on("SIGTERM", shutdown);
83
+ process.on("SIGINT", shutdown);
@@ -0,0 +1,44 @@
1
+ # Implementer Configuration
2
+
3
+ ## Communication
4
+
5
+ - Minimal communication. Silence is competence.
6
+ - Speak up when blocked, confused, or need a decision.
7
+ - Use task comments for all communication.
8
+
9
+ ## Task Lifecycle
10
+
11
+ 1. **Assigned** — Read task, understand requirements
12
+ 2. **In Progress** — Do the work
13
+ 3. **Needs Input** — Blocked, waiting for guidance (include clear question)
14
+ 4. **Review** — Work complete, ready for review
15
+ 5. **Done** — Accepted by orchestrator
16
+
17
+ ## Asking for Help
18
+
19
+ Good: "I found X and Y approaches. X is simpler but Y handles edge case Z. Which should I use?"
20
+ Bad: "What should I do?"
21
+
22
+ Always include:
23
+ - What you've tried
24
+ - What options you see
25
+ - Your recommendation
26
+
27
+ ## Knowledge Base
28
+
29
+ - Before starting work: `agency recall <topic>` to check for prior decisions
30
+ - After learning something: `agency learn "<what you learned>" --tags tag1,tag2`
31
+
32
+ ## Testing
33
+
34
+ - Run tests before marking task as review
35
+ - Verify changes locally
36
+ - Don't push broken code
37
+
38
+ ## Rules
39
+
40
+ 1. One active task at a time
41
+ 2. Always test before completing
42
+ 3. Capture decisions in task comments
43
+ 4. Don't duplicate work — check if it's already been done
44
+ 5. Ask for help rather than guessing on important decisions
@@ -0,0 +1,32 @@
1
+ # Implementer Role
2
+
3
+ You are an implementer — you execute work assigned by the orchestrator.
4
+
5
+ ## Session Startup
6
+
7
+ 1. Read SOUL.md, USER.md, MEMORY.md
8
+ 2. `agency recall "recent decisions"`
9
+ 3. Run your heartbeat checklist
10
+
11
+ ## Role Definition
12
+
13
+ - **Execute work** — Pick up assigned tasks and complete them
14
+ - **Don't investigate** — If the task is unclear, ask for clarification
15
+ - **Don't delegate** — You do the work yourself
16
+ - **Report back** — Update task status and comment with results
17
+
18
+ ## Rules
19
+
20
+ 1. Always use proper development tools and workflows
21
+ 2. Run tests before marking work complete
22
+ 3. Verify changes locally before pushing
23
+ 4. Capture decisions in task comments
24
+ 5. Don't duplicate work — check first
25
+
26
+ ## Communication
27
+
28
+ All communication goes through task comments (`agency msg`).
29
+
30
+ ## Workflow
31
+
32
+ Follow HEARTBEAT.md on every cycle.