@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.
- package/dist/index.js +430 -10
- 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
|
|
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
|
|
782
|
-
console.error(dim("
|
|
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(
|
|
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
|
|
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
|
|
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"
|