@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.
- package/README.md +272 -0
- package/bin/agency.js +2 -0
- package/dashboard/out/404.html +1 -0
- package/dashboard/out/_next/static/chunks/255-67e8754147461423.js +1 -0
- package/dashboard/out/_next/static/chunks/4bd1b696-c023c6e3521b1417.js +1 -0
- package/dashboard/out/_next/static/chunks/app/_not-found/page-ad40673d821037f6.js +1 -0
- package/dashboard/out/_next/static/chunks/app/layout-056f12675e691d12.js +1 -0
- package/dashboard/out/_next/static/chunks/app/page-80f01fdbb09b43c8.js +1 -0
- package/dashboard/out/_next/static/chunks/framework-de98b93a850cfc71.js +1 -0
- package/dashboard/out/_next/static/chunks/main-1a0dcce460eb61ce.js +1 -0
- package/dashboard/out/_next/static/chunks/main-app-1d848b791b823fa6.js +1 -0
- package/dashboard/out/_next/static/chunks/pages/_app-7d307437aca18ad4.js +1 -0
- package/dashboard/out/_next/static/chunks/pages/_error-cb2a52f75f2162e2.js +1 -0
- package/dashboard/out/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
- package/dashboard/out/_next/static/chunks/webpack-4e6bf084ac60582b.js +1 -0
- package/dashboard/out/_next/static/css/27d1ea794f04e96a.css +1 -0
- package/dashboard/out/_next/static/pU1nwWH_dNUOCI8y4nl3C/_buildManifest.js +1 -0
- package/dashboard/out/_next/static/pU1nwWH_dNUOCI8y4nl3C/_ssgManifest.js +1 -0
- package/dashboard/out/index.html +1 -0
- package/dashboard/out/index.txt +19 -0
- package/docs/images/agency_cli_ps.png +0 -0
- package/docs/images/agency_ui_ai_prodivder_settings.png +0 -0
- package/docs/images/agency_ui_aws_settings.png +0 -0
- package/docs/images/agency_ui_identity_settings.png +0 -0
- package/docs/images/agency_ui_mission_control.png +0 -0
- package/docs/images/agent_ui_agent_config.png +0 -0
- package/package.json +31 -0
- package/src/api/db/client.ts +16 -0
- package/src/api/db/migrate.ts +37 -0
- package/src/api/db/migrations/001_initial.ts +193 -0
- package/src/api/db/migrations/002_configs.ts +76 -0
- package/src/api/db/migrations/003_settings_columns.ts +13 -0
- package/src/api/db/seed.ts +142 -0
- package/src/api/db/types.ts +126 -0
- package/src/api/index.ts +73 -0
- package/src/api/lib/activity.ts +13 -0
- package/src/api/lib/fleet-sync.ts +156 -0
- package/src/api/lib/mentions.ts +59 -0
- package/src/api/lib/processes.ts +45 -0
- package/src/api/lib/resolve-agent.ts +5 -0
- package/src/api/lib/tunnels.ts +99 -0
- package/src/api/routes/activities.ts +27 -0
- package/src/api/routes/agents.ts +311 -0
- package/src/api/routes/documents.ts +41 -0
- package/src/api/routes/knowledge.ts +60 -0
- package/src/api/routes/messages.ts +54 -0
- package/src/api/routes/notifications.ts +40 -0
- package/src/api/routes/oauth.ts +171 -0
- package/src/api/routes/role-configs.ts +71 -0
- package/src/api/routes/settings.ts +94 -0
- package/src/api/routes/skills.ts +76 -0
- package/src/api/routes/tasks.ts +154 -0
- package/src/cli/commands/config.ts +42 -0
- package/src/cli/commands/daemon.ts +173 -0
- package/src/cli/commands/doc.ts +47 -0
- package/src/cli/commands/init.ts +105 -0
- package/src/cli/commands/learn.ts +51 -0
- package/src/cli/commands/logs.ts +31 -0
- package/src/cli/commands/msg.ts +18 -0
- package/src/cli/commands/ps.ts +19 -0
- package/src/cli/commands/recall.ts +18 -0
- package/src/cli/commands/skills.ts +66 -0
- package/src/cli/commands/ssh.ts +68 -0
- package/src/cli/commands/start.ts +14 -0
- package/src/cli/commands/status.ts +33 -0
- package/src/cli/commands/stop.ts +11 -0
- package/src/cli/commands/tasks.ts +150 -0
- package/src/cli/index.ts +70 -0
- package/src/cli/lib/api.ts +16 -0
- package/src/cli/lib/config.ts +5 -0
- package/src/cli/lib/find-root.ts +32 -0
- package/src/cli/lib/prompt.ts +20 -0
- package/src/daemon.ts +83 -0
- package/src/templates/implementer/agents-config.md +44 -0
- package/src/templates/implementer/agents.md +32 -0
- package/src/templates/implementer/heartbeat.md +47 -0
- package/src/templates/implementer/tools.md +33 -0
- package/src/templates/orchestrator/agents-config.md +44 -0
- package/src/templates/orchestrator/agents.md +27 -0
- package/src/templates/orchestrator/heartbeat.md +40 -0
- package/src/templates/orchestrator/tools.md +40 -0
- package/src/templates/shared/environment.md +20 -0
- package/src/templates/shared/memory.md +20 -0
- package/src/templates/shared/soul.md +26 -0
- package/src/templates/shared/user.md +12 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { db } from "../db/client.js";
|
|
3
|
+
import { resolveAgent } from "../lib/resolve-agent.js";
|
|
4
|
+
import { logActivity } from "../lib/activity.js";
|
|
5
|
+
|
|
6
|
+
export const documents = new Hono();
|
|
7
|
+
|
|
8
|
+
documents.get("/", async (c) => {
|
|
9
|
+
const taskId = c.req.query("task_id");
|
|
10
|
+
let q = db.selectFrom("documents").selectAll();
|
|
11
|
+
if (taskId) q = q.where("task_id", "=", taskId);
|
|
12
|
+
const rows = await q.orderBy("created_at", "desc").execute();
|
|
13
|
+
return c.json(rows);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
documents.get("/:id", async (c) => {
|
|
17
|
+
const doc = await db.selectFrom("documents").where("id", "=", c.req.param("id")).selectAll().executeTakeFirst();
|
|
18
|
+
if (!doc) return c.json({ error: "not found" }, 404);
|
|
19
|
+
return c.json(doc);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
documents.post("/", async (c) => {
|
|
23
|
+
const body = await c.req.json<{
|
|
24
|
+
title: string; content: string; doc_type?: string;
|
|
25
|
+
task_id?: string; from: string;
|
|
26
|
+
}>();
|
|
27
|
+
const agent = await resolveAgent(body.from);
|
|
28
|
+
if (!agent) return c.json({ error: "unknown agent" }, 400);
|
|
29
|
+
|
|
30
|
+
const doc = await db.insertInto("documents").values({
|
|
31
|
+
id: crypto.randomUUID(),
|
|
32
|
+
title: body.title,
|
|
33
|
+
content: body.content,
|
|
34
|
+
doc_type: body.doc_type ?? "general",
|
|
35
|
+
task_id: body.task_id ?? null,
|
|
36
|
+
created_by: agent.id,
|
|
37
|
+
}).returningAll().executeTakeFirstOrThrow();
|
|
38
|
+
|
|
39
|
+
await logActivity("document_created", agent.id, `Created doc: ${doc.title}`, doc.task_id);
|
|
40
|
+
return c.json(doc, 201);
|
|
41
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { db } from "../db/client.js";
|
|
3
|
+
import { resolveAgent } from "../lib/resolve-agent.js";
|
|
4
|
+
|
|
5
|
+
export const knowledge = new Hono();
|
|
6
|
+
|
|
7
|
+
knowledge.get("/", async (c) => {
|
|
8
|
+
const tags = c.req.query("tags");
|
|
9
|
+
const search = c.req.query("search");
|
|
10
|
+
|
|
11
|
+
let q = db.selectFrom("knowledge").selectAll();
|
|
12
|
+
|
|
13
|
+
if (tags) {
|
|
14
|
+
const tagArr = tags.split(",");
|
|
15
|
+
for (const tag of tagArr) {
|
|
16
|
+
q = q.where("tags", "like", `%"${tag}"%`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (search) {
|
|
21
|
+
q = q.where((eb) =>
|
|
22
|
+
eb.or([
|
|
23
|
+
eb("content", "like", `%${search}%`),
|
|
24
|
+
eb("key", "like", `%${search}%`),
|
|
25
|
+
])
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const rows = await q.orderBy("created_at", "desc").execute();
|
|
30
|
+
return c.json(rows.map((r) => ({ ...r, tags: JSON.parse(r.tags) })));
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
knowledge.post("/", async (c) => {
|
|
34
|
+
const body = await c.req.json<{
|
|
35
|
+
key: string; content: string; from: string;
|
|
36
|
+
task_id?: string; tags?: string[];
|
|
37
|
+
}>();
|
|
38
|
+
const agent = await resolveAgent(body.from);
|
|
39
|
+
if (!agent) return c.json({ error: "unknown agent" }, 400);
|
|
40
|
+
|
|
41
|
+
const tagsJson = JSON.stringify(body.tags ?? []);
|
|
42
|
+
|
|
43
|
+
const row = await db.insertInto("knowledge").values({
|
|
44
|
+
id: crypto.randomUUID(),
|
|
45
|
+
key: body.key,
|
|
46
|
+
content: body.content,
|
|
47
|
+
source: agent.id,
|
|
48
|
+
task_id: body.task_id ?? null,
|
|
49
|
+
tags: tagsJson,
|
|
50
|
+
})
|
|
51
|
+
.onConflict((oc) => oc.column("key").doUpdateSet({
|
|
52
|
+
content: body.content,
|
|
53
|
+
source: agent.id,
|
|
54
|
+
tags: tagsJson,
|
|
55
|
+
}))
|
|
56
|
+
.returningAll()
|
|
57
|
+
.executeTakeFirstOrThrow();
|
|
58
|
+
|
|
59
|
+
return c.json({ ...row, tags: JSON.parse(row.tags) }, 201);
|
|
60
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { db } from "../db/client.js";
|
|
3
|
+
import { resolveAgent } from "../lib/resolve-agent.js";
|
|
4
|
+
import { logActivity } from "../lib/activity.js";
|
|
5
|
+
import { parseMentions, getTaskSubscribers } from "../lib/mentions.js";
|
|
6
|
+
|
|
7
|
+
export const messages = new Hono();
|
|
8
|
+
|
|
9
|
+
messages.get("/:taskId/messages", async (c) => {
|
|
10
|
+
const taskId = c.req.param("taskId");
|
|
11
|
+
const rows = await db
|
|
12
|
+
.selectFrom("messages")
|
|
13
|
+
.leftJoin("agents", "agents.id", "messages.from_agent")
|
|
14
|
+
.where("task_id", "=", taskId)
|
|
15
|
+
.select([
|
|
16
|
+
"messages.id", "messages.task_id", "messages.from_agent",
|
|
17
|
+
"messages.content", "messages.created_at",
|
|
18
|
+
"agents.name as from_name",
|
|
19
|
+
])
|
|
20
|
+
.orderBy("messages.created_at", "asc")
|
|
21
|
+
.execute();
|
|
22
|
+
return c.json(rows);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
messages.post("/:taskId/messages", async (c) => {
|
|
26
|
+
const taskId = c.req.param("taskId");
|
|
27
|
+
const { from, content } = await c.req.json<{ from: string; content: string }>();
|
|
28
|
+
const agent = await resolveAgent(from);
|
|
29
|
+
if (!agent) return c.json({ error: "unknown agent" }, 400);
|
|
30
|
+
|
|
31
|
+
const msg = await db.insertInto("messages").values({
|
|
32
|
+
id: crypto.randomUUID(),
|
|
33
|
+
task_id: taskId, from_agent: agent.id, content,
|
|
34
|
+
}).returningAll().executeTakeFirstOrThrow();
|
|
35
|
+
|
|
36
|
+
// Collect notification targets
|
|
37
|
+
const mentionIds = await parseMentions(content, agent.id);
|
|
38
|
+
const subscriberIds = await getTaskSubscribers(taskId, agent.id);
|
|
39
|
+
const targetIds = new Set([...mentionIds, ...subscriberIds]);
|
|
40
|
+
|
|
41
|
+
// Create notifications
|
|
42
|
+
for (const targetId of targetIds) {
|
|
43
|
+
await db.insertInto("notifications").values({
|
|
44
|
+
id: crypto.randomUUID(),
|
|
45
|
+
target_agent: targetId,
|
|
46
|
+
source_agent: agent.id,
|
|
47
|
+
task_id: taskId,
|
|
48
|
+
content: `${from}: ${content}`,
|
|
49
|
+
}).execute();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
await logActivity("message", agent.id, `Message on task`, taskId);
|
|
53
|
+
return c.json(msg, 201);
|
|
54
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { db } from "../db/client.js";
|
|
3
|
+
import { resolveAgent } from "../lib/resolve-agent.js";
|
|
4
|
+
|
|
5
|
+
export const notifications = new Hono();
|
|
6
|
+
|
|
7
|
+
notifications.get("/pending", async (c) => {
|
|
8
|
+
const rows = await db
|
|
9
|
+
.selectFrom("notifications")
|
|
10
|
+
.innerJoin("agents", "agents.id", "notifications.target_agent")
|
|
11
|
+
.where("delivered", "=", 0)
|
|
12
|
+
.select([
|
|
13
|
+
"notifications.id", "notifications.target_agent", "notifications.source_agent",
|
|
14
|
+
"notifications.task_id", "notifications.content", "notifications.delivered",
|
|
15
|
+
"notifications.created_at",
|
|
16
|
+
"agents.name as target_name", "agents.session_key",
|
|
17
|
+
])
|
|
18
|
+
.orderBy("notifications.created_at", "asc")
|
|
19
|
+
.execute();
|
|
20
|
+
return c.json(rows);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
notifications.get("/pending/:agentName", async (c) => {
|
|
24
|
+
const agent = await resolveAgent(c.req.param("agentName"));
|
|
25
|
+
if (!agent) return c.json({ error: "not found" }, 404);
|
|
26
|
+
const rows = await db
|
|
27
|
+
.selectFrom("notifications")
|
|
28
|
+
.where("target_agent", "=", agent.id)
|
|
29
|
+
.where("delivered", "=", 0)
|
|
30
|
+
.selectAll()
|
|
31
|
+
.orderBy("created_at", "asc")
|
|
32
|
+
.execute();
|
|
33
|
+
return c.json(rows);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
notifications.post("/deliver/:id", async (c) => {
|
|
37
|
+
const id = c.req.param("id");
|
|
38
|
+
await db.updateTable("notifications").where("id", "=", id).set("delivered", 1).execute();
|
|
39
|
+
return c.json({ ok: true });
|
|
40
|
+
});
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import { db } from "../db/client.js";
|
|
5
|
+
|
|
6
|
+
export const oauth = new Hono();
|
|
7
|
+
|
|
8
|
+
// In-memory PKCE store (single-user system)
|
|
9
|
+
let pendingVerifier: string | null = null;
|
|
10
|
+
|
|
11
|
+
function base64UrlEncode(buf: ArrayBuffer): string {
|
|
12
|
+
return Buffer.from(buf)
|
|
13
|
+
.toString("base64")
|
|
14
|
+
.replace(/\+/g, "-")
|
|
15
|
+
.replace(/\//g, "_")
|
|
16
|
+
.replace(/=+$/, "");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function generateCodeChallenge(verifier: string): Promise<string> {
|
|
20
|
+
const encoder = new TextEncoder();
|
|
21
|
+
const data = encoder.encode(verifier);
|
|
22
|
+
const digest = await crypto.subtle.digest("SHA-256", data);
|
|
23
|
+
return base64UrlEncode(digest);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function generateCodeVerifier(): string {
|
|
27
|
+
const buf = new Uint8Array(32);
|
|
28
|
+
crypto.getRandomValues(buf);
|
|
29
|
+
return base64UrlEncode(buf.buffer);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const CLIENT_ID = ""; // Must be configured — extract from Claude Code or set via env
|
|
33
|
+
const CLAUDE_CREDENTIALS_PATH = path.join(
|
|
34
|
+
process.env.HOME ?? "",
|
|
35
|
+
".claude",
|
|
36
|
+
".credentials.json"
|
|
37
|
+
);
|
|
38
|
+
const REDIRECT_URI = "http://localhost:3100/oauth/claude/callback";
|
|
39
|
+
const AUTH_URL = "https://console.anthropic.com/oauth/authorize";
|
|
40
|
+
const TOKEN_URL = "https://console.anthropic.com/oauth/token";
|
|
41
|
+
|
|
42
|
+
// Import credentials from ~/.claude/.credentials.json
|
|
43
|
+
oauth.post("/claude/import", async (c) => {
|
|
44
|
+
try {
|
|
45
|
+
if (!fs.existsSync(CLAUDE_CREDENTIALS_PATH)) {
|
|
46
|
+
return c.json({ error: "No Claude Code credentials found at ~/.claude/.credentials.json" }, 404);
|
|
47
|
+
}
|
|
48
|
+
const raw = fs.readFileSync(CLAUDE_CREDENTIALS_PATH, "utf-8");
|
|
49
|
+
const creds = JSON.parse(raw);
|
|
50
|
+
const oauth = creds.claudeAiOauth;
|
|
51
|
+
if (!oauth?.accessToken) {
|
|
52
|
+
return c.json({ error: "No OAuth tokens found in credentials file" }, 400);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const expiresAt = oauth.expiresAt
|
|
56
|
+
? new Date(oauth.expiresAt).toISOString()
|
|
57
|
+
: "";
|
|
58
|
+
|
|
59
|
+
const tokenSettings = [
|
|
60
|
+
{ key: "ai.oauth_access_token", value: oauth.accessToken },
|
|
61
|
+
{ key: "ai.oauth_refresh_token", value: oauth.refreshToken ?? "" },
|
|
62
|
+
{ key: "ai.oauth_expires_at", value: expiresAt },
|
|
63
|
+
{ key: "ai.oauth_subscription_type", value: oauth.subscriptionType ?? "" },
|
|
64
|
+
{ key: "ai.auth_method", value: "oauth" },
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
for (const s of tokenSettings) {
|
|
68
|
+
await db
|
|
69
|
+
.updateTable("settings")
|
|
70
|
+
.where("key", "=", s.key)
|
|
71
|
+
.set({ value: s.value, updated_at: new Date().toISOString() })
|
|
72
|
+
.execute();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return c.json({ ok: true, subscriptionType: oauth.subscriptionType ?? "" });
|
|
76
|
+
} catch (err: any) {
|
|
77
|
+
return c.json({ error: err.message }, 500);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Step 1: Generate authorize URL
|
|
82
|
+
oauth.get("/claude/authorize", async (c) => {
|
|
83
|
+
const clientId = process.env.CLAUDE_OAUTH_CLIENT_ID || CLIENT_ID;
|
|
84
|
+
if (!clientId) {
|
|
85
|
+
return c.json({ error: "CLAUDE_OAUTH_CLIENT_ID not configured" }, 400);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const codeVerifier = generateCodeVerifier();
|
|
89
|
+
const codeChallenge = await generateCodeChallenge(codeVerifier);
|
|
90
|
+
pendingVerifier = codeVerifier;
|
|
91
|
+
|
|
92
|
+
const params = new URLSearchParams({
|
|
93
|
+
response_type: "code",
|
|
94
|
+
client_id: clientId,
|
|
95
|
+
redirect_uri: REDIRECT_URI,
|
|
96
|
+
code_challenge: codeChallenge,
|
|
97
|
+
code_challenge_method: "S256",
|
|
98
|
+
scope: "user:inference",
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
return c.json({ url: `${AUTH_URL}?${params.toString()}` });
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Step 2: Handle callback, exchange code for tokens
|
|
105
|
+
oauth.get("/claude/callback", async (c) => {
|
|
106
|
+
const code = c.req.query("code");
|
|
107
|
+
const error = c.req.query("error");
|
|
108
|
+
|
|
109
|
+
if (error) {
|
|
110
|
+
return c.html(`<html><body><p>OAuth error: ${error}</p><script>setTimeout(()=>window.close(),2000)</script></body></html>`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (!code || !pendingVerifier) {
|
|
114
|
+
return c.html(`<html><body><p>Missing code or verifier</p><script>setTimeout(()=>window.close(),2000)</script></body></html>`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const clientId = process.env.CLAUDE_OAUTH_CLIENT_ID || CLIENT_ID;
|
|
118
|
+
const verifier = pendingVerifier;
|
|
119
|
+
pendingVerifier = null;
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const tokenRes = await fetch(TOKEN_URL, {
|
|
123
|
+
method: "POST",
|
|
124
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
125
|
+
body: new URLSearchParams({
|
|
126
|
+
grant_type: "authorization_code",
|
|
127
|
+
code,
|
|
128
|
+
redirect_uri: REDIRECT_URI,
|
|
129
|
+
client_id: clientId,
|
|
130
|
+
code_verifier: verifier,
|
|
131
|
+
}),
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
if (!tokenRes.ok) {
|
|
135
|
+
const err = await tokenRes.text();
|
|
136
|
+
return c.html(`<html><body><p>Token exchange failed: ${err}</p><script>setTimeout(()=>window.close(),3000)</script></body></html>`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const tokens = await tokenRes.json() as {
|
|
140
|
+
access_token: string;
|
|
141
|
+
refresh_token?: string;
|
|
142
|
+
expires_in?: number;
|
|
143
|
+
subscription_type?: string;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const expiresAt = tokens.expires_in
|
|
147
|
+
? new Date(Date.now() + tokens.expires_in * 1000).toISOString()
|
|
148
|
+
: "";
|
|
149
|
+
|
|
150
|
+
// Store tokens in settings
|
|
151
|
+
const tokenSettings = [
|
|
152
|
+
{ key: "ai.oauth_access_token", value: tokens.access_token },
|
|
153
|
+
{ key: "ai.oauth_refresh_token", value: tokens.refresh_token ?? "" },
|
|
154
|
+
{ key: "ai.oauth_expires_at", value: expiresAt },
|
|
155
|
+
{ key: "ai.oauth_subscription_type", value: tokens.subscription_type ?? "" },
|
|
156
|
+
{ key: "ai.auth_method", value: "oauth" },
|
|
157
|
+
];
|
|
158
|
+
|
|
159
|
+
for (const s of tokenSettings) {
|
|
160
|
+
await db
|
|
161
|
+
.updateTable("settings")
|
|
162
|
+
.where("key", "=", s.key)
|
|
163
|
+
.set({ value: s.value, updated_at: new Date().toISOString() })
|
|
164
|
+
.execute();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return c.html(`<html><body><p>Connected successfully! This window will close.</p><script>setTimeout(()=>window.close(),1000)</script></body></html>`);
|
|
168
|
+
} catch (err: any) {
|
|
169
|
+
return c.html(`<html><body><p>Error: ${err.message}</p><script>setTimeout(()=>window.close(),3000)</script></body></html>`);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { db } from "../db/client.js";
|
|
3
|
+
|
|
4
|
+
export const roleConfigs = new Hono();
|
|
5
|
+
|
|
6
|
+
// List all role configs, optional ?role=
|
|
7
|
+
roleConfigs.get("/", async (c) => {
|
|
8
|
+
const role = c.req.query("role");
|
|
9
|
+
let q = db.selectFrom("role_configs").selectAll();
|
|
10
|
+
if (role) q = q.where("role", "=", role);
|
|
11
|
+
const rows = await q.orderBy("role").orderBy("config_type").execute();
|
|
12
|
+
return c.json(rows);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// Get one role config by role + config_type
|
|
16
|
+
roleConfigs.get("/:role/:configType", async (c) => {
|
|
17
|
+
const row = await db
|
|
18
|
+
.selectFrom("role_configs")
|
|
19
|
+
.where("role", "=", c.req.param("role"))
|
|
20
|
+
.where("config_type", "=", c.req.param("configType"))
|
|
21
|
+
.selectAll()
|
|
22
|
+
.executeTakeFirst();
|
|
23
|
+
if (!row) return c.json({ error: "not found" }, 404);
|
|
24
|
+
return c.json(row);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Upsert a role config
|
|
28
|
+
roleConfigs.put("/:role/:configType", async (c) => {
|
|
29
|
+
const role = c.req.param("role");
|
|
30
|
+
const configType = c.req.param("configType");
|
|
31
|
+
const { content } = await c.req.json<{ content: string }>();
|
|
32
|
+
|
|
33
|
+
const existing = await db
|
|
34
|
+
.selectFrom("role_configs")
|
|
35
|
+
.where("role", "=", role)
|
|
36
|
+
.where("config_type", "=", configType)
|
|
37
|
+
.selectAll()
|
|
38
|
+
.executeTakeFirst();
|
|
39
|
+
|
|
40
|
+
if (existing) {
|
|
41
|
+
const updated = await db
|
|
42
|
+
.updateTable("role_configs")
|
|
43
|
+
.where("id", "=", existing.id)
|
|
44
|
+
.set({ content, updated_at: new Date().toISOString() })
|
|
45
|
+
.returningAll()
|
|
46
|
+
.executeTakeFirstOrThrow();
|
|
47
|
+
return c.json(updated);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const row = await db
|
|
51
|
+
.insertInto("role_configs")
|
|
52
|
+
.values({
|
|
53
|
+
id: crypto.randomUUID(),
|
|
54
|
+
role,
|
|
55
|
+
config_type: configType,
|
|
56
|
+
content,
|
|
57
|
+
})
|
|
58
|
+
.returningAll()
|
|
59
|
+
.executeTakeFirstOrThrow();
|
|
60
|
+
return c.json(row, 201);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Delete a role config
|
|
64
|
+
roleConfigs.delete("/:role/:configType", async (c) => {
|
|
65
|
+
await db
|
|
66
|
+
.deleteFrom("role_configs")
|
|
67
|
+
.where("role", "=", c.req.param("role"))
|
|
68
|
+
.where("config_type", "=", c.req.param("configType"))
|
|
69
|
+
.execute();
|
|
70
|
+
return c.json({ ok: true });
|
|
71
|
+
});
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { db } from "../db/client.js";
|
|
3
|
+
|
|
4
|
+
export const settings = new Hono();
|
|
5
|
+
|
|
6
|
+
const MASKED = "********";
|
|
7
|
+
|
|
8
|
+
// List all settings, optional ?category=
|
|
9
|
+
settings.get("/", async (c) => {
|
|
10
|
+
const category = c.req.query("category");
|
|
11
|
+
let q = db.selectFrom("settings").selectAll();
|
|
12
|
+
if (category) q = q.where("category", "=", category);
|
|
13
|
+
const rows = await q.orderBy("category").orderBy("key").execute();
|
|
14
|
+
// Mask sensitive values
|
|
15
|
+
const masked = rows.map((r) => ({
|
|
16
|
+
...r,
|
|
17
|
+
value: r.sensitive ? MASKED : r.value,
|
|
18
|
+
}));
|
|
19
|
+
return c.json(masked);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Get one setting
|
|
23
|
+
settings.get("/:key", async (c) => {
|
|
24
|
+
const key = c.req.param("key");
|
|
25
|
+
const row = await db.selectFrom("settings").where("key", "=", key).selectAll().executeTakeFirst();
|
|
26
|
+
if (!row) return c.json({ error: "not found" }, 404);
|
|
27
|
+
return c.json({
|
|
28
|
+
...row,
|
|
29
|
+
value: row.sensitive ? MASKED : row.value,
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Upsert a setting
|
|
34
|
+
settings.put("/:key", async (c) => {
|
|
35
|
+
const key = c.req.param("key");
|
|
36
|
+
const body = await c.req.json<{
|
|
37
|
+
value: string;
|
|
38
|
+
category?: string;
|
|
39
|
+
description?: string;
|
|
40
|
+
sensitive?: number;
|
|
41
|
+
input_type?: string;
|
|
42
|
+
}>();
|
|
43
|
+
|
|
44
|
+
// If value is the mask placeholder, skip updating value (user didn't change it)
|
|
45
|
+
const skipValue = body.value === MASKED;
|
|
46
|
+
|
|
47
|
+
const existing = await db.selectFrom("settings").where("key", "=", key).selectAll().executeTakeFirst();
|
|
48
|
+
|
|
49
|
+
if (existing) {
|
|
50
|
+
const updates: Record<string, any> = {
|
|
51
|
+
updated_at: new Date().toISOString(),
|
|
52
|
+
};
|
|
53
|
+
if (!skipValue) updates.value = body.value;
|
|
54
|
+
if (body.category !== undefined) updates.category = body.category;
|
|
55
|
+
if (body.description !== undefined) updates.description = body.description;
|
|
56
|
+
if (body.sensitive !== undefined) updates.sensitive = body.sensitive;
|
|
57
|
+
if (body.input_type !== undefined) updates.input_type = body.input_type;
|
|
58
|
+
|
|
59
|
+
const updated = await db
|
|
60
|
+
.updateTable("settings")
|
|
61
|
+
.where("key", "=", key)
|
|
62
|
+
.set(updates)
|
|
63
|
+
.returningAll()
|
|
64
|
+
.executeTakeFirstOrThrow();
|
|
65
|
+
return c.json({
|
|
66
|
+
...updated,
|
|
67
|
+
value: updated.sensitive ? MASKED : updated.value,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const row = await db
|
|
72
|
+
.insertInto("settings")
|
|
73
|
+
.values({
|
|
74
|
+
key,
|
|
75
|
+
value: skipValue ? "" : body.value,
|
|
76
|
+
category: body.category ?? "general",
|
|
77
|
+
description: body.description ?? null,
|
|
78
|
+
sensitive: body.sensitive ?? 0,
|
|
79
|
+
input_type: body.input_type ?? "text",
|
|
80
|
+
})
|
|
81
|
+
.returningAll()
|
|
82
|
+
.executeTakeFirstOrThrow();
|
|
83
|
+
return c.json({
|
|
84
|
+
...row,
|
|
85
|
+
value: row.sensitive ? MASKED : row.value,
|
|
86
|
+
}, 201);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Delete a setting
|
|
90
|
+
settings.delete("/:key", async (c) => {
|
|
91
|
+
const key = c.req.param("key");
|
|
92
|
+
await db.deleteFrom("settings").where("key", "=", key).execute();
|
|
93
|
+
return c.json({ ok: true });
|
|
94
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { db } from "../db/client.js";
|
|
3
|
+
|
|
4
|
+
export const skills = new Hono();
|
|
5
|
+
|
|
6
|
+
// List skills, optional ?category=, ?search=
|
|
7
|
+
skills.get("/", async (c) => {
|
|
8
|
+
const category = c.req.query("category");
|
|
9
|
+
const search = c.req.query("search");
|
|
10
|
+
|
|
11
|
+
let q = db.selectFrom("skills").selectAll();
|
|
12
|
+
if (category) q = q.where("category", "=", category);
|
|
13
|
+
if (search) {
|
|
14
|
+
q = q.where((eb) =>
|
|
15
|
+
eb.or([
|
|
16
|
+
eb("name", "like", `%${search}%`),
|
|
17
|
+
eb("body", "like", `%${search}%`),
|
|
18
|
+
])
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const rows = await q.orderBy("name").execute();
|
|
23
|
+
return c.json(rows.map((r) => ({ ...r, tags: JSON.parse(r.tags) })));
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Get one skill
|
|
27
|
+
skills.get("/:id", async (c) => {
|
|
28
|
+
const row = await db.selectFrom("skills").where("id", "=", c.req.param("id")).selectAll().executeTakeFirst();
|
|
29
|
+
if (!row) return c.json({ error: "not found" }, 404);
|
|
30
|
+
return c.json({ ...row, tags: JSON.parse(row.tags) });
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Create skill
|
|
34
|
+
skills.post("/", async (c) => {
|
|
35
|
+
const body = await c.req.json<{
|
|
36
|
+
name: string; body: string; category?: string; tags?: string[];
|
|
37
|
+
}>();
|
|
38
|
+
|
|
39
|
+
const row = await db
|
|
40
|
+
.insertInto("skills")
|
|
41
|
+
.values({
|
|
42
|
+
id: crypto.randomUUID(),
|
|
43
|
+
name: body.name,
|
|
44
|
+
body: body.body,
|
|
45
|
+
category: body.category ?? "general",
|
|
46
|
+
tags: JSON.stringify(body.tags ?? []),
|
|
47
|
+
})
|
|
48
|
+
.returningAll()
|
|
49
|
+
.executeTakeFirstOrThrow();
|
|
50
|
+
|
|
51
|
+
return c.json({ ...row, tags: JSON.parse(row.tags) }, 201);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Update skill
|
|
55
|
+
skills.put("/:id", async (c) => {
|
|
56
|
+
const id = c.req.param("id");
|
|
57
|
+
const body = await c.req.json<{
|
|
58
|
+
name?: string; body?: string; category?: string; tags?: string[];
|
|
59
|
+
}>();
|
|
60
|
+
|
|
61
|
+
let q = db.updateTable("skills").where("id", "=", id);
|
|
62
|
+
if (body.name !== undefined) q = q.set("name", body.name);
|
|
63
|
+
if (body.body !== undefined) q = q.set("body", body.body);
|
|
64
|
+
if (body.category !== undefined) q = q.set("category", body.category);
|
|
65
|
+
if (body.tags !== undefined) q = q.set("tags", JSON.stringify(body.tags));
|
|
66
|
+
q = q.set("updated_at", new Date().toISOString());
|
|
67
|
+
|
|
68
|
+
const row = await q.returningAll().executeTakeFirstOrThrow();
|
|
69
|
+
return c.json({ ...row, tags: JSON.parse(row.tags) });
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Delete skill
|
|
73
|
+
skills.delete("/:id", async (c) => {
|
|
74
|
+
await db.deleteFrom("skills").where("id", "=", c.req.param("id")).execute();
|
|
75
|
+
return c.json({ ok: true });
|
|
76
|
+
});
|