seaworthycode 1.0.0

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/README.md ADDED
@@ -0,0 +1,72 @@
1
+ # @seaworthy/cli
2
+
3
+ The CLI distribution surface for Seaworthy. Thin wrapper around `@seaworthy/core`.
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ # Scan current directory
9
+ npx seaworthy .
10
+
11
+ # Scan specific directory
12
+ npx seaworthy /path/to/project
13
+
14
+ # Output JSON
15
+ npx seaworthy --json /path/to/project
16
+
17
+ # Write HTML report
18
+ npx seaworthy --output report.html /path/to/project
19
+
20
+ # Override tier
21
+ npx seaworthy --tier free /path/to/project
22
+
23
+ # Use a specific provider (e.g. Kimi, DeepSeek, Ollama)
24
+ npx seaworthy --provider kimi /path/to/project
25
+ ```
26
+
27
+ ## Flags
28
+
29
+ | Flag | Description |
30
+ |------|-------------|
31
+ | `[path]` | Target directory (default: `.`) |
32
+ | `--json` | Output JSON to stdout |
33
+ | `--output <path>` | Write HTML report to file |
34
+ | `--tier <tier>` | Override tier (free/pro) |
35
+ | `--provider <provider>` | LLM provider (anthropic, openai, kimi, deepseek, gemini, ollama, openrouter) |
36
+ | `--help` | Show help |
37
+
38
+ ## Config
39
+
40
+ Config is stored in `~/.seaworthy/config` with `0600` permissions.
41
+
42
+ ```json
43
+ {
44
+ "licenseKey": "your-key",
45
+ "llmProvider": "anthropic",
46
+ "llmApiKey": "sk-...",
47
+ "llmModel": "claude-3-5-sonnet-latest",
48
+ "llmApiUrl": "https://api.anthropic.com"
49
+ }
50
+ ```
51
+
52
+ ### Provider presets
53
+
54
+ Vibe-coder friendly aliases so you don't need to remember base URLs:
55
+
56
+ | Provider | Preset | Default model |
57
+ |----------|--------|---------------|
58
+ | Anthropic | `anthropic` | `claude-3-5-sonnet-latest` |
59
+ | OpenAI | `openai` | `gpt-4o-mini` |
60
+ | Kimi (Moonshot) | `kimi` / `moonshot` | `kimi-k2-5` |
61
+ | DeepSeek | `deepseek` | `deepseek-chat` |
62
+ | Google Gemini | `gemini` | `gemini-2.0-flash` |
63
+ | Ollama (local) | `ollama` | `llama3.2` |
64
+ | OpenRouter | `openrouter` | `anthropic/claude-3.5-sonnet` |
65
+
66
+ Set `llmModel` to override the default. Set `llmApiUrl` only if you need a custom endpoint.
67
+
68
+ ## Running Tests
69
+
70
+ ```bash
71
+ pnpm test
72
+ ```
@@ -0,0 +1,2 @@
1
+
2
+ export { }
package/dist/index.js ADDED
@@ -0,0 +1,544 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { config as loadEnv } from "dotenv";
5
+ import { existsSync } from "fs";
6
+ import { dirname as dirname3, resolve as resolve3 } from "path";
7
+ import { fileURLToPath as fileURLToPath2 } from "url";
8
+ import { cac } from "cac";
9
+ import { createRequire } from "module";
10
+
11
+ // src/cli-action.ts
12
+ import { stat } from "fs/promises";
13
+ import { resolve as resolve2 } from "path";
14
+
15
+ // src/commands/scan.ts
16
+ import {
17
+ ScanRunner,
18
+ TerminalReporter,
19
+ JSONReporter,
20
+ HTMLReporter,
21
+ SarifReporter,
22
+ createProvider,
23
+ getCopy as getCoreCopy
24
+ } from "@seaworthy/core";
25
+ import { promises as fs } from "fs";
26
+ import { dirname, resolve, relative, isAbsolute } from "path";
27
+
28
+ // src/copy/index.ts
29
+ import { SITE_URL } from "@seaworthy/core";
30
+ var copy = {
31
+ "cli.help.description": "Seaworthy \u2014 static analysis for vibe coders",
32
+ "cli.help.usage": "Usage: seaworthy [path] [options]",
33
+ "cli.help.options": "Options",
34
+ "cli.help.learnMore": `Documentation: ${SITE_URL}/docs`,
35
+ "cli.scanning": "Scanning...",
36
+ "cli.reportWritten": (path) => `Report written to ${path}`,
37
+ "cli.error.notADirectory": (path) => `Path is not a directory: ${path}`,
38
+ "cli.error.notFound": (path) => `Path not found: ${path}`,
39
+ "cli.error.mutuallyExclusive": "--json and --output cannot be used together",
40
+ "cli.error.outputInsideTarget": "Cannot write report inside the scanned directory.",
41
+ "cli.help.flag.sarif": "Write SARIF report to file",
42
+ "cli.help.flag.sarifStdout": "Write SARIF report to stdout",
43
+ "cli.sarif.mutually_exclusive": "Flags --sarif and --sarif-stdout cannot be used together.",
44
+ "cli.sarif.stdout_conflict": "Flags --sarif-stdout and --json cannot be used together. Use --output to send JSON to a file.",
45
+ "cli.error.llmRequired": "LLM API key required for Pro checks. Set it with seaworthy config or the SEAWORTHY_LLM_API_KEY environment variable.",
46
+ "cli.warn.llmSkipped": "Pro checks skipped: no LLM API key configured. Set it with seaworthy config or the SEAWORTHY_LLM_API_KEY environment variable to enable semantic analysis.",
47
+ "cli.error.config.missingKey": "Missing config key. Usage: seaworthy config <set|get> <key>",
48
+ "cli.error.config.missingValue": "Missing config value. Usage: seaworthy config set <key> <value>",
49
+ "cli.error.config.unknownKey": "Unknown config key. Valid keys: licenseKey, llmProvider, llmApiKey, llmModel, llmApiUrl",
50
+ "cli.error.config.invalidProvider": "Invalid provider. Valid providers: anthropic, openai, kimi, moonshot, deepseek, gemini, ollama, openrouter",
51
+ "cli.error.config.invalidAction": "Invalid config action. Usage: seaworthy config <set|get|list>",
52
+ "cli.config.setSuccess": (key) => `${key} updated.`
53
+ };
54
+ function getCopy(key, ...args) {
55
+ const value = copy[key];
56
+ if (typeof value === "function") {
57
+ return value(...args);
58
+ }
59
+ return value;
60
+ }
61
+
62
+ // src/commands/createCommand.ts
63
+ function createCommand(definition, options) {
64
+ const requireLicense = options?.requireLicense ?? true;
65
+ return {
66
+ ...definition,
67
+ handler: async (args, ctx) => {
68
+ if (requireLicense && !ctx.tier) {
69
+ return { ok: false, code: "errors.license.invalid" };
70
+ }
71
+ try {
72
+ return await definition.handler(args, ctx);
73
+ } catch (err) {
74
+ return {
75
+ ok: false,
76
+ code: "errors.unexpected",
77
+ detail: err instanceof Error ? err.message : String(err)
78
+ };
79
+ }
80
+ }
81
+ };
82
+ }
83
+
84
+ // src/commands/scan.ts
85
+ var FREE_SCAN_TIMEOUT_MS = 6e4;
86
+ var PRO_SCAN_TIMEOUT_MS = 6e5;
87
+ function resolveScanTimeoutMs(tier) {
88
+ const env = process.env.SEAWORTHY_SCAN_TIMEOUT_MS;
89
+ if (env) {
90
+ const parsed = Number(env);
91
+ if (Number.isFinite(parsed) && parsed > 0) {
92
+ return parsed;
93
+ }
94
+ }
95
+ return tier === "free" ? FREE_SCAN_TIMEOUT_MS : PRO_SCAN_TIMEOUT_MS;
96
+ }
97
+ function withTimeout(promise, ms, message) {
98
+ return new Promise((resolve4, reject) => {
99
+ const timer = setTimeout(() => reject(new Error(message)), ms);
100
+ promise.then((value) => {
101
+ clearTimeout(timer);
102
+ resolve4(value);
103
+ }).catch((err) => {
104
+ clearTimeout(timer);
105
+ reject(err);
106
+ });
107
+ });
108
+ }
109
+ function isInsideDirectory(outputPath, targetDir) {
110
+ const resolvedOutput = resolve(outputPath);
111
+ const resolvedTarget = resolve(targetDir);
112
+ const rel = relative(resolvedTarget, resolvedOutput);
113
+ return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
114
+ }
115
+ async function executeScan(args, ctx) {
116
+ if (args.sarif && args.sarifStdout) {
117
+ return { ok: false, code: "cli.sarif.mutually_exclusive" };
118
+ }
119
+ if (args.sarifStdout && args.format === "json") {
120
+ return { ok: false, code: "cli.sarif.stdout_conflict" };
121
+ }
122
+ if (ctx.tier === "pro" && !args.llmApiKey && !process.env.SEAWORTHY_SKIP_LLM_CHECK) {
123
+ console.error(getCopy("cli.warn.llmSkipped"));
124
+ }
125
+ let llm = null;
126
+ if (args.llmApiKey && args.llmProvider && !process.env.SEAWORTHY_SKIP_LLM_CHECK) {
127
+ llm = createProvider({ provider: args.llmProvider, apiKey: args.llmApiKey, model: args.llmModel, baseUrl: args.llmApiUrl });
128
+ }
129
+ const runner = new ScanRunner();
130
+ const scanTimeoutMs = resolveScanTimeoutMs(ctx.tier);
131
+ const result = await withTimeout(
132
+ runner.run({
133
+ targetDir: args.targetDir,
134
+ tier: ctx.tier,
135
+ llm,
136
+ maxFileSize: args.maxFileSize,
137
+ debug: args.debug,
138
+ baseline: args.baseline,
139
+ minConfidence: args.minConfidence
140
+ }),
141
+ scanTimeoutMs,
142
+ getCoreCopy("errors.scan.timed_out", Math.round(scanTimeoutMs / 1e3))
143
+ );
144
+ const reportInput = {
145
+ findings: result.findings,
146
+ targetDir: args.targetDir,
147
+ tier: ctx.tier,
148
+ licensedTo: ctx.licensedTo ?? "free tier",
149
+ scannedFileCount: result.scannedFileCount
150
+ };
151
+ let primaryOutput;
152
+ switch (args.format) {
153
+ case "json":
154
+ primaryOutput = new JSONReporter().generate(reportInput);
155
+ break;
156
+ case "html":
157
+ primaryOutput = new HTMLReporter().generate(reportInput);
158
+ break;
159
+ case "terminal":
160
+ default:
161
+ primaryOutput = new TerminalReporter().generate(reportInput);
162
+ break;
163
+ }
164
+ let sarifOutput;
165
+ if (args.sarif || args.sarifStdout) {
166
+ sarifOutput = new SarifReporter().generate(reportInput);
167
+ }
168
+ if (args.sarif && sarifOutput) {
169
+ const sarifPath = resolve(args.sarif);
170
+ if (isInsideDirectory(sarifPath, args.targetDir)) {
171
+ return { ok: false, code: "cli.error.outputInsideTarget" };
172
+ }
173
+ await fs.mkdir(dirname(sarifPath), { recursive: true });
174
+ await fs.writeFile(sarifPath, sarifOutput);
175
+ }
176
+ if (args.output) {
177
+ const outputPath = resolve(args.output);
178
+ if (isInsideDirectory(outputPath, args.targetDir)) {
179
+ return { ok: false, code: "cli.error.outputInsideTarget" };
180
+ }
181
+ await fs.mkdir(dirname(outputPath), { recursive: true });
182
+ await fs.writeFile(outputPath, primaryOutput);
183
+ if (args.failOnFindings && result.findings.length > 0) {
184
+ return { ok: false, code: "scan.findings_found" };
185
+ }
186
+ return { ok: true, output: getCopy("cli.reportWritten", outputPath) };
187
+ }
188
+ if (args.failOnFindings && result.findings.length > 0) {
189
+ return { ok: false, code: "scan.findings_found" };
190
+ }
191
+ const stdoutOutput = args.sarifStdout ? sarifOutput : primaryOutput;
192
+ return { ok: true, output: stdoutOutput };
193
+ }
194
+ var scanCommand = createCommand({
195
+ name: "scan",
196
+ description: "Scan a directory for issues",
197
+ handler: executeScan
198
+ });
199
+
200
+ // src/config/store.ts
201
+ import { promises as fs2 } from "fs";
202
+ import { homedir } from "os";
203
+ import { join } from "path";
204
+ function getConfigDir() {
205
+ return process.env.SEAWORTHY_CONFIG_DIR ?? join(homedir(), ".seaworthy");
206
+ }
207
+ function getConfigFile() {
208
+ return join(getConfigDir(), "config");
209
+ }
210
+ async function readConfig() {
211
+ try {
212
+ const raw = await fs2.readFile(getConfigFile(), "utf-8");
213
+ return JSON.parse(raw);
214
+ } catch {
215
+ return {};
216
+ }
217
+ }
218
+ async function writeConfig(config) {
219
+ await fs2.mkdir(getConfigDir(), { recursive: true });
220
+ await fs2.writeFile(getConfigFile(), JSON.stringify(config, null, 2), {
221
+ mode: 384
222
+ });
223
+ await fs2.chmod(getConfigFile(), 384);
224
+ }
225
+
226
+ // src/auth/fs-credential-provider.ts
227
+ var FsCredentialProvider = class {
228
+ async getCredentials() {
229
+ const config = await readConfig();
230
+ return {
231
+ licenseKey: config.licenseKey,
232
+ llmApiKey: config.llmApiKey,
233
+ llmProvider: config.llmProvider,
234
+ llmModel: config.llmModel,
235
+ llmApiUrl: config.llmApiUrl
236
+ };
237
+ }
238
+ };
239
+
240
+ // src/cli-action.ts
241
+ import {
242
+ LicenseClient,
243
+ resolveAuth,
244
+ LICENSE_SERVER_URL,
245
+ resolveErrorMessage
246
+ } from "@seaworthy/core";
247
+
248
+ // src/update-notifier.ts
249
+ import { readFile } from "fs/promises";
250
+ import { fileURLToPath } from "url";
251
+ import { dirname as dirname2, join as join2 } from "path";
252
+ var NPM_REGISTRY_URL = "https://registry.npmjs.org/seaworthy";
253
+ var UPDATE_TIMEOUT_MS = 3e3;
254
+ function isLowerVersion(current, latest) {
255
+ const parse = (v) => v.split(".").map((n) => Number(n));
256
+ const [cMajor = 0, cMinor = 0, cPatch = 0] = parse(current);
257
+ const [lMajor = 0, lMinor = 0, lPatch = 0] = parse(latest);
258
+ if (lMajor > cMajor) return true;
259
+ if (lMajor < cMajor) return false;
260
+ if (lMinor > cMinor) return true;
261
+ if (lMinor < cMinor) return false;
262
+ return lPatch > cPatch;
263
+ }
264
+ async function getInstalledVersion() {
265
+ const pkgPath = join2(dirname2(fileURLToPath(import.meta.url)), "../package.json");
266
+ const pkg = JSON.parse(await readFile(pkgPath, "utf-8"));
267
+ return pkg.version;
268
+ }
269
+ async function checkUpdate() {
270
+ const controller = new AbortController();
271
+ const timeout = setTimeout(() => controller.abort(), UPDATE_TIMEOUT_MS);
272
+ try {
273
+ const response = await fetch(NPM_REGISTRY_URL, { signal: controller.signal });
274
+ clearTimeout(timeout);
275
+ if (!response.ok) {
276
+ return void 0;
277
+ }
278
+ const data = await response.json();
279
+ const latest = data["dist-tags"]?.latest;
280
+ if (!latest) {
281
+ return void 0;
282
+ }
283
+ const installed = await getInstalledVersion();
284
+ if (isLowerVersion(installed, latest)) {
285
+ return `Update available: ${installed} \u2192 ${latest}. Run npm i -g seaworthy to update.`;
286
+ }
287
+ return void 0;
288
+ } catch {
289
+ clearTimeout(timeout);
290
+ return void 0;
291
+ }
292
+ }
293
+
294
+ // src/cli-action.ts
295
+ function resolveExitCode(code) {
296
+ const normalized = code.replace(/^errors\./, "");
297
+ if (["license.invalid", "license.expired"].includes(normalized)) return 1;
298
+ if ([
299
+ "config.invalid",
300
+ "fs.not_a_directory",
301
+ "fs.not_found",
302
+ "fs.permission_denied"
303
+ ].includes(normalized) || code.startsWith("cli.error.") || code.startsWith("cli.sarif."))
304
+ return 2;
305
+ if ([
306
+ "license.server_unreachable",
307
+ "license.rate_limited",
308
+ "network.unreachable",
309
+ "llm.timeout",
310
+ "llm.rate_limited"
311
+ ].includes(normalized))
312
+ return 3;
313
+ if (["scan.failed", "scan.timed_out"].includes(normalized)) return 4;
314
+ if (normalized === "scan.findings_found") return 5;
315
+ return 4;
316
+ }
317
+ async function runCli(options) {
318
+ const dir = options.targetDir ?? ".";
319
+ if (options.json && options.output) {
320
+ return {
321
+ code: resolveExitCode("cli.error.mutuallyExclusive"),
322
+ error: getCopy("cli.error.mutuallyExclusive")
323
+ };
324
+ }
325
+ if (options.sarif && options.sarifStdout) {
326
+ return {
327
+ code: resolveExitCode("cli.sarif.mutually_exclusive"),
328
+ error: getCopy("cli.sarif.mutually_exclusive")
329
+ };
330
+ }
331
+ if (options.sarifStdout && options.json) {
332
+ return {
333
+ code: resolveExitCode("cli.sarif.stdout_conflict"),
334
+ error: getCopy("cli.sarif.stdout_conflict")
335
+ };
336
+ }
337
+ const outputPath = options.output ? resolve2(options.output) : void 0;
338
+ try {
339
+ const s = await stat(dir);
340
+ if (!s.isDirectory()) {
341
+ return {
342
+ code: resolveExitCode("fs.not_a_directory"),
343
+ error: getCopy("cli.error.notADirectory", dir)
344
+ };
345
+ }
346
+ } catch {
347
+ return {
348
+ code: resolveExitCode("fs.not_found"),
349
+ error: getCopy("cli.error.notFound", dir)
350
+ };
351
+ }
352
+ const provider = new FsCredentialProvider();
353
+ const license = new LicenseClient({ baseUrl: LICENSE_SERVER_URL });
354
+ const authResult = await resolveAuth(provider, license);
355
+ if (!authResult.ok) {
356
+ const message = resolveErrorMessage(authResult.code);
357
+ return { code: resolveExitCode(authResult.code), error: message };
358
+ }
359
+ const auth = authResult.value;
360
+ const allowedTiers = {
361
+ free: ["free"],
362
+ pro: ["free", "pro"]
363
+ };
364
+ const effectiveTier = options.tier ?? auth.tier;
365
+ if (!Object.prototype.hasOwnProperty.call(allowedTiers, auth.tier) || !allowedTiers[auth.tier].includes(effectiveTier)) {
366
+ return {
367
+ code: resolveExitCode("invalid.tier"),
368
+ error: resolveErrorMessage("invalid.tier")
369
+ };
370
+ }
371
+ const format = options.json ? "json" : outputPath ? "html" : "terminal";
372
+ const selectedProvider = options.provider ?? auth.llmProvider;
373
+ const providerChanged = Boolean(options.provider && options.provider !== auth.llmProvider);
374
+ const args = {
375
+ targetDir: dir,
376
+ format,
377
+ output: outputPath,
378
+ sarif: options.sarif,
379
+ sarifStdout: options.sarifStdout,
380
+ failOnFindings: options.failOnFindings,
381
+ llmApiKey: auth.llmApiKey,
382
+ llmProvider: selectedProvider,
383
+ llmModel: providerChanged ? void 0 : auth.llmModel,
384
+ llmApiUrl: providerChanged ? void 0 : auth.llmApiUrl,
385
+ maxFileSize: options.maxFileSize,
386
+ debug: options.debug,
387
+ baseline: options.baseline,
388
+ minConfidence: options.minConfidence
389
+ };
390
+ const updateStart = Date.now();
391
+ const updatePromise = checkUpdate().catch(() => void 0);
392
+ let result;
393
+ try {
394
+ result = await scanCommand.handler(args, { tier: effectiveTier, licensedTo: auth.licensedTo });
395
+ } catch (err) {
396
+ const message = err instanceof Error ? err.message : String(err);
397
+ return { code: resolveExitCode("scan.failed"), error: message };
398
+ }
399
+ if (!result.ok) {
400
+ const message = resolveErrorMessage(result.code);
401
+ return { code: resolveExitCode(result.code), error: message };
402
+ }
403
+ const remaining = Math.max(0, 3e3 - (Date.now() - updateStart));
404
+ const updateMessage = await Promise.race([
405
+ updatePromise,
406
+ new Promise((resolve4) => setTimeout(() => resolve4(void 0), remaining))
407
+ ]);
408
+ return {
409
+ code: 0,
410
+ output: result.output ?? "",
411
+ updateMessage
412
+ };
413
+ }
414
+
415
+ // src/commands/config.ts
416
+ import { listPresetProviders } from "@seaworthy/core";
417
+ var VALID_KEYS = ["licenseKey", "llmProvider", "llmApiKey", "llmModel", "llmApiUrl"];
418
+ function formatOutputValue(key, value) {
419
+ if (value === void 0) return "";
420
+ if (key === "llmApiKey" || key === "licenseKey") return "********";
421
+ return value;
422
+ }
423
+ async function executeConfig(args) {
424
+ const config = await readConfig();
425
+ if (args.action === "list") {
426
+ const lines = [];
427
+ for (const key2 of VALID_KEYS) {
428
+ const value = config[key2];
429
+ lines.push(`${key2}=${formatOutputValue(key2, value)}`);
430
+ }
431
+ return { ok: true, output: lines.join("\n") };
432
+ }
433
+ if (!args.key) {
434
+ return { ok: false, code: "cli.error.config.missingKey" };
435
+ }
436
+ if (!VALID_KEYS.includes(args.key)) {
437
+ return { ok: false, code: "cli.error.config.unknownKey", detail: args.key };
438
+ }
439
+ const key = args.key;
440
+ if (args.action === "get") {
441
+ const value = config[key];
442
+ return { ok: true, output: formatOutputValue(key, value) };
443
+ }
444
+ if (args.action === "set") {
445
+ if (args.value === void 0) {
446
+ return { ok: false, code: "cli.error.config.missingValue" };
447
+ }
448
+ if (key === "llmProvider" && !listPresetProviders().includes(args.value)) {
449
+ return { ok: false, code: "cli.error.config.invalidProvider", detail: args.value };
450
+ }
451
+ const next = { ...config, [key]: args.value };
452
+ await writeConfig(next);
453
+ return { ok: true, output: getCopy("cli.config.setSuccess", args.key) };
454
+ }
455
+ return { ok: false, code: "cli.error.config.invalidAction" };
456
+ }
457
+ var configCommand = createCommand(
458
+ {
459
+ name: "config",
460
+ description: "Manage Seaworthy configuration",
461
+ handler: async (args) => executeConfig(args)
462
+ },
463
+ { requireLicense: false }
464
+ );
465
+
466
+ // src/index.ts
467
+ loadEnv({ path: resolve3(process.cwd(), ".env") });
468
+ var repoRootEnv = resolve3(dirname3(fileURLToPath2(import.meta.url)), "../../..", ".env");
469
+ if (existsSync(repoRootEnv)) {
470
+ loadEnv({ path: repoRootEnv, override: false });
471
+ }
472
+ var require2 = createRequire(import.meta.url);
473
+ var { version } = require2("../package.json");
474
+ var cli = cac("seaworthy");
475
+ cli.version(version);
476
+ cli.command("[path]", "Scan a directory for issues").option("--json", "Output JSON to stdout").option("--output <path>", "Write HTML report to file").option("--sarif <path>", "Write SARIF report to file").option("--sarif-stdout", "Write SARIF report to stdout").option("--fail-on-findings", "Exit with code 5 when findings are found").option("--tier <tier>", "Select tier (free, pro)").option("--provider <provider>", "LLM provider (anthropic, openai, kimi, deepseek, gemini, minimax, ollama, openrouter)").option("--max-file-size <bytes>", "Skip files larger than this (default: 1 MB)").option("--debug", "Enable debug logging").option("--baseline [ref]", "Compare against a baseline file or save current findings as baseline (--baseline save)").option("--min-confidence <level>", "Hide findings below this confidence (high, medium, low)").action(async (targetDir, options) => {
477
+ const maxFileSize = options.maxFileSize ? Number(options.maxFileSize) : void 0;
478
+ const tier = options.tier;
479
+ const baseline = options.baseline === "save" ? "save" : options.baseline;
480
+ const minConfidence = options.minConfidence;
481
+ const result = await runCli({ targetDir, json: options.json, output: options.output, sarif: options.sarif, sarifStdout: options.sarifStdout, failOnFindings: options.failOnFindings, tier, provider: options.provider, maxFileSize, debug: options.debug, baseline, minConfidence });
482
+ if (result.updateMessage) {
483
+ console.error(result.updateMessage);
484
+ }
485
+ if (result.error) {
486
+ console.error(result.error);
487
+ process.exit(result.code);
488
+ }
489
+ if (result.output) {
490
+ console.log(result.output);
491
+ }
492
+ });
493
+ cli.command("config <action> [key] [value]", "Manage configuration").action(async (action, key, value) => {
494
+ const args = { action, key, value };
495
+ const result = await configCommand.handler(args, { tier: "free", licensedTo: "free tier" });
496
+ if (!result.ok) {
497
+ const message = result.detail ? `${getCopy(result.code)} (${result.detail})` : getCopy(result.code);
498
+ console.error(message);
499
+ process.exit(2);
500
+ }
501
+ if (result.output) {
502
+ console.log(result.output);
503
+ }
504
+ });
505
+ cli.help(() => {
506
+ return [
507
+ {
508
+ title: "Examples",
509
+ body: ` $ npx seaworthy ./my-project
510
+ $ npx seaworthy ./my-project --json
511
+ $ npx seaworthy ./my-project --output report.html
512
+ $ npx seaworthy ./my-project --sarif report.sarif
513
+ $ npx seaworthy --provider kimi ./my-project`
514
+ },
515
+ {
516
+ title: "Options",
517
+ body: ` --json Output JSON to stdout
518
+ --output <path> Write HTML report to file
519
+ --sarif <path> ${getCopy("cli.help.flag.sarif")}
520
+ --sarif-stdout ${getCopy("cli.help.flag.sarifStdout")}
521
+ --fail-on-findings Exit with code 5 when findings are found
522
+ --tier <tier> Override tier (free, pro)
523
+ --provider <name> LLM provider (anthropic, openai, kimi, deepseek, gemini, minimax, ollama, openrouter)
524
+ --max-file-size <n> Skip files larger than n bytes (default: 1 MB)
525
+ --baseline [ref] Compare against baseline file or save (--baseline save)
526
+ --min-confidence <level> Hide findings below confidence (high, medium, low)
527
+ --debug Enable debug logging`
528
+ },
529
+ {
530
+ title: "Configuration",
531
+ body: ` ~/.seaworthy/config # license key, LLM provider, API key, model, url`
532
+ },
533
+ {
534
+ title: "Environment",
535
+ body: ` ANTHROPIC_API_KEY, OPENAI_API_KEY`
536
+ },
537
+ {
538
+ title: "",
539
+ body: getCopy("cli.help.learnMore")
540
+ }
541
+ ];
542
+ });
543
+ cli.parse();
544
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/cli-action.ts","../src/commands/scan.ts","../src/copy/index.ts","../src/commands/createCommand.ts","../src/config/store.ts","../src/auth/fs-credential-provider.ts","../src/update-notifier.ts","../src/commands/config.ts"],"sourcesContent":["import { config as loadEnv } from 'dotenv'\nimport { existsSync } from 'fs'\nimport { dirname, resolve } from 'path'\nimport { fileURLToPath } from 'url'\nimport { cac } from 'cac'\nimport { createRequire } from 'module'\nimport { runCli } from './cli-action.js'\n\nloadEnv({ path: resolve(process.cwd(), '.env') })\n\nconst repoRootEnv = resolve(dirname(fileURLToPath(import.meta.url)), '../../..', '.env')\nif (existsSync(repoRootEnv)) {\n loadEnv({ path: repoRootEnv, override: false })\n}\nimport { configCommand, type ConfigArgs } from './commands/config.js'\nimport { getCopy, type CopyKey } from './copy/index.js'\n\nconst require = createRequire(import.meta.url)\nconst { version } = require('../package.json')\n\nconst cli = cac('seaworthy')\n\ncli.version(version)\n\ncli\n .command('[path]', 'Scan a directory for issues')\n .option('--json', 'Output JSON to stdout')\n .option('--output <path>', 'Write HTML report to file')\n .option('--sarif <path>', 'Write SARIF report to file')\n .option('--sarif-stdout', 'Write SARIF report to stdout')\n .option('--fail-on-findings', 'Exit with code 5 when findings are found')\n .option('--tier <tier>', 'Select tier (free, pro)')\n .option('--provider <provider>', 'LLM provider (anthropic, openai, kimi, deepseek, gemini, minimax, ollama, openrouter)')\n .option('--max-file-size <bytes>', 'Skip files larger than this (default: 1 MB)')\n .option('--debug', 'Enable debug logging')\n .option('--baseline [ref]', 'Compare against a baseline file or save current findings as baseline (--baseline save)')\n .option('--min-confidence <level>', 'Hide findings below this confidence (high, medium, low)')\n .action(async (targetDir: string | undefined, options: { json?: boolean; output?: string; sarif?: string; sarifStdout?: boolean; failOnFindings?: boolean; tier?: string; provider?: string; maxFileSize?: string; debug?: boolean; baseline?: string; minConfidence?: string }) => {\n const maxFileSize = options.maxFileSize ? Number(options.maxFileSize) : undefined\n const tier = options.tier as 'free' | 'pro' | undefined\n const baseline = options.baseline === 'save' ? 'save' : options.baseline\n const minConfidence = options.minConfidence as 'high' | 'medium' | 'low' | undefined\n const result = await runCli({ targetDir, json: options.json, output: options.output, sarif: options.sarif, sarifStdout: options.sarifStdout, failOnFindings: options.failOnFindings, tier, provider: options.provider, maxFileSize, debug: options.debug, baseline, minConfidence })\n\n if (result.updateMessage) {\n console.error(result.updateMessage)\n }\n\n if (result.error) {\n console.error(result.error)\n process.exit(result.code)\n }\n\n if (result.output) {\n console.log(result.output)\n }\n })\n\ncli\n .command('config <action> [key] [value]', 'Manage configuration')\n .action(async (action: string, key?: string, value?: string) => {\n const args: ConfigArgs = { action: action as ConfigArgs['action'], key, value }\n const result = await configCommand.handler(args, { tier: 'free', licensedTo: 'free tier' })\n\n if (!result.ok) {\n const message = result.detail\n ? `${getCopy(result.code as CopyKey)} (${result.detail})`\n : getCopy(result.code as CopyKey)\n console.error(message)\n process.exit(2)\n }\n\n if (result.output) {\n console.log(result.output)\n }\n })\n\ncli.help(() => {\n return [\n {\n title: 'Examples',\n body: ` $ npx seaworthy ./my-project\n $ npx seaworthy ./my-project --json\n $ npx seaworthy ./my-project --output report.html\n $ npx seaworthy ./my-project --sarif report.sarif\n $ npx seaworthy --provider kimi ./my-project`,\n },\n {\n title: 'Options',\n body: ` --json Output JSON to stdout\n --output <path> Write HTML report to file\n --sarif <path> ${getCopy('cli.help.flag.sarif')}\n --sarif-stdout ${getCopy('cli.help.flag.sarifStdout')}\n --fail-on-findings Exit with code 5 when findings are found\n --tier <tier> Override tier (free, pro)\n --provider <name> LLM provider (anthropic, openai, kimi, deepseek, gemini, minimax, ollama, openrouter)\n --max-file-size <n> Skip files larger than n bytes (default: 1 MB)\n --baseline [ref] Compare against baseline file or save (--baseline save)\n --min-confidence <level> Hide findings below confidence (high, medium, low)\n --debug Enable debug logging`,\n },\n {\n title: 'Configuration',\n body: ` ~/.seaworthy/config # license key, LLM provider, API key, model, url`,\n },\n {\n title: 'Environment',\n body: ` ANTHROPIC_API_KEY, OPENAI_API_KEY`,\n },\n {\n title: '',\n body: getCopy('cli.help.learnMore'),\n },\n ]\n})\n\ncli.parse()\n","import { stat } from 'fs/promises'\nimport { resolve } from 'path'\nimport { scanCommand, type ScanArgs } from './commands/scan.js'\nimport { getCopy } from './copy/index.js'\nimport { FsCredentialProvider } from './auth/index.js'\nimport {\n LicenseClient,\n resolveAuth,\n LICENSE_SERVER_URL,\n resolveErrorMessage,\n} from '@seaworthy/core'\nimport { checkUpdate } from './update-notifier.js'\n\nexport interface CliOptions {\n targetDir?: string\n json?: boolean\n output?: string\n sarif?: string\n sarifStdout?: boolean\n failOnFindings?: boolean\n tier?: 'free' | 'pro'\n provider?: string\n maxFileSize?: number\n debug?: boolean\n baseline?: 'save' | string\n minConfidence?: 'high' | 'medium' | 'low'\n}\n\nexport interface CliResult {\n code: number\n output?: string\n error?: string\n updateMessage?: string\n}\n\nfunction resolveExitCode(code: string): number {\n const normalized = code.replace(/^errors\\./, '')\n\n // License invalid / expired\n if (['license.invalid', 'license.expired'].includes(normalized)) return 1\n\n // User config error\n if (\n [\n 'config.invalid',\n 'fs.not_a_directory',\n 'fs.not_found',\n 'fs.permission_denied',\n ].includes(normalized) ||\n code.startsWith('cli.error.') ||\n code.startsWith('cli.sarif.')\n )\n return 2\n\n // Network / LLM unavailable\n if (\n [\n 'license.server_unreachable',\n 'license.rate_limited',\n 'network.unreachable',\n 'llm.timeout',\n 'llm.rate_limited',\n ].includes(normalized)\n )\n return 3\n\n // Scan failed (unhandled)\n if (['scan.failed', 'scan.timed_out'].includes(normalized)) return 4\n\n // No findings (when --fail-on-findings)\n if (normalized === 'scan.findings_found') return 5\n\n return 4\n}\n\nexport async function runCli(options: CliOptions): Promise<CliResult> {\n const dir = options.targetDir ?? '.'\n\n if (options.json && options.output) {\n return {\n code: resolveExitCode('cli.error.mutuallyExclusive'),\n error: getCopy('cli.error.mutuallyExclusive'),\n }\n }\n\n if (options.sarif && options.sarifStdout) {\n return {\n code: resolveExitCode('cli.sarif.mutually_exclusive'),\n error: getCopy('cli.sarif.mutually_exclusive'),\n }\n }\n\n if (options.sarifStdout && options.json) {\n return {\n code: resolveExitCode('cli.sarif.stdout_conflict'),\n error: getCopy('cli.sarif.stdout_conflict'),\n }\n }\n\n const outputPath = options.output ? resolve(options.output) : undefined\n\n try {\n const s = await stat(dir)\n if (!s.isDirectory()) {\n return {\n code: resolveExitCode('fs.not_a_directory'),\n error: getCopy('cli.error.notADirectory', dir),\n }\n }\n } catch {\n return {\n code: resolveExitCode('fs.not_found'),\n error: getCopy('cli.error.notFound', dir),\n }\n }\n\n const provider = new FsCredentialProvider()\n const license = new LicenseClient({ baseUrl: LICENSE_SERVER_URL })\n\n const authResult = await resolveAuth(provider, license)\n if (!authResult.ok) {\n const message = resolveErrorMessage(authResult.code)\n return { code: resolveExitCode(authResult.code), error: message }\n }\n\n const auth = authResult.value\n\n // CLI --tier overrides license tier when explicitly provided, but cannot exceed it\n const allowedTiers: Record<'free' | 'pro', Array<'free' | 'pro'>> = {\n free: ['free'],\n pro: ['free', 'pro'],\n }\n\n const effectiveTier = options.tier ?? auth.tier\n if (!Object.prototype.hasOwnProperty.call(allowedTiers, auth.tier) || !allowedTiers[auth.tier as 'free' | 'pro'].includes(effectiveTier)) {\n return {\n code: resolveExitCode('invalid.tier'),\n error: resolveErrorMessage('invalid.tier'),\n }\n }\n\n const format = options.json ? 'json' : outputPath ? 'html' : 'terminal'\n\n const selectedProvider = options.provider ?? auth.llmProvider\n const providerChanged = Boolean(options.provider && options.provider !== auth.llmProvider)\n\n const args: ScanArgs = {\n targetDir: dir,\n format,\n output: outputPath,\n sarif: options.sarif,\n sarifStdout: options.sarifStdout,\n failOnFindings: options.failOnFindings,\n llmApiKey: auth.llmApiKey,\n llmProvider: selectedProvider,\n llmModel: providerChanged ? undefined : auth.llmModel,\n llmApiUrl: providerChanged ? undefined : auth.llmApiUrl,\n maxFileSize: options.maxFileSize,\n debug: options.debug,\n baseline: options.baseline,\n minConfidence: options.minConfidence,\n }\n\n const updateStart = Date.now()\n const updatePromise = checkUpdate().catch(() => undefined)\n\n let result\n try {\n result = await scanCommand.handler(args, { tier: effectiveTier, licensedTo: auth.licensedTo })\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n return { code: resolveExitCode('scan.failed'), error: message }\n }\n\n if (!result.ok) {\n const message = resolveErrorMessage(result.code)\n return { code: resolveExitCode(result.code), error: message }\n }\n\n const remaining = Math.max(0, 3_000 - (Date.now() - updateStart))\n const updateMessage = await Promise.race([\n updatePromise,\n new Promise<undefined>((resolve) => setTimeout(() => resolve(undefined), remaining)),\n ])\n\n return {\n code: 0,\n output: result.output ?? '',\n updateMessage,\n }\n}\n","import {\n ScanRunner,\n TerminalReporter,\n JSONReporter,\n HTMLReporter,\n SarifReporter,\n createProvider,\n getCopy as getCoreCopy,\n type ScanResult,\n type Tier,\n type Confidence,\n} from '@seaworthy/core'\nimport { promises as fs } from 'fs'\nimport { dirname, resolve, relative, isAbsolute } from 'path'\nimport { getCopy } from '../copy/index.js'\nimport { createCommand, type CommandContext, type CommandResult } from './createCommand.js'\n\nconst FREE_SCAN_TIMEOUT_MS = 60_000\nconst PRO_SCAN_TIMEOUT_MS = 600_000\n\nfunction resolveScanTimeoutMs(tier: Tier): number {\n const env = process.env.SEAWORTHY_SCAN_TIMEOUT_MS\n if (env) {\n const parsed = Number(env)\n if (Number.isFinite(parsed) && parsed > 0) {\n return parsed\n }\n }\n\n return tier === 'free' ? FREE_SCAN_TIMEOUT_MS : PRO_SCAN_TIMEOUT_MS\n}\n\nexport interface ScanArgs {\n targetDir: string\n format: 'terminal' | 'json' | 'html'\n output?: string\n sarif?: string\n sarifStdout?: boolean\n failOnFindings?: boolean\n llmApiKey?: string\n llmProvider?: string\n llmModel?: string\n llmApiUrl?: string\n maxFileSize?: number\n debug?: boolean\n baseline?: 'save' | string\n minConfidence?: 'high' | 'medium' | 'low'\n}\n\nfunction withTimeout<T>(promise: Promise<T>, ms: number, message: string): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n const timer = setTimeout(() => reject(new Error(message)), ms)\n promise\n .then((value) => {\n clearTimeout(timer)\n resolve(value)\n })\n .catch((err) => {\n clearTimeout(timer)\n reject(err)\n })\n })\n}\n\n/**\n * Determine whether `outputPath` lies inside `targetDir` (including sub-directories).\n */\nfunction isInsideDirectory(outputPath: string, targetDir: string): boolean {\n const resolvedOutput = resolve(outputPath)\n const resolvedTarget = resolve(targetDir)\n const rel = relative(resolvedTarget, resolvedOutput)\n return rel === '' || (!rel.startsWith('..') && !isAbsolute(rel))\n}\n\nasync function executeScan(args: ScanArgs, ctx: CommandContext): Promise<CommandResult> {\n if (args.sarif && args.sarifStdout) {\n return { ok: false, code: 'cli.sarif.mutually_exclusive' }\n }\n if (args.sarifStdout && args.format === 'json') {\n return { ok: false, code: 'cli.sarif.stdout_conflict' }\n }\n\n if (ctx.tier === 'pro' && !args.llmApiKey && !process.env.SEAWORTHY_SKIP_LLM_CHECK) {\n console.error(getCopy('cli.warn.llmSkipped'))\n }\n\n let llm = null\n if (args.llmApiKey && args.llmProvider && !process.env.SEAWORTHY_SKIP_LLM_CHECK) {\n llm = createProvider({ provider: args.llmProvider, apiKey: args.llmApiKey, model: args.llmModel, baseUrl: args.llmApiUrl })\n }\n\n const runner = new ScanRunner()\n const scanTimeoutMs = resolveScanTimeoutMs(ctx.tier!)\n const result: ScanResult = await withTimeout(\n runner.run({\n targetDir: args.targetDir,\n tier: ctx.tier!,\n llm,\n maxFileSize: args.maxFileSize,\n debug: args.debug,\n baseline: args.baseline,\n minConfidence: args.minConfidence as Confidence | undefined,\n }),\n scanTimeoutMs,\n getCoreCopy('errors.scan.timed_out', Math.round(scanTimeoutMs / 1000))\n )\n\n const reportInput = {\n findings: result.findings,\n targetDir: args.targetDir,\n tier: ctx.tier!,\n licensedTo: ctx.licensedTo ?? 'free tier',\n scannedFileCount: result.scannedFileCount,\n }\n\n let primaryOutput: string\n switch (args.format) {\n case 'json':\n primaryOutput = new JSONReporter().generate(reportInput)\n break\n case 'html':\n primaryOutput = new HTMLReporter().generate(reportInput)\n break\n case 'terminal':\n default:\n primaryOutput = new TerminalReporter().generate(reportInput)\n break\n }\n\n let sarifOutput: string | undefined\n if (args.sarif || args.sarifStdout) {\n sarifOutput = new SarifReporter().generate(reportInput)\n }\n\n if (args.sarif && sarifOutput) {\n const sarifPath = resolve(args.sarif)\n if (isInsideDirectory(sarifPath, args.targetDir)) {\n return { ok: false, code: 'cli.error.outputInsideTarget' }\n }\n await fs.mkdir(dirname(sarifPath), { recursive: true })\n await fs.writeFile(sarifPath, sarifOutput)\n }\n\n if (args.output) {\n const outputPath = resolve(args.output)\n if (isInsideDirectory(outputPath, args.targetDir)) {\n return { ok: false, code: 'cli.error.outputInsideTarget' }\n }\n await fs.mkdir(dirname(outputPath), { recursive: true })\n await fs.writeFile(outputPath, primaryOutput)\n if (args.failOnFindings && result.findings.length > 0) {\n return { ok: false, code: 'scan.findings_found' }\n }\n return { ok: true, output: getCopy('cli.reportWritten', outputPath) }\n }\n\n if (args.failOnFindings && result.findings.length > 0) {\n return { ok: false, code: 'scan.findings_found' }\n }\n\n const stdoutOutput = args.sarifStdout ? sarifOutput! : primaryOutput\n return { ok: true, output: stdoutOutput }\n}\n\nexport const scanCommand = createCommand<ScanArgs>({\n name: 'scan',\n description: 'Scan a directory for issues',\n handler: executeScan,\n})\n","import { SITE_URL } from '@seaworthy/core'\n\nconst copy = {\n 'cli.help.description': 'Seaworthy — static analysis for vibe coders',\n 'cli.help.usage': 'Usage: seaworthy [path] [options]',\n 'cli.help.options': 'Options',\n 'cli.help.learnMore': `Documentation: ${SITE_URL}/docs`,\n 'cli.scanning': 'Scanning...',\n 'cli.reportWritten': (path: string) => `Report written to ${path}`,\n 'cli.error.notADirectory': (path: string) => `Path is not a directory: ${path}`,\n 'cli.error.notFound': (path: string) => `Path not found: ${path}`,\n 'cli.error.mutuallyExclusive': '--json and --output cannot be used together',\n 'cli.error.outputInsideTarget': 'Cannot write report inside the scanned directory.',\n 'cli.help.flag.sarif': 'Write SARIF report to file',\n 'cli.help.flag.sarifStdout': 'Write SARIF report to stdout',\n 'cli.sarif.mutually_exclusive': 'Flags --sarif and --sarif-stdout cannot be used together.',\n 'cli.sarif.stdout_conflict': 'Flags --sarif-stdout and --json cannot be used together. Use --output to send JSON to a file.',\n 'cli.error.llmRequired':\n 'LLM API key required for Pro checks. Set it with seaworthy config or the SEAWORTHY_LLM_API_KEY environment variable.',\n 'cli.warn.llmSkipped':\n 'Pro checks skipped: no LLM API key configured. Set it with seaworthy config or the SEAWORTHY_LLM_API_KEY environment variable to enable semantic analysis.',\n 'cli.error.config.missingKey': 'Missing config key. Usage: seaworthy config <set|get> <key>',\n 'cli.error.config.missingValue': 'Missing config value. Usage: seaworthy config set <key> <value>',\n 'cli.error.config.unknownKey': 'Unknown config key. Valid keys: licenseKey, llmProvider, llmApiKey, llmModel, llmApiUrl',\n 'cli.error.config.invalidProvider': 'Invalid provider. Valid providers: anthropic, openai, kimi, moonshot, deepseek, gemini, ollama, openrouter',\n 'cli.error.config.invalidAction': 'Invalid config action. Usage: seaworthy config <set|get|list>',\n 'cli.config.setSuccess': (key: string) => `${key} updated.`,\n} as const\n\nexport type CopyKey = keyof typeof copy\n\ntype CopyValue<K extends CopyKey> = (typeof copy)[K]\n\ntype CopyArgs<K extends CopyKey> = CopyValue<K> extends (...args: infer P) => unknown\n ? P\n : never[]\n\nexport function getCopy<K extends CopyKey>(key: K, ...args: CopyArgs<K>): string {\n const value = copy[key]\n if (typeof value === 'function') {\n return (value as (...args: unknown[]) => string)(...args)\n }\n return value as string\n}\n","import type { LicenseTier } from '@seaworthy/core'\n\nexport interface CommandContext {\n tier?: LicenseTier\n licensedTo?: string\n}\n\nexport type CommandResult =\n | { ok: true; output?: string; filePath?: string }\n | { ok: false; code: string; detail?: string }\n\nexport interface CommandDefinition<TArgs> {\n name: string\n description: string\n handler: (args: TArgs, ctx: CommandContext) => Promise<CommandResult>\n}\n\nexport interface CreateCommandOptions {\n requireLicense?: boolean\n}\n\n/**\n * Factory for CLI commands. Wraps the raw handler to enforce license\n * validation and consistent error handling. Every `seaworthy` subcommand\n * (scan, config, logout) must use this factory per `route_handlers.md`.\n *\n * Wraps the handler in a try/catch so that infrastructure failures\n * (network, disk, LLM) produce a `{ ok: false }` result instead of\n * crashing the process.\n */\nexport function createCommand<TArgs>(\n definition: CommandDefinition<TArgs>,\n options?: CreateCommandOptions\n): CommandDefinition<TArgs> {\n const requireLicense = options?.requireLicense ?? true\n return {\n ...definition,\n handler: async (args: TArgs, ctx: CommandContext): Promise<CommandResult> => {\n if (requireLicense && !ctx.tier) {\n return { ok: false, code: 'errors.license.invalid' }\n }\n\n try {\n return await definition.handler(args, ctx)\n } catch (err) {\n return {\n ok: false,\n code: 'errors.unexpected',\n detail: err instanceof Error ? err.message : String(err),\n }\n }\n },\n }\n}\n","import { promises as fs } from 'fs'\nimport { homedir } from 'os'\nimport { join } from 'path'\n\nfunction getConfigDir(): string {\n return process.env.SEAWORTHY_CONFIG_DIR ?? join(homedir(), '.seaworthy')\n}\n\nfunction getConfigFile(): string {\n return join(getConfigDir(), 'config')\n}\n\nexport interface UserConfig {\n licenseKey?: string\n llmProvider?: string\n llmApiKey?: string\n llmModel?: string\n llmApiUrl?: string\n}\n\nexport async function readConfig(): Promise<UserConfig> {\n try {\n const raw = await fs.readFile(getConfigFile(), 'utf-8')\n return JSON.parse(raw) as UserConfig\n } catch {\n return {}\n }\n}\n\nexport async function writeConfig(config: UserConfig): Promise<void> {\n await fs.mkdir(getConfigDir(), { recursive: true })\n await fs.writeFile(getConfigFile(), JSON.stringify(config, null, 2), {\n mode: 0o600,\n })\n await fs.chmod(getConfigFile(), 0o600)\n}\n","import type { CredentialProvider } from '@seaworthy/core'\nimport { readConfig } from '../config/store.js'\n\n/**\n * Credential provider that reads from the local filesystem\n * (`~/.seaworthy/config`).\n */\nexport class FsCredentialProvider implements CredentialProvider {\n async getCredentials() {\n const config = await readConfig()\n return {\n licenseKey: config.licenseKey,\n llmApiKey: config.llmApiKey,\n llmProvider: config.llmProvider,\n llmModel: config.llmModel,\n llmApiUrl: config.llmApiUrl,\n }\n }\n}\n","import { readFile } from 'fs/promises'\nimport { fileURLToPath } from 'url'\nimport { dirname, join } from 'path'\n\nconst NPM_REGISTRY_URL = 'https://registry.npmjs.org/seaworthy'\nconst UPDATE_TIMEOUT_MS = 3_000\n\ninterface NpmRegistryResponse {\n 'dist-tags'?: {\n latest?: string\n }\n}\n\n/**\n * Compare two semantic version strings (simple x.y.z comparison).\n *\n * @param current - The currently installed version.\n * @param latest - The latest version from the registry.\n * @returns True when `current` is strictly lower than `latest`.\n */\nfunction isLowerVersion(current: string, latest: string): boolean {\n const parse = (v: string) => v.split('.').map((n) => Number(n))\n const [cMajor = 0, cMinor = 0, cPatch = 0] = parse(current)\n const [lMajor = 0, lMinor = 0, lPatch = 0] = parse(latest)\n\n if (lMajor > cMajor) return true\n if (lMajor < cMajor) return false\n if (lMinor > cMinor) return true\n if (lMinor < cMinor) return false\n return lPatch > cPatch\n}\n\n/**\n * Read the version field from this package's package.json.\n */\nasync function getInstalledVersion(): Promise<string> {\n const pkgPath = join(dirname(fileURLToPath(import.meta.url)), '../package.json')\n const pkg = JSON.parse(await readFile(pkgPath, 'utf-8')) as { version: string }\n return pkg.version\n}\n\n/**\n * Check the npm registry for a newer version of Seaworthy.\n *\n * @returns An update notification string when a newer version exists,\n * otherwise `undefined`. Never throws.\n */\nexport async function checkUpdate(): Promise<string | undefined> {\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), UPDATE_TIMEOUT_MS)\n\n try {\n const response = await fetch(NPM_REGISTRY_URL, { signal: controller.signal })\n clearTimeout(timeout)\n\n if (!response.ok) {\n return undefined\n }\n\n const data = (await response.json()) as NpmRegistryResponse\n const latest = data['dist-tags']?.latest\n\n if (!latest) {\n return undefined\n }\n\n const installed = await getInstalledVersion()\n\n if (isLowerVersion(installed, latest)) {\n return `Update available: ${installed} → ${latest}. Run npm i -g seaworthy to update.`\n }\n\n return undefined\n } catch {\n clearTimeout(timeout)\n return undefined\n }\n}\n","import { readConfig, writeConfig, type UserConfig } from '../config/store.js'\nimport { getCopy } from '../copy/index.js'\nimport { listPresetProviders } from '@seaworthy/core'\nimport { createCommand, type CommandResult } from './createCommand.js'\n\nconst VALID_KEYS: (keyof UserConfig)[] = ['licenseKey', 'llmProvider', 'llmApiKey', 'llmModel', 'llmApiUrl']\n\nexport interface ConfigArgs {\n action: 'set' | 'get' | 'list'\n key?: string\n value?: string\n}\n\nfunction formatOutputValue(key: keyof UserConfig, value: UserConfig[keyof UserConfig]): string {\n if (value === undefined) return ''\n if (key === 'llmApiKey' || key === 'licenseKey') return '********'\n return value\n}\n\nasync function executeConfig(args: ConfigArgs): Promise<CommandResult> {\n const config = await readConfig()\n\n if (args.action === 'list') {\n const lines: string[] = []\n for (const key of VALID_KEYS) {\n const value = config[key]\n lines.push(`${key}=${formatOutputValue(key, value)}`)\n }\n return { ok: true, output: lines.join('\\n') }\n }\n\n if (!args.key) {\n return { ok: false, code: 'cli.error.config.missingKey' }\n }\n\n if (!VALID_KEYS.includes(args.key as keyof UserConfig)) {\n return { ok: false, code: 'cli.error.config.unknownKey', detail: args.key }\n }\n\n const key = args.key as keyof UserConfig\n\n if (args.action === 'get') {\n const value = config[key]\n return { ok: true, output: formatOutputValue(key, value) }\n }\n\n if (args.action === 'set') {\n if (args.value === undefined) {\n return { ok: false, code: 'cli.error.config.missingValue' }\n }\n\n if (key === 'llmProvider' && !listPresetProviders().includes(args.value)) {\n return { ok: false, code: 'cli.error.config.invalidProvider', detail: args.value }\n }\n\n const next: UserConfig = { ...config, [key]: args.value }\n await writeConfig(next)\n return { ok: true, output: getCopy('cli.config.setSuccess', args.key) }\n }\n\n return { ok: false, code: 'cli.error.config.invalidAction' }\n}\n\nexport const configCommand = createCommand<ConfigArgs>(\n {\n name: 'config',\n description: 'Manage Seaworthy configuration',\n handler: async (args) => executeConfig(args),\n },\n { requireLicense: false }\n)\n"],"mappings":";;;AAAA,SAAS,UAAU,eAAe;AAClC,SAAS,kBAAkB;AAC3B,SAAS,WAAAA,UAAS,WAAAC,gBAAe;AACjC,SAAS,iBAAAC,sBAAqB;AAC9B,SAAS,WAAW;AACpB,SAAS,qBAAqB;;;ACL9B,SAAS,YAAY;AACrB,SAAS,WAAAC,gBAAe;;;ACDxB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,OAIN;AACP,SAAS,YAAY,UAAU;AAC/B,SAAS,SAAS,SAAS,UAAU,kBAAkB;;;ACbvD,SAAS,gBAAgB;AAEzB,IAAM,OAAO;AAAA,EACX,wBAAwB;AAAA,EACxB,kBAAkB;AAAA,EAClB,oBAAoB;AAAA,EACpB,sBAAsB,kBAAkB,QAAQ;AAAA,EAChD,gBAAgB;AAAA,EAChB,qBAAqB,CAAC,SAAiB,qBAAqB,IAAI;AAAA,EAChE,2BAA2B,CAAC,SAAiB,4BAA4B,IAAI;AAAA,EAC7E,sBAAsB,CAAC,SAAiB,mBAAmB,IAAI;AAAA,EAC/D,+BAA+B;AAAA,EAC/B,gCAAgC;AAAA,EAChC,uBAAuB;AAAA,EACvB,6BAA6B;AAAA,EAC7B,gCAAgC;AAAA,EAChC,6BAA6B;AAAA,EAC7B,yBACE;AAAA,EACF,uBACE;AAAA,EACF,+BAA+B;AAAA,EAC/B,iCAAiC;AAAA,EACjC,+BAA+B;AAAA,EAC/B,oCAAoC;AAAA,EACpC,kCAAkC;AAAA,EAClC,yBAAyB,CAAC,QAAgB,GAAG,GAAG;AAClD;AAUO,SAAS,QAA2B,QAAW,MAA2B;AAC/E,QAAM,QAAQ,KAAK,GAAG;AACtB,MAAI,OAAO,UAAU,YAAY;AAC/B,WAAQ,MAAyC,GAAG,IAAI;AAAA,EAC1D;AACA,SAAO;AACT;;;ACbO,SAAS,cACd,YACA,SAC0B;AAC1B,QAAM,iBAAiB,SAAS,kBAAkB;AAClD,SAAO;AAAA,IACL,GAAG;AAAA,IACH,SAAS,OAAO,MAAa,QAAgD;AAC3E,UAAI,kBAAkB,CAAC,IAAI,MAAM;AAC/B,eAAO,EAAE,IAAI,OAAO,MAAM,yBAAyB;AAAA,MACrD;AAEA,UAAI;AACF,eAAO,MAAM,WAAW,QAAQ,MAAM,GAAG;AAAA,MAC3C,SAAS,KAAK;AACZ,eAAO;AAAA,UACL,IAAI;AAAA,UACJ,MAAM;AAAA,UACN,QAAQ,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,QACzD;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;AFpCA,IAAM,uBAAuB;AAC7B,IAAM,sBAAsB;AAE5B,SAAS,qBAAqB,MAAoB;AAChD,QAAM,MAAM,QAAQ,IAAI;AACxB,MAAI,KAAK;AACP,UAAM,SAAS,OAAO,GAAG;AACzB,QAAI,OAAO,SAAS,MAAM,KAAK,SAAS,GAAG;AACzC,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO,SAAS,SAAS,uBAAuB;AAClD;AAmBA,SAAS,YAAe,SAAqB,IAAY,SAA6B;AACpF,SAAO,IAAI,QAAW,CAACC,UAAS,WAAW;AACzC,UAAM,QAAQ,WAAW,MAAM,OAAO,IAAI,MAAM,OAAO,CAAC,GAAG,EAAE;AAC7D,YACG,KAAK,CAAC,UAAU;AACf,mBAAa,KAAK;AAClB,MAAAA,SAAQ,KAAK;AAAA,IACf,CAAC,EACA,MAAM,CAAC,QAAQ;AACd,mBAAa,KAAK;AAClB,aAAO,GAAG;AAAA,IACZ,CAAC;AAAA,EACL,CAAC;AACH;AAKA,SAAS,kBAAkB,YAAoB,WAA4B;AACzE,QAAM,iBAAiB,QAAQ,UAAU;AACzC,QAAM,iBAAiB,QAAQ,SAAS;AACxC,QAAM,MAAM,SAAS,gBAAgB,cAAc;AACnD,SAAO,QAAQ,MAAO,CAAC,IAAI,WAAW,IAAI,KAAK,CAAC,WAAW,GAAG;AAChE;AAEA,eAAe,YAAY,MAAgB,KAA6C;AACtF,MAAI,KAAK,SAAS,KAAK,aAAa;AAClC,WAAO,EAAE,IAAI,OAAO,MAAM,+BAA+B;AAAA,EAC3D;AACA,MAAI,KAAK,eAAe,KAAK,WAAW,QAAQ;AAC9C,WAAO,EAAE,IAAI,OAAO,MAAM,4BAA4B;AAAA,EACxD;AAEA,MAAI,IAAI,SAAS,SAAS,CAAC,KAAK,aAAa,CAAC,QAAQ,IAAI,0BAA0B;AAClF,YAAQ,MAAM,QAAQ,qBAAqB,CAAC;AAAA,EAC9C;AAEA,MAAI,MAAM;AACV,MAAI,KAAK,aAAa,KAAK,eAAe,CAAC,QAAQ,IAAI,0BAA0B;AAC/E,UAAM,eAAe,EAAE,UAAU,KAAK,aAAa,QAAQ,KAAK,WAAW,OAAO,KAAK,UAAU,SAAS,KAAK,UAAU,CAAC;AAAA,EAC5H;AAEA,QAAM,SAAS,IAAI,WAAW;AAC9B,QAAM,gBAAgB,qBAAqB,IAAI,IAAK;AACpD,QAAM,SAAqB,MAAM;AAAA,IAC/B,OAAO,IAAI;AAAA,MACT,WAAW,KAAK;AAAA,MAChB,MAAM,IAAI;AAAA,MACV;AAAA,MACA,aAAa,KAAK;AAAA,MAClB,OAAO,KAAK;AAAA,MACZ,UAAU,KAAK;AAAA,MACf,eAAe,KAAK;AAAA,IACtB,CAAC;AAAA,IACD;AAAA,IACA,YAAY,yBAAyB,KAAK,MAAM,gBAAgB,GAAI,CAAC;AAAA,EACvE;AAEA,QAAM,cAAc;AAAA,IAClB,UAAU,OAAO;AAAA,IACjB,WAAW,KAAK;AAAA,IAChB,MAAM,IAAI;AAAA,IACV,YAAY,IAAI,cAAc;AAAA,IAC9B,kBAAkB,OAAO;AAAA,EAC3B;AAEA,MAAI;AACJ,UAAQ,KAAK,QAAQ;AAAA,IACnB,KAAK;AACH,sBAAgB,IAAI,aAAa,EAAE,SAAS,WAAW;AACvD;AAAA,IACF,KAAK;AACH,sBAAgB,IAAI,aAAa,EAAE,SAAS,WAAW;AACvD;AAAA,IACF,KAAK;AAAA,IACL;AACE,sBAAgB,IAAI,iBAAiB,EAAE,SAAS,WAAW;AAC3D;AAAA,EACJ;AAEA,MAAI;AACJ,MAAI,KAAK,SAAS,KAAK,aAAa;AAClC,kBAAc,IAAI,cAAc,EAAE,SAAS,WAAW;AAAA,EACxD;AAEA,MAAI,KAAK,SAAS,aAAa;AAC7B,UAAM,YAAY,QAAQ,KAAK,KAAK;AACpC,QAAI,kBAAkB,WAAW,KAAK,SAAS,GAAG;AAChD,aAAO,EAAE,IAAI,OAAO,MAAM,+BAA+B;AAAA,IAC3D;AACA,UAAM,GAAG,MAAM,QAAQ,SAAS,GAAG,EAAE,WAAW,KAAK,CAAC;AACtD,UAAM,GAAG,UAAU,WAAW,WAAW;AAAA,EAC3C;AAEA,MAAI,KAAK,QAAQ;AACf,UAAM,aAAa,QAAQ,KAAK,MAAM;AACtC,QAAI,kBAAkB,YAAY,KAAK,SAAS,GAAG;AACjD,aAAO,EAAE,IAAI,OAAO,MAAM,+BAA+B;AAAA,IAC3D;AACA,UAAM,GAAG,MAAM,QAAQ,UAAU,GAAG,EAAE,WAAW,KAAK,CAAC;AACvD,UAAM,GAAG,UAAU,YAAY,aAAa;AAC5C,QAAI,KAAK,kBAAkB,OAAO,SAAS,SAAS,GAAG;AACrD,aAAO,EAAE,IAAI,OAAO,MAAM,sBAAsB;AAAA,IAClD;AACA,WAAO,EAAE,IAAI,MAAM,QAAQ,QAAQ,qBAAqB,UAAU,EAAE;AAAA,EACtE;AAEA,MAAI,KAAK,kBAAkB,OAAO,SAAS,SAAS,GAAG;AACrD,WAAO,EAAE,IAAI,OAAO,MAAM,sBAAsB;AAAA,EAClD;AAEA,QAAM,eAAe,KAAK,cAAc,cAAe;AACvD,SAAO,EAAE,IAAI,MAAM,QAAQ,aAAa;AAC1C;AAEO,IAAM,cAAc,cAAwB;AAAA,EACjD,MAAM;AAAA,EACN,aAAa;AAAA,EACb,SAAS;AACX,CAAC;;;AGxKD,SAAS,YAAYC,WAAU;AAC/B,SAAS,eAAe;AACxB,SAAS,YAAY;AAErB,SAAS,eAAuB;AAC9B,SAAO,QAAQ,IAAI,wBAAwB,KAAK,QAAQ,GAAG,YAAY;AACzE;AAEA,SAAS,gBAAwB;AAC/B,SAAO,KAAK,aAAa,GAAG,QAAQ;AACtC;AAUA,eAAsB,aAAkC;AACtD,MAAI;AACF,UAAM,MAAM,MAAMA,IAAG,SAAS,cAAc,GAAG,OAAO;AACtD,WAAO,KAAK,MAAM,GAAG;AAAA,EACvB,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAEA,eAAsB,YAAY,QAAmC;AACnE,QAAMA,IAAG,MAAM,aAAa,GAAG,EAAE,WAAW,KAAK,CAAC;AAClD,QAAMA,IAAG,UAAU,cAAc,GAAG,KAAK,UAAU,QAAQ,MAAM,CAAC,GAAG;AAAA,IACnE,MAAM;AAAA,EACR,CAAC;AACD,QAAMA,IAAG,MAAM,cAAc,GAAG,GAAK;AACvC;;;AC5BO,IAAM,uBAAN,MAAyD;AAAA,EAC9D,MAAM,iBAAiB;AACrB,UAAM,SAAS,MAAM,WAAW;AAChC,WAAO;AAAA,MACL,YAAY,OAAO;AAAA,MACnB,WAAW,OAAO;AAAA,MAClB,aAAa,OAAO;AAAA,MACpB,UAAU,OAAO;AAAA,MACjB,WAAW,OAAO;AAAA,IACpB;AAAA,EACF;AACF;;;ALbA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;;;AMVP,SAAS,gBAAgB;AACzB,SAAS,qBAAqB;AAC9B,SAAS,WAAAC,UAAS,QAAAC,aAAY;AAE9B,IAAM,mBAAmB;AACzB,IAAM,oBAAoB;AAe1B,SAAS,eAAe,SAAiB,QAAyB;AAChE,QAAM,QAAQ,CAAC,MAAc,EAAE,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,OAAO,CAAC,CAAC;AAC9D,QAAM,CAAC,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC,IAAI,MAAM,OAAO;AAC1D,QAAM,CAAC,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC,IAAI,MAAM,MAAM;AAEzD,MAAI,SAAS,OAAQ,QAAO;AAC5B,MAAI,SAAS,OAAQ,QAAO;AAC5B,MAAI,SAAS,OAAQ,QAAO;AAC5B,MAAI,SAAS,OAAQ,QAAO;AAC5B,SAAO,SAAS;AAClB;AAKA,eAAe,sBAAuC;AACpD,QAAM,UAAUA,MAAKD,SAAQ,cAAc,YAAY,GAAG,CAAC,GAAG,iBAAiB;AAC/E,QAAM,MAAM,KAAK,MAAM,MAAM,SAAS,SAAS,OAAO,CAAC;AACvD,SAAO,IAAI;AACb;AAQA,eAAsB,cAA2C;AAC/D,QAAM,aAAa,IAAI,gBAAgB;AACvC,QAAM,UAAU,WAAW,MAAM,WAAW,MAAM,GAAG,iBAAiB;AAEtE,MAAI;AACF,UAAM,WAAW,MAAM,MAAM,kBAAkB,EAAE,QAAQ,WAAW,OAAO,CAAC;AAC5E,iBAAa,OAAO;AAEpB,QAAI,CAAC,SAAS,IAAI;AAChB,aAAO;AAAA,IACT;AAEA,UAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,UAAM,SAAS,KAAK,WAAW,GAAG;AAElC,QAAI,CAAC,QAAQ;AACX,aAAO;AAAA,IACT;AAEA,UAAM,YAAY,MAAM,oBAAoB;AAE5C,QAAI,eAAe,WAAW,MAAM,GAAG;AACrC,aAAO,qBAAqB,SAAS,WAAM,MAAM;AAAA,IACnD;AAEA,WAAO;AAAA,EACT,QAAQ;AACN,iBAAa,OAAO;AACpB,WAAO;AAAA,EACT;AACF;;;AN1CA,SAAS,gBAAgB,MAAsB;AAC7C,QAAM,aAAa,KAAK,QAAQ,aAAa,EAAE;AAG/C,MAAI,CAAC,mBAAmB,iBAAiB,EAAE,SAAS,UAAU,EAAG,QAAO;AAGxE,MACE;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,SAAS,UAAU,KACrB,KAAK,WAAW,YAAY,KAC5B,KAAK,WAAW,YAAY;AAE5B,WAAO;AAGT,MACE;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,SAAS,UAAU;AAErB,WAAO;AAGT,MAAI,CAAC,eAAe,gBAAgB,EAAE,SAAS,UAAU,EAAG,QAAO;AAGnE,MAAI,eAAe,sBAAuB,QAAO;AAEjD,SAAO;AACT;AAEA,eAAsB,OAAO,SAAyC;AACpE,QAAM,MAAM,QAAQ,aAAa;AAEjC,MAAI,QAAQ,QAAQ,QAAQ,QAAQ;AAClC,WAAO;AAAA,MACL,MAAM,gBAAgB,6BAA6B;AAAA,MACnD,OAAO,QAAQ,6BAA6B;AAAA,IAC9C;AAAA,EACF;AAEA,MAAI,QAAQ,SAAS,QAAQ,aAAa;AACxC,WAAO;AAAA,MACL,MAAM,gBAAgB,8BAA8B;AAAA,MACpD,OAAO,QAAQ,8BAA8B;AAAA,IAC/C;AAAA,EACF;AAEA,MAAI,QAAQ,eAAe,QAAQ,MAAM;AACvC,WAAO;AAAA,MACL,MAAM,gBAAgB,2BAA2B;AAAA,MACjD,OAAO,QAAQ,2BAA2B;AAAA,IAC5C;AAAA,EACF;AAEA,QAAM,aAAa,QAAQ,SAASE,SAAQ,QAAQ,MAAM,IAAI;AAE9D,MAAI;AACF,UAAM,IAAI,MAAM,KAAK,GAAG;AACxB,QAAI,CAAC,EAAE,YAAY,GAAG;AACpB,aAAO;AAAA,QACL,MAAM,gBAAgB,oBAAoB;AAAA,QAC1C,OAAO,QAAQ,2BAA2B,GAAG;AAAA,MAC/C;AAAA,IACF;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,MACL,MAAM,gBAAgB,cAAc;AAAA,MACpC,OAAO,QAAQ,sBAAsB,GAAG;AAAA,IAC1C;AAAA,EACF;AAEA,QAAM,WAAW,IAAI,qBAAqB;AAC1C,QAAM,UAAU,IAAI,cAAc,EAAE,SAAS,mBAAmB,CAAC;AAEjE,QAAM,aAAa,MAAM,YAAY,UAAU,OAAO;AACtD,MAAI,CAAC,WAAW,IAAI;AAClB,UAAM,UAAU,oBAAoB,WAAW,IAAI;AACnD,WAAO,EAAE,MAAM,gBAAgB,WAAW,IAAI,GAAG,OAAO,QAAQ;AAAA,EAClE;AAEA,QAAM,OAAO,WAAW;AAGxB,QAAM,eAA8D;AAAA,IAClE,MAAM,CAAC,MAAM;AAAA,IACb,KAAK,CAAC,QAAQ,KAAK;AAAA,EACrB;AAEA,QAAM,gBAAgB,QAAQ,QAAQ,KAAK;AAC3C,MAAI,CAAC,OAAO,UAAU,eAAe,KAAK,cAAc,KAAK,IAAI,KAAK,CAAC,aAAa,KAAK,IAAsB,EAAE,SAAS,aAAa,GAAG;AACxI,WAAO;AAAA,MACL,MAAM,gBAAgB,cAAc;AAAA,MACpC,OAAO,oBAAoB,cAAc;AAAA,IAC3C;AAAA,EACF;AAEA,QAAM,SAAS,QAAQ,OAAO,SAAS,aAAa,SAAS;AAE7D,QAAM,mBAAmB,QAAQ,YAAY,KAAK;AAClD,QAAM,kBAAkB,QAAQ,QAAQ,YAAY,QAAQ,aAAa,KAAK,WAAW;AAEzF,QAAM,OAAiB;AAAA,IACrB,WAAW;AAAA,IACX;AAAA,IACA,QAAQ;AAAA,IACR,OAAO,QAAQ;AAAA,IACf,aAAa,QAAQ;AAAA,IACrB,gBAAgB,QAAQ;AAAA,IACxB,WAAW,KAAK;AAAA,IAChB,aAAa;AAAA,IACb,UAAU,kBAAkB,SAAY,KAAK;AAAA,IAC7C,WAAW,kBAAkB,SAAY,KAAK;AAAA,IAC9C,aAAa,QAAQ;AAAA,IACrB,OAAO,QAAQ;AAAA,IACf,UAAU,QAAQ;AAAA,IAClB,eAAe,QAAQ;AAAA,EACzB;AAEA,QAAM,cAAc,KAAK,IAAI;AAC7B,QAAM,gBAAgB,YAAY,EAAE,MAAM,MAAM,MAAS;AAEzD,MAAI;AACJ,MAAI;AACF,aAAS,MAAM,YAAY,QAAQ,MAAM,EAAE,MAAM,eAAe,YAAY,KAAK,WAAW,CAAC;AAAA,EAC/F,SAAS,KAAK;AACZ,UAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,WAAO,EAAE,MAAM,gBAAgB,aAAa,GAAG,OAAO,QAAQ;AAAA,EAChE;AAEA,MAAI,CAAC,OAAO,IAAI;AACd,UAAM,UAAU,oBAAoB,OAAO,IAAI;AAC/C,WAAO,EAAE,MAAM,gBAAgB,OAAO,IAAI,GAAG,OAAO,QAAQ;AAAA,EAC9D;AAEA,QAAM,YAAY,KAAK,IAAI,GAAG,OAAS,KAAK,IAAI,IAAI,YAAY;AAChE,QAAM,gBAAgB,MAAM,QAAQ,KAAK;AAAA,IACvC;AAAA,IACA,IAAI,QAAmB,CAACA,aAAY,WAAW,MAAMA,SAAQ,MAAS,GAAG,SAAS,CAAC;AAAA,EACrF,CAAC;AAED,SAAO;AAAA,IACL,MAAM;AAAA,IACN,QAAQ,OAAO,UAAU;AAAA,IACzB;AAAA,EACF;AACF;;;AO5LA,SAAS,2BAA2B;AAGpC,IAAM,aAAmC,CAAC,cAAc,eAAe,aAAa,YAAY,WAAW;AAQ3G,SAAS,kBAAkB,KAAuB,OAA6C;AAC7F,MAAI,UAAU,OAAW,QAAO;AAChC,MAAI,QAAQ,eAAe,QAAQ,aAAc,QAAO;AACxD,SAAO;AACT;AAEA,eAAe,cAAc,MAA0C;AACrE,QAAM,SAAS,MAAM,WAAW;AAEhC,MAAI,KAAK,WAAW,QAAQ;AAC1B,UAAM,QAAkB,CAAC;AACzB,eAAWC,QAAO,YAAY;AAC5B,YAAM,QAAQ,OAAOA,IAAG;AACxB,YAAM,KAAK,GAAGA,IAAG,IAAI,kBAAkBA,MAAK,KAAK,CAAC,EAAE;AAAA,IACtD;AACA,WAAO,EAAE,IAAI,MAAM,QAAQ,MAAM,KAAK,IAAI,EAAE;AAAA,EAC9C;AAEA,MAAI,CAAC,KAAK,KAAK;AACb,WAAO,EAAE,IAAI,OAAO,MAAM,8BAA8B;AAAA,EAC1D;AAEA,MAAI,CAAC,WAAW,SAAS,KAAK,GAAuB,GAAG;AACtD,WAAO,EAAE,IAAI,OAAO,MAAM,+BAA+B,QAAQ,KAAK,IAAI;AAAA,EAC5E;AAEA,QAAM,MAAM,KAAK;AAEjB,MAAI,KAAK,WAAW,OAAO;AACzB,UAAM,QAAQ,OAAO,GAAG;AACxB,WAAO,EAAE,IAAI,MAAM,QAAQ,kBAAkB,KAAK,KAAK,EAAE;AAAA,EAC3D;AAEA,MAAI,KAAK,WAAW,OAAO;AACzB,QAAI,KAAK,UAAU,QAAW;AAC5B,aAAO,EAAE,IAAI,OAAO,MAAM,gCAAgC;AAAA,IAC5D;AAEA,QAAI,QAAQ,iBAAiB,CAAC,oBAAoB,EAAE,SAAS,KAAK,KAAK,GAAG;AACxE,aAAO,EAAE,IAAI,OAAO,MAAM,oCAAoC,QAAQ,KAAK,MAAM;AAAA,IACnF;AAEA,UAAM,OAAmB,EAAE,GAAG,QAAQ,CAAC,GAAG,GAAG,KAAK,MAAM;AACxD,UAAM,YAAY,IAAI;AACtB,WAAO,EAAE,IAAI,MAAM,QAAQ,QAAQ,yBAAyB,KAAK,GAAG,EAAE;AAAA,EACxE;AAEA,SAAO,EAAE,IAAI,OAAO,MAAM,iCAAiC;AAC7D;AAEO,IAAM,gBAAgB;AAAA,EAC3B;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,SAAS,OAAO,SAAS,cAAc,IAAI;AAAA,EAC7C;AAAA,EACA,EAAE,gBAAgB,MAAM;AAC1B;;;AR9DA,QAAQ,EAAE,MAAMC,SAAQ,QAAQ,IAAI,GAAG,MAAM,EAAE,CAAC;AAEhD,IAAM,cAAcA,SAAQC,SAAQC,eAAc,YAAY,GAAG,CAAC,GAAG,YAAY,MAAM;AACvF,IAAI,WAAW,WAAW,GAAG;AAC3B,UAAQ,EAAE,MAAM,aAAa,UAAU,MAAM,CAAC;AAChD;AAIA,IAAMC,WAAU,cAAc,YAAY,GAAG;AAC7C,IAAM,EAAE,QAAQ,IAAIA,SAAQ,iBAAiB;AAE7C,IAAM,MAAM,IAAI,WAAW;AAE3B,IAAI,QAAQ,OAAO;AAEnB,IACG,QAAQ,UAAU,6BAA6B,EAC/C,OAAO,UAAU,uBAAuB,EACxC,OAAO,mBAAmB,2BAA2B,EACrD,OAAO,kBAAkB,4BAA4B,EACrD,OAAO,kBAAkB,8BAA8B,EACvD,OAAO,sBAAsB,0CAA0C,EACvE,OAAO,iBAAiB,yBAAyB,EACjD,OAAO,yBAAyB,uFAAuF,EACvH,OAAO,2BAA2B,6CAA6C,EAC/E,OAAO,WAAW,sBAAsB,EACxC,OAAO,oBAAoB,wFAAwF,EACnH,OAAO,4BAA4B,yDAAyD,EAC5F,OAAO,OAAO,WAA+B,YAAsO;AAClR,QAAM,cAAc,QAAQ,cAAc,OAAO,QAAQ,WAAW,IAAI;AACxE,QAAM,OAAO,QAAQ;AACrB,QAAM,WAAW,QAAQ,aAAa,SAAS,SAAS,QAAQ;AAChE,QAAM,gBAAgB,QAAQ;AAC9B,QAAM,SAAS,MAAM,OAAO,EAAE,WAAW,MAAM,QAAQ,MAAM,QAAQ,QAAQ,QAAQ,OAAO,QAAQ,OAAO,aAAa,QAAQ,aAAa,gBAAgB,QAAQ,gBAAgB,MAAM,UAAU,QAAQ,UAAU,aAAa,OAAO,QAAQ,OAAO,UAAU,cAAc,CAAC;AAEnR,MAAI,OAAO,eAAe;AACxB,YAAQ,MAAM,OAAO,aAAa;AAAA,EACpC;AAEA,MAAI,OAAO,OAAO;AAChB,YAAQ,MAAM,OAAO,KAAK;AAC1B,YAAQ,KAAK,OAAO,IAAI;AAAA,EAC1B;AAEA,MAAI,OAAO,QAAQ;AACjB,YAAQ,IAAI,OAAO,MAAM;AAAA,EAC3B;AACF,CAAC;AAEH,IACG,QAAQ,iCAAiC,sBAAsB,EAC/D,OAAO,OAAO,QAAgB,KAAc,UAAmB;AAC9D,QAAM,OAAmB,EAAE,QAAwC,KAAK,MAAM;AAC9E,QAAM,SAAS,MAAM,cAAc,QAAQ,MAAM,EAAE,MAAM,QAAQ,YAAY,YAAY,CAAC;AAE1F,MAAI,CAAC,OAAO,IAAI;AACd,UAAM,UAAU,OAAO,SACnB,GAAG,QAAQ,OAAO,IAAe,CAAC,KAAK,OAAO,MAAM,MACpD,QAAQ,OAAO,IAAe;AAClC,YAAQ,MAAM,OAAO;AACrB,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,OAAO,QAAQ;AACjB,YAAQ,IAAI,OAAO,MAAM;AAAA,EAC3B;AACF,CAAC;AAEH,IAAI,KAAK,MAAM;AACb,SAAO;AAAA,IACL;AAAA,MACE,OAAO;AAAA,MACP,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA,IAKR;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,MAAM;AAAA;AAAA,2BAEe,QAAQ,qBAAqB,CAAC;AAAA,2BAC9B,QAAQ,2BAA2B,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQ3D;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,MAAM;AAAA,IACR;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,MAAM;AAAA,IACR;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,MAAM,QAAQ,oBAAoB;AAAA,IACpC;AAAA,EACF;AACF,CAAC;AAED,IAAI,MAAM;","names":["dirname","resolve","fileURLToPath","resolve","resolve","fs","dirname","join","resolve","key","resolve","dirname","fileURLToPath","require"]}
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "seaworthycode",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "files": [
6
+ "dist"
7
+ ],
8
+ "bin": {
9
+ "seaworthy": "./dist/index.js"
10
+ },
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js"
15
+ }
16
+ },
17
+ "dependencies": {
18
+ "cac": "^7.0.0",
19
+ "chalk": "^5.6.2",
20
+ "dotenv": "^16.6.1",
21
+ "@seaworthy/core": "0.0.0"
22
+ },
23
+ "devDependencies": {
24
+ "eslint": "^9.39.4",
25
+ "@types/node": "^22.10.5",
26
+ "tsup": "^8.3.6",
27
+ "typescript": "^5.7.3",
28
+ "vitest": "^4.1.0"
29
+ },
30
+ "scripts": {
31
+ "build": "tsup",
32
+ "dev": "tsup --watch",
33
+ "lint": "eslint src/",
34
+ "check-types": "tsc --noEmit",
35
+ "test": "vitest run"
36
+ }
37
+ }