@telelabsai/ship 1.1.4 → 1.1.6

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.
@@ -64,7 +64,7 @@ function login() {
64
64
 
65
65
  server.listen(0, () => {
66
66
  const port = server.address().port;
67
- const authUrl = `${DASHBOARD_URL}/cli-auth?port=${port}`;
67
+ const authUrl = `${DASHBOARD_URL}/authorize-cli?port=${port}`;
68
68
 
69
69
  console.log(` Auth URL: ${authUrl}\n`);
70
70
  console.log(' Waiting for authorization...\n');
@@ -2,12 +2,14 @@ const https = require('https');
2
2
  const http = require('http');
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
+ const readline = require('readline');
5
6
  const { loadCredentials, DASHBOARD_URL } = require('./auth.js');
6
7
 
7
8
  /**
8
- * ship sync --workspace <slug> [--org <slug>] [--key <key>] [--dir <path>]
9
+ * ship sync [--workspace <slug>] [--org <slug>] [--key <key>] [--dir <path>]
9
10
  *
10
- * Pulls workspace config files from Ship dashboard and writes to .claude/
11
+ * Interactive mode (no flags): picks org + workspace from list
12
+ * CI mode (with flags): uses provided org + workspace directly
11
13
  */
12
14
  function sync(args) {
13
15
  const workspace = getArg(args, '--workspace') || getArg(args, '-w');
@@ -15,54 +17,113 @@ function sync(args) {
15
17
  const key = getArg(args, '--key') || getArg(args, '-k');
16
18
  const dir = getArg(args, '--dir') || process.cwd();
17
19
 
18
- if (!workspace) {
19
- console.error(' Error: --workspace is required.');
20
- console.error(' Usage: ship sync --workspace <slug>\n');
21
- process.exit(1);
22
- }
23
-
24
- // Resolve auth token: --key flag > saved credentials
20
+ // Resolve auth token
25
21
  let token = key;
26
22
  if (!token) {
27
23
  const creds = loadCredentials();
28
- if (creds?.token) {
29
- token = creds.token;
30
- }
24
+ if (creds?.token) token = creds.token;
31
25
  }
32
26
 
33
27
  if (!token) {
34
28
  console.error(' Error: No authentication found.');
35
- console.error(' Run: ship auth login');
36
- console.error(' Or use: ship sync --workspace <slug> --key <api-key>\n');
29
+ console.error(' Run: ship auth login\n');
30
+ process.exit(1);
31
+ }
32
+
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 — fetch orgs + workspaces, let user pick
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');
37
43
  process.exit(1);
38
44
  }
39
45
 
40
- // Resolve org slug: --org flag > try to find from workspace
41
- // For personal token auth, org is required
42
- let orgSlug = org;
43
- if (!orgSlug) {
44
- // If using workspace API key, we need org slug
45
- // For now, require it explicitly or try to read from saved config
46
- const savedOrg = loadSavedOrg(dir);
47
- if (savedOrg) {
48
- orgSlug = savedOrg;
49
- } else {
50
- console.error(' Error: --org is required (or run ship auth login and set default org).');
51
- console.error(' Usage: ship sync --org <org-slug> --workspace <slug>\n');
46
+ console.log(' Fetching your workspaces...\n');
47
+
48
+ fetchJson(`${DASHBOARD_URL}/api/cli/workspaces`, token, (err, data) => {
49
+ if (err) {
50
+ console.error(` Error: ${err.message}\n`);
51
+ process.exit(1);
52
+ }
53
+ if (data.error) {
54
+ console.error(` Error: ${data.error}\n`);
52
55
  process.exit(1);
53
56
  }
54
- }
55
57
 
56
- console.log(` Syncing workspace "${workspace}" from org "${orgSlug}"...\n`);
58
+ const orgs = data.orgs || [];
59
+ if (orgs.length === 0) {
60
+ console.log(' No organizations found. Create one at your Ship dashboard.\n');
61
+ process.exit(0);
62
+ }
57
63
 
58
- const url = `${DASHBOARD_URL}/api/sync/${orgSlug}?workspace=${workspace}`;
64
+ // Flatten all workspaces with org info
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
+ });
75
+ }
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
+
83
+ // Show picker
84
+ console.log(' Available workspaces:\n');
85
+ allWorkspaces.forEach((w, i) => {
86
+ console.log(` ${i + 1}. ${w.wsName} (${w.orgName}) — ${w.files} files`);
87
+ });
88
+ console.log('');
89
+
90
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
91
+
92
+ rl.question(' Select workspace [1]: ', (answer) => {
93
+ const idx = parseInt(answer || '1', 10) - 1;
94
+ if (idx < 0 || idx >= allWorkspaces.length) {
95
+ console.error(' Invalid selection.\n');
96
+ rl.close();
97
+ process.exit(1);
98
+ }
99
+
100
+ const selected = allWorkspaces[idx];
101
+ console.log(`\n Will sync "${selected.wsName}" (${selected.files} files) to ${path.join(dir, '.claude')}`);
102
+
103
+ rl.question(' Proceed? [Y/n]: ', (confirm) => {
104
+ rl.close();
105
+ const c = (confirm || 'y').toLowerCase().trim();
106
+ if (c !== 'y' && c !== 'yes' && c !== '') {
107
+ console.log(' Cancelled.\n');
108
+ process.exit(0);
109
+ }
110
+ console.log('');
111
+ doSync(selected.orgSlug, selected.wsSlug, token, dir);
112
+ });
113
+ });
114
+ });
115
+ }
116
+
117
+ function doSync(orgSlug, workspaceSlug, token, dir) {
118
+ console.log(` Syncing workspace "${workspaceSlug}" from org "${orgSlug}"...\n`);
119
+
120
+ const url = `${DASHBOARD_URL}/api/sync/${orgSlug}?workspace=${workspaceSlug}`;
59
121
 
60
122
  fetchJson(url, token, (err, data) => {
61
123
  if (err) {
62
124
  console.error(` Error: ${err.message}\n`);
63
125
  process.exit(1);
64
126
  }
65
-
66
127
  if (data.error) {
67
128
  console.error(` Error: ${data.error}\n`);
68
129
  process.exit(1);
@@ -72,7 +133,6 @@ function sync(args) {
72
133
  let written = 0;
73
134
 
74
135
  for (const file of data.files || []) {
75
- // Skip .gitkeep files
76
136
  if (file.path.endsWith('.gitkeep')) continue;
77
137
 
78
138
  const filePath = path.join(claudeDir, file.path);
@@ -89,8 +149,8 @@ function sync(args) {
89
149
 
90
150
  console.log(`\n Synced ${written} files to ${claudeDir}\n`);
91
151
 
92
- // Save org for future syncs
93
- saveSyncConfig(dir, orgSlug, workspace);
152
+ // Save config for future syncs
153
+ saveSyncConfig(dir, orgSlug, workspaceSlug);
94
154
  });
95
155
  }
96
156
 
@@ -128,15 +188,4 @@ function saveSyncConfig(dir, orgSlug, workspace) {
128
188
  fs.writeFileSync(configFile, JSON.stringify(config, null, 2), 'utf8');
129
189
  }
130
190
 
131
- function loadSavedOrg(dir) {
132
- try {
133
- const configFile = path.join(dir, '.ship.json');
134
- if (fs.existsSync(configFile)) {
135
- const config = JSON.parse(fs.readFileSync(configFile, 'utf8'));
136
- return config.org || null;
137
- }
138
- } catch {}
139
- return null;
140
- }
141
-
142
191
  module.exports = sync;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telelabsai/ship",
3
- "version": "1.1.4",
3
+ "version": "1.1.6",
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"