@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,35 @@
1
+ # Push Workflow
2
+
3
+ ## Steps
4
+
5
+ ### 1. Pre-check
6
+ ```bash
7
+ git status
8
+ git branch --show-current
9
+ ```
10
+ Warn if uncommitted changes exist.
11
+
12
+ ### 2. Check Remote Tracking
13
+ ```bash
14
+ git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null
15
+ ```
16
+ - If tracking exists → `git push`
17
+ - If no tracking → `git push -u origin <branch>`
18
+
19
+ ### 3. Handle Errors
20
+
21
+ | Error | Action |
22
+ |-------|--------|
23
+ | Rejected (non-fast-forward) | Suggest `git pull --rebase` then push |
24
+ | Permission denied | Tell user to check remote access |
25
+ | Remote not found | Suggest `git remote add origin <url>` |
26
+
27
+ ### 4. Confirm
28
+ ```bash
29
+ git log --oneline origin/<branch>..HEAD
30
+ ```
31
+ Show what was pushed.
32
+
33
+ ## Safety
34
+ - Never force push (`--force` or `-f`)
35
+ - Never push to `main`/`master`/`production` without user confirmation
package/CLAUDE.md ADDED
@@ -0,0 +1,17 @@
1
+ # CLAUDE.md
2
+
3
+ ## Project
4
+ Ship — AI-powered development toolkit by TeleLabs AI.
5
+
6
+ ## Commands
7
+ - Lint: `npm run lint`
8
+ - Test: `npm test`
9
+ - Build: `npm run build`
10
+
11
+ ## Skills
12
+ - `/ship-version` — Git version control (commit, push, pr, branch, diff, changelog)
13
+
14
+ ## Rules
15
+ - Use conventional commits with optional ticket ID (--ticket for Jira, Trello, ClickUp, etc.)
16
+ - Never commit secrets or .env files
17
+ - Run lint before committing
package/README.md ADDED
@@ -0,0 +1,239 @@
1
+ # Ship
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@telelabsai/ship.svg)](https://www.npmjs.com/package/@telelabsai/ship)
4
+ [![license](https://img.shields.io/npm/l/@telelabsai/ship.svg)](https://github.com/telelabs-ai/ship/blob/main/LICENSE)
5
+
6
+ **Ship helps you go from code to production.** AI-powered agents, skills, and guardrails that plug into Claude Code — so you spend less time on process and more time building.
7
+
8
+ ```
9
+ idea → plan → code → test → review → commit → push → PR → deploy
10
+ Ship handles this ──────────────────────────→
11
+ ```
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install -g @telelabsai/ship
17
+ ```
18
+
19
+ ## Quick Start
20
+
21
+ ```bash
22
+ # Add Ship to any project
23
+ ship init
24
+
25
+ # Or start fresh
26
+ ship new my-app
27
+
28
+ # Use from terminal (delegates to Claude Code)
29
+ ship version commit --ticket PROJ-42 "add auth"
30
+
31
+ # Or inside Claude Code directly
32
+ claude
33
+ /ship-version commit --ticket PROJ-42 "add auth"
34
+ ```
35
+
36
+ Both paths use the same skill. One source of truth.
37
+
38
+ ## CLI
39
+
40
+ ```bash
41
+ # Project setup
42
+ ship init Add Ship to current directory
43
+ ship init --dir ./my-app Add Ship to specific directory
44
+ ship new <name> Create new project with Ship config
45
+ ship update Update .claude/ to latest version
46
+ ship update --force Update without asking
47
+
48
+ # Skills (routes to Claude Code)
49
+ ship version commit Conventional commits with ticket tracking
50
+ ship version diff --pr Preview what a PR would contain
51
+ ship version push Push to remote safely
52
+ ship version pr Create pull request
53
+ ship version changelog Auto-generate changelog
54
+ ship version branch <name> Create and switch branch
55
+ ship plan "add auth" Plan implementation (coming soon)
56
+ ship test Run tests (coming soon)
57
+ ship review Code review (coming soon)
58
+ ship deploy staging Deploy (coming soon)
59
+ ```
60
+
61
+ Any `ship <skill>` command translates to `claude -p "/ship-<skill>"`. When new skills are added, they work from the terminal immediately — no CLI update needed.
62
+
63
+ ## How It Works
64
+
65
+ Ship adds a `.claude/` directory to your project with four layers:
66
+
67
+ | Layer | What it does | When it loads |
68
+ |-------|-------------|---------------|
69
+ | **Skills** | Step-by-step workflows you invoke | On demand (`/ship-version commit`) |
70
+ | **Agents** | Isolated AI subprocesses for heavy tasks | When needed (large diffs, PRs) |
71
+ | **Rules** | Conventions Claude always follows | Every message |
72
+ | **Hooks** | Code-level guardrails | Automatically (blocks secrets, force push) |
73
+
74
+ ```
75
+ .claude/
76
+ ├── skills/ Slash-command workflows
77
+ ├── agents/ Subagent definitions
78
+ ├── rules/ Always-loaded instructions
79
+ ├── hooks/ Lifecycle automation
80
+ └── settings.json Configuration
81
+ ```
82
+
83
+ ## Skills
84
+
85
+ ### ship-version — Git Version Control
86
+
87
+ Conventional commits, ticket tracking, security scanning, changelogs.
88
+
89
+ ```bash
90
+ # Commit
91
+ ship version commit # auto-detect type + scope
92
+ ship version commit "add login page" # custom message
93
+ ship version commit --type fix "null check" # explicit type
94
+ ship version commit --ticket PROJ-42 "user auth" # with ticket ref
95
+
96
+ # Diff
97
+ ship version diff # working tree changes
98
+ ship version diff --staged # staged only
99
+ ship version diff --pr # preview PR diff
100
+ ship version diff v1.0.0..v2.0.0 # between tags
101
+
102
+ # Changelog
103
+ ship version changelog # auto-detect from tags
104
+ ship version changelog v1.0.0 v2.0.0 # specific range
105
+
106
+ # Push / PR / Branch
107
+ ship version push # push current branch
108
+ ship version pr --ticket PROJ-42 "Add auth" # create PR with ticket
109
+ ship version branch feature/user-auth # create + switch
110
+ ```
111
+
112
+ ### Ticket Tracking
113
+
114
+ Works with any project management tool:
115
+
116
+ | Tool | Example |
117
+ |------|---------|
118
+ | Jira | `--ticket PROJ-123` |
119
+ | ClickUp | `--ticket CU-abc123` |
120
+ | Linear | `--ticket ENG-123` |
121
+ | GitHub Issues | `--ticket #42` |
122
+ | Trello | `--ticket #abc123` |
123
+ | Shortcut | `--ticket sc-12345` |
124
+ | Asana | `--ticket 1234567890` |
125
+ | Custom | `--ticket ANYTHING` |
126
+
127
+ Multiple tickets: `--ticket PROJ-42 --ticket #15`
128
+
129
+ ### Commit Format
130
+
131
+ ```
132
+ type(scope): description
133
+
134
+ [optional body]
135
+
136
+ Refs: TICKET-ID
137
+ ```
138
+
139
+ Types: `feat` | `fix` | `refactor` | `test` | `docs` | `chore` | `perf` | `style` | `ci` | `build`
140
+
141
+ ## Safety
142
+
143
+ Ship includes automatic guardrails that can't be bypassed:
144
+
145
+ | Guardrail | What it prevents |
146
+ |-----------|-----------------|
147
+ | Secret detection | Blocks committing `.env`, API keys, credentials |
148
+ | Force push block | Prevents `git push --force` to any branch |
149
+ | Blind staging block | Prevents `git add .` / `git add -A` |
150
+ | Hard reset block | Prevents `git reset --hard` without confirmation |
151
+ | Branch protection | Prevents deleting main/master/production |
152
+
153
+ ## Extending Ship
154
+
155
+ ### Add a skill
156
+
157
+ ```bash
158
+ mkdir -p .claude/skills/ship-deploy
159
+ ```
160
+
161
+ ```yaml
162
+ # .claude/skills/ship-deploy/SKILL.md
163
+ ---
164
+ name: ship-deploy
165
+ description: "Deploy to staging or production."
166
+ argument-hint: "<staging|production>"
167
+ ---
168
+
169
+ # Instructions here...
170
+ ```
171
+
172
+ Now `ship deploy staging` works — both from terminal and inside Claude Code.
173
+
174
+ ### Add an agent
175
+
176
+ ```yaml
177
+ # .claude/agents/my-agent.md
178
+ ---
179
+ name: my-agent
180
+ description: "When to use this agent."
181
+ model: sonnet
182
+ tools: Bash, Read, Edit, Glob, Grep
183
+ ---
184
+
185
+ Instructions here...
186
+ ```
187
+
188
+ ### Add a rule
189
+
190
+ ```markdown
191
+ # .claude/rules/my-rule.md
192
+ - Always use TypeScript strict mode
193
+ - Never use `any` type
194
+ ```
195
+
196
+ Rules load every message. Keep them short.
197
+
198
+ ### Add a hook
199
+
200
+ ```js
201
+ // .claude/hooks/my-hook.cjs
202
+ const input = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8'));
203
+ // Exit 0 = allow, Exit 2 = block (stdout = reason)
204
+ process.exit(0);
205
+ ```
206
+
207
+ Wire in `.claude/settings.json`:
208
+ ```json
209
+ { "hooks": { "PreToolUse": [{ "matcher": "Bash", "hooks": [
210
+ { "type": "command", "command": "node .claude/hooks/my-hook.cjs" }
211
+ ]}]}}
212
+ ```
213
+
214
+ ## Roadmap
215
+
216
+ - [x] `ship-version` — Git version control
217
+ - [ ] `ship-plan` — Implementation planning
218
+ - [ ] `ship-test` — Test runner with coverage
219
+ - [ ] `ship-review` — Code review
220
+ - [ ] `ship-fix` — Structured bug fixing
221
+ - [ ] `ship-deploy` — Deployment workflows
222
+ - [ ] `ship-docs` — Documentation management
223
+ - [ ] `ship-secure` — Security scanning
224
+
225
+ ## Requirements
226
+
227
+ - [Node.js](https://nodejs.org) 18+
228
+ - [Claude Code](https://claude.ai/code) (for skill execution)
229
+
230
+ ## Links
231
+
232
+ - **Website:** [telelabs.ai](https://telelabs.ai)
233
+ - **Email:** [tele@telelabs.ai](mailto:tele@telelabs.ai)
234
+ - **LinkedIn:** [linkedin.com/company/telelabsai](https://linkedin.com/company/telelabsai)
235
+ - **Issues:** [github.com/telelabs-ai/ship/issues](https://github.com/telelabs-ai/ship/issues)
236
+
237
+ ## License
238
+
239
+ MIT — [TeleLabs AI](https://telelabs.ai)
@@ -0,0 +1,155 @@
1
+ const { describe, it, before, after } = require('node:test');
2
+ const assert = require('node:assert');
3
+ const { execSync } = require('child_process');
4
+ const { existsSync, rmSync, readFileSync } = require('fs');
5
+ const { join, resolve } = require('path');
6
+
7
+ const CLI = resolve(__dirname, '..', 'bin.js');
8
+ const TMP = resolve(__dirname, '..', '..', '.test-output');
9
+
10
+ // Helper: run CLI command and return stdout
11
+ function run(args) {
12
+ return execSync(`node ${CLI} ${args}`, { encoding: 'utf8', cwd: TMP });
13
+ }
14
+
15
+ describe('ship CLI', () => {
16
+ before(() => {
17
+ // Clean up any leftover test output
18
+ if (existsSync(TMP)) rmSync(TMP, { recursive: true });
19
+ });
20
+
21
+ after(() => {
22
+ if (existsSync(TMP)) rmSync(TMP, { recursive: true });
23
+ });
24
+
25
+ describe('--help', () => {
26
+ it('shows help text', () => {
27
+ const out = execSync(`node ${CLI} --help`, { encoding: 'utf8' });
28
+ assert.match(out, /Ship/);
29
+ assert.match(out, /ship init/);
30
+ assert.match(out, /ship new/);
31
+ assert.match(out, /telelabs\.ai/);
32
+ });
33
+
34
+ it('shows help with -h alias', () => {
35
+ const out = execSync(`node ${CLI} -h`, { encoding: 'utf8' });
36
+ assert.match(out, /ship init/);
37
+ });
38
+
39
+ it('shows help when no args', () => {
40
+ const out = execSync(`node ${CLI}`, { encoding: 'utf8' });
41
+ assert.match(out, /ship init/);
42
+ });
43
+ });
44
+
45
+ describe('--version', () => {
46
+ it('prints version number', () => {
47
+ const out = execSync(`node ${CLI} --version`, { encoding: 'utf8' }).trim();
48
+ assert.match(out, /^\d+\.\d+\.\d+$/);
49
+ });
50
+
51
+ it('works with -v alias', () => {
52
+ const out = execSync(`node ${CLI} -v`, { encoding: 'utf8' }).trim();
53
+ assert.match(out, /^\d+\.\d+\.\d+$/);
54
+ });
55
+ });
56
+
57
+ describe('skill delegation', () => {
58
+ it('delegates unknown commands to Claude Code as skills', () => {
59
+ // Any non-builtin command gets routed to Claude Code as /ship-<command>
60
+ // This will fail if Claude Code is not installed, which is expected in CI
61
+ try {
62
+ execSync(`node ${CLI} foobar`, { encoding: 'utf8', timeout: 5000 });
63
+ } catch (err) {
64
+ // Expected: either Claude not installed or skill not found
65
+ assert.ok(
66
+ err.stderr.includes('Claude Code') || err.status !== 0,
67
+ 'Should attempt to delegate to Claude Code'
68
+ );
69
+ }
70
+ });
71
+ });
72
+
73
+ describe('ship new', () => {
74
+ const projectDir = join(TMP, 'test-new-project');
75
+
76
+ before(() => {
77
+ if (existsSync(TMP)) rmSync(TMP, { recursive: true });
78
+ execSync(`mkdir -p ${TMP}`);
79
+ });
80
+
81
+ it('creates project directory with .claude/', () => {
82
+ execSync(`node ${CLI} new ${projectDir}`, { encoding: 'utf8' });
83
+ assert.ok(existsSync(projectDir));
84
+ assert.ok(existsSync(join(projectDir, '.claude')));
85
+ });
86
+
87
+ it('copies all expected files', () => {
88
+ // Agents
89
+ assert.ok(existsSync(join(projectDir, '.claude', 'agents', 'git-ops.md')));
90
+ // Skills
91
+ assert.ok(existsSync(join(projectDir, '.claude', 'skills', 'ship-version', 'SKILL.md')));
92
+ // Rules
93
+ assert.ok(existsSync(join(projectDir, '.claude', 'rules', 'git-conventions.md')));
94
+ // Hooks
95
+ assert.ok(existsSync(join(projectDir, '.claude', 'hooks', 'git-safety.cjs')));
96
+ // Settings
97
+ assert.ok(existsSync(join(projectDir, '.claude', 'settings.json')));
98
+ // CLAUDE.md
99
+ assert.ok(existsSync(join(projectDir, 'CLAUDE.md')));
100
+ });
101
+
102
+ it('copies skill references', () => {
103
+ const refs = join(projectDir, '.claude', 'skills', 'ship-version', 'references');
104
+ assert.ok(existsSync(join(refs, 'commit-standards.md')));
105
+ assert.ok(existsSync(join(refs, 'workflow-commit.md')));
106
+ assert.ok(existsSync(join(refs, 'workflow-push.md')));
107
+ assert.ok(existsSync(join(refs, 'workflow-pr.md')));
108
+ assert.ok(existsSync(join(refs, 'workflow-diff.md')));
109
+ assert.ok(existsSync(join(refs, 'workflow-changelog.md')));
110
+ assert.ok(existsSync(join(refs, 'safety-protocols.md')));
111
+ });
112
+
113
+ it('customizes CLAUDE.md with project name', () => {
114
+ const content = readFileSync(join(projectDir, 'CLAUDE.md'), 'utf8');
115
+ assert.match(content, /test-new-project/);
116
+ assert.match(content, /powered by Ship/);
117
+ });
118
+
119
+ it('fails if project already exists', () => {
120
+ assert.throws(
121
+ () => execSync(`node ${CLI} new ${projectDir}`, { encoding: 'utf8' }),
122
+ /already exists/
123
+ );
124
+ });
125
+
126
+ it('fails without project name', () => {
127
+ assert.throws(
128
+ () => execSync(`node ${CLI} new`, { encoding: 'utf8' }),
129
+ /project name required/
130
+ );
131
+ });
132
+ });
133
+
134
+ describe('ship init', () => {
135
+ const initDir = join(TMP, 'test-init-project');
136
+
137
+ before(() => {
138
+ execSync(`mkdir -p ${initDir}`);
139
+ });
140
+
141
+ it('scaffolds .claude/ into existing directory', () => {
142
+ execSync(`node ${CLI} init --dir ${initDir}`, { encoding: 'utf8' });
143
+ assert.ok(existsSync(join(initDir, '.claude')));
144
+ assert.ok(existsSync(join(initDir, '.claude', 'settings.json')));
145
+ assert.ok(existsSync(join(initDir, 'CLAUDE.md')));
146
+ });
147
+
148
+ it('fails if .claude/ already exists', () => {
149
+ assert.throws(
150
+ () => execSync(`node ${CLI} init --dir ${initDir}`, { encoding: 'utf8' }),
151
+ /already exists/
152
+ );
153
+ });
154
+ });
155
+ });
package/cli/bin.js ADDED
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { resolve } = require('path');
4
+ const { execSync, spawn } = require('child_process');
5
+ const args = process.argv.slice(2);
6
+ const command = args[0];
7
+
8
+ const HELP = `
9
+ Ship — AI-powered development toolkit by TeleLabs AI
10
+
11
+ Usage:
12
+ ship init [--dir <path>] Scaffold .claude/ into a project
13
+ ship new <name> Create new project with Ship config
14
+ ship update [--force] Update .claude/ to latest Ship version
15
+ ship <skill> [args] Run a Ship skill via Claude Code
16
+ ship --version Show version
17
+ ship --help Show this help
18
+
19
+ Skills (any ship-* skill installed in .claude/skills/):
20
+ ship version commit → /ship-version commit
21
+ ship version diff --pr → /ship-version diff --pr
22
+ ship plan "add auth" → /ship-plan add auth
23
+ ship deploy staging → /ship-deploy staging
24
+ ship test → /ship-test
25
+ ship review → /ship-review
26
+
27
+ Examples:
28
+ ship init
29
+ ship new my-app
30
+ ship update
31
+ ship version commit --ticket PROJ-42 "add auth"
32
+ ship plan "implement user dashboard"
33
+
34
+ Requires: Claude Code (https://claude.ai/code)
35
+ https://telelabs.ai
36
+ `;
37
+
38
+ if (!command || command === '--help' || command === '-h') {
39
+ console.log(HELP);
40
+ process.exit(0);
41
+ }
42
+
43
+ if (command === '--version' || command === '-v') {
44
+ const pkg = require('../package.json');
45
+ console.log(pkg.version);
46
+ process.exit(0);
47
+ }
48
+
49
+ if (command === 'init') {
50
+ const init = require('./commands/init.js');
51
+ const dirIdx = args.indexOf('--dir');
52
+ const targetDir = dirIdx !== -1 ? resolve(args[dirIdx + 1]) : process.cwd();
53
+ init(targetDir);
54
+ } else if (command === 'new') {
55
+ const newProject = require('./commands/new.js');
56
+ const projectName = args[1];
57
+ if (!projectName) {
58
+ console.error(' Error: project name required. Usage: ship new <name>');
59
+ process.exit(1);
60
+ }
61
+ newProject(projectName);
62
+ } else if (command === 'update') {
63
+ const update = require('./commands/update.js');
64
+ const force = args.includes('--force');
65
+ update(process.cwd(), force);
66
+ } else {
67
+ // Generic skill routing: ship <skill> [args] → claude -p "/ship-<skill> [args]"
68
+ delegateToClaude(args);
69
+ }
70
+
71
+ /**
72
+ * Delegates any non-builtin command to Claude Code as a skill invocation.
73
+ * `ship version commit --ticket X` → `claude -p "/ship-version commit --ticket X"`
74
+ *
75
+ * @param {string[]} cliArgs - All arguments after `ship`
76
+ */
77
+ function delegateToClaude(cliArgs) {
78
+ // Check Claude Code is installed
79
+ try {
80
+ execSync('claude --version', { stdio: 'pipe' });
81
+ } catch {
82
+ console.error(' Error: Claude Code is not installed.');
83
+ console.error(' Install it at: https://claude.ai/code');
84
+ process.exit(1);
85
+ }
86
+
87
+ // Build skill prompt: ship version commit → /ship-version commit
88
+ const skill = cliArgs[0];
89
+ const rest = cliArgs.slice(1).join(' ');
90
+ const prompt = `/ship-${skill}${rest ? ' ' + rest : ''}`;
91
+
92
+ const claude = spawn('claude', ['-p', prompt], {
93
+ stdio: 'inherit',
94
+ cwd: process.cwd(),
95
+ });
96
+
97
+ claude.on('close', (code) => process.exit(code || 0));
98
+ claude.on('error', (err) => {
99
+ console.error(` Error running Claude Code: ${err.message}`);
100
+ process.exit(1);
101
+ });
102
+ }
@@ -0,0 +1,49 @@
1
+ const { resolve, join } = require('path');
2
+ const { cpSync, existsSync, mkdirSync, readFileSync, writeFileSync } = require('fs');
3
+
4
+ // Source: .claude/ and CLAUDE.md from the package root (single source of truth)
5
+ const PKG_ROOT = resolve(__dirname, '..', '..');
6
+ const SOURCE_CLAUDE_DIR = join(PKG_ROOT, '.claude');
7
+ const SOURCE_CLAUDE_MD = join(PKG_ROOT, 'CLAUDE.md');
8
+
9
+ module.exports = function init(targetDir) {
10
+ const targetClaude = join(targetDir, '.claude');
11
+ const targetClaudeMd = join(targetDir, 'CLAUDE.md');
12
+
13
+ // Safety: warn if .claude/ already exists
14
+ if (existsSync(targetClaude)) {
15
+ console.error(' .claude/ already exists in this directory.');
16
+ console.error(' Use --force to overwrite (not yet implemented).');
17
+ console.error(' Skipping to avoid losing your customizations.');
18
+ process.exit(1);
19
+ }
20
+
21
+ // Ensure target directory exists
22
+ if (!existsSync(targetDir)) {
23
+ mkdirSync(targetDir, { recursive: true });
24
+ }
25
+
26
+ // Copy .claude/ directory
27
+ cpSync(SOURCE_CLAUDE_DIR, targetClaude, { recursive: true });
28
+ console.log(' .claude/ scaffolded');
29
+
30
+ // Copy CLAUDE.md (only if it doesn't exist)
31
+ if (!existsSync(targetClaudeMd)) {
32
+ // Read and customize the template CLAUDE.md
33
+ let claudeMd = readFileSync(SOURCE_CLAUDE_MD, 'utf8');
34
+
35
+ // Replace project name placeholder with directory name
36
+ const projectName = targetDir.split('/').pop();
37
+ claudeMd = claudeMd.replace(
38
+ 'Ship — AI-powered development toolkit by TeleLabs AI.',
39
+ `${projectName} — powered by Ship (TeleLabs AI).`
40
+ );
41
+
42
+ writeFileSync(targetClaudeMd, claudeMd);
43
+ console.log(' CLAUDE.md created');
44
+ } else {
45
+ console.log(' CLAUDE.md already exists, skipped');
46
+ }
47
+
48
+ console.log('\n Ship initialized. Run "claude" to start.\n');
49
+ };
@@ -0,0 +1,18 @@
1
+ const { resolve, join } = require('path');
2
+ const { existsSync, mkdirSync } = require('fs');
3
+ const init = require('./init.js');
4
+
5
+ module.exports = function newProject(name) {
6
+ const targetDir = resolve(process.cwd(), name);
7
+
8
+ if (existsSync(targetDir)) {
9
+ console.error(` Error: "${name}" already exists.`);
10
+ process.exit(1);
11
+ }
12
+
13
+ mkdirSync(targetDir, { recursive: true });
14
+ console.log(`\n Creating ${name}/\n`);
15
+
16
+ // Scaffold Ship config into the new directory
17
+ init(targetDir);
18
+ };