@shipwellapp/cli 0.1.2 → 0.2.1

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/dist/index.js +430 -10
  2. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -2,10 +2,7 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
-
6
- // src/commands/analyze.ts
7
- import ora from "ora";
8
- import chalk from "chalk";
5
+ import chalk7 from "chalk";
9
6
 
10
7
  // ../../packages/core/dist/models.js
11
8
  var AVAILABLE_MODELS = [
@@ -735,6 +732,70 @@ function extractTag(text, tag) {
735
732
  return match ? match[1].trim() : null;
736
733
  }
737
734
 
735
+ // src/commands/analyze.ts
736
+ import ora from "ora";
737
+ import chalk from "chalk";
738
+
739
+ // src/lib/store.ts
740
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
741
+ import { join as join2 } from "path";
742
+ import { homedir } from "os";
743
+ var CONFIG_DIR = join2(homedir(), ".shipwell");
744
+ var CONFIG_FILE = join2(CONFIG_DIR, "config.json");
745
+ function ensureDir() {
746
+ if (!existsSync(CONFIG_DIR)) {
747
+ mkdirSync(CONFIG_DIR, { recursive: true });
748
+ }
749
+ }
750
+ function loadConfig() {
751
+ try {
752
+ ensureDir();
753
+ if (existsSync(CONFIG_FILE)) {
754
+ return JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
755
+ }
756
+ } catch {
757
+ }
758
+ return {};
759
+ }
760
+ function saveConfig(config2) {
761
+ ensureDir();
762
+ writeFileSync(CONFIG_FILE, JSON.stringify(config2, null, 2) + "\n", { mode: 384 });
763
+ }
764
+ function getUser() {
765
+ return loadConfig().user;
766
+ }
767
+ function setUser(user) {
768
+ const config2 = loadConfig();
769
+ config2.user = user;
770
+ saveConfig(config2);
771
+ }
772
+ function clearUser() {
773
+ const config2 = loadConfig();
774
+ delete config2.user;
775
+ saveConfig(config2);
776
+ }
777
+ function getApiKey() {
778
+ return loadConfig().apiKey;
779
+ }
780
+ function setApiKey(key) {
781
+ const config2 = loadConfig();
782
+ config2.apiKey = key;
783
+ saveConfig(config2);
784
+ }
785
+ function clearApiKey() {
786
+ const config2 = loadConfig();
787
+ delete config2.apiKey;
788
+ saveConfig(config2);
789
+ }
790
+ function getModel() {
791
+ return loadConfig().model;
792
+ }
793
+ function setModel(model) {
794
+ const config2 = loadConfig();
795
+ config2.model = model;
796
+ saveConfig(config2);
797
+ }
798
+
738
799
  // src/commands/analyze.ts
739
800
  var accent = chalk.hex("#6366f1");
740
801
  var dim = chalk.dim;
@@ -775,14 +836,21 @@ function formatMetric(m) {
775
836
  return ` ${dim("\u2022")} ${m.label}: ${chalk.red(m.before)} ${dim("\u2192")} ${chalk.green(m.after)}${m.unit ? dim(` ${m.unit}`) : ""}`;
776
837
  }
777
838
  async function analyzeCommand(operation, source, options) {
778
- const apiKey = options.apiKey || process.env.ANTHROPIC_API_KEY;
839
+ const user = getUser();
840
+ if (!user) {
841
+ console.error(chalk.red("\n Error: Not logged in.\n"));
842
+ console.error(dim(" Run ") + chalk.cyan("shipwell login") + dim(" to sign in with Google.\n"));
843
+ process.exit(1);
844
+ }
845
+ const apiKey = options.apiKey || process.env.ANTHROPIC_API_KEY || getApiKey();
779
846
  if (!apiKey) {
780
847
  console.error(chalk.red("\n Error: Anthropic API key is required.\n"));
781
- console.error(dim(" Set ANTHROPIC_API_KEY env var or use --api-key flag"));
782
- console.error(dim(" Example: shipwell audit ./my-repo --api-key sk-ant-...\n"));
848
+ console.error(dim(" Set it with: ") + chalk.cyan("shipwell config set api-key sk-ant-..."));
849
+ console.error(dim(" Or pass it: ") + chalk.cyan("shipwell audit ./repo --api-key sk-ant-..."));
850
+ console.error(dim(" Or set env: ") + chalk.cyan("export ANTHROPIC_API_KEY=sk-ant-...\n"));
783
851
  process.exit(1);
784
852
  }
785
- const model = options.model || process.env.SHIPWELL_MODEL || "claude-sonnet-4-5-20250929";
853
+ const model = options.model || process.env.SHIPWELL_MODEL || getModel() || "claude-sonnet-4-5-20250929";
786
854
  const startTime = Date.now();
787
855
  console.log();
788
856
  console.log(accent(" \u26F5 Shipwell"), dim("\u2014 Full Codebase Autopilot"));
@@ -879,9 +947,321 @@ async function analyzeCommand(operation, source, options) {
879
947
  console.log();
880
948
  }
881
949
 
950
+ // src/commands/login.ts
951
+ import chalk2 from "chalk";
952
+ import ora2 from "ora";
953
+
954
+ // src/lib/auth.ts
955
+ import http from "http";
956
+ import { exec } from "child_process";
957
+ import { platform } from "os";
958
+ function openBrowser(url) {
959
+ const plat = platform();
960
+ if (plat === "darwin") exec(`open "${url}"`);
961
+ else if (plat === "win32") exec(`start "" "${url}"`);
962
+ else exec(`xdg-open "${url}"`);
963
+ }
964
+ function startAuthFlow(baseUrl) {
965
+ return new Promise((resolve, reject) => {
966
+ const server = http.createServer((req, res) => {
967
+ const url = new URL(req.url || "/", "http://localhost");
968
+ if (url.pathname === "/callback") {
969
+ const name = url.searchParams.get("name") || "";
970
+ const email = url.searchParams.get("email") || "";
971
+ const uid = url.searchParams.get("uid") || "";
972
+ const photo = url.searchParams.get("photo") || void 0;
973
+ res.writeHead(200, { "Content-Type": "text/html" });
974
+ res.end(`<!DOCTYPE html>
975
+ <html>
976
+ <head><title>Shipwell</title>
977
+ <style>
978
+ body { background: #0a0a0f; color: #e4e4e7; font-family: -apple-system, BlinkMacSystemFont, sans-serif; display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; }
979
+ .card { text-align: center; padding: 3rem; }
980
+ .icon { font-size: 3rem; margin-bottom: 1rem; }
981
+ h1 { font-size: 1.5rem; margin: 0 0 0.5rem; }
982
+ p { color: #71717a; font-size: 0.875rem; margin: 0.25rem 0; }
983
+ .name { color: #818cf8; font-weight: 600; }
984
+ .hint { margin-top: 1.5rem; padding: 1rem; background: #18181b; border-radius: 0.75rem; border: 1px solid #27272a; }
985
+ code { color: #22d3ee; font-size: 0.8rem; }
986
+ </style>
987
+ </head>
988
+ <body>
989
+ <div class="card">
990
+ <div class="icon">\u26F5</div>
991
+ <h1>Welcome to Shipwell</h1>
992
+ <p>Logged in as <span class="name">${name}</span></p>
993
+ <p style="margin-top: 1rem; color: #52525b;">You can close this tab and return to your terminal.</p>
994
+ <div class="hint">
995
+ <p style="color: #a1a1aa; margin-bottom: 0.5rem;">Next, set your API key:</p>
996
+ <code>shipwell config set api-key sk-ant-...</code>
997
+ </div>
998
+ </div>
999
+ </body>
1000
+ </html>`);
1001
+ server.close();
1002
+ resolve({ name, email, uid, photo });
1003
+ } else {
1004
+ res.writeHead(404);
1005
+ res.end("Not found");
1006
+ }
1007
+ });
1008
+ server.listen(0, "127.0.0.1", () => {
1009
+ const addr = server.address();
1010
+ const port = typeof addr === "object" && addr ? addr.port : 0;
1011
+ openBrowser(`${baseUrl}/cli-auth?port=${port}`);
1012
+ });
1013
+ setTimeout(() => {
1014
+ server.close();
1015
+ reject(new Error("Authentication timed out (5 minutes)"));
1016
+ }, 5 * 60 * 1e3);
1017
+ });
1018
+ }
1019
+
1020
+ // src/commands/login.ts
1021
+ var accent2 = chalk2.hex("#6366f1");
1022
+ async function loginCommand() {
1023
+ const existing = getUser();
1024
+ if (existing) {
1025
+ console.log();
1026
+ console.log(` Already logged in as ${accent2(existing.name)} (${chalk2.dim(existing.email)})`);
1027
+ console.log(chalk2.dim(" Run 'shipwell logout' first to switch accounts."));
1028
+ console.log();
1029
+ return;
1030
+ }
1031
+ console.log();
1032
+ console.log(` ${accent2("\u26F5")} Opening browser to sign in...`);
1033
+ console.log();
1034
+ const spinner = ora2({ text: "Waiting for authentication...", color: "cyan", prefixText: " " }).start();
1035
+ try {
1036
+ const result = await startAuthFlow("https://shipwell.app");
1037
+ setUser(result);
1038
+ spinner.succeed(`Logged in as ${accent2(result.name)} (${chalk2.dim(result.email)})`);
1039
+ console.log();
1040
+ console.log(chalk2.dim(" Next, set your API key:"));
1041
+ console.log(` ${chalk2.cyan("shipwell config set api-key")} ${chalk2.dim("sk-ant-...")}`);
1042
+ console.log();
1043
+ } catch (err) {
1044
+ spinner.fail(err.message);
1045
+ process.exit(1);
1046
+ }
1047
+ }
1048
+
1049
+ // src/commands/logout.ts
1050
+ import chalk3 from "chalk";
1051
+ var accent3 = chalk3.hex("#6366f1");
1052
+ function logoutCommand() {
1053
+ const user = getUser();
1054
+ if (!user) {
1055
+ console.log();
1056
+ console.log(chalk3.dim(" Not logged in."));
1057
+ console.log();
1058
+ return;
1059
+ }
1060
+ clearUser();
1061
+ console.log();
1062
+ console.log(` ${chalk3.green("\u2713")} Logged out ${accent3(user.name)}`);
1063
+ console.log();
1064
+ }
1065
+
1066
+ // src/commands/whoami.ts
1067
+ import chalk4 from "chalk";
1068
+ var accent4 = chalk4.hex("#6366f1");
1069
+ var dim2 = chalk4.dim;
1070
+ function whoamiCommand() {
1071
+ const user = getUser();
1072
+ const apiKey = getApiKey();
1073
+ const model = getModel();
1074
+ console.log();
1075
+ if (user) {
1076
+ console.log(` ${accent4("\u26F5")} ${chalk4.bold(user.name)}`);
1077
+ console.log(` ${dim2(user.email)}`);
1078
+ } else {
1079
+ console.log(` ${dim2("Not logged in.")} Run ${chalk4.cyan("shipwell login")} to sign in.`);
1080
+ }
1081
+ console.log();
1082
+ console.log(` ${dim2("API Key")} ${apiKey ? chalk4.green("\u25CF") + " configured " + dim2(`(${apiKey.slice(0, 12)}...)`) : chalk4.yellow("\u25CF") + " not set"}`);
1083
+ console.log(` ${dim2("Model")} ${accent4(model || DEFAULT_MODEL)}${!model ? dim2(" (default)") : ""}`);
1084
+ console.log(` ${dim2("Config")} ${dim2("~/.shipwell/config.json")}`);
1085
+ console.log();
1086
+ }
1087
+
1088
+ // src/commands/config-cmd.ts
1089
+ import chalk5 from "chalk";
1090
+ var accent5 = chalk5.hex("#6366f1");
1091
+ var dim3 = chalk5.dim;
1092
+ function configShowCommand() {
1093
+ const config2 = loadConfig();
1094
+ console.log();
1095
+ console.log(` ${chalk5.bold("Configuration")} ${dim3("~/.shipwell/config.json")}`);
1096
+ console.log();
1097
+ console.log(` ${dim3("api-key")} ${config2.apiKey ? chalk5.green("\u25CF") + " " + dim3(`${config2.apiKey.slice(0, 12)}...`) : chalk5.yellow("\u25CF") + " not set"}`);
1098
+ console.log(` ${dim3("model")} ${accent5(config2.model || DEFAULT_MODEL)}${!config2.model ? dim3(" (default)") : ""}`);
1099
+ if (config2.user) {
1100
+ console.log(` ${dim3("user")} ${config2.user.name} ${dim3(`(${config2.user.email})`)}`);
1101
+ }
1102
+ console.log();
1103
+ console.log(dim3(" Set values:"));
1104
+ console.log(` ${chalk5.cyan("shipwell config set api-key")} ${dim3("<key>")}`);
1105
+ console.log(` ${chalk5.cyan("shipwell config set model")} ${dim3("<model-id>")}`);
1106
+ console.log();
1107
+ }
1108
+ function configSetCommand(key, value) {
1109
+ switch (key) {
1110
+ case "api-key": {
1111
+ if (!value.startsWith("sk-ant-")) {
1112
+ console.log(chalk5.yellow("\n Warning: API key doesn't look like an Anthropic key (expected sk-ant-...).\n"));
1113
+ }
1114
+ setApiKey(value);
1115
+ console.log(`
1116
+ ${chalk5.green("\u2713")} API key saved
1117
+ `);
1118
+ break;
1119
+ }
1120
+ case "model": {
1121
+ const validIds = AVAILABLE_MODELS.map((m) => m.id);
1122
+ if (!validIds.includes(value)) {
1123
+ console.error(chalk5.red(`
1124
+ Unknown model: ${value}`));
1125
+ console.error(dim3(` Available: ${validIds.join(", ")}
1126
+ `));
1127
+ process.exit(1);
1128
+ }
1129
+ setModel(value);
1130
+ console.log(`
1131
+ ${chalk5.green("\u2713")} Default model set to ${accent5(value)}
1132
+ `);
1133
+ break;
1134
+ }
1135
+ default:
1136
+ console.error(chalk5.red(`
1137
+ Unknown config key: ${key}`));
1138
+ console.error(dim3(` Available keys: api-key, model
1139
+ `));
1140
+ process.exit(1);
1141
+ }
1142
+ }
1143
+ function configDeleteCommand(key) {
1144
+ switch (key) {
1145
+ case "api-key":
1146
+ clearApiKey();
1147
+ console.log(`
1148
+ ${chalk5.green("\u2713")} API key removed
1149
+ `);
1150
+ break;
1151
+ case "model":
1152
+ setModel("");
1153
+ console.log(`
1154
+ ${chalk5.green("\u2713")} Model reset to default (${DEFAULT_MODEL})
1155
+ `);
1156
+ break;
1157
+ default:
1158
+ console.error(chalk5.red(`
1159
+ Unknown config key: ${key}`));
1160
+ process.exit(1);
1161
+ }
1162
+ }
1163
+
1164
+ // src/commands/models.ts
1165
+ import chalk6 from "chalk";
1166
+ var accent6 = chalk6.hex("#6366f1");
1167
+ var dim4 = chalk6.dim;
1168
+ function modelsCommand() {
1169
+ const currentModel = getModel() || DEFAULT_MODEL;
1170
+ console.log();
1171
+ console.log(` ${chalk6.bold("Available Models")}`);
1172
+ console.log();
1173
+ for (const m of AVAILABLE_MODELS) {
1174
+ const isCurrent = m.id === currentModel;
1175
+ const marker = isCurrent ? accent6("\u25CF") : dim4("\u25CB");
1176
+ const label = isCurrent ? chalk6.bold(m.label) : m.label;
1177
+ const id = dim4(m.id);
1178
+ const ctx = dim4(`${Math.round(m.contextWindow / 1e3)}K context`);
1179
+ const dflt = "default" in m && m.default ? chalk6.green(" default") : "";
1180
+ const active = isCurrent ? accent6(" \u2190 active") : "";
1181
+ console.log(` ${marker} ${label} ${id} ${ctx}${dflt}${active}`);
1182
+ }
1183
+ console.log();
1184
+ console.log(dim4(` Switch: shipwell config set model <model-id>`));
1185
+ console.log();
1186
+ }
1187
+
882
1188
  // src/index.ts
1189
+ var VERSION = "0.2.1";
1190
+ var accent7 = chalk7.hex("#6366f1");
1191
+ var dim5 = chalk7.dim;
1192
+ var bold2 = chalk7.bold;
1193
+ function stripAnsi(s) {
1194
+ return s.replace(/\x1b\[[0-9;]*m/g, "");
1195
+ }
1196
+ function visLen(s) {
1197
+ return stripAnsi(s).length;
1198
+ }
1199
+ function padR(s, w) {
1200
+ const gap = w - visLen(s);
1201
+ return gap > 0 ? s + " ".repeat(gap) : s;
1202
+ }
1203
+ function centerStr(s, w) {
1204
+ const gap = w - visLen(s);
1205
+ if (gap <= 0) return s;
1206
+ const l = Math.floor(gap / 2);
1207
+ return " ".repeat(l) + s + " ".repeat(gap - l);
1208
+ }
1209
+ function showBanner() {
1210
+ const user = getUser();
1211
+ const apiKey = getApiKey();
1212
+ const storedModel = getModel();
1213
+ const modelId = storedModel || DEFAULT_MODEL;
1214
+ const modelObj = AVAILABLE_MODELS.find((m) => m.id === modelId);
1215
+ const modelLabel = modelObj?.label || modelId;
1216
+ const termW = process.stdout.columns || 90;
1217
+ const W = Math.min(Math.max(termW, 80), 100);
1218
+ const LW = Math.floor((W - 7) / 2);
1219
+ const RW = W - 7 - LW;
1220
+ const g = dim5;
1221
+ const row = (l, r) => `${g("\u2502")} ${padR(l, LW)} ${g("\u2502")} ${padR(r, RW)} ${g("\u2502")}`;
1222
+ const empty = () => row("", "");
1223
+ const title = `${accent7("\u26F5 Shipwell")} ${g(`v${VERSION}`)}`;
1224
+ const titleVis = visLen(title);
1225
+ const dashes = W - 5 - titleVis;
1226
+ const top = `${g("\u256D\u2500")} ${title} ${g("\u2500".repeat(Math.max(0, dashes)))}${g("\u256E")}`;
1227
+ const bot = `${g("\u2570")}${g("\u2500".repeat(W - 2))}${g("\u256F")}`;
1228
+ const lines = [];
1229
+ lines.push("");
1230
+ lines.push(top);
1231
+ lines.push(empty());
1232
+ const welcome = user ? `Welcome back, ${accent7(user.name)}!` : `Welcome to ${accent7("Shipwell")}`;
1233
+ lines.push(row(centerStr(welcome, LW), bold2("Analysis")));
1234
+ lines.push(row("", `${chalk7.cyan("audit")} ${g("<path>")} ${g("Security audit")}`));
1235
+ lines.push(row(centerStr("\u26F5", LW), `${chalk7.cyan("migrate")} ${g("<path>")} ${g("Migration plan")}`));
1236
+ lines.push(row(centerStr(g("~^~^~^~^~"), LW), `${chalk7.cyan("refactor")} ${g("<path>")} ${g("Refactor analysis")}`));
1237
+ lines.push(row("", `${chalk7.cyan("docs")} ${g("<path>")} ${g("Documentation")}`));
1238
+ lines.push(row("", `${chalk7.cyan("upgrade")} ${g("<path>")} ${g("Dep upgrade plan")}`));
1239
+ const keyDot = apiKey ? chalk7.green("\u25CF") : chalk7.yellow("\u25CB");
1240
+ const keyText = apiKey ? g("API Key") : chalk7.yellow("No API Key");
1241
+ const info = `${accent7(modelLabel)} \xB7 ${keyDot} ${keyText}`;
1242
+ lines.push(row(centerStr(info, LW), g("\u2500".repeat(RW))));
1243
+ if (user) {
1244
+ lines.push(row(centerStr(g(user.email), LW), bold2("Account & Config")));
1245
+ } else {
1246
+ lines.push(row(
1247
+ centerStr(`${g("Run")} ${chalk7.cyan("shipwell login")} ${g("to start")}`, LW),
1248
+ bold2("Account & Config")
1249
+ ));
1250
+ }
1251
+ lines.push(row("", `${chalk7.cyan("login")} ${g("Sign in with Google")}`));
1252
+ lines.push(row("", `${chalk7.cyan("logout")} ${g("Sign out")}`));
1253
+ lines.push(row("", `${chalk7.cyan("config")} ${g("View/set configuration")}`));
1254
+ lines.push(row("", `${chalk7.cyan("models")} ${g("Available Claude models")}`));
1255
+ lines.push(row("", `${chalk7.cyan("update")} ${g("Update to latest version")}`));
1256
+ lines.push(empty());
1257
+ lines.push(bot);
1258
+ lines.push("");
1259
+ console.log(lines.join("\n"));
1260
+ }
883
1261
  var program = new Command();
884
- program.name("shipwell").description("Full Codebase Autopilot \u2014 deep cross-file analysis powered by Claude").version("0.1.0");
1262
+ program.name("shipwell").description("Full Codebase Autopilot \u2014 deep cross-file analysis powered by Claude").version(VERSION).action(() => {
1263
+ showBanner();
1264
+ });
885
1265
  var operations = ["audit", "migrate", "refactor", "docs", "upgrade"];
886
1266
  var opDesc = {
887
1267
  audit: "Run a security audit on a codebase",
@@ -891,8 +1271,48 @@ var opDesc = {
891
1271
  upgrade: "Analyze dependencies & plan safe upgrades"
892
1272
  };
893
1273
  for (const op of operations) {
894
- program.command(op).description(opDesc[op] || `Run ${op} analysis on a codebase`).argument("<source>", "Local path or GitHub URL").option("-k, --api-key <key>", "Anthropic API key (or set ANTHROPIC_API_KEY env var)").option("-m, --model <model>", "Claude model to use (or set SHIPWELL_MODEL env var)").option("-t, --target <target>", "Migration target (for migrate operation)").option("-c, --context <context>", "Additional context for the analysis").option("-r, --raw", "Also print raw streaming output").action((source, options) => {
1274
+ program.command(op).description(opDesc[op] || `Run ${op} analysis on a codebase`).argument("<source>", "Local path or GitHub URL").option("-k, --api-key <key>", "Anthropic API key").option("-m, --model <model>", "Claude model to use").option("-t, --target <target>", "Migration target (for migrate)").option("-c, --context <context>", "Additional context for the analysis").option("-r, --raw", "Also print raw streaming output").action((source, options) => {
895
1275
  analyzeCommand(op, source, options);
896
1276
  });
897
1277
  }
1278
+ program.command("login").description("Sign in with Google via browser").action(() => {
1279
+ loginCommand();
1280
+ });
1281
+ program.command("logout").description("Sign out and clear stored credentials").action(() => {
1282
+ logoutCommand();
1283
+ });
1284
+ program.command("whoami").description("Show current user, API key status, and model").action(() => {
1285
+ whoamiCommand();
1286
+ });
1287
+ var config = program.command("config").description("View or modify configuration").action(() => {
1288
+ configShowCommand();
1289
+ });
1290
+ config.command("set").description("Set a config value (api-key, model)").argument("<key>", "Config key (api-key, model)").argument("<value>", "Config value").action((key, value) => {
1291
+ configSetCommand(key, value);
1292
+ });
1293
+ config.command("delete").description("Delete a config value").argument("<key>", "Config key (api-key, model)").action((key) => {
1294
+ configDeleteCommand(key);
1295
+ });
1296
+ program.command("models").description("List available Claude models").action(() => {
1297
+ modelsCommand();
1298
+ });
1299
+ program.command("update").description("Update Shipwell CLI to the latest version").action(async () => {
1300
+ const { execSync } = await import("child_process");
1301
+ const ora3 = (await import("ora")).default;
1302
+ const spinner = ora3({ text: "Checking for updates...", prefixText: " " }).start();
1303
+ try {
1304
+ const latest = execSync("npm view @shipwellapp/cli version", { encoding: "utf-8" }).trim();
1305
+ if (latest === VERSION) {
1306
+ spinner.succeed(`Already on the latest version (${accent7(VERSION)})`);
1307
+ } else {
1308
+ spinner.text = `Updating to v${latest}...`;
1309
+ execSync("npm install -g @shipwellapp/cli@latest", { stdio: "pipe" });
1310
+ spinner.succeed(`Updated to v${accent7(latest)} ${dim5(`(was v${VERSION})`)}`);
1311
+ }
1312
+ } catch {
1313
+ spinner.fail("Update failed. Try manually:");
1314
+ console.log(` ${chalk7.cyan("npm install -g @shipwellapp/cli@latest")}`);
1315
+ }
1316
+ console.log();
1317
+ });
898
1318
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shipwellapp/cli",
3
- "version": "0.1.2",
3
+ "version": "0.2.1",
4
4
  "description": "Full Codebase Autopilot — deep cross-file analysis powered by Claude",
5
5
  "type": "module",
6
6
  "bin": {
@@ -51,6 +51,7 @@
51
51
  "simple-git": "^3.27.0"
52
52
  },
53
53
  "devDependencies": {
54
+ "@shipwell/core": "workspace:*",
54
55
  "@types/node": "^22.10.0",
55
56
  "tsup": "^8.5.1",
56
57
  "typescript": "^5.7.0"