create-lore 0.1.1 → 0.1.2

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 ADDED
@@ -0,0 +1,38 @@
1
+ # create-lore
2
+
3
+ Create a new [Lore](https://github.com/lorehq/lore) knowledge-persistent AI coding framework repo.
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ npx create-lore myproject
9
+ ```
10
+
11
+ This creates `lore-myproject/` with the full Lore framework — hooks, skills, scripts, and operating instructions that teach Claude Code to learn and remember across sessions.
12
+
13
+ ## What you get
14
+
15
+ - **AGENTS.md / CLAUDE.md** — Operating instructions Claude Code reads automatically
16
+ - **Hooks** — Session init, memory guard, post-action capture reminders
17
+ - **Skills** — `create-skill` and `create-agent` for building your knowledge base
18
+ - **Scripts** — Registry generation, agent generation, consistency validation
19
+
20
+ ## Options
21
+
22
+ ```bash
23
+ npx create-lore myproject # creates ./lore-myproject/
24
+ npx create-lore ./custom-path # creates at specific path
25
+ ```
26
+
27
+ ## After setup
28
+
29
+ ```bash
30
+ cd lore-myproject
31
+ git add -A && git commit -m "Init Lore"
32
+ ```
33
+
34
+ Then open Claude Code in the project. The hooks will fire automatically and the self-learning loop begins.
35
+
36
+ ## License
37
+
38
+ Apache-2.0
@@ -1,98 +1,69 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ // create-lore: Bootstrap a new Lore knowledge-persistent agent repo.
4
+ //
5
+ // Usage: npx create-lore <name|path>
6
+ //
7
+ // How it works:
8
+ // 1. Clones the Lore template from GitHub (or copies from LORE_TEMPLATE env var)
9
+ // 2. Strips the template's .git history
10
+ // 3. Writes a .lore-config with the project name and creation date
11
+ // 4. Runs git init for a clean start
12
+ //
13
+ // The LORE_TEMPLATE env var is used by tests to point at a local template
14
+ // directory instead of cloning from GitHub.
15
+
3
16
  const { execSync } = require('child_process');
4
17
  const fs = require('fs');
5
18
  const path = require('path');
6
- const readline = require('readline');
7
19
 
8
- const TOOLS = ['Claude Code', 'Cursor', 'OpenCode'];
9
20
  const REPO_URL = 'https://github.com/lorehq/lore.git';
10
21
 
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'];
22
+ // -- Parse arguments --
23
+ const name = process.argv[2];
24
+ if (!name) {
25
+ console.error('Usage: create-lore <name>');
26
+ process.exit(1);
24
27
  }
25
28
 
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
- }
29
+ // If the name contains a path separator, treat it as a custom path.
30
+ // Otherwise, create a {name}/ directory in the current folder.
31
+ const isPath = name.includes('/') || name.includes(path.sep);
32
+ const targetDir = path.resolve(isPath ? name : `./${name}`);
54
33
 
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"`);
34
+ if (fs.existsSync(targetDir)) {
35
+ console.error(`Error: ${targetDir} already exists`);
36
+ process.exit(1);
83
37
  }
84
38
 
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
- }
39
+ // -- Copy template to target --
40
+ // Clone from GitHub or copy from a local template directory.
41
+ // Uses a temp dir so we can strip .git before copying to the final location.
42
+ const tmpDir = path.join(require('os').tmpdir(), `create-lore-${Date.now()}`);
43
+ try {
44
+ const templateDir = process.env.LORE_TEMPLATE;
45
+ if (templateDir) {
46
+ fs.cpSync(templateDir, tmpDir, { recursive: true });
47
+ } else {
48
+ execSync(`git clone --depth 1 ${REPO_URL} "${tmpDir}"`, { stdio: 'pipe' });
95
49
  }
50
+ fs.rmSync(path.join(tmpDir, '.git'), { recursive: true, force: true });
51
+ fs.cpSync(tmpDir, targetDir, { recursive: true });
52
+ } finally {
53
+ // Always clean up the temp dir
54
+ if (fs.existsSync(tmpDir)) fs.rmSync(tmpDir, { recursive: true });
96
55
  }
97
56
 
98
- main().catch(err => { console.error(err.message); process.exit(1); });
57
+ // -- Write .lore-config --
58
+ const projectName = isPath ? path.basename(targetDir) : name;
59
+ const config = { name: projectName, created: new Date().toISOString().split('T')[0] };
60
+ fs.writeFileSync(path.join(targetDir, '.lore-config'), JSON.stringify(config, null, 2) + '\n');
61
+
62
+ // -- Initialize git --
63
+ execSync('git init', { cwd: targetDir, stdio: 'pipe' });
64
+
65
+ // -- Done --
66
+ console.log(`\nCreated ${targetDir}`);
67
+ console.log(`\nNext steps:`);
68
+ console.log(` cd ${targetDir}`);
69
+ console.log(` git add -A && git commit -m "Init Lore"`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-lore",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Create a new Lore knowledge-persistent agent repo",
5
5
  "bin": {
6
6
  "create-lore": "bin/create-lore.js"
@@ -1,3 +1,7 @@
1
+ // Tests for create-lore installer.
2
+ // Uses the local lore repo (sibling directory) as the template via LORE_TEMPLATE env var.
3
+ // Runs the installer in a subprocess and validates the output directory structure.
4
+
1
5
  const { describe, it, before, after } = require('node:test');
2
6
  const assert = require('node:assert/strict');
3
7
  const { execSync } = require('child_process');
@@ -5,9 +9,10 @@ const fs = require('fs');
5
9
  const path = require('path');
6
10
 
7
11
  const BIN = path.resolve(__dirname, '../bin/create-lore.js');
8
- const TEMPLATE = path.resolve(__dirname, '../../lore');
12
+ const TEMPLATE = path.resolve(__dirname, '../../lore'); // Sibling lore repo
9
13
  const OUTPUT = path.resolve(__dirname, '../test-output');
10
14
 
15
+ // Run the installer with LORE_TEMPLATE pointing at the local template
11
16
  function run(args = '') {
12
17
  return execSync(`node ${BIN} ${args}`, {
13
18
  env: { ...process.env, LORE_TEMPLATE: TEMPLATE },
@@ -29,59 +34,54 @@ describe('create-lore', () => {
29
34
  });
30
35
 
31
36
  it('creates project directory with expected structure', () => {
32
- const output = run(`${OUTPUT} --template ${TEMPLATE}`);
37
+ run(OUTPUT);
33
38
 
34
39
  assert.ok(fs.existsSync(OUTPUT), 'output directory exists');
35
40
 
36
- // .lore-config written with correct fields
41
+ // .lore-config has required fields
37
42
  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');
43
+ assert.ok(config.name, 'name present');
40
44
  assert.ok(config.created, 'created date present');
41
45
 
42
- // git repo initialized
46
+ // Fresh git repo initialized (not cloned template history)
43
47
  assert.ok(fs.existsSync(path.join(OUTPUT, '.git')), 'git initialized');
44
-
45
- // no template .git leaked
46
48
  const entries = fs.readdirSync(path.join(OUTPUT, '.git'));
47
49
  assert.ok(entries.includes('HEAD'), '.git looks like a fresh init');
48
50
  });
49
51
 
50
52
  it('fails if target directory already exists', () => {
51
53
  fs.mkdirSync(OUTPUT, { recursive: true });
52
- assert.throws(() => run(`${OUTPUT} --template ${TEMPLATE}`), /already exists/);
54
+ assert.throws(() => run(OUTPUT), /already exists/);
53
55
  fs.rmSync(OUTPUT, { recursive: true });
54
56
  });
55
57
 
56
- // These tests activate as lore repo gets populated (Phase 2+)
58
+ // -- Template content tests --
59
+ // These skip gracefully if the template hasn't reached that phase yet.
57
60
 
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
+ it('has CLAUDE.md when template provides it', () => {
62
+ if (!fs.existsSync(path.join(TEMPLATE, 'CLAUDE.md'))) return;
61
63
 
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');
64
+ run(OUTPUT);
65
+ assert.ok(fs.existsSync(path.join(OUTPUT, 'CLAUDE.md')), 'CLAUDE.md copied');
66
+ const content = fs.readFileSync(path.join(OUTPUT, 'CLAUDE.md'), 'utf8');
67
+ assert.ok(content.length > 0, 'CLAUDE.md is non-empty');
66
68
  cleanup();
67
69
  });
68
70
 
69
71
  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
+ if (!fs.existsSync(path.join(TEMPLATE, '.claude/settings.json'))) return;
72
73
 
73
- run(`${OUTPUT} --template ${TEMPLATE}`);
74
+ run(OUTPUT);
74
75
  const parsed = JSON.parse(fs.readFileSync(path.join(OUTPUT, '.claude/settings.json'), 'utf8'));
75
76
  assert.ok(parsed.hooks, 'hooks key exists in settings');
76
77
  cleanup();
77
78
  });
78
79
 
79
80
  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
81
+ if (!fs.existsSync(path.join(TEMPLATE, 'scripts/validate-consistency.sh'))) return;
82
82
 
83
- run(`${OUTPUT} --template ${TEMPLATE}`);
84
- const result = execSync(`bash scripts/validate-consistency.sh`, {
83
+ run(OUTPUT);
84
+ const result = execSync('bash scripts/validate-consistency.sh', {
85
85
  cwd: OUTPUT,
86
86
  encoding: 'utf8',
87
87
  stdio: 'pipe',