chainlesschain 0.37.9 → 0.37.11
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 +309 -19
- package/bin/chainlesschain.js +4 -0
- package/package.json +1 -1
- package/src/commands/a2a.js +374 -0
- package/src/commands/audit.js +286 -0
- package/src/commands/auth.js +387 -0
- package/src/commands/bi.js +240 -0
- package/src/commands/browse.js +184 -0
- package/src/commands/cowork.js +317 -0
- package/src/commands/did.js +376 -0
- package/src/commands/economy.js +375 -0
- package/src/commands/encrypt.js +233 -0
- package/src/commands/evolution.js +398 -0
- package/src/commands/export.js +125 -0
- package/src/commands/git.js +215 -0
- package/src/commands/hmemory.js +273 -0
- package/src/commands/hook.js +260 -0
- package/src/commands/import.js +259 -0
- package/src/commands/init.js +184 -0
- package/src/commands/instinct.js +202 -0
- package/src/commands/llm.js +155 -4
- package/src/commands/lowcode.js +320 -0
- package/src/commands/mcp.js +302 -0
- package/src/commands/memory.js +282 -0
- package/src/commands/note.js +187 -0
- package/src/commands/org.js +505 -0
- package/src/commands/p2p.js +274 -0
- package/src/commands/plugin.js +451 -0
- package/src/commands/sandbox.js +366 -0
- package/src/commands/search.js +237 -0
- package/src/commands/session.js +238 -0
- package/src/commands/skill.js +254 -201
- package/src/commands/sync.js +249 -0
- package/src/commands/tokens.js +214 -0
- package/src/commands/wallet.js +416 -0
- package/src/commands/workflow.js +359 -0
- package/src/commands/zkp.js +277 -0
- package/src/index.js +93 -1
- package/src/lib/a2a-protocol.js +371 -0
- package/src/lib/agent-coordinator.js +273 -0
- package/src/lib/agent-economy.js +369 -0
- package/src/lib/app-builder.js +377 -0
- package/src/lib/audit-logger.js +364 -0
- package/src/lib/bi-engine.js +299 -0
- package/src/lib/bm25-search.js +322 -0
- package/src/lib/browser-automation.js +216 -0
- package/src/lib/cowork/ab-comparator-cli.js +180 -0
- package/src/lib/cowork/code-knowledge-graph-cli.js +232 -0
- package/src/lib/cowork/debate-review-cli.js +144 -0
- package/src/lib/cowork/decision-kb-cli.js +153 -0
- package/src/lib/cowork/project-style-analyzer-cli.js +168 -0
- package/src/lib/cowork-adapter.js +106 -0
- package/src/lib/crypto-manager.js +246 -0
- package/src/lib/did-manager.js +270 -0
- package/src/lib/ensure-utf8.js +59 -0
- package/src/lib/evolution-system.js +508 -0
- package/src/lib/git-integration.js +220 -0
- package/src/lib/hierarchical-memory.js +471 -0
- package/src/lib/hook-manager.js +387 -0
- package/src/lib/instinct-manager.js +190 -0
- package/src/lib/knowledge-exporter.js +302 -0
- package/src/lib/knowledge-importer.js +293 -0
- package/src/lib/llm-providers.js +325 -0
- package/src/lib/mcp-client.js +413 -0
- package/src/lib/memory-manager.js +211 -0
- package/src/lib/note-versioning.js +244 -0
- package/src/lib/org-manager.js +424 -0
- package/src/lib/p2p-manager.js +317 -0
- package/src/lib/pdf-parser.js +96 -0
- package/src/lib/permission-engine.js +374 -0
- package/src/lib/plan-mode.js +333 -0
- package/src/lib/plugin-manager.js +430 -0
- package/src/lib/project-detector.js +53 -0
- package/src/lib/response-cache.js +156 -0
- package/src/lib/sandbox-v2.js +503 -0
- package/src/lib/service-container.js +183 -0
- package/src/lib/session-manager.js +189 -0
- package/src/lib/skill-loader.js +274 -0
- package/src/lib/sync-manager.js +347 -0
- package/src/lib/token-tracker.js +200 -0
- package/src/lib/wallet-manager.js +348 -0
- package/src/lib/workflow-engine.js +503 -0
- package/src/lib/zkp-engine.js +241 -0
- package/src/repl/agent-repl.js +259 -124
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook management commands
|
|
3
|
+
* chainlesschain hook list|add|remove|run|stats|events
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
import { logger } from "../lib/logger.js";
|
|
8
|
+
import { bootstrap, shutdown } from "../runtime/bootstrap.js";
|
|
9
|
+
import {
|
|
10
|
+
HookPriority,
|
|
11
|
+
HookType,
|
|
12
|
+
HookEvents,
|
|
13
|
+
registerHook,
|
|
14
|
+
unregisterHook,
|
|
15
|
+
listHooks,
|
|
16
|
+
executeHooks,
|
|
17
|
+
getHookStats,
|
|
18
|
+
} from "../lib/hook-manager.js";
|
|
19
|
+
|
|
20
|
+
export function registerHookCommand(program) {
|
|
21
|
+
const hook = program.command("hook").description("Lifecycle hook management");
|
|
22
|
+
|
|
23
|
+
// hook list
|
|
24
|
+
hook
|
|
25
|
+
.command("list", { isDefault: true })
|
|
26
|
+
.description("List all registered hooks")
|
|
27
|
+
.option("--event <name>", "Filter by event name")
|
|
28
|
+
.option("--enabled", "Show only enabled hooks")
|
|
29
|
+
.option("--json", "Output as JSON")
|
|
30
|
+
.action(async (options) => {
|
|
31
|
+
try {
|
|
32
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
33
|
+
if (!ctx.db) {
|
|
34
|
+
logger.error("Database not available");
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
const db = ctx.db.getDatabase();
|
|
38
|
+
const hooks = listHooks(db, {
|
|
39
|
+
event: options.event,
|
|
40
|
+
enabledOnly: options.enabled,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
if (options.json) {
|
|
44
|
+
console.log(
|
|
45
|
+
JSON.stringify(
|
|
46
|
+
hooks.map((h) => ({
|
|
47
|
+
id: h.id,
|
|
48
|
+
event: h.event,
|
|
49
|
+
name: h.name,
|
|
50
|
+
type: h.type,
|
|
51
|
+
priority: h.priority,
|
|
52
|
+
enabled: h.enabled === 1,
|
|
53
|
+
matcher: h.matcher,
|
|
54
|
+
description: h.description,
|
|
55
|
+
})),
|
|
56
|
+
null,
|
|
57
|
+
2,
|
|
58
|
+
),
|
|
59
|
+
);
|
|
60
|
+
} else if (hooks.length === 0) {
|
|
61
|
+
logger.info(
|
|
62
|
+
'No hooks registered. Add one with "chainlesschain hook add <event> <name>"',
|
|
63
|
+
);
|
|
64
|
+
} else {
|
|
65
|
+
logger.log(chalk.bold(`Hooks (${hooks.length}):\n`));
|
|
66
|
+
for (const h of hooks) {
|
|
67
|
+
const status = h.enabled
|
|
68
|
+
? chalk.green("enabled")
|
|
69
|
+
: chalk.gray("disabled");
|
|
70
|
+
const pLabel = Object.entries(HookPriority).find(
|
|
71
|
+
([, v]) => v === h.priority,
|
|
72
|
+
);
|
|
73
|
+
const priorityStr = pLabel ? pLabel[0] : String(h.priority);
|
|
74
|
+
logger.log(
|
|
75
|
+
` ${chalk.cyan(h.name)} [${h.event}] priority=${priorityStr} type=${h.type} [${status}]`,
|
|
76
|
+
);
|
|
77
|
+
if (h.description) logger.log(` ${chalk.gray(h.description)}`);
|
|
78
|
+
if (h.matcher)
|
|
79
|
+
logger.log(` matcher: ${chalk.yellow(h.matcher)}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
await shutdown();
|
|
84
|
+
} catch (err) {
|
|
85
|
+
logger.error(`Failed: ${err.message}`);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// hook add
|
|
91
|
+
hook
|
|
92
|
+
.command("add")
|
|
93
|
+
.description("Register a new hook")
|
|
94
|
+
.argument("<event>", "Event name (e.g. PreIPCCall, PostToolUse)")
|
|
95
|
+
.argument("<name>", "Hook name")
|
|
96
|
+
.option("--type <type>", "Hook type (sync, async, command, script)", "sync")
|
|
97
|
+
.option(
|
|
98
|
+
"--priority <n>",
|
|
99
|
+
"Priority (0=system, 100=high, 500=normal, 900=low, 1000=monitor)",
|
|
100
|
+
"500",
|
|
101
|
+
)
|
|
102
|
+
.option(
|
|
103
|
+
"--command <cmd>",
|
|
104
|
+
"Shell command to execute (for command/script type)",
|
|
105
|
+
)
|
|
106
|
+
.option(
|
|
107
|
+
"--matcher <pattern>",
|
|
108
|
+
"Matcher pattern (wildcards, pipe-separated, or /regex/)",
|
|
109
|
+
)
|
|
110
|
+
.option("--description <desc>", "Hook description")
|
|
111
|
+
.action(async (event, name, options) => {
|
|
112
|
+
try {
|
|
113
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
114
|
+
if (!ctx.db) {
|
|
115
|
+
logger.error("Database not available");
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
const db = ctx.db.getDatabase();
|
|
119
|
+
|
|
120
|
+
const result = registerHook(db, {
|
|
121
|
+
event,
|
|
122
|
+
name,
|
|
123
|
+
type: options.type,
|
|
124
|
+
priority: parseInt(options.priority, 10),
|
|
125
|
+
handler: options.command || null,
|
|
126
|
+
matcher: options.matcher || null,
|
|
127
|
+
description: options.description || null,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
logger.success(
|
|
131
|
+
`Hook registered: ${result.name} [${result.event}] (id: ${result.id})`,
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
await shutdown();
|
|
135
|
+
} catch (err) {
|
|
136
|
+
logger.error(`Failed: ${err.message}`);
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// hook remove
|
|
142
|
+
hook
|
|
143
|
+
.command("remove")
|
|
144
|
+
.description("Remove a hook by ID")
|
|
145
|
+
.argument("<id>", "Hook ID")
|
|
146
|
+
.action(async (id) => {
|
|
147
|
+
try {
|
|
148
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
149
|
+
if (!ctx.db) {
|
|
150
|
+
logger.error("Database not available");
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
const db = ctx.db.getDatabase();
|
|
154
|
+
const ok = unregisterHook(db, id);
|
|
155
|
+
|
|
156
|
+
if (ok) {
|
|
157
|
+
logger.success(`Hook removed: ${id}`);
|
|
158
|
+
} else {
|
|
159
|
+
logger.error(`Hook not found: ${id}`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
await shutdown();
|
|
163
|
+
} catch (err) {
|
|
164
|
+
logger.error(`Failed: ${err.message}`);
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// hook run
|
|
170
|
+
hook
|
|
171
|
+
.command("run")
|
|
172
|
+
.description("Manually trigger hooks for an event")
|
|
173
|
+
.argument("<event>", "Event name to trigger")
|
|
174
|
+
.option("--context <json>", "JSON context to pass to hooks", "{}")
|
|
175
|
+
.action(async (event, options) => {
|
|
176
|
+
try {
|
|
177
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
178
|
+
if (!ctx.db) {
|
|
179
|
+
logger.error("Database not available");
|
|
180
|
+
process.exit(1);
|
|
181
|
+
}
|
|
182
|
+
const db = ctx.db.getDatabase();
|
|
183
|
+
|
|
184
|
+
let context = {};
|
|
185
|
+
try {
|
|
186
|
+
context = JSON.parse(options.context);
|
|
187
|
+
} catch (_err) {
|
|
188
|
+
logger.warn("Invalid JSON context, using empty object");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
logger.info(`Triggering hooks for event: ${event}`);
|
|
192
|
+
const results = await executeHooks(db, event, context);
|
|
193
|
+
|
|
194
|
+
if (results.length === 0) {
|
|
195
|
+
logger.info("No hooks matched this event");
|
|
196
|
+
} else {
|
|
197
|
+
for (const r of results) {
|
|
198
|
+
const icon = r.success ? chalk.green("OK") : chalk.red("FAIL");
|
|
199
|
+
logger.log(` [${icon}] ${r.hookName} (${r.executionTime}ms)`);
|
|
200
|
+
if (r.error) logger.log(` ${chalk.red(r.error)}`);
|
|
201
|
+
if (r.result) logger.log(` ${chalk.gray(r.result)}`);
|
|
202
|
+
}
|
|
203
|
+
logger.info(`Executed ${results.length} hook(s)`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
await shutdown();
|
|
207
|
+
} catch (err) {
|
|
208
|
+
logger.error(`Failed: ${err.message}`);
|
|
209
|
+
process.exit(1);
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// hook stats
|
|
214
|
+
hook
|
|
215
|
+
.command("stats")
|
|
216
|
+
.description("Show hook execution statistics")
|
|
217
|
+
.option("--json", "Output as JSON")
|
|
218
|
+
.action(async (options) => {
|
|
219
|
+
try {
|
|
220
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
221
|
+
if (!ctx.db) {
|
|
222
|
+
logger.error("Database not available");
|
|
223
|
+
process.exit(1);
|
|
224
|
+
}
|
|
225
|
+
const db = ctx.db.getDatabase();
|
|
226
|
+
const stats = getHookStats(db);
|
|
227
|
+
|
|
228
|
+
if (options.json) {
|
|
229
|
+
console.log(JSON.stringify(stats, null, 2));
|
|
230
|
+
} else if (stats.length === 0) {
|
|
231
|
+
logger.info("No hooks registered");
|
|
232
|
+
} else {
|
|
233
|
+
logger.log(chalk.bold("Hook Statistics:\n"));
|
|
234
|
+
for (const s of stats) {
|
|
235
|
+
logger.log(` ${chalk.cyan(s.name)} [${s.event}]`);
|
|
236
|
+
logger.log(
|
|
237
|
+
` executions: ${s.executionCount} errors: ${s.errorCount} avg: ${s.avgExecutionTime}ms`,
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
await shutdown();
|
|
243
|
+
} catch (err) {
|
|
244
|
+
logger.error(`Failed: ${err.message}`);
|
|
245
|
+
process.exit(1);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// hook events
|
|
250
|
+
hook
|
|
251
|
+
.command("events")
|
|
252
|
+
.description("List all valid hook event types")
|
|
253
|
+
.action(() => {
|
|
254
|
+
const events = Object.values(HookEvents);
|
|
255
|
+
logger.log(chalk.bold(`Hook Events (${events.length}):\n`));
|
|
256
|
+
for (const ev of events) {
|
|
257
|
+
logger.log(` ${chalk.cyan(ev)}`);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Knowledge import commands
|
|
3
|
+
* chainlesschain import markdown|evernote|notion|pdf <path>
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
import ora from "ora";
|
|
8
|
+
import { existsSync, statSync } from "fs";
|
|
9
|
+
import { resolve } from "path";
|
|
10
|
+
import { logger } from "../lib/logger.js";
|
|
11
|
+
import { bootstrap, shutdown } from "../runtime/bootstrap.js";
|
|
12
|
+
import {
|
|
13
|
+
importMarkdownDir,
|
|
14
|
+
importEnexFile,
|
|
15
|
+
importNotionDir,
|
|
16
|
+
} from "../lib/knowledge-importer.js";
|
|
17
|
+
|
|
18
|
+
export function registerImportCommand(program) {
|
|
19
|
+
const imp = program
|
|
20
|
+
.command("import")
|
|
21
|
+
.description("Import knowledge from external sources");
|
|
22
|
+
|
|
23
|
+
// import markdown
|
|
24
|
+
imp
|
|
25
|
+
.command("markdown")
|
|
26
|
+
.description("Import markdown files from a directory")
|
|
27
|
+
.argument("<dir>", "Directory containing .md files")
|
|
28
|
+
.option("--json", "Output as JSON")
|
|
29
|
+
.action(async (dir, options) => {
|
|
30
|
+
try {
|
|
31
|
+
const absDir = resolve(dir);
|
|
32
|
+
if (!existsSync(absDir) || !statSync(absDir).isDirectory()) {
|
|
33
|
+
logger.error(`Directory not found: ${absDir}`);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const spinner = ora("Importing markdown files...").start();
|
|
38
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
39
|
+
if (!ctx.db) {
|
|
40
|
+
spinner.fail("Database not available");
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const db = ctx.db.getDatabase();
|
|
45
|
+
const imported = importMarkdownDir(db, absDir);
|
|
46
|
+
spinner.stop();
|
|
47
|
+
|
|
48
|
+
if (options.json) {
|
|
49
|
+
console.log(
|
|
50
|
+
JSON.stringify(
|
|
51
|
+
{ count: imported.length, notes: imported },
|
|
52
|
+
null,
|
|
53
|
+
2,
|
|
54
|
+
),
|
|
55
|
+
);
|
|
56
|
+
} else if (imported.length === 0) {
|
|
57
|
+
logger.info("No .md files found in the directory");
|
|
58
|
+
} else {
|
|
59
|
+
logger.success(
|
|
60
|
+
`Imported ${chalk.cyan(imported.length)} markdown notes`,
|
|
61
|
+
);
|
|
62
|
+
for (const n of imported.slice(0, 10)) {
|
|
63
|
+
logger.log(
|
|
64
|
+
` ${chalk.gray(n.id.slice(0, 8))} ${chalk.white(n.title)} ${chalk.gray(n.source || "")}`,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
if (imported.length > 10) {
|
|
68
|
+
logger.log(chalk.gray(` ... and ${imported.length - 10} more`));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
await shutdown();
|
|
73
|
+
} catch (err) {
|
|
74
|
+
logger.error(`Import failed: ${err.message}`);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// import evernote
|
|
80
|
+
imp
|
|
81
|
+
.command("evernote")
|
|
82
|
+
.description("Import from Evernote ENEX export file")
|
|
83
|
+
.argument("<file>", "Path to .enex file")
|
|
84
|
+
.option("--json", "Output as JSON")
|
|
85
|
+
.action(async (file, options) => {
|
|
86
|
+
try {
|
|
87
|
+
const absFile = resolve(file);
|
|
88
|
+
if (!existsSync(absFile)) {
|
|
89
|
+
logger.error(`File not found: ${absFile}`);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const spinner = ora("Importing Evernote notes...").start();
|
|
94
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
95
|
+
if (!ctx.db) {
|
|
96
|
+
spinner.fail("Database not available");
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const db = ctx.db.getDatabase();
|
|
101
|
+
const imported = importEnexFile(db, absFile);
|
|
102
|
+
spinner.stop();
|
|
103
|
+
|
|
104
|
+
if (options.json) {
|
|
105
|
+
console.log(
|
|
106
|
+
JSON.stringify(
|
|
107
|
+
{ count: imported.length, notes: imported },
|
|
108
|
+
null,
|
|
109
|
+
2,
|
|
110
|
+
),
|
|
111
|
+
);
|
|
112
|
+
} else if (imported.length === 0) {
|
|
113
|
+
logger.info("No notes found in the ENEX file");
|
|
114
|
+
} else {
|
|
115
|
+
logger.success(
|
|
116
|
+
`Imported ${chalk.cyan(imported.length)} Evernote notes`,
|
|
117
|
+
);
|
|
118
|
+
for (const n of imported.slice(0, 10)) {
|
|
119
|
+
const tags =
|
|
120
|
+
n.tags.length > 0 ? chalk.gray(` [${n.tags.join(", ")}]`) : "";
|
|
121
|
+
logger.log(
|
|
122
|
+
` ${chalk.gray(n.id.slice(0, 8))} ${chalk.white(n.title)}${tags}`,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
if (imported.length > 10) {
|
|
126
|
+
logger.log(chalk.gray(` ... and ${imported.length - 10} more`));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
await shutdown();
|
|
131
|
+
} catch (err) {
|
|
132
|
+
logger.error(`Import failed: ${err.message}`);
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// import notion
|
|
138
|
+
imp
|
|
139
|
+
.command("notion")
|
|
140
|
+
.description("Import from Notion export directory")
|
|
141
|
+
.argument("<dir>", "Notion export directory")
|
|
142
|
+
.option("--json", "Output as JSON")
|
|
143
|
+
.action(async (dir, options) => {
|
|
144
|
+
try {
|
|
145
|
+
const absDir = resolve(dir);
|
|
146
|
+
if (!existsSync(absDir) || !statSync(absDir).isDirectory()) {
|
|
147
|
+
logger.error(`Directory not found: ${absDir}`);
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const spinner = ora("Importing Notion pages...").start();
|
|
152
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
153
|
+
if (!ctx.db) {
|
|
154
|
+
spinner.fail("Database not available");
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const db = ctx.db.getDatabase();
|
|
159
|
+
const imported = importNotionDir(db, absDir);
|
|
160
|
+
spinner.stop();
|
|
161
|
+
|
|
162
|
+
if (options.json) {
|
|
163
|
+
console.log(
|
|
164
|
+
JSON.stringify(
|
|
165
|
+
{ count: imported.length, notes: imported },
|
|
166
|
+
null,
|
|
167
|
+
2,
|
|
168
|
+
),
|
|
169
|
+
);
|
|
170
|
+
} else if (imported.length === 0) {
|
|
171
|
+
logger.info("No markdown pages found in Notion export");
|
|
172
|
+
} else {
|
|
173
|
+
logger.success(
|
|
174
|
+
`Imported ${chalk.cyan(imported.length)} Notion pages`,
|
|
175
|
+
);
|
|
176
|
+
for (const n of imported.slice(0, 10)) {
|
|
177
|
+
const tags =
|
|
178
|
+
n.tags.length > 0 ? chalk.gray(` [${n.tags.join(", ")}]`) : "";
|
|
179
|
+
logger.log(
|
|
180
|
+
` ${chalk.gray(n.id.slice(0, 8))} ${chalk.white(n.title)}${tags}`,
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
if (imported.length > 10) {
|
|
184
|
+
logger.log(chalk.gray(` ... and ${imported.length - 10} more`));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
await shutdown();
|
|
189
|
+
} catch (err) {
|
|
190
|
+
logger.error(`Import failed: ${err.message}`);
|
|
191
|
+
process.exit(1);
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// import pdf
|
|
196
|
+
imp
|
|
197
|
+
.command("pdf")
|
|
198
|
+
.description("Import text from a PDF file")
|
|
199
|
+
.argument("<file>", "Path to .pdf file")
|
|
200
|
+
.option("--json", "Output as JSON")
|
|
201
|
+
.action(async (file, options) => {
|
|
202
|
+
try {
|
|
203
|
+
const absFile = resolve(file);
|
|
204
|
+
if (!existsSync(absFile)) {
|
|
205
|
+
logger.error(`File not found: ${absFile}`);
|
|
206
|
+
process.exit(1);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const spinner = ora("Extracting text from PDF...").start();
|
|
210
|
+
const ctx = await bootstrap({ verbose: program.opts().verbose });
|
|
211
|
+
if (!ctx.db) {
|
|
212
|
+
spinner.fail("Database not available");
|
|
213
|
+
process.exit(1);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const db = ctx.db.getDatabase();
|
|
217
|
+
|
|
218
|
+
// Lazy-load pdf-parser to keep it optional
|
|
219
|
+
const { parsePdfText } = await import("../lib/pdf-parser.js");
|
|
220
|
+
const { insertNote, ensureNotesTable } =
|
|
221
|
+
await import("../lib/knowledge-importer.js");
|
|
222
|
+
ensureNotesTable(db);
|
|
223
|
+
|
|
224
|
+
const parsed = await parsePdfText(absFile);
|
|
225
|
+
spinner.stop();
|
|
226
|
+
|
|
227
|
+
if (!parsed.content || parsed.content.trim().length === 0) {
|
|
228
|
+
logger.info("No text could be extracted from the PDF");
|
|
229
|
+
await shutdown();
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const note = insertNote(db, {
|
|
234
|
+
title: parsed.title,
|
|
235
|
+
content: parsed.content,
|
|
236
|
+
tags: ["pdf"],
|
|
237
|
+
category: "pdf",
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
if (options.json) {
|
|
241
|
+
console.log(JSON.stringify(note, null, 2));
|
|
242
|
+
} else {
|
|
243
|
+
logger.success(`Imported PDF as note: ${chalk.cyan(parsed.title)}`);
|
|
244
|
+
logger.log(` ${chalk.gray("ID:")} ${note.id.slice(0, 8)}`);
|
|
245
|
+
logger.log(
|
|
246
|
+
` ${chalk.gray("Length:")} ${parsed.content.length} chars`,
|
|
247
|
+
);
|
|
248
|
+
if (parsed.pages) {
|
|
249
|
+
logger.log(` ${chalk.gray("Pages:")} ${parsed.pages}`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
await shutdown();
|
|
254
|
+
} catch (err) {
|
|
255
|
+
logger.error(`PDF import failed: ${err.message}`);
|
|
256
|
+
process.exit(1);
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project initialization command
|
|
3
|
+
* chainlesschain init [--template <name>] [--yes] [--bare]
|
|
4
|
+
*
|
|
5
|
+
* Creates .chainlesschain/ project structure in the current directory.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import chalk from "chalk";
|
|
9
|
+
import fs from "fs";
|
|
10
|
+
import path from "path";
|
|
11
|
+
import { logger } from "../lib/logger.js";
|
|
12
|
+
import { isInsideProject, findProjectRoot } from "../lib/project-detector.js";
|
|
13
|
+
|
|
14
|
+
const TEMPLATES = {
|
|
15
|
+
"code-project": {
|
|
16
|
+
description:
|
|
17
|
+
"Software development project with code review and refactoring skills",
|
|
18
|
+
rules: `# Project Rules
|
|
19
|
+
|
|
20
|
+
## Code Style
|
|
21
|
+
- Follow the project's existing code style
|
|
22
|
+
- Use meaningful variable and function names
|
|
23
|
+
- Keep functions small and focused
|
|
24
|
+
|
|
25
|
+
## AI Assistant Guidelines
|
|
26
|
+
- Prefer editing existing files over creating new ones
|
|
27
|
+
- Run tests after making changes
|
|
28
|
+
- Use code-review skill before committing
|
|
29
|
+
`,
|
|
30
|
+
skills: ["code-review", "refactor", "unit-test", "debug"],
|
|
31
|
+
},
|
|
32
|
+
"data-science": {
|
|
33
|
+
description:
|
|
34
|
+
"Data science / ML project with analysis and visualization skills",
|
|
35
|
+
rules: `# Project Rules
|
|
36
|
+
|
|
37
|
+
## Data Handling
|
|
38
|
+
- Never commit raw data files
|
|
39
|
+
- Document data transformations
|
|
40
|
+
- Use reproducible random seeds
|
|
41
|
+
|
|
42
|
+
## AI Assistant Guidelines
|
|
43
|
+
- Use data-analysis skill for exploration
|
|
44
|
+
- Document findings in markdown
|
|
45
|
+
- Validate results before reporting
|
|
46
|
+
`,
|
|
47
|
+
skills: ["data-analysis", "summarize", "explain-code"],
|
|
48
|
+
},
|
|
49
|
+
devops: {
|
|
50
|
+
description:
|
|
51
|
+
"DevOps / infrastructure project with deployment and monitoring skills",
|
|
52
|
+
rules: `# Project Rules
|
|
53
|
+
|
|
54
|
+
## Infrastructure
|
|
55
|
+
- Use infrastructure as code
|
|
56
|
+
- Tag all resources appropriately
|
|
57
|
+
- Follow least-privilege principle
|
|
58
|
+
|
|
59
|
+
## AI Assistant Guidelines
|
|
60
|
+
- Always validate configs before applying
|
|
61
|
+
- Use dry-run when available
|
|
62
|
+
- Document infrastructure changes
|
|
63
|
+
`,
|
|
64
|
+
skills: ["debug", "summarize", "code-review"],
|
|
65
|
+
},
|
|
66
|
+
empty: {
|
|
67
|
+
description: "Bare project with minimal configuration",
|
|
68
|
+
rules: `# Project Rules
|
|
69
|
+
|
|
70
|
+
Add your project-specific rules and conventions here.
|
|
71
|
+
The AI assistant will follow these guidelines when working in this project.
|
|
72
|
+
`,
|
|
73
|
+
skills: [],
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export function registerInitCommand(program) {
|
|
78
|
+
program
|
|
79
|
+
.command("init")
|
|
80
|
+
.description(
|
|
81
|
+
"Initialize a .chainlesschain/ project in the current directory",
|
|
82
|
+
)
|
|
83
|
+
.option(
|
|
84
|
+
"-t, --template <name>",
|
|
85
|
+
"Project template (code-project, data-science, devops, empty)",
|
|
86
|
+
"empty",
|
|
87
|
+
)
|
|
88
|
+
.option("-y, --yes", "Skip prompts, use defaults")
|
|
89
|
+
.option(
|
|
90
|
+
"--bare",
|
|
91
|
+
"Create minimal structure (alias for --template empty --yes)",
|
|
92
|
+
)
|
|
93
|
+
.action(async (options) => {
|
|
94
|
+
const cwd = process.cwd();
|
|
95
|
+
const ccDir = path.join(cwd, ".chainlesschain");
|
|
96
|
+
|
|
97
|
+
// Check if already initialized
|
|
98
|
+
if (fs.existsSync(path.join(ccDir, "config.json"))) {
|
|
99
|
+
const existingRoot = findProjectRoot(cwd);
|
|
100
|
+
logger.error(
|
|
101
|
+
`Already initialized at ${existingRoot || cwd}. Remove .chainlesschain/ to reinitialize.`,
|
|
102
|
+
);
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Determine template
|
|
107
|
+
let template = options.bare ? "empty" : options.template;
|
|
108
|
+
if (!TEMPLATES[template]) {
|
|
109
|
+
logger.error(
|
|
110
|
+
`Unknown template: ${template}. Available: ${Object.keys(TEMPLATES).join(", ")}`,
|
|
111
|
+
);
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Interactive selection if not --yes/--bare
|
|
116
|
+
if (!options.yes && !options.bare) {
|
|
117
|
+
try {
|
|
118
|
+
const { select } = await import("@inquirer/prompts");
|
|
119
|
+
template = await select({
|
|
120
|
+
message: "Select a project template:",
|
|
121
|
+
choices: Object.entries(TEMPLATES).map(([key, val]) => ({
|
|
122
|
+
name: `${key} — ${val.description}`,
|
|
123
|
+
value: key,
|
|
124
|
+
})),
|
|
125
|
+
default: template,
|
|
126
|
+
});
|
|
127
|
+
} catch {
|
|
128
|
+
// Ctrl+C or non-interactive — use default
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const tmpl = TEMPLATES[template];
|
|
133
|
+
const projectName = path.basename(cwd);
|
|
134
|
+
|
|
135
|
+
// Create directory structure
|
|
136
|
+
try {
|
|
137
|
+
fs.mkdirSync(ccDir, { recursive: true });
|
|
138
|
+
fs.mkdirSync(path.join(ccDir, "skills"), { recursive: true });
|
|
139
|
+
|
|
140
|
+
// config.json
|
|
141
|
+
const config = {
|
|
142
|
+
name: projectName,
|
|
143
|
+
template,
|
|
144
|
+
version: "1.0.0",
|
|
145
|
+
createdAt: new Date().toISOString(),
|
|
146
|
+
skills: {
|
|
147
|
+
workspace: "./skills",
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
fs.writeFileSync(
|
|
151
|
+
path.join(ccDir, "config.json"),
|
|
152
|
+
JSON.stringify(config, null, 2),
|
|
153
|
+
"utf-8",
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
// rules.md
|
|
157
|
+
fs.writeFileSync(path.join(ccDir, "rules.md"), tmpl.rules, "utf-8");
|
|
158
|
+
|
|
159
|
+
logger.success(
|
|
160
|
+
`Initialized ChainlessChain project in ${chalk.cyan(cwd)}`,
|
|
161
|
+
);
|
|
162
|
+
logger.log("");
|
|
163
|
+
logger.log(` Template: ${chalk.cyan(template)}`);
|
|
164
|
+
logger.log(` Config: ${chalk.gray(".chainlesschain/config.json")}`);
|
|
165
|
+
logger.log(` Rules: ${chalk.gray(".chainlesschain/rules.md")}`);
|
|
166
|
+
logger.log(` Skills: ${chalk.gray(".chainlesschain/skills/")}`);
|
|
167
|
+
logger.log("");
|
|
168
|
+
logger.log(chalk.bold("Next steps:"));
|
|
169
|
+
logger.log(
|
|
170
|
+
` ${chalk.cyan("chainlesschain skill add <name>")} Create a custom project skill`,
|
|
171
|
+
);
|
|
172
|
+
logger.log(
|
|
173
|
+
` ${chalk.cyan("chainlesschain skill list")} List all available skills`,
|
|
174
|
+
);
|
|
175
|
+
logger.log(
|
|
176
|
+
` ${chalk.cyan("chainlesschain agent")} Start the AI agent`,
|
|
177
|
+
);
|
|
178
|
+
logger.log("");
|
|
179
|
+
} catch (err) {
|
|
180
|
+
logger.error(`Failed to initialize: ${err.message}`);
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
}
|