@whatasoda/agent-tools 0.1.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 (51) hide show
  1. package/dist/agents/codex-review/body.md +98 -0
  2. package/dist/agents/team-reviewer/body.md +78 -0
  3. package/dist/agents/team-worker/body.md +46 -0
  4. package/dist/scripts/codex-review.js +237 -0
  5. package/dist/scripts/detect-base-branch.js +185 -0
  6. package/dist/scripts/resolve-session.js +76 -0
  7. package/dist/skills/soda-brief/body.md +73 -0
  8. package/dist/skills/soda-discuss/README.md +25 -0
  9. package/dist/skills/soda-discuss/body.md +216 -0
  10. package/dist/skills/soda-fix/body.md +137 -0
  11. package/dist/skills/soda-plan/body.md +333 -0
  12. package/dist/skills/soda-research/body.md +127 -0
  13. package/dist/skills/soda-review/body.md +165 -0
  14. package/dist/skills/soda-review-todos/body.md +19 -0
  15. package/dist/skills/soda-team-init/body.md +313 -0
  16. package/dist/skills/soda-team-init/references/coordination-files.md +188 -0
  17. package/dist/skills/soda-team-run/body.md +329 -0
  18. package/dist/skills/soda-todo/body.md +86 -0
  19. package/dist/src/cli/commands/agent.js +29 -0
  20. package/dist/src/cli/commands/codex-review.js +14 -0
  21. package/dist/src/cli/commands/decision.js +103 -0
  22. package/dist/src/cli/commands/import.js +174 -0
  23. package/dist/src/cli/commands/link.js +52 -0
  24. package/dist/src/cli/commands/list.js +12 -0
  25. package/dist/src/cli/commands/node.js +118 -0
  26. package/dist/src/cli/commands/review.js +23 -0
  27. package/dist/src/cli/commands/session.js +23 -0
  28. package/dist/src/cli/commands/skill.js +29 -0
  29. package/dist/src/cli/commands/tag.js +31 -0
  30. package/dist/src/cli/helpers.js +51 -0
  31. package/dist/src/cli/index.js +48 -0
  32. package/dist/src/cli.js +59 -0
  33. package/dist/src/core/database.js +209 -0
  34. package/dist/src/core/ensure-dirs.js +8 -0
  35. package/dist/src/core/index.js +1 -0
  36. package/dist/src/core/kinds.js +46 -0
  37. package/dist/src/core/schema.sql +36 -0
  38. package/dist/src/core/schemas.js +41 -0
  39. package/dist/src/core/search.js +80 -0
  40. package/dist/src/core/types.js +0 -0
  41. package/dist/src/tui/App.js +130 -0
  42. package/dist/src/tui/actions.js +9 -0
  43. package/dist/src/tui/components/FilterBar.js +46 -0
  44. package/dist/src/tui/components/LinkList.js +53 -0
  45. package/dist/src/tui/components/NodeDetail.js +111 -0
  46. package/dist/src/tui/components/NodeList.js +62 -0
  47. package/dist/src/tui/components/StatusBar.js +90 -0
  48. package/dist/src/tui/hooks/useNavigation.js +57 -0
  49. package/dist/src/tui/hooks/useNodes.js +44 -0
  50. package/dist/src/tui/index.js +4 -0
  51. package/package.json +29 -0
@@ -0,0 +1,174 @@
1
+ import path from "path";
2
+ import { Glob } from "bun";
3
+ import { exitWithError, outputJson } from "../helpers.js";
4
+ function parseFrontmatter(content) {
5
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
6
+ if (!match)
7
+ return { topic: "", status: "", date: "" };
8
+ const fm = match[1];
9
+ const topic = fm.match(/^topic:\s*(.+)$/m)?.[1]?.trim() ?? "";
10
+ const status = fm.match(/^status:\s*(.+)$/m)?.[1]?.trim() ?? "";
11
+ const date = fm.match(/^date:\s*(.+)$/m)?.[1]?.trim() ?? "";
12
+ return { topic, status, date };
13
+ }
14
+ function parseLDD(content, filePath) {
15
+ const fm = parseFrontmatter(content);
16
+ const decisions = [];
17
+ const topicSections = content.split(/^## Topic \d+:/m).slice(1);
18
+ for (const section of topicSections) {
19
+ const sectionDDs = [];
20
+ const sectionRAs = [];
21
+ const ddBlocks = section.split(/^#### DD-\d+:\s*/m).slice(1);
22
+ for (const block of ddBlocks) {
23
+ const name = block.split(`
24
+ `)[0].trim();
25
+ const constraint = block.match(/\*\*Constraint\*\*:\s*([\s\S]*?)(?=\n- \*\*)/)?.[1]?.trim() ?? "";
26
+ const why = block.match(/\*\*Why\*\*:\s*([\s\S]*?)(?=\n- \*\*|\n\n|$)/)?.[1]?.trim() ?? "";
27
+ const scope = block.match(/\*\*Scope\*\*:\s*([\s\S]*?)(?=\n\n|$)/)?.[1]?.trim() ?? "";
28
+ if (constraint || name) {
29
+ sectionDDs.push({ name, constraint, why, scope, rejectedAlternatives: [] });
30
+ }
31
+ }
32
+ const raBlocks = section.split(/^#### RA-\d+:\s*/m).slice(1);
33
+ for (const block of raBlocks) {
34
+ const what = block.match(/\*\*What\*\*:\s*([\s\S]*?)(?=\n- \*\*)/)?.[1]?.trim() ?? block.split(`
35
+ `)[0].trim();
36
+ const whyRejected = block.match(/\*\*Why rejected\*\*:\s*([\s\S]*?)(?=\n\n|$)/)?.[1]?.trim() ?? "";
37
+ if (what) {
38
+ sectionRAs.push({ what, why_rejected: whyRejected });
39
+ }
40
+ }
41
+ if (sectionRAs.length > 0 && sectionDDs.length > 0) {
42
+ sectionDDs[sectionDDs.length - 1].rejectedAlternatives = sectionRAs;
43
+ }
44
+ decisions.push(...sectionDDs);
45
+ }
46
+ return { ...fm, decisions, filePath };
47
+ }
48
+ async function detectRepo() {
49
+ try {
50
+ const proc = Bun.spawn(["git", "remote", "get-url", "origin"], {
51
+ stdout: "pipe",
52
+ stderr: "pipe"
53
+ });
54
+ const url = (await new Response(proc.stdout).text()).trim();
55
+ await proc.exited;
56
+ if (proc.exitCode !== 0)
57
+ return null;
58
+ const sshMatch = url.match(/[:/]([^/]+)\/([^/]+?)(?:\.git)?$/);
59
+ if (sshMatch)
60
+ return { owner: sshMatch[1], name: sshMatch[2] };
61
+ return null;
62
+ } catch {
63
+ return null;
64
+ }
65
+ }
66
+ async function scanForLDDs() {
67
+ const repoRoot = await getRepoRoot();
68
+ if (!repoRoot) {
69
+ return exitWithError("Not in a git repository");
70
+ }
71
+ const patterns = [
72
+ ".agent-discussions/*.md",
73
+ ".worktrees/*/.agent-discussions/*.md",
74
+ ".claude/worktrees/*/.agent-discussions/*.md"
75
+ ];
76
+ const found = new Map;
77
+ for (const pattern of patterns) {
78
+ const glob = new Glob(pattern);
79
+ for await (const file of glob.scan({ cwd: repoRoot, absolute: true, dot: true })) {
80
+ const basename = path.basename(file);
81
+ if (!found.has(basename)) {
82
+ found.set(basename, file);
83
+ }
84
+ }
85
+ }
86
+ return [...found.values()].sort();
87
+ }
88
+ async function getRepoRoot() {
89
+ try {
90
+ const proc = Bun.spawn(["git", "rev-parse", "--show-toplevel"], {
91
+ stdout: "pipe",
92
+ stderr: "pipe"
93
+ });
94
+ const root = (await new Response(proc.stdout).text()).trim();
95
+ await proc.exited;
96
+ return root || null;
97
+ } catch {
98
+ return null;
99
+ }
100
+ }
101
+ export async function handleImport(db, args) {
102
+ const scanMode = args.includes("--scan");
103
+ const dryRun = args.includes("--dry-run");
104
+ const fileArgs = args.filter((a) => !a.startsWith("--"));
105
+ let files;
106
+ if (scanMode) {
107
+ files = await scanForLDDs();
108
+ if (files.length === 0) {
109
+ console.log("No .agent-discussions files found.");
110
+ return;
111
+ }
112
+ console.log(`Found ${files.length} LDD file(s):`);
113
+ for (const f of files)
114
+ console.log(` ${f}`);
115
+ console.log("");
116
+ } else if (fileArgs.length > 0) {
117
+ files = fileArgs.map((f) => path.resolve(f));
118
+ } else {
119
+ return exitWithError(`Usage: sd decision import --scan OR sd decision import <file...>
120
+ --dry-run Preview without writing to DB`);
121
+ }
122
+ const repo = await detectRepo();
123
+ const topicTag = (filePath) => {
124
+ const slug = path.basename(filePath, ".md").replace(/^\d{4}-\d{2}-\d{2}-/, "");
125
+ return `topic:${slug}`;
126
+ };
127
+ let totalImported = 0;
128
+ const results = [];
129
+ for (const file of files) {
130
+ const content = await Bun.file(file).text();
131
+ const ldd = parseLDD(content, file);
132
+ if (ldd.decisions.length === 0) {
133
+ results.push({ file, imported: 0, decisions: [] });
134
+ continue;
135
+ }
136
+ const tag = topicTag(file);
137
+ const importedNames = [];
138
+ for (const dd of ldd.decisions) {
139
+ if (dryRun) {
140
+ console.log(`[dry-run] Would create: "${dd.name}" (${tag})`);
141
+ if (dd.rejectedAlternatives.length > 0) {
142
+ console.log(` with ${dd.rejectedAlternatives.length} rejected alternative(s)`);
143
+ }
144
+ importedNames.push(dd.name);
145
+ continue;
146
+ }
147
+ const properties = {
148
+ constraint: dd.constraint || dd.name,
149
+ why: dd.why,
150
+ scope: dd.scope,
151
+ rejected_alternatives: dd.rejectedAlternatives
152
+ };
153
+ if (repo) {
154
+ properties.repo_owner = repo.owner;
155
+ properties.repo_name = repo.name;
156
+ }
157
+ db.createNode({
158
+ kind: "decision",
159
+ body: dd.name,
160
+ properties,
161
+ tags: [tag]
162
+ });
163
+ importedNames.push(dd.name);
164
+ totalImported++;
165
+ }
166
+ results.push({ file, imported: importedNames.length, decisions: importedNames });
167
+ }
168
+ if (dryRun) {
169
+ console.log(`
170
+ [dry-run] Would import ${results.reduce((s, r) => s + r.imported, 0)} decision(s) total`);
171
+ } else {
172
+ outputJson({ imported: totalImported, files: results });
173
+ }
174
+ }
@@ -0,0 +1,52 @@
1
+ import { exitWithError, outputJson, parseCli } from "../helpers.js";
2
+ export async function handleLink(db, args) {
3
+ const [action] = args;
4
+ const rest = args.slice(1);
5
+ switch (action) {
6
+ case "create":
7
+ return linkCreate(db, rest);
8
+ case "delete":
9
+ return linkDelete(db, rest);
10
+ case "list":
11
+ return linkList(db, rest);
12
+ default:
13
+ exitWithError("Usage: sd link <create|delete|list>");
14
+ }
15
+ }
16
+ async function linkCreate(db, args) {
17
+ const { positionals, values } = parseCli(args, {
18
+ type: { type: "string" }
19
+ });
20
+ const [fromId, toId] = positionals;
21
+ if (!fromId || !toId || !values.type) {
22
+ exitWithError("Usage: sd link create <from-id> <to-id> --type <link-type>");
23
+ }
24
+ const result = db.createLink(fromId, toId, values.type);
25
+ outputJson(result);
26
+ }
27
+ async function linkDelete(db, args) {
28
+ const { positionals, values } = parseCli(args, {
29
+ type: { type: "string" }
30
+ });
31
+ const [fromId, toId] = positionals;
32
+ if (!fromId || !toId || !values.type) {
33
+ exitWithError("Usage: sd link delete <from-id> <to-id> --type <link-type>");
34
+ }
35
+ db.deleteLink(fromId, toId, values.type);
36
+ outputJson({ success: true });
37
+ }
38
+ async function linkList(db, args) {
39
+ const { positionals, values } = parseCli(args, {
40
+ direction: { type: "string" }
41
+ });
42
+ const [nodeId] = positionals;
43
+ if (!nodeId) {
44
+ exitWithError("Usage: sd link list <node-id> [--direction from|to|both]");
45
+ }
46
+ const direction = values.direction ?? "both";
47
+ if (direction !== "from" && direction !== "to" && direction !== "both") {
48
+ exitWithError("Error: --direction must be from, to, or both");
49
+ }
50
+ const result = db.getLinks(nodeId, direction);
51
+ outputJson(result);
52
+ }
@@ -0,0 +1,12 @@
1
+ import { exitWithError, outputJson } from "../helpers.js";
2
+ export async function handleList(db, args) {
3
+ const [action] = args;
4
+ switch (action) {
5
+ case "kinds":
6
+ return outputJson(db.listKinds());
7
+ case "tags":
8
+ return outputJson(db.listTags());
9
+ default:
10
+ exitWithError("Usage: sd list <kinds|tags>");
11
+ }
12
+ }
@@ -0,0 +1,118 @@
1
+ import { exitWithError, outputJson, parseCli, parseProps, readStdin } from "../helpers.js";
2
+ export async function handleNode(db, args) {
3
+ const [action] = args;
4
+ const rest = args.slice(1);
5
+ switch (action) {
6
+ case "create":
7
+ return nodeCreate(db, rest);
8
+ case "get":
9
+ return nodeGet(db, rest);
10
+ case "update":
11
+ return nodeUpdate(db, rest);
12
+ case "delete":
13
+ return nodeDelete(db, rest);
14
+ case "search":
15
+ return nodeSearch(db, rest);
16
+ default:
17
+ exitWithError("Usage: sd node <create|get|update|delete|search>");
18
+ }
19
+ }
20
+ async function nodeCreate(db, args) {
21
+ const { values } = parseCli(args, {
22
+ body: { type: "string" },
23
+ kind: { type: "string" },
24
+ prop: { multiple: true, type: "string" },
25
+ "props-json": { type: "string" },
26
+ stdin: { default: false, type: "boolean" },
27
+ tags: { type: "string" }
28
+ });
29
+ if (values.stdin) {
30
+ const input = await readStdin();
31
+ const result = db.createNode({
32
+ body: input.body,
33
+ kind: input.kind,
34
+ properties: input.properties,
35
+ tags: input.tags
36
+ });
37
+ return outputJson(result);
38
+ }
39
+ if (!values.kind) {
40
+ exitWithError("Error: --kind is required");
41
+ }
42
+ const properties = parseProps(values.prop, values["props-json"]);
43
+ const tags = values.tags?.split(",");
44
+ const result = db.createNode({
45
+ body: values.body,
46
+ kind: values.kind,
47
+ properties,
48
+ tags
49
+ });
50
+ outputJson(result);
51
+ }
52
+ async function nodeGet(db, args) {
53
+ const { positionals } = parseCli(args, {});
54
+ const [id] = positionals;
55
+ if (!id) {
56
+ exitWithError("Usage: sd node get <id>");
57
+ }
58
+ const result = db.getNode(id);
59
+ outputJson(result);
60
+ }
61
+ async function nodeUpdate(db, args) {
62
+ const { positionals, values } = parseCli(args, {
63
+ body: { type: "string" },
64
+ kind: { type: "string" },
65
+ prop: { multiple: true, type: "string" },
66
+ "props-json": { type: "string" },
67
+ stdin: { default: false, type: "boolean" }
68
+ });
69
+ const [id] = positionals;
70
+ if (!id) {
71
+ exitWithError("Usage: sd node update <id> [--body ...] [--kind ...] [--prop ...] [--stdin]");
72
+ }
73
+ if (values.stdin) {
74
+ const input = await readStdin();
75
+ const result = db.updateNode({
76
+ body: input.body,
77
+ id,
78
+ kind: input.kind,
79
+ properties: input.properties
80
+ });
81
+ return outputJson(result);
82
+ }
83
+ const properties = parseProps(values.prop, values["props-json"]);
84
+ const result = db.updateNode({
85
+ body: values.body,
86
+ id,
87
+ kind: values.kind,
88
+ properties
89
+ });
90
+ outputJson(result);
91
+ }
92
+ async function nodeDelete(db, args) {
93
+ const { positionals } = parseCli(args, {});
94
+ const [id] = positionals;
95
+ if (!id) {
96
+ exitWithError("Usage: sd node delete <id>");
97
+ }
98
+ db.deleteNode(id);
99
+ outputJson({ success: true });
100
+ }
101
+ async function nodeSearch(db, args) {
102
+ const { values } = parseCli(args, {
103
+ kind: { type: "string" },
104
+ limit: { type: "string" },
105
+ offset: { type: "string" },
106
+ query: { type: "string" },
107
+ tags: { type: "string" }
108
+ });
109
+ const tags = values.tags?.split(",");
110
+ const result = db.search({
111
+ kind: values.kind,
112
+ limit: values.limit ? parseInt(values.limit, 10) : 20,
113
+ offset: values.offset ? parseInt(values.offset, 10) : 0,
114
+ query: values.query,
115
+ tags
116
+ });
117
+ outputJson(result);
118
+ }
@@ -0,0 +1,23 @@
1
+ import path from "path";
2
+ import { exitWithError } from "../helpers.js";
3
+ const packageRoot = path.resolve(import.meta.dir, "../../../");
4
+ export async function handleReview(args) {
5
+ const [action, ...rest] = args;
6
+ switch (action) {
7
+ case "detect-base-branch":
8
+ return reviewDetectBaseBranch(rest);
9
+ default:
10
+ exitWithError("Usage: soda review <detect-base-branch>");
11
+ }
12
+ }
13
+ async function reviewDetectBaseBranch(args) {
14
+ const scriptPath = path.join(packageRoot, "scripts", "detect-base-branch.ts");
15
+ const proc = Bun.spawn(["bun", scriptPath, ...args], {
16
+ stdout: "inherit",
17
+ stderr: "inherit"
18
+ });
19
+ const exitCode = await proc.exited;
20
+ if (exitCode !== 0) {
21
+ process.exit(exitCode);
22
+ }
23
+ }
@@ -0,0 +1,23 @@
1
+ import path from "path";
2
+ import { exitWithError } from "../helpers.js";
3
+ const packageRoot = path.resolve(import.meta.dir, "../../../");
4
+ export async function handleSession(args) {
5
+ const [action, ...rest] = args;
6
+ switch (action) {
7
+ case "resolve":
8
+ return sessionResolve(rest);
9
+ default:
10
+ exitWithError("Usage: sd session <resolve>");
11
+ }
12
+ }
13
+ async function sessionResolve(args) {
14
+ const scriptPath = path.join(packageRoot, "scripts", "resolve-session.ts");
15
+ const proc = Bun.spawn(["bun", scriptPath, ...args], {
16
+ stdout: "inherit",
17
+ stderr: "inherit"
18
+ });
19
+ const exitCode = await proc.exited;
20
+ if (exitCode !== 0) {
21
+ process.exit(exitCode);
22
+ }
23
+ }
@@ -0,0 +1,29 @@
1
+ import path from "path";
2
+ import { exitWithError } from "../helpers.js";
3
+ const packageRoot = path.resolve(import.meta.dir, "../../../");
4
+ export async function handleSkill(args) {
5
+ const [action, name] = args;
6
+ switch (action) {
7
+ case "print":
8
+ return skillPrint(name);
9
+ default:
10
+ exitWithError("Usage: soda skill <print> <name>");
11
+ }
12
+ }
13
+ async function skillPrint(name) {
14
+ if (!name) {
15
+ exitWithError("Usage: soda skill print <name>");
16
+ }
17
+ const baseDir = path.join(packageRoot, "skills");
18
+ const filePath = path.resolve(baseDir, name, "body.md");
19
+ if (!filePath.startsWith(baseDir + path.sep)) {
20
+ exitWithError(`Invalid skill name: ${name}`);
21
+ }
22
+ const file = Bun.file(filePath);
23
+ const exists = await file.exists();
24
+ if (!exists) {
25
+ exitWithError(`Skill not found: ${name}`);
26
+ }
27
+ const content = await file.text();
28
+ process.stdout.write(content);
29
+ }
@@ -0,0 +1,31 @@
1
+ import { exitWithError, outputJson, parseCli } from "../helpers.js";
2
+ export async function handleTag(db, args) {
3
+ const [action] = args;
4
+ const rest = args.slice(1);
5
+ switch (action) {
6
+ case "add":
7
+ return tagAdd(db, rest);
8
+ case "remove":
9
+ return tagRemove(db, rest);
10
+ default:
11
+ exitWithError("Usage: sd tag <add|remove> <node-id> <tag1> [tag2 ...]");
12
+ }
13
+ }
14
+ async function tagAdd(db, args) {
15
+ const { positionals } = parseCli(args, {});
16
+ const [nodeId, ...tags] = positionals;
17
+ if (!nodeId || tags.length === 0) {
18
+ exitWithError("Usage: sd tag add <node-id> <tag1> [tag2 ...]");
19
+ }
20
+ db.addTags(nodeId, tags);
21
+ outputJson({ success: true });
22
+ }
23
+ async function tagRemove(db, args) {
24
+ const { positionals } = parseCli(args, {});
25
+ const [nodeId, ...tags] = positionals;
26
+ if (!nodeId || tags.length === 0) {
27
+ exitWithError("Usage: sd tag remove <node-id> <tag1> [tag2 ...]");
28
+ }
29
+ db.removeTags(nodeId, tags);
30
+ outputJson({ success: true });
31
+ }
@@ -0,0 +1,51 @@
1
+ import { parseArgs } from "node:util";
2
+ export function outputJson(data) {
3
+ console.log(JSON.stringify(data));
4
+ }
5
+ export function exitWithError(message) {
6
+ console.error(message);
7
+ process.exit(1);
8
+ }
9
+ export async function readStdin() {
10
+ const text = await Bun.stdin.text();
11
+ const trimmed = text.trim();
12
+ if (!trimmed) {
13
+ exitWithError("Error: --stdin specified but no input received");
14
+ }
15
+ try {
16
+ return JSON.parse(trimmed);
17
+ } catch {
18
+ exitWithError("Error: invalid JSON on stdin");
19
+ }
20
+ }
21
+ export function parseProps(propArgs, propsJson) {
22
+ if (!propArgs?.length && !propsJson) {
23
+ return;
24
+ }
25
+ let base = {};
26
+ if (propsJson) {
27
+ try {
28
+ base = JSON.parse(propsJson);
29
+ } catch {
30
+ exitWithError("Error: invalid JSON in --props-json");
31
+ }
32
+ }
33
+ if (propArgs?.length) {
34
+ for (const prop of propArgs) {
35
+ const eq = prop.indexOf("=");
36
+ if (eq === -1) {
37
+ exitWithError(`Error: invalid --prop format "${prop}", expected key=value`);
38
+ }
39
+ base[prop.slice(0, eq)] = prop.slice(eq + 1);
40
+ }
41
+ }
42
+ return base;
43
+ }
44
+ export function parseCli(args, options) {
45
+ return parseArgs({
46
+ allowPositionals: true,
47
+ args,
48
+ options,
49
+ strict: false
50
+ });
51
+ }
@@ -0,0 +1,48 @@
1
+ import os from "os";
2
+ import path from "path";
3
+ import { Database } from "../core/database.js";
4
+ import { ensureDbDir } from "../core/ensure-dirs.js";
5
+ import { handleDecision } from "./commands/decision.js";
6
+ import { handleLink } from "./commands/link.js";
7
+ import { handleList } from "./commands/list.js";
8
+ import { handleNode } from "./commands/node.js";
9
+ import { handleTag } from "./commands/tag.js";
10
+ import { exitWithError } from "./helpers.js";
11
+ export async function runCli(resource, args) {
12
+ const DB_PATH = process.env.SODA_AGENT_TOOLS_DB ?? path.join(os.homedir(), ".soda-agent-tools", "data.db");
13
+ ensureDbDir(DB_PATH);
14
+ const db = new Database(DB_PATH);
15
+ try {
16
+ switch (resource) {
17
+ case "node":
18
+ await handleNode(db, args);
19
+ break;
20
+ case "tag":
21
+ await handleTag(db, args);
22
+ break;
23
+ case "link":
24
+ await handleLink(db, args);
25
+ break;
26
+ case "list":
27
+ await handleList(db, args);
28
+ break;
29
+ case "decision":
30
+ await handleDecision(db, args);
31
+ break;
32
+ default:
33
+ exitWithError(`Usage: sd <node|tag|link|list|tui|setup>
34
+
35
+ Commands:
36
+ node Create, read, update, delete, search nodes
37
+ tag Add or remove tags
38
+ link Create, delete, list links
39
+ list List kinds or tags
40
+ tui Launch the TUI browser
41
+ setup Configure Claude Code integration`);
42
+ }
43
+ } catch (e) {
44
+ exitWithError(`Error: ${String(e instanceof Error ? e.message : e)}`);
45
+ } finally {
46
+ db.close();
47
+ }
48
+ }
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env bun
2
+ export {};
3
+ const [, , command] = process.argv;
4
+ switch (command) {
5
+ case "node":
6
+ case "tag":
7
+ case "link":
8
+ case "list":
9
+ case "decision": {
10
+ const { runCli } = await import("./cli/index.js");
11
+ await runCli(command, process.argv.slice(3));
12
+ break;
13
+ }
14
+ case "skill": {
15
+ const { handleSkill } = await import("./cli/commands/skill.js");
16
+ await handleSkill(process.argv.slice(3));
17
+ break;
18
+ }
19
+ case "agent": {
20
+ const { handleAgent } = await import("./cli/commands/agent.js");
21
+ await handleAgent(process.argv.slice(3));
22
+ break;
23
+ }
24
+ case "review": {
25
+ const { handleReview } = await import("./cli/commands/review.js");
26
+ await handleReview(process.argv.slice(3));
27
+ break;
28
+ }
29
+ case "codex-review": {
30
+ const { handleCodexReview } = await import("./cli/commands/codex-review.js");
31
+ await handleCodexReview(process.argv.slice(3));
32
+ break;
33
+ }
34
+ case "session": {
35
+ const { handleSession } = await import("./cli/commands/session.js");
36
+ await handleSession(process.argv.slice(3));
37
+ break;
38
+ }
39
+ case "tui":
40
+ case undefined:
41
+ await import("./tui/index.tsx");
42
+ break;
43
+ default:
44
+ console.error(`Usage: sd <node|tag|link|list|decision|skill|agent|review|codex-review|session|tui>
45
+
46
+ Commands:
47
+ node Create, read, update, delete, search nodes
48
+ tag Add or remove tags
49
+ link Create, delete, list links
50
+ list List kinds or tags
51
+ decision Create or list design decisions
52
+ skill Print skill body
53
+ agent Print agent body
54
+ review Review utilities
55
+ codex-review Run codex review
56
+ session Session utilities
57
+ tui Launch the TUI browser`);
58
+ process.exit(1);
59
+ }