@telelabsai/ship 1.1.4 → 1.1.7

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 prompts = require('prompts');
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 workspace with arrow keys
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,114 @@ 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
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, async (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
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
+ // 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
+ });
94
+
95
+ if (!selected) {
96
+ console.log(' Cancelled.\n');
97
+ process.exit(0);
98
+ }
99
+
100
+ // 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);
111
+ }
112
+
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}`;
59
122
 
60
123
  fetchJson(url, token, (err, data) => {
61
124
  if (err) {
62
125
  console.error(` Error: ${err.message}\n`);
63
126
  process.exit(1);
64
127
  }
65
-
66
128
  if (data.error) {
67
129
  console.error(` Error: ${data.error}\n`);
68
130
  process.exit(1);
@@ -72,7 +134,6 @@ function sync(args) {
72
134
  let written = 0;
73
135
 
74
136
  for (const file of data.files || []) {
75
- // Skip .gitkeep files
76
137
  if (file.path.endsWith('.gitkeep')) continue;
77
138
 
78
139
  const filePath = path.join(claudeDir, file.path);
@@ -87,10 +148,9 @@ function sync(args) {
87
148
  console.log(` + .claude/${file.path}`);
88
149
  }
89
150
 
90
- console.log(`\n Synced ${written} files to ${claudeDir}\n`);
151
+ console.log(`\n Synced ${written} files to ${claudeDir}\n`);
91
152
 
92
- // Save org for future syncs
93
- saveSyncConfig(dir, orgSlug, workspace);
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.7",
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"
@@ -37,5 +37,8 @@
37
37
  },
38
38
  "engines": {
39
39
  "node": ">=18.0.0"
40
+ },
41
+ "dependencies": {
42
+ "prompts": "^2.4.2"
40
43
  }
41
44
  }