@telelabsai/ship 1.0.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,119 @@
1
+ const { resolve, join, relative } = require('path');
2
+ const { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, statSync, cpSync } = require('fs');
3
+ const { createInterface } = require('readline');
4
+
5
+ const PKG_ROOT = resolve(__dirname, '..', '..');
6
+ const SOURCE_DIR = join(PKG_ROOT, '.claude');
7
+
8
+ /**
9
+ * Recursively collects all file paths under a directory.
10
+ * @param {string} dir - Directory to scan
11
+ * @param {string} base - Base path for relative paths
12
+ * @returns {string[]} Array of relative file paths
13
+ */
14
+ function collectFiles(dir, base) {
15
+ const files = [];
16
+ if (!existsSync(dir)) return files;
17
+
18
+ for (const entry of readdirSync(dir)) {
19
+ const full = join(dir, entry);
20
+ if (statSync(full).isDirectory()) {
21
+ files.push(...collectFiles(full, base));
22
+ } else {
23
+ files.push(relative(base, full));
24
+ }
25
+ }
26
+ return files;
27
+ }
28
+
29
+ /**
30
+ * Prompts the user with a yes/no question via stdin.
31
+ * @param {string} question - The question to ask
32
+ * @returns {Promise<boolean>}
33
+ */
34
+ function ask(question) {
35
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
36
+ return new Promise((res) => {
37
+ rl.question(question, (answer) => {
38
+ rl.close();
39
+ res(answer.toLowerCase().startsWith('y'));
40
+ });
41
+ });
42
+ }
43
+
44
+ /**
45
+ * Updates .claude/ in the target directory to match the latest Ship version.
46
+ * - New files: added automatically
47
+ * - Unchanged files: updated automatically
48
+ * - Modified files: asks user before overwriting (unless --force)
49
+ *
50
+ * @param {string} targetDir - Project directory to update
51
+ * @param {boolean} force - Skip confirmation prompts
52
+ */
53
+ module.exports = async function update(targetDir, force) {
54
+ const targetClaude = join(targetDir, '.claude');
55
+
56
+ if (!existsSync(targetClaude)) {
57
+ console.error(' No .claude/ directory found. Run "ship init" first.');
58
+ process.exit(1);
59
+ }
60
+
61
+ const sourceFiles = collectFiles(SOURCE_DIR, SOURCE_DIR);
62
+ const added = [];
63
+ const updated = [];
64
+ const skipped = [];
65
+
66
+ for (const relPath of sourceFiles) {
67
+ const sourcePath = join(SOURCE_DIR, relPath);
68
+ const targetPath = join(targetClaude, relPath);
69
+ const sourceContent = readFileSync(sourcePath);
70
+
71
+ if (!existsSync(targetPath)) {
72
+ // New file — add it
73
+ const dir = join(targetPath, '..');
74
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
75
+ writeFileSync(targetPath, sourceContent);
76
+ added.push(relPath);
77
+ continue;
78
+ }
79
+
80
+ const targetContent = readFileSync(targetPath);
81
+
82
+ // Files are identical — skip
83
+ if (sourceContent.equals(targetContent)) continue;
84
+
85
+ if (force) {
86
+ // Force mode — overwrite without asking
87
+ writeFileSync(targetPath, sourceContent);
88
+ updated.push(relPath);
89
+ } else {
90
+ // File was modified locally — ask user
91
+ const yes = await ask(` Overwrite ${relPath}? (y/n) `);
92
+ if (yes) {
93
+ writeFileSync(targetPath, sourceContent);
94
+ updated.push(relPath);
95
+ } else {
96
+ skipped.push(relPath);
97
+ }
98
+ }
99
+ }
100
+
101
+ // Summary
102
+ console.log('');
103
+ if (added.length) {
104
+ console.log(' Added:');
105
+ added.forEach((f) => console.log(` ${f}`));
106
+ }
107
+ if (updated.length) {
108
+ console.log(' Updated:');
109
+ updated.forEach((f) => console.log(` ${f}`));
110
+ }
111
+ if (skipped.length) {
112
+ console.log(' Skipped (locally modified):');
113
+ skipped.forEach((f) => console.log(` ${f}`));
114
+ }
115
+ if (!added.length && !updated.length && !skipped.length) {
116
+ console.log(' Already up to date.');
117
+ }
118
+ console.log('');
119
+ };
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@telelabsai/ship",
3
+ "version": "1.0.0",
4
+ "description": "AI-powered development toolkit. Ship code faster with pre-configured agents, skills, rules, and hooks for Claude Code.",
5
+ "bin": {
6
+ "ship": "cli/bin.js"
7
+ },
8
+ "files": [
9
+ "cli/",
10
+ ".claude/",
11
+ "CLAUDE.md",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "keywords": [
16
+ "claude-code",
17
+ "ai-agents",
18
+ "development-toolkit",
19
+ "cli",
20
+ "devtools",
21
+ "automation",
22
+ "conventional-commits"
23
+ ],
24
+ "author": "TeleLabs AI <tele@telelabs.ai> (https://telelabs.ai)",
25
+ "license": "MIT",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/telelabs-ai/ship.git"
29
+ },
30
+ "bugs": {
31
+ "url": "https://github.com/telelabs-ai/ship/issues"
32
+ },
33
+ "homepage": "https://telelabs.ai",
34
+ "scripts": {
35
+ "test": "node --test cli/__tests__/*.test.js"
36
+ },
37
+ "engines": {
38
+ "node": ">=18.0.0"
39
+ }
40
+ }