create-lore 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.
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { execSync } = require('child_process');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const readline = require('readline');
7
+
8
+ const TOOLS = ['Claude Code', 'Cursor', 'OpenCode'];
9
+ const REPO_URL = 'https://github.com/lorehq/lore.git';
10
+
11
+ async function prompt(question) {
12
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
13
+ return new Promise(resolve => rl.question(question, answer => { rl.close(); resolve(answer); }));
14
+ }
15
+
16
+ async function selectTools() {
17
+ console.log('\nWhich AI coding tools will you use?\n');
18
+ TOOLS.forEach((t, i) => console.log(` ${i + 1}. ${t}`));
19
+ console.log();
20
+ const answer = await prompt('Select tools (comma-separated numbers, e.g. 1,3): ');
21
+ const indices = answer.split(',').map(s => parseInt(s.trim(), 10) - 1);
22
+ const selected = indices.filter(i => i >= 0 && i < TOOLS.length).map(i => TOOLS[i]);
23
+ return selected.length > 0 ? selected : ['Claude Code'];
24
+ }
25
+
26
+ async function main() {
27
+ const args = process.argv.slice(2);
28
+ const name = args.find(a => !a.startsWith('-'));
29
+ const templateDir = args.find((a, i) => args[i - 1] === '--template') || process.env.LORE_TEMPLATE;
30
+
31
+ if (!name) {
32
+ console.error('Usage: create-lore <name> [--template <path>]');
33
+ process.exit(1);
34
+ }
35
+
36
+ const isPath = name.includes('/') || name.includes(path.sep);
37
+ const targetDir = path.resolve(isPath ? name : `./lore-${name}`);
38
+
39
+ if (fs.existsSync(targetDir)) {
40
+ console.error(`Error: ${targetDir} already exists`);
41
+ process.exit(1);
42
+ }
43
+
44
+ // Clone or copy template
45
+ const tmpDir = path.join(require('os').tmpdir(), `create-lore-${Date.now()}`);
46
+ try {
47
+ if (templateDir) {
48
+ console.log(`Copying template from ${templateDir}...`);
49
+ copyDir(templateDir, tmpDir);
50
+ } else {
51
+ console.log(`Cloning ${REPO_URL}...`);
52
+ execSync(`git clone --depth 1 ${REPO_URL} "${tmpDir}"`, { stdio: 'pipe' });
53
+ }
54
+
55
+ // Remove .git from clone
56
+ const gitDir = path.join(tmpDir, '.git');
57
+ if (fs.existsSync(gitDir)) fs.rmSync(gitDir, { recursive: true });
58
+
59
+ // Copy to target
60
+ copyDir(tmpDir, targetDir);
61
+ } finally {
62
+ if (fs.existsSync(tmpDir)) fs.rmSync(tmpDir, { recursive: true });
63
+ }
64
+
65
+ // Select tools (skip in non-interactive mode)
66
+ let tools = ['Claude Code'];
67
+ if (process.stdin.isTTY) {
68
+ tools = await selectTools();
69
+ }
70
+
71
+ // Write .lore-config
72
+ const config = { name, tools, created: new Date().toISOString().split('T')[0] };
73
+ fs.writeFileSync(path.join(targetDir, '.lore-config'), JSON.stringify(config, null, 2) + '\n');
74
+
75
+ // Init git repo
76
+ execSync('git init', { cwd: targetDir, stdio: 'pipe' });
77
+
78
+ console.log(`\nCreated lore-${name} at ${targetDir}`);
79
+ console.log(`Tools: ${tools.join(', ')}`);
80
+ console.log(`\nNext steps:`);
81
+ console.log(` cd ${targetDir}`);
82
+ console.log(` git add -A && git commit -m "Init Lore"`);
83
+ }
84
+
85
+ function copyDir(src, dest) {
86
+ fs.mkdirSync(dest, { recursive: true });
87
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
88
+ const srcPath = path.join(src, entry.name);
89
+ const destPath = path.join(dest, entry.name);
90
+ if (entry.isDirectory()) {
91
+ copyDir(srcPath, destPath);
92
+ } else {
93
+ fs.copyFileSync(srcPath, destPath);
94
+ }
95
+ }
96
+ }
97
+
98
+ main().catch(err => { console.error(err.message); process.exit(1); });
package/package.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "create-lore",
3
+ "version": "0.1.0",
4
+ "description": "Create a new Lore knowledge-persistent agent repo",
5
+ "bin": {
6
+ "create-lore": "bin/create-lore.js"
7
+ },
8
+ "scripts": {
9
+ "test": "node --test test/create-lore.test.js"
10
+ },
11
+ "keywords": ["lore", "agent", "ai", "knowledge"],
12
+ "license": "MIT",
13
+ "engines": {
14
+ "node": ">=18"
15
+ }
16
+ }
@@ -0,0 +1,92 @@
1
+ const { describe, it, before, after } = require('node:test');
2
+ const assert = require('node:assert/strict');
3
+ const { execSync } = require('child_process');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+
7
+ const BIN = path.resolve(__dirname, '../bin/create-lore.js');
8
+ const TEMPLATE = path.resolve(__dirname, '../../lore');
9
+ const OUTPUT = path.resolve(__dirname, '../test-output');
10
+
11
+ function run(args = '') {
12
+ return execSync(`node ${BIN} ${args}`, {
13
+ env: { ...process.env, LORE_TEMPLATE: TEMPLATE },
14
+ stdio: 'pipe',
15
+ encoding: 'utf8',
16
+ });
17
+ }
18
+
19
+ function cleanup() {
20
+ if (fs.existsSync(OUTPUT)) fs.rmSync(OUTPUT, { recursive: true });
21
+ }
22
+
23
+ describe('create-lore', () => {
24
+ before(cleanup);
25
+ after(cleanup);
26
+
27
+ it('exits with error when no name given', () => {
28
+ assert.throws(() => run(''), { status: 1 });
29
+ });
30
+
31
+ it('creates project directory with expected structure', () => {
32
+ const output = run(`${OUTPUT} --template ${TEMPLATE}`);
33
+
34
+ assert.ok(fs.existsSync(OUTPUT), 'output directory exists');
35
+
36
+ // .lore-config written with correct fields
37
+ const config = JSON.parse(fs.readFileSync(path.join(OUTPUT, '.lore-config'), 'utf8'));
38
+ assert.equal(config.name, OUTPUT);
39
+ assert.ok(Array.isArray(config.tools), 'tools is an array');
40
+ assert.ok(config.created, 'created date present');
41
+
42
+ // git repo initialized
43
+ assert.ok(fs.existsSync(path.join(OUTPUT, '.git')), 'git initialized');
44
+
45
+ // no template .git leaked
46
+ const entries = fs.readdirSync(path.join(OUTPUT, '.git'));
47
+ assert.ok(entries.includes('HEAD'), '.git looks like a fresh init');
48
+ });
49
+
50
+ it('fails if target directory already exists', () => {
51
+ fs.mkdirSync(OUTPUT, { recursive: true });
52
+ assert.throws(() => run(`${OUTPUT} --template ${TEMPLATE}`), /already exists/);
53
+ fs.rmSync(OUTPUT, { recursive: true });
54
+ });
55
+
56
+ // These tests activate as lore repo gets populated (Phase 2+)
57
+
58
+ it('has AGENTS.md when template provides it', () => {
59
+ const agentsMd = path.join(TEMPLATE, 'AGENTS.md');
60
+ if (!fs.existsSync(agentsMd)) return; // skip until Phase 2
61
+
62
+ run(`${OUTPUT} --template ${TEMPLATE}`);
63
+ assert.ok(fs.existsSync(path.join(OUTPUT, 'AGENTS.md')), 'AGENTS.md copied');
64
+ const content = fs.readFileSync(path.join(OUTPUT, 'AGENTS.md'), 'utf8');
65
+ assert.ok(content.length > 0, 'AGENTS.md is non-empty');
66
+ cleanup();
67
+ });
68
+
69
+ it('has hooks wired in .claude/settings.json when template provides it', () => {
70
+ const settings = path.join(TEMPLATE, '.claude/settings.json');
71
+ if (!fs.existsSync(settings)) return; // skip until Phase 3
72
+
73
+ run(`${OUTPUT} --template ${TEMPLATE}`);
74
+ const parsed = JSON.parse(fs.readFileSync(path.join(OUTPUT, '.claude/settings.json'), 'utf8'));
75
+ assert.ok(parsed.hooks, 'hooks key exists in settings');
76
+ cleanup();
77
+ });
78
+
79
+ it('passes validate-consistency.sh when template provides it', () => {
80
+ const script = path.join(TEMPLATE, 'scripts/validate-consistency.sh');
81
+ if (!fs.existsSync(script)) return; // skip until Phase 5
82
+
83
+ run(`${OUTPUT} --template ${TEMPLATE}`);
84
+ const result = execSync(`bash scripts/validate-consistency.sh`, {
85
+ cwd: OUTPUT,
86
+ encoding: 'utf8',
87
+ stdio: 'pipe',
88
+ });
89
+ assert.ok(result.includes('PASSED'), 'validation passes');
90
+ cleanup();
91
+ });
92
+ });