@dnai/dynamicllm 0.1.1 → 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/README.md +64 -0
- package/dist/bin/dynamicllm.js +7 -0
- package/dist/cli/formatters.d.ts +11 -0
- package/dist/cli/formatters.js +138 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +345 -0
- package/dist/cli/init.d.ts +1 -0
- package/dist/{setup.js → cli/init.js} +29 -48
- package/dist/core/bootstrap.d.ts +14 -0
- package/dist/core/bootstrap.js +38 -0
- package/dist/core/constants.d.ts +4 -0
- package/dist/core/constants.js +71 -0
- package/dist/{lib → core}/fetcher.d.ts +6 -7
- package/dist/core/fetcher.js +247 -0
- package/dist/core/index.d.ts +9 -0
- package/dist/core/index.js +8 -0
- package/dist/{lib → core}/lint.js +0 -5
- package/dist/core/log.d.ts +13 -0
- package/dist/core/log.js +65 -0
- package/dist/core/report.d.ts +5 -0
- package/dist/{lib → core}/report.js +7 -14
- package/dist/{lib → core}/types.d.ts +43 -0
- package/dist/core/types.js +2 -0
- package/dist/{server.js → mcp/server.js} +48 -68
- package/framework/CONVENTIONS.md +40 -0
- package/framework/SYSTEM_PROMPT.md +203 -0
- package/framework/antidotes/constructive-optimism.md +37 -0
- package/framework/antidotes/liberated-agentism.md +37 -0
- package/framework/antidotes/objective-fallibilism.md +39 -0
- package/framework/antidotes/oxidative-creativism.md +41 -0
- package/framework/antidotes/polycentric-nodalism.md +38 -0
- package/framework/antidotes/vertical-authenticism.md +39 -0
- package/framework/lenses/logos.md +32 -0
- package/framework/lenses/mythos.md +32 -0
- package/framework/lenses/pathos.md +32 -0
- package/framework/qualities/beauty.md +29 -0
- package/framework/qualities/infinity.md +29 -0
- package/framework/qualities/love.md +29 -0
- package/framework/qualities/mystery.md +29 -0
- package/framework/qualities/play.md +29 -0
- package/framework/qualities/story.md +29 -0
- package/package.json +29 -5
- package/dist/cli.js +0 -20
- package/dist/lib/fetcher.js +0 -210
- package/dist/lib/report.d.ts +0 -19
- package/dist/lib/types.js +0 -1
- package/dist/setup.d.ts +0 -1
- /package/dist/{cli.d.ts → bin/dynamicllm.d.ts} +0 -0
- /package/dist/{lib → core}/cache.d.ts +0 -0
- /package/dist/{lib → core}/cache.js +0 -0
- /package/dist/{lib → core}/context.d.ts +0 -0
- /package/dist/{lib → core}/context.js +0 -0
- /package/dist/{lib → core}/lint.d.ts +0 -0
- /package/dist/{lib → core}/search.d.ts +0 -0
- /package/dist/{lib → core}/search.js +0 -0
- /package/dist/{index.d.ts → mcp/index.d.ts} +0 -0
- /package/dist/{index.js → mcp/index.js} +0 -0
- /package/dist/{server.d.ts → mcp/server.d.ts} +0 -0
package/README.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# DynamicLLM
|
|
2
|
+
|
|
3
|
+
Perspective engine for general intelligence. MCP server + CLI providing three analysis modes for any text.
|
|
4
|
+
|
|
5
|
+
## Modes
|
|
6
|
+
|
|
7
|
+
- **Lens Analysis** — Evaluate balance between Logos (logic), Pathos (emotion), and their synthesis Mythos
|
|
8
|
+
- **Quality Cultivation** — Develop one of six qualities: Play, Beauty, Mystery, Love, Infinity, Story
|
|
9
|
+
- **Virus Scan** — Detect memetic patterns across six axes from Liberated Agentism to Oxidative Creativism
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm i -g @dnai/dynamicllm && dynamicllm init
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
This installs the CLI and walks you through agent registration (Claude Code, Cursor, Windsurf, Codex).
|
|
18
|
+
|
|
19
|
+
CLI commands work immediately without `init` — defaults are created on first use.
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# Or register manually with Claude Code
|
|
25
|
+
claude mcp add --scope user dynamicllm -- dynamicllm mcp
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## CLI
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
dynamicllm help # Show all commands
|
|
32
|
+
dynamicllm list # List all network nodes
|
|
33
|
+
dynamicllm search <query> # Search the network
|
|
34
|
+
dynamicllm load <slug> # Load a node's content
|
|
35
|
+
dynamicllm lint # Validate network integrity
|
|
36
|
+
dynamicllm ingest <name> # Generate frontmatter for a new node
|
|
37
|
+
dynamicllm save <slug> <file> # Save a node to your local network
|
|
38
|
+
dynamicllm report <mode> # Generate an analysis report
|
|
39
|
+
dynamicllm log # View activity log
|
|
40
|
+
dynamicllm mcp # Start MCP server (stdio transport)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Architecture
|
|
44
|
+
|
|
45
|
+
Content is organized in three layers:
|
|
46
|
+
|
|
47
|
+
1. **Framework** (bundled) — System prompt, conventions, 3 lenses, 6 qualities, 6 antidotes in `framework/`
|
|
48
|
+
2. **User Network** (local) — Your nodes in `DYNAMICLLM_NETWORK_DIR`, managed via CLI or MCP
|
|
49
|
+
3. **DNA Network** (optional) — Curated thinker profiles deployed to R2 for the web demo
|
|
50
|
+
|
|
51
|
+
Framework slugs are protected — user files cannot override bundled content.
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
framework/
|
|
55
|
+
SYSTEM_PROMPT.md
|
|
56
|
+
CONVENTIONS.md
|
|
57
|
+
lenses/ logos.md, pathos.md, mythos.md
|
|
58
|
+
qualities/ play.md, beauty.md, story.md, infinity.md, mystery.md, love.md
|
|
59
|
+
antidotes/ liberated-agentism.md, constructive-optimism.md, ...
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## License
|
|
63
|
+
|
|
64
|
+
MIT
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { SearchResult, LintReport, LogEntry } from "../core/types.js";
|
|
2
|
+
export declare function formatHelp(): string;
|
|
3
|
+
export declare function formatSearchResults(results: SearchResult[]): string;
|
|
4
|
+
export declare function formatNodeList(entries: {
|
|
5
|
+
slug: string;
|
|
6
|
+
displayName: string;
|
|
7
|
+
type: string;
|
|
8
|
+
tags: string[];
|
|
9
|
+
}[]): string;
|
|
10
|
+
export declare function formatLintReport(report: LintReport): string;
|
|
11
|
+
export declare function formatLogEntries(entries: LogEntry[]): string;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
const BOLD = "\x1b[1m";
|
|
2
|
+
const DIM = "\x1b[2m";
|
|
3
|
+
const RESET = "\x1b[0m";
|
|
4
|
+
const GREEN = "\x1b[32m";
|
|
5
|
+
const YELLOW = "\x1b[33m";
|
|
6
|
+
const RED = "\x1b[31m";
|
|
7
|
+
const CYAN = "\x1b[36m";
|
|
8
|
+
export function formatHelp() {
|
|
9
|
+
return `
|
|
10
|
+
${BOLD}DynamicLLM${RESET} — perspective engine for general intelligence
|
|
11
|
+
|
|
12
|
+
${BOLD}USAGE${RESET}
|
|
13
|
+
dynamicllm <command> [options]
|
|
14
|
+
|
|
15
|
+
${BOLD}COMMANDS${RESET}
|
|
16
|
+
${CYAN}mcp${RESET} Start MCP server (for AI tool registration)
|
|
17
|
+
${CYAN}init${RESET} Interactive setup (Claude Code, Cursor, Windsurf, Codex)
|
|
18
|
+
${CYAN}search${RESET} <query> Search network by tags/names
|
|
19
|
+
${CYAN}load${RESET} <slug> Display a node's content
|
|
20
|
+
${CYAN}list${RESET} [--type=X] List all nodes
|
|
21
|
+
${CYAN}lint${RESET} Validate network health
|
|
22
|
+
${CYAN}ingest${RESET} Format content as v5-conformant node (interactive)
|
|
23
|
+
${CYAN}save${RESET} <slug> <file> Persist a node to local network
|
|
24
|
+
${CYAN}source-search${RESET} <query> Search within source works
|
|
25
|
+
${CYAN}source-excerpt${RESET} <slug> <q> Retrieve passage from a source
|
|
26
|
+
${CYAN}report${RESET} Generate styled analysis report
|
|
27
|
+
${CYAN}log${RESET} [--limit=N] View activity log
|
|
28
|
+
|
|
29
|
+
${BOLD}ENVIRONMENT${RESET}
|
|
30
|
+
DYNAMICLLM_NETWORK_DIR Path to user's network directory (default: ~/.dynamicllm/network/)
|
|
31
|
+
|
|
32
|
+
${BOLD}EXAMPLES${RESET}
|
|
33
|
+
${DIM}# Start MCP server for Claude Code${RESET}
|
|
34
|
+
dynamicllm mcp
|
|
35
|
+
|
|
36
|
+
${DIM}# Search for nodes about epistemology${RESET}
|
|
37
|
+
dynamicllm search epistemology
|
|
38
|
+
|
|
39
|
+
${DIM}# Load a specific thinker${RESET}
|
|
40
|
+
dynamicllm load david-deutsch
|
|
41
|
+
|
|
42
|
+
${DIM}# List all antidotes${RESET}
|
|
43
|
+
dynamicllm list --type=antidote
|
|
44
|
+
|
|
45
|
+
${DIM}# Run network health check${RESET}
|
|
46
|
+
dynamicllm lint
|
|
47
|
+
|
|
48
|
+
${DIM}# Register with Claude Code${RESET}
|
|
49
|
+
dynamicllm init
|
|
50
|
+
`;
|
|
51
|
+
}
|
|
52
|
+
export function formatSearchResults(results) {
|
|
53
|
+
if (results.length === 0)
|
|
54
|
+
return `${DIM}No results found.${RESET}`;
|
|
55
|
+
const lines = [`${BOLD}${results.length} result(s):${RESET}`, ""];
|
|
56
|
+
for (const r of results) {
|
|
57
|
+
const typeLabel = `[${r.type}]`.padEnd(12);
|
|
58
|
+
lines.push(` ${CYAN}${r.slug}${RESET} ${DIM}${typeLabel}${RESET} ${r.displayName} ${DIM}(score: ${r.score})${RESET}`);
|
|
59
|
+
if (r.tags.length > 0) {
|
|
60
|
+
lines.push(` ${DIM}tags: ${r.tags.slice(0, 8).join(", ")}${r.tags.length > 8 ? ", ..." : ""}${RESET}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return lines.join("\n");
|
|
64
|
+
}
|
|
65
|
+
export function formatNodeList(entries) {
|
|
66
|
+
if (entries.length === 0)
|
|
67
|
+
return `${DIM}No nodes found.${RESET}`;
|
|
68
|
+
const lines = [`${BOLD}${entries.length} node(s):${RESET}`, ""];
|
|
69
|
+
// Group by type
|
|
70
|
+
const byType = new Map();
|
|
71
|
+
for (const e of entries) {
|
|
72
|
+
const list = byType.get(e.type) ?? [];
|
|
73
|
+
list.push(e);
|
|
74
|
+
byType.set(e.type, list);
|
|
75
|
+
}
|
|
76
|
+
for (const [type, items] of byType) {
|
|
77
|
+
lines.push(` ${BOLD}${type}${RESET} (${items.length})`);
|
|
78
|
+
for (const item of items) {
|
|
79
|
+
lines.push(` ${CYAN}${item.slug}${RESET} ${DIM}${item.displayName}${RESET}`);
|
|
80
|
+
}
|
|
81
|
+
lines.push("");
|
|
82
|
+
}
|
|
83
|
+
return lines.join("\n");
|
|
84
|
+
}
|
|
85
|
+
export function formatLintReport(report) {
|
|
86
|
+
const lines = [];
|
|
87
|
+
lines.push(`${BOLD}Network Health Report${RESET}`);
|
|
88
|
+
lines.push(`${"─".repeat(40)}`);
|
|
89
|
+
lines.push(` Total nodes: ${report.stats.totalNodes}`);
|
|
90
|
+
for (const [type, count] of Object.entries(report.stats.byType)) {
|
|
91
|
+
lines.push(` ${type}: ${count}`);
|
|
92
|
+
}
|
|
93
|
+
lines.push("");
|
|
94
|
+
if (report.errors.length > 0) {
|
|
95
|
+
lines.push(`${RED}${BOLD}Errors (${report.errors.length}):${RESET}`);
|
|
96
|
+
for (const e of report.errors) {
|
|
97
|
+
lines.push(` ${RED}✗${RESET} ${CYAN}${e.slug}${RESET}: ${e.message}`);
|
|
98
|
+
}
|
|
99
|
+
lines.push("");
|
|
100
|
+
}
|
|
101
|
+
if (report.warnings.length > 0) {
|
|
102
|
+
lines.push(`${YELLOW}${BOLD}Warnings (${report.warnings.length}):${RESET}`);
|
|
103
|
+
for (const w of report.warnings) {
|
|
104
|
+
lines.push(` ${YELLOW}!${RESET} ${CYAN}${w.slug}${RESET}: ${w.message}`);
|
|
105
|
+
}
|
|
106
|
+
lines.push("");
|
|
107
|
+
}
|
|
108
|
+
if (report.tensions.length > 0) {
|
|
109
|
+
lines.push(`${GREEN}${BOLD}Productive Tensions (${report.tensions.length}):${RESET}`);
|
|
110
|
+
for (const t of report.tensions) {
|
|
111
|
+
lines.push(` ${GREEN}⟷${RESET} ${t.description}`);
|
|
112
|
+
lines.push(` ${DIM}shared: ${t.sharedTags.join(", ")}${RESET}`);
|
|
113
|
+
}
|
|
114
|
+
lines.push("");
|
|
115
|
+
}
|
|
116
|
+
if (report.stats.orphanTags.length > 0) {
|
|
117
|
+
lines.push(`${DIM}Orphan tags (used by only 1 node): ${report.stats.orphanTags.slice(0, 10).join(", ")}${report.stats.orphanTags.length > 10 ? `, ... (${report.stats.orphanTags.length} total)` : ""}${RESET}`);
|
|
118
|
+
}
|
|
119
|
+
const status = report.errors.length === 0
|
|
120
|
+
? `${GREEN}${BOLD}Network healthy${RESET}`
|
|
121
|
+
: `${RED}${BOLD}${report.errors.length} error(s) found${RESET}`;
|
|
122
|
+
lines.push("");
|
|
123
|
+
lines.push(status);
|
|
124
|
+
return lines.join("\n");
|
|
125
|
+
}
|
|
126
|
+
export function formatLogEntries(entries) {
|
|
127
|
+
if (entries.length === 0)
|
|
128
|
+
return `${DIM}No activity log entries.${RESET}`;
|
|
129
|
+
const lines = [`${BOLD}Activity Log (${entries.length} entries):${RESET}`, ""];
|
|
130
|
+
for (const e of entries) {
|
|
131
|
+
const modeStr = e.mode ? ` ${DIM}[${e.mode}]${RESET}` : "";
|
|
132
|
+
lines.push(` ${DIM}${e.date}${RESET} ${CYAN}${e.action}${RESET} ${e.slug}${modeStr}`);
|
|
133
|
+
if (e.summary) {
|
|
134
|
+
lines.push(` ${DIM}${e.summary}${RESET}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return lines.join("\n");
|
|
138
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runCli(args: string[]): Promise<void>;
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { NetworkFetcher, FRAMEWORK_SLUGS } from "../core/fetcher.js";
|
|
4
|
+
import { bootstrap } from "../core/bootstrap.js";
|
|
5
|
+
import { search } from "../core/search.js";
|
|
6
|
+
import { lint } from "../core/lint.js";
|
|
7
|
+
import { generateReport } from "../core/report.js";
|
|
8
|
+
import { appendLog, readLog } from "../core/log.js";
|
|
9
|
+
import { formatHelp, formatSearchResults, formatNodeList, formatLintReport, formatLogEntries, } from "./formatters.js";
|
|
10
|
+
function createFetcher(hint) {
|
|
11
|
+
const { networkDir, created } = bootstrap();
|
|
12
|
+
if (hint && created) {
|
|
13
|
+
process.stderr.write("DynamicLLM: created default config. Run `dynamicllm init` to customize.\n");
|
|
14
|
+
}
|
|
15
|
+
return new NetworkFetcher({ networkDir });
|
|
16
|
+
}
|
|
17
|
+
/** Parse --key=value flags from argv */
|
|
18
|
+
function parseFlags(args) {
|
|
19
|
+
const flags = {};
|
|
20
|
+
const positional = [];
|
|
21
|
+
for (const arg of args) {
|
|
22
|
+
if (arg.startsWith("--")) {
|
|
23
|
+
const eq = arg.indexOf("=");
|
|
24
|
+
if (eq > 0) {
|
|
25
|
+
flags[arg.slice(2, eq)] = arg.slice(eq + 1);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
flags[arg.slice(2)] = "true";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
positional.push(arg);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return { flags, positional };
|
|
36
|
+
}
|
|
37
|
+
export async function runCli(args) {
|
|
38
|
+
const command = args[0];
|
|
39
|
+
const restArgs = args.slice(1);
|
|
40
|
+
const { flags, positional } = parseFlags(restArgs);
|
|
41
|
+
switch (command) {
|
|
42
|
+
case "mcp": {
|
|
43
|
+
const { startServer } = await import("../mcp/server.js");
|
|
44
|
+
await startServer();
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
case "init": {
|
|
48
|
+
const { init } = await import("./init.js");
|
|
49
|
+
await init();
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
case "search": {
|
|
53
|
+
const query = positional.join(" ");
|
|
54
|
+
if (!query) {
|
|
55
|
+
console.error("Usage: dynamicllm search <query> [--type=X] [--limit=N]");
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
const fetcher = createFetcher(true);
|
|
59
|
+
const index = await fetcher.fetchIndex();
|
|
60
|
+
const types = flags.type ? [flags.type] : undefined;
|
|
61
|
+
const limit = flags.limit ? parseInt(flags.limit, 10) : 10;
|
|
62
|
+
const results = search(index, query, { types, limit });
|
|
63
|
+
console.log(formatSearchResults(results));
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
case "load": {
|
|
67
|
+
const slug = positional[0];
|
|
68
|
+
if (!slug) {
|
|
69
|
+
console.error("Usage: dynamicllm load <slug>");
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
const fetcher = createFetcher(true);
|
|
73
|
+
const index = await fetcher.fetchIndex();
|
|
74
|
+
const isNode = index.nodes.some((n) => n.slug === slug);
|
|
75
|
+
const isSource = index.sources.some((s) => s.slug === slug);
|
|
76
|
+
const isMeta = index.meta.some((m) => m.slug === slug);
|
|
77
|
+
let content;
|
|
78
|
+
if (isNode) {
|
|
79
|
+
content = await fetcher.fetchNode(slug);
|
|
80
|
+
}
|
|
81
|
+
else if (isSource) {
|
|
82
|
+
content = await fetcher.fetchSource(slug);
|
|
83
|
+
}
|
|
84
|
+
else if (isMeta) {
|
|
85
|
+
content = await fetcher.fetchMeta(slug);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
console.error(`Node '${slug}' not found in the DynamicLLM network.`);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
console.log(content);
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
case "list": {
|
|
95
|
+
const fetcher = createFetcher(true);
|
|
96
|
+
const index = await fetcher.fetchIndex();
|
|
97
|
+
let entries = [
|
|
98
|
+
...index.nodes.map((n) => ({
|
|
99
|
+
slug: n.slug,
|
|
100
|
+
displayName: n.displayName,
|
|
101
|
+
type: n.type,
|
|
102
|
+
tags: n.tags,
|
|
103
|
+
})),
|
|
104
|
+
...index.sources.map((s) => ({
|
|
105
|
+
slug: s.slug,
|
|
106
|
+
displayName: s.displayName,
|
|
107
|
+
type: s.type,
|
|
108
|
+
tags: s.tags,
|
|
109
|
+
})),
|
|
110
|
+
];
|
|
111
|
+
if (flags.type) {
|
|
112
|
+
entries = entries.filter((e) => e.type === flags.type);
|
|
113
|
+
}
|
|
114
|
+
console.log(formatNodeList(entries));
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
case "lint": {
|
|
118
|
+
const fetcher = createFetcher(true);
|
|
119
|
+
const index = await fetcher.fetchIndex();
|
|
120
|
+
const report = lint(index);
|
|
121
|
+
appendLog("lint", "network", { summary: `Errors: ${report.errors.length}, Warnings: ${report.warnings.length}` });
|
|
122
|
+
console.log(formatLintReport(report));
|
|
123
|
+
process.exit(report.errors.length > 0 ? 1 : 0);
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
case "ingest": {
|
|
127
|
+
// Interactive ingest — read from stdin or provided arguments
|
|
128
|
+
const type = flags.type;
|
|
129
|
+
const name = positional[0] || flags.name;
|
|
130
|
+
if (!type || !name) {
|
|
131
|
+
console.error("Usage: dynamicllm ingest <name> --type=<type> [--tags=a,b,c] [--mind_virus=X] [--derived_from=a,b]");
|
|
132
|
+
console.error(" Reads content from stdin.");
|
|
133
|
+
console.error(" Types: person, idea, antidote, lens, quality, source, synthesis");
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
// Read content from stdin
|
|
137
|
+
const chunks = [];
|
|
138
|
+
for await (const chunk of process.stdin) {
|
|
139
|
+
chunks.push(chunk);
|
|
140
|
+
}
|
|
141
|
+
const content = Buffer.concat(chunks).toString("utf-8").trim();
|
|
142
|
+
if (!content) {
|
|
143
|
+
console.error("No content provided on stdin.");
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
const slug = name
|
|
147
|
+
.toLowerCase()
|
|
148
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
149
|
+
.replace(/^-|-$/g, "");
|
|
150
|
+
const tags = flags.tags?.split(",").map((t) => t.trim()).filter(Boolean) ?? [];
|
|
151
|
+
let frontmatter = `---\ntype: ${type}\nname: ${name}\n`;
|
|
152
|
+
if (type === "antidote" && flags.mind_virus) {
|
|
153
|
+
frontmatter += `mind_virus: ${flags.mind_virus}\n`;
|
|
154
|
+
}
|
|
155
|
+
if (tags.length > 0) {
|
|
156
|
+
frontmatter += `tags: [${tags.join(", ")}]\n`;
|
|
157
|
+
}
|
|
158
|
+
if (type === "synthesis" && flags.derived_from) {
|
|
159
|
+
const derived = flags.derived_from.split(",").map((s) => s.trim()).filter(Boolean);
|
|
160
|
+
frontmatter += `derived_from: [${derived.join(", ")}]\n`;
|
|
161
|
+
}
|
|
162
|
+
frontmatter += "---\n\n";
|
|
163
|
+
const output = `${frontmatter}# ${name}\n\n${content}`;
|
|
164
|
+
appendLog("ingest", slug, { summary: `Type: ${type}` });
|
|
165
|
+
console.log(output);
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
case "save": {
|
|
169
|
+
const slug = positional[0];
|
|
170
|
+
const filePath = positional[1];
|
|
171
|
+
if (!slug || !filePath) {
|
|
172
|
+
console.error("Usage: dynamicllm save <slug> <file>");
|
|
173
|
+
console.error(" Or pipe content: echo '...' | dynamicllm save <slug> --stdin");
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
const { networkDir, created } = bootstrap();
|
|
177
|
+
if (created) {
|
|
178
|
+
process.stderr.write("DynamicLLM: created default config. Run `dynamicllm init` to customize.\n");
|
|
179
|
+
}
|
|
180
|
+
if (FRAMEWORK_SLUGS.has(slug)) {
|
|
181
|
+
console.error(`Error: "${slug}" is a protected framework slug and cannot be overridden.`);
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
const { mkdirSync, writeFileSync } = await import("node:fs");
|
|
185
|
+
if (!existsSync(networkDir)) {
|
|
186
|
+
mkdirSync(networkDir, { recursive: true });
|
|
187
|
+
}
|
|
188
|
+
let content;
|
|
189
|
+
if (flags.stdin === "true") {
|
|
190
|
+
const chunks = [];
|
|
191
|
+
for await (const chunk of process.stdin) {
|
|
192
|
+
chunks.push(chunk);
|
|
193
|
+
}
|
|
194
|
+
content = Buffer.concat(chunks).toString("utf-8");
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
if (!existsSync(filePath)) {
|
|
198
|
+
console.error(`File not found: ${filePath}`);
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
content = readFileSync(filePath, "utf-8");
|
|
202
|
+
}
|
|
203
|
+
const savePath = join(networkDir, `${slug}.md`);
|
|
204
|
+
writeFileSync(savePath, content, "utf-8");
|
|
205
|
+
appendLog("save", slug, { summary: `Saved: ${slug}.md` });
|
|
206
|
+
console.log(`Saved ${slug}.md to ${savePath}`);
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
case "source-search": {
|
|
210
|
+
const query = positional.join(" ");
|
|
211
|
+
if (!query) {
|
|
212
|
+
console.error("Usage: dynamicllm source-search <query> [--source=slug] [--limit=N]");
|
|
213
|
+
process.exit(1);
|
|
214
|
+
}
|
|
215
|
+
const fetcher = createFetcher(true);
|
|
216
|
+
const index = await fetcher.fetchIndex();
|
|
217
|
+
if (index.sources.length === 0) {
|
|
218
|
+
console.log("No source works are available in the network yet.");
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
const sources = flags.source
|
|
222
|
+
? index.sources.filter((s) => s.slug === flags.source)
|
|
223
|
+
: index.sources;
|
|
224
|
+
const queryLower = query.toLowerCase();
|
|
225
|
+
const maxResults = flags.limit ? parseInt(flags.limit, 10) : 5;
|
|
226
|
+
const results = [];
|
|
227
|
+
for (const source of sources) {
|
|
228
|
+
if (results.length >= maxResults)
|
|
229
|
+
break;
|
|
230
|
+
const content = await fetcher.fetchSource(source.slug);
|
|
231
|
+
const lines = content.split("\n");
|
|
232
|
+
for (let i = 0; i < lines.length; i++) {
|
|
233
|
+
if (results.length >= maxResults)
|
|
234
|
+
break;
|
|
235
|
+
if (lines[i].toLowerCase().includes(queryLower)) {
|
|
236
|
+
const start = Math.max(0, i - 2);
|
|
237
|
+
const end = Math.min(lines.length, i + 3);
|
|
238
|
+
results.push({
|
|
239
|
+
source: source.displayName,
|
|
240
|
+
excerpt: lines.slice(start, end).join("\n"),
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (results.length === 0) {
|
|
246
|
+
console.log(`No matches found for "${query}" in source works.`);
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
for (const r of results) {
|
|
250
|
+
console.log(`\n\x1b[1m${r.source}\x1b[0m`);
|
|
251
|
+
console.log(r.excerpt);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
case "source-excerpt": {
|
|
257
|
+
const sourceSlug = positional[0];
|
|
258
|
+
const query = positional.slice(1).join(" ");
|
|
259
|
+
if (!sourceSlug || !query) {
|
|
260
|
+
console.error("Usage: dynamicllm source-excerpt <slug> <query>");
|
|
261
|
+
process.exit(1);
|
|
262
|
+
}
|
|
263
|
+
const fetcher = createFetcher(true);
|
|
264
|
+
const index = await fetcher.fetchIndex();
|
|
265
|
+
const source = index.sources.find((s) => s.slug === sourceSlug);
|
|
266
|
+
if (!source) {
|
|
267
|
+
console.error(`Source '${sourceSlug}' not found.`);
|
|
268
|
+
process.exit(1);
|
|
269
|
+
}
|
|
270
|
+
const content = await fetcher.fetchSource(sourceSlug);
|
|
271
|
+
const lines = content.split("\n");
|
|
272
|
+
const queryLower = query.toLowerCase();
|
|
273
|
+
let found = false;
|
|
274
|
+
for (let i = 0; i < lines.length; i++) {
|
|
275
|
+
if (lines[i].toLowerCase().includes(queryLower)) {
|
|
276
|
+
const start = Math.max(0, i - 5);
|
|
277
|
+
const end = Math.min(lines.length, i + 10);
|
|
278
|
+
console.log(`\n\x1b[1m${source.displayName}\x1b[0m (around line ${i + 1}):\n`);
|
|
279
|
+
console.log(lines.slice(start, end).join("\n"));
|
|
280
|
+
found = true;
|
|
281
|
+
break;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
if (!found) {
|
|
285
|
+
console.log(`No passage matching "${query}" found in ${source.displayName}.`);
|
|
286
|
+
}
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
case "report": {
|
|
290
|
+
const mode = flags.mode;
|
|
291
|
+
if (!mode) {
|
|
292
|
+
console.error("Usage: dynamicllm report --mode=<virus-scan|lens|quality>");
|
|
293
|
+
console.error(" Reads analysis text from stdin.");
|
|
294
|
+
process.exit(1);
|
|
295
|
+
}
|
|
296
|
+
const chunks = [];
|
|
297
|
+
for await (const chunk of process.stdin) {
|
|
298
|
+
chunks.push(chunk);
|
|
299
|
+
}
|
|
300
|
+
const analysisText = Buffer.concat(chunks).toString("utf-8").trim();
|
|
301
|
+
if (!analysisText) {
|
|
302
|
+
console.error("No analysis text provided on stdin.");
|
|
303
|
+
process.exit(1);
|
|
304
|
+
}
|
|
305
|
+
// Parse scores from --scores flag if provided (JSON string)
|
|
306
|
+
let scores;
|
|
307
|
+
if (flags.scores) {
|
|
308
|
+
try {
|
|
309
|
+
scores = JSON.parse(flags.scores);
|
|
310
|
+
}
|
|
311
|
+
catch {
|
|
312
|
+
console.error("Invalid --scores JSON.");
|
|
313
|
+
process.exit(1);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
const result = generateReport({
|
|
317
|
+
mode,
|
|
318
|
+
analysisText,
|
|
319
|
+
contentLabel: flags.label,
|
|
320
|
+
quality: flags.quality,
|
|
321
|
+
scores,
|
|
322
|
+
});
|
|
323
|
+
appendLog("report", mode, { summary: `Report: ${result.filePath}` });
|
|
324
|
+
console.log(`Report saved to: ${result.filePath}`);
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
case "log": {
|
|
328
|
+
const limit = flags.limit ? parseInt(flags.limit, 10) : 20;
|
|
329
|
+
const action = flags.action;
|
|
330
|
+
const entries = readLog({ limit, action });
|
|
331
|
+
console.log(formatLogEntries(entries));
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
334
|
+
case "help":
|
|
335
|
+
case "--help":
|
|
336
|
+
case "-h":
|
|
337
|
+
case undefined:
|
|
338
|
+
console.log(formatHelp());
|
|
339
|
+
break;
|
|
340
|
+
default:
|
|
341
|
+
console.error(`Unknown command: ${command}`);
|
|
342
|
+
console.error("Run 'dynamicllm help' for available commands.");
|
|
343
|
+
process.exit(1);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function init(): Promise<void>;
|