luckiest-co 1.0.34 → 1.0.36
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/bin/bump-version.js +62 -0
- package/bin/install.js +57 -1
- package/bin/sync-plugin-repo.js +57 -0
- package/package.json +8 -2
- package/skills/luckiest-charms/SKILL.md +1 -0
- package/skills/luckiest-finish/SKILL.md +1 -0
- package/skills/luckiest-go/SKILL.md +1 -0
- package/skills/luckiest-helpers/SKILL.md +1 -0
- package/skills/luckiest-home/SKILL.md +1 -0
- package/skills/luckiest-leaderboard/SKILL.md +1 -0
- package/skills/luckiest-merge/SKILL.md +1 -0
- package/skills/luckiest-next/SKILL.md +1 -0
- package/skills/luckiest-overlaps/SKILL.md +1 -0
- package/skills/luckiest-plan/SKILL.md +1 -0
- package/skills/luckiest-skills/SKILL.md +1 -0
- package/skills/luckiest-start/SKILL.md +1 -0
- package/skills/luckiest-status/SKILL.md +1 -0
- package/skills/luckiest-updates/SKILL.md +1 -0
- package/skills/luckiest-vouch/SKILL.md +1 -0
- package/skills/luckiest-wishes/SKILL.md +1 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Release bump: patch-bumps the npm version and the plugin version, then dates
|
|
4
|
+
// the CHANGELOG (turns "## Unreleased" into the release header, or adds one).
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
const green = '\x1b[32m';
|
|
10
|
+
const reset = '\x1b[0m';
|
|
11
|
+
|
|
12
|
+
const VERSION_RE = /("version":\s*")(\d+)\.(\d+)\.(\d+)(")/;
|
|
13
|
+
|
|
14
|
+
const patch = (v) => v.replace(/\d+$/, (n) => Number(n) + 1);
|
|
15
|
+
|
|
16
|
+
/** Bumps all four release files in dir. Returns { npm, plugin }. Writes nothing if the plugin versions disagree. */
|
|
17
|
+
function bumpVersion({ dir = path.join(__dirname, '..'), date = new Date().toLocaleDateString('en-CA') } = {}) {
|
|
18
|
+
const files = {
|
|
19
|
+
pkg: path.join(dir, 'package.json'),
|
|
20
|
+
plugin: path.join(dir, '.claude-plugin/plugin.json'),
|
|
21
|
+
market: path.join(dir, '.claude-plugin/marketplace.json'),
|
|
22
|
+
changelog: path.join(dir, 'CHANGELOG.md'),
|
|
23
|
+
};
|
|
24
|
+
const text = Object.fromEntries(Object.entries(files).map(([k, f]) => [k, fs.readFileSync(f, 'utf8')]));
|
|
25
|
+
const current = (k) => {
|
|
26
|
+
const m = text[k].match(VERSION_RE);
|
|
27
|
+
if (!m) throw new Error(`No version found in ${files[k]}`);
|
|
28
|
+
return `${m[2]}.${m[3]}.${m[4]}`;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const oldPlugin = current('plugin');
|
|
32
|
+
if (current('market') !== oldPlugin) {
|
|
33
|
+
throw new Error(`plugin.json (${oldPlugin}) and marketplace.json (${current('market')}) disagree. Fix them by hand first.`);
|
|
34
|
+
}
|
|
35
|
+
const npm = patch(current('pkg'));
|
|
36
|
+
const plugin = patch(oldPlugin);
|
|
37
|
+
|
|
38
|
+
// ponytail: replaces the first "version" in each file, which is the right one in all three today.
|
|
39
|
+
const write = (k, v) => fs.writeFileSync(files[k], text[k].replace(VERSION_RE, `$1${v}$5`));
|
|
40
|
+
write('pkg', npm);
|
|
41
|
+
write('plugin', plugin);
|
|
42
|
+
write('market', plugin);
|
|
43
|
+
// An "## Unreleased" section becomes the dated header, notes and all. Otherwise a fresh header goes on top.
|
|
44
|
+
const header = `## ${plugin} — ${date} (npm luckiest-co ${npm})`;
|
|
45
|
+
const unreleased = /^## Unreleased$/m;
|
|
46
|
+
fs.writeFileSync(files.changelog, unreleased.test(text.changelog)
|
|
47
|
+
? text.changelog.replace(unreleased, header)
|
|
48
|
+
: `${header}\n\n${text.changelog}`);
|
|
49
|
+
return { npm, plugin };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (require.main === module) {
|
|
53
|
+
try {
|
|
54
|
+
const { npm, plugin } = bumpVersion();
|
|
55
|
+
console.log(` ${green}Bumped to npm luckiest-co ${npm}, plugin ${plugin}.${reset} Check the notes under the new CHANGELOG.md header.`);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
console.error(` ${err.message}`);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
module.exports = { bumpVersion };
|
package/bin/install.js
CHANGED
|
@@ -224,6 +224,58 @@ function placeSkills(extractDir, skillsRoot, fallbackName) {
|
|
|
224
224
|
}
|
|
225
225
|
|
|
226
226
|
async function syncSkills() {
|
|
227
|
+
try {
|
|
228
|
+
await syncOwnedSkills();
|
|
229
|
+
} finally {
|
|
230
|
+
// Self-heal the luckiest-x + luckiest:luckiest-x duplicate: the marketplace
|
|
231
|
+
// plugin already registers its skills, so a copy in ~/.claude/skills (from
|
|
232
|
+
// older installers or from this sync) lists every one of them twice.
|
|
233
|
+
const globalDir = expandTilde(explicitConfigDir) || expandTilde(process.env.CLAUDE_CONFIG_DIR) || path.join(os.homedir(), '.claude');
|
|
234
|
+
const removed = dropPluginDuplicates(path.join(globalDir, 'skills'), pluginSkillNames(globalDir));
|
|
235
|
+
if (removed.length) {
|
|
236
|
+
console.log(` ${green}✓${reset} Removed ${removed.length} duplicate skill(s) from skills/ (plugin marketplace already provides them)`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Skill folder names shipped by the enabled marketplace plugin, read from its
|
|
243
|
+
* installPath. Empty when the plugin is absent or disabled.
|
|
244
|
+
*/
|
|
245
|
+
function pluginSkillNames(globalClaudeDir) {
|
|
246
|
+
const names = new Set();
|
|
247
|
+
if (!hasMarketplacePlugin(globalClaudeDir)) return names;
|
|
248
|
+
try {
|
|
249
|
+
const manifest = JSON.parse(
|
|
250
|
+
fs.readFileSync(path.join(globalClaudeDir, 'plugins', 'installed_plugins.json'), 'utf8')
|
|
251
|
+
);
|
|
252
|
+
for (const [key, value] of Object.entries(manifest.plugins || {})) {
|
|
253
|
+
if (!key.startsWith('luckiest@')) continue;
|
|
254
|
+
for (const item of Array.isArray(value) ? value : [value]) {
|
|
255
|
+
const dir = item && item.installPath && path.join(item.installPath, 'skills');
|
|
256
|
+
if (dir && fs.existsSync(dir)) fs.readdirSync(dir).forEach((n) => names.add(n));
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
} catch {
|
|
260
|
+
// Unreadable manifest: remove nothing.
|
|
261
|
+
}
|
|
262
|
+
return names;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** Remove skills/<name> for every name the plugin ships. Returns removed names. */
|
|
266
|
+
function dropPluginDuplicates(skillsRoot, names) {
|
|
267
|
+
const removed = [];
|
|
268
|
+
for (const name of names) {
|
|
269
|
+
const dest = path.join(skillsRoot, name);
|
|
270
|
+
if (fs.existsSync(path.join(dest, 'SKILL.md'))) {
|
|
271
|
+
fs.rmSync(dest, { recursive: true, force: true });
|
|
272
|
+
removed.push(name);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return removed;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async function syncOwnedSkills() {
|
|
227
279
|
// Self-heal the /luckiest:luckiest:* duplicate: older installers copied
|
|
228
280
|
// commands into ~/.claude/commands/luckiest even when the marketplace
|
|
229
281
|
// plugin already registers them. Sync runs every session, so clean it here.
|
|
@@ -428,8 +480,12 @@ function install(isGlobal) {
|
|
|
428
480
|
}
|
|
429
481
|
|
|
430
482
|
// Copy references/, templates/, .claude-plugin/ into the target root
|
|
483
|
+
// skills/ is skipped when the marketplace plugin ships them, for the same
|
|
484
|
+
// reason as commands/ above (luckiest-x and luckiest:luckiest-x).
|
|
485
|
+
const pluginPresent = hasMarketplacePlugin(defaultGlobalDir);
|
|
431
486
|
const topLevelDirs = ['references', 'templates', 'skills', '.claude-plugin', 'hooks'];
|
|
432
487
|
for (const dir of topLevelDirs) {
|
|
488
|
+
if (dir === 'skills' && pluginPresent) continue;
|
|
433
489
|
const dirSrc = path.join(src, dir);
|
|
434
490
|
const dirDest = path.join(claudeDir, dir);
|
|
435
491
|
if (fs.existsSync(dirSrc)) {
|
|
@@ -718,4 +774,4 @@ if (require.main === module) {
|
|
|
718
774
|
});
|
|
719
775
|
}
|
|
720
776
|
|
|
721
|
-
module.exports = { placeSkills };
|
|
777
|
+
module.exports = { placeSkills, dropPluginDuplicates };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Plugin repo sync: mirrors this package into the public marketplace repo
|
|
4
|
+
// (luckiestco/luckiest-plugin) and opens a PR there. Run after the release is
|
|
5
|
+
// bumped and published. --dry-run stops before anything is pushed.
|
|
6
|
+
|
|
7
|
+
const { execFileSync } = require('child_process');
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const os = require('os');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
const green = '\x1b[32m';
|
|
13
|
+
const yellow = '\x1b[33m';
|
|
14
|
+
const reset = '\x1b[0m';
|
|
15
|
+
|
|
16
|
+
const REPO = process.env.LUCKIEST_PLUGIN_REPO || 'luckiestco/luckiest-plugin';
|
|
17
|
+
|
|
18
|
+
const sh = (cmd, args, cwd) => execFileSync(cmd, args, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trimEnd();
|
|
19
|
+
const versionOf = (dir, file) => JSON.parse(fs.readFileSync(path.join(dir, file), 'utf8')).version;
|
|
20
|
+
|
|
21
|
+
/** Returns { status: 'synced' | 'dry-run' | 'pr', dir, changes, url }. */
|
|
22
|
+
function syncPluginRepo({ src = path.join(__dirname, '..'), remote = `https://github.com/${REPO}.git`, dryRun = false } = {}) {
|
|
23
|
+
const plugin = versionOf(src, '.claude-plugin/plugin.json');
|
|
24
|
+
const npm = versionOf(src, 'package.json');
|
|
25
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'luckiest-plugin-sync-'));
|
|
26
|
+
sh('git', ['clone', '--depth', '1', remote, dir]);
|
|
27
|
+
|
|
28
|
+
if (versionOf(dir, '.claude-plugin/plugin.json') === plugin) return { status: 'synced', dir, changes: [] };
|
|
29
|
+
|
|
30
|
+
// --checksum: a same-size edit with a matching mtime is otherwise skipped (a version bump is same-size).
|
|
31
|
+
sh('rsync', ['-a', '--checksum', '--delete', '--exclude', '.git', '--exclude', '.DS_Store', '--exclude', '__pycache__', `${src}/`, `${dir}/`]);
|
|
32
|
+
const changes = sh('git', ['status', '--porcelain'], dir).split('\n').filter(Boolean);
|
|
33
|
+
if (dryRun) return { status: 'dry-run', dir, changes };
|
|
34
|
+
|
|
35
|
+
const branch = `sync-${plugin}`;
|
|
36
|
+
const title = `Sync plugin ${plugin} (npm luckiest-co ${npm})`;
|
|
37
|
+
sh('git', ['switch', '-c', branch], dir);
|
|
38
|
+
sh('git', ['add', '-A'], dir);
|
|
39
|
+
sh('git', ['commit', '-m', title], dir);
|
|
40
|
+
sh('git', ['push', '-u', 'origin', branch], dir);
|
|
41
|
+
const url = sh('gh', ['pr', 'create', '--repo', REPO, '--head', branch, '--title', title, '--body', `Mirrors packages/luckiest-plugin at plugin ${plugin}.`], dir);
|
|
42
|
+
return { status: 'pr', dir, changes, url };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (require.main === module) {
|
|
46
|
+
try {
|
|
47
|
+
const { status, dir, changes, url } = syncPluginRepo({ dryRun: process.argv.includes('--dry-run') });
|
|
48
|
+
if (status === 'synced') console.log(` ${green}${REPO} already has this plugin version.${reset}`);
|
|
49
|
+
if (status === 'dry-run') console.log(` ${yellow}Dry run, nothing pushed.${reset} ${changes.length} changed files in ${dir}:\n${changes.map((c) => ` ${c}`).join('\n')}`);
|
|
50
|
+
if (status === 'pr') console.log(` ${green}Opened ${url}${reset}`);
|
|
51
|
+
} catch (err) {
|
|
52
|
+
console.error(` ${(err.stderr || err.message).toString().trim()}`);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = { syncPluginRepo };
|
package/package.json
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "luckiest-co",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.36",
|
|
4
4
|
"description": "Luckiest for Claude Code: plan, go, finish. Your luckiest.co skills and tribe, inside Claude.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"luckiest-co": "./bin/install.js"
|
|
7
7
|
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"prepublishOnly": "node bin/check-version.js",
|
|
10
|
+
"bump": "node bin/bump-version.js",
|
|
11
|
+
"sync-plugin": "node bin/sync-plugin-repo.js"
|
|
12
|
+
},
|
|
8
13
|
"files": [
|
|
9
14
|
"bin",
|
|
10
15
|
"commands",
|
|
@@ -15,7 +20,8 @@
|
|
|
15
20
|
".claude-plugin",
|
|
16
21
|
"!**/__pycache__",
|
|
17
22
|
"!**/*.pyc",
|
|
18
|
-
"!bin/*.test.js"
|
|
23
|
+
"!bin/*.test.js",
|
|
24
|
+
"!bin/check-version.js"
|
|
19
25
|
],
|
|
20
26
|
"license": "SEE LICENSE IN LICENSE",
|
|
21
27
|
"engines": {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: luckiest-plan
|
|
3
|
+
user-invocable: false
|
|
3
4
|
description: "Use when the user says /luckiest plan or luckiest plan, or wants to plan a feature, a launch, or their next piece of work before building it, even if they do not say plan (for example \"what should I build next\" or \"break this down into tasks\"). Looks at the project first, drafts 3 to 7 tasks with a suggested skill, model, and runnable check each, saves the context for /luckiest go, and stages the plan after one approval question."
|
|
4
5
|
---
|
|
5
6
|
|