@telelabsai/ship 1.1.11 → 1.1.14

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.
Files changed (2) hide show
  1. package/cli/commands/sync.js +82 -105
  2. package/package.json +1 -1
@@ -1,47 +1,29 @@
1
1
  const https = require('https');
2
2
  const http = require('http');
3
+ const { execSync, spawn } = require('child_process');
3
4
  const fs = require('fs');
4
5
  const path = require('path');
5
6
  const prompts = require('prompts');
6
7
  const { loadCredentials, DASHBOARD_URL } = require('./auth.js');
7
8
 
8
9
  /**
9
- * ship sync [--workspace <slug>] [--org <slug>] [--key <key>] [--dir <path>]
10
+ * ship sync [--workspace <slug>]
10
11
  *
11
12
  * Interactive mode (no flags): picks workspace with arrow keys
12
- * CI mode (with flags): uses provided org + workspace directly
13
+ * CI mode (with --workspace flag): direct install
13
14
  */
14
15
  function sync(args) {
15
- const workspace = getArg(args, '--workspace') || getArg(args, '-w');
16
- const org = getArg(args, '--org') || getArg(args, '-o');
17
- const key = getArg(args, '--key') || getArg(args, '-k');
18
- const dir = getArg(args, '--dir') || process.cwd();
16
+ const workspaceSlug = getArg(args, '--workspace') || getArg(args, '-w');
19
17
 
20
18
  // Resolve auth token
21
- let token = key;
22
- if (!token) {
23
- const creds = loadCredentials();
24
- if (creds?.token) token = creds.token;
25
- }
26
-
27
- if (!token) {
19
+ const creds = loadCredentials();
20
+ if (!creds?.token) {
28
21
  console.error(' Error: No authentication found.');
29
22
  console.error(' Run: ship auth login\n');
30
23
  process.exit(1);
31
24
  }
32
25
 
33
- // If both org and workspace provided → direct sync (CI mode)
34
- if (org && workspace) {
35
- doSync(org, workspace, token, dir);
36
- return;
37
- }
38
-
39
- // Interactive mode
40
- if (!process.stdin.isTTY) {
41
- console.error(' Error: --org and --workspace required in non-interactive mode.');
42
- console.error(' Usage: ship sync --org <slug> --workspace <slug>\n');
43
- process.exit(1);
44
- }
26
+ const token = creds.token;
45
27
 
46
28
  console.log(' Fetching your workspaces...\n');
47
29
 
@@ -55,102 +37,97 @@ function sync(args) {
55
37
  process.exit(1);
56
38
  }
57
39
 
58
- const orgs = data.orgs || [];
59
- if (orgs.length === 0) {
60
- console.log(' No organizations found. Create one at your Ship dashboard.\n');
40
+ const workspaces = data.workspaces || [];
41
+ if (workspaces.length === 0) {
42
+ console.log(' No workspaces found. Join one at your Ship dashboard.\n');
61
43
  process.exit(0);
62
44
  }
63
45
 
64
- // Flatten all workspaces
65
- const allWorkspaces = [];
66
- for (const o of orgs) {
67
- for (const w of o.workspaces) {
68
- allWorkspaces.push({
69
- orgSlug: o.slug,
70
- orgName: o.name,
71
- wsSlug: w.slug,
72
- wsName: w.name,
73
- files: w.files,
74
- });
46
+ let selected;
47
+
48
+ // If workspace slug provided → direct mode
49
+ if (workspaceSlug) {
50
+ selected = workspaces.find((w) => w.slug === workspaceSlug);
51
+ if (!selected) {
52
+ console.error(` Error: Workspace "${workspaceSlug}" not found.\n`);
53
+ console.log(' Available workspaces:');
54
+ workspaces.forEach((w) => console.log(` - ${w.slug} (${w.name})`));
55
+ console.log('');
56
+ process.exit(1);
57
+ }
58
+ } else {
59
+ // Interactive mode
60
+ if (!process.stdin.isTTY) {
61
+ console.error(' Error: --workspace required in non-interactive mode.');
62
+ console.error(' Usage: ship sync --workspace <slug>\n');
63
+ process.exit(1);
75
64
  }
76
- }
77
-
78
- if (allWorkspaces.length === 0) {
79
- console.log(' No workspaces found. Create one at your Ship dashboard.\n');
80
- process.exit(0);
81
- }
82
65
 
83
- // Arrow-key workspace picker
84
- const { selected } = await prompts({
85
- type: 'select',
86
- name: 'selected',
87
- message: 'Select workspace',
88
- choices: allWorkspaces.map((w) => ({
89
- title: `${w.wsName} (${w.orgName})`,
90
- description: `${w.files} files`,
91
- value: w,
92
- })),
93
- });
66
+ const { picked } = await prompts({
67
+ type: 'select',
68
+ name: 'picked',
69
+ message: 'Select workspace',
70
+ choices: workspaces.map((w) => ({
71
+ title: w.name,
72
+ description: w.description || '',
73
+ value: w,
74
+ })),
75
+ });
76
+
77
+ if (!picked) {
78
+ console.log(' Cancelled.\n');
79
+ process.exit(0);
80
+ }
94
81
 
95
- if (!selected) {
96
- console.log(' Cancelled.\n');
97
- process.exit(0);
82
+ selected = picked;
98
83
  }
99
84
 
100
85
  // Confirmation
101
- const { confirmed } = await prompts({
102
- type: 'confirm',
103
- name: 'confirmed',
104
- message: `Sync "${selected.wsName}" (${selected.files} files) to ${path.join(dir, '.claude')}?`,
105
- initial: true,
106
- });
107
-
108
- if (!confirmed) {
109
- console.log(' Cancelled.\n');
110
- process.exit(0);
86
+ if (process.stdin.isTTY && !workspaceSlug) {
87
+ const { confirmed } = await prompts({
88
+ type: 'confirm',
89
+ name: 'confirmed',
90
+ message: `Install "${selected.name}" plugin into Claude Code?`,
91
+ initial: true,
92
+ });
93
+
94
+ if (!confirmed) {
95
+ console.log(' Cancelled.\n');
96
+ process.exit(0);
97
+ }
111
98
  }
112
99
 
113
- console.log('');
114
- doSync(selected.orgSlug, selected.wsSlug, token, dir);
115
- });
116
- }
117
-
118
- function doSync(orgSlug, workspaceSlug, token, dir) {
119
- console.log(` Syncing workspace "${workspaceSlug}"...\n`);
120
-
121
- const url = `${DASHBOARD_URL}/api/sync/${orgSlug}?workspace=${workspaceSlug}`;
100
+ // Install via claude plugin add
101
+ console.log(`\n Installing "${selected.name}"...\n`);
122
102
 
123
- fetchJson(url, token, (err, data) => {
124
- if (err) {
125
- console.error(` Error: ${err.message}\n`);
126
- process.exit(1);
127
- }
128
- if (data.error) {
129
- console.error(` Error: ${data.error}\n`);
103
+ try {
104
+ // Check if claude is installed
105
+ execSync('claude --version', { stdio: 'pipe' });
106
+ } catch {
107
+ console.error(' Error: Claude Code is not installed.');
108
+ console.error(' Install it at: https://claude.ai/code\n');
130
109
  process.exit(1);
131
110
  }
132
111
 
133
- const claudeDir = path.join(dir, '.claude');
134
- let written = 0;
135
-
136
- for (const file of data.files || []) {
137
- if (file.path.endsWith('.gitkeep')) continue;
138
-
139
- const filePath = path.join(claudeDir, file.path);
140
- const fileDir = path.dirname(filePath);
112
+ const claude = spawn('claude', ['/plugin', 'marketplace', 'add', selected.deployUrl], {
113
+ stdio: 'inherit',
114
+ cwd: process.cwd(),
115
+ });
141
116
 
142
- if (!fs.existsSync(fileDir)) {
143
- fs.mkdirSync(fileDir, { recursive: true });
117
+ claude.on('close', (code) => {
118
+ if (code === 0) {
119
+ console.log(`\n ✓ "${selected.name}" installed successfully.\n`);
120
+ saveSyncConfig(process.cwd(), selected.slug);
121
+ } else {
122
+ console.error(`\n Plugin install exited with code ${code}\n`);
144
123
  }
124
+ process.exit(code || 0);
125
+ });
145
126
 
146
- fs.writeFileSync(filePath, file.content, 'utf8');
147
- written++;
148
- console.log(` + .claude/${file.path}`);
149
- }
150
-
151
- console.log(`\n ✓ Synced ${written} files to ${claudeDir}\n`);
152
-
153
- saveSyncConfig(dir, orgSlug, workspaceSlug);
127
+ claude.on('error', (err) => {
128
+ console.error(` Error running Claude Code: ${err.message}\n`);
129
+ process.exit(1);
130
+ });
154
131
  });
155
132
  }
156
133
 
@@ -182,9 +159,9 @@ function getArg(args, flag) {
182
159
  return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : null;
183
160
  }
184
161
 
185
- function saveSyncConfig(dir, orgSlug, workspace) {
162
+ function saveSyncConfig(dir, workspaceSlug) {
186
163
  const configFile = path.join(dir, '.ship.json');
187
- const config = { org: orgSlug, workspace, lastSynced: new Date().toISOString() };
164
+ const config = { workspace: workspaceSlug, lastSynced: new Date().toISOString() };
188
165
  fs.writeFileSync(configFile, JSON.stringify(config, null, 2), 'utf8');
189
166
  }
190
167
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telelabsai/ship",
3
- "version": "1.1.11",
3
+ "version": "1.1.14",
4
4
  "description": "Ship code faster with pre-configured agents, skills, rules, and hooks for Claude Code.",
5
5
  "bin": {
6
6
  "ship": "cli/bin.js"