ship-safe 9.2.2 → 9.2.3

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 CHANGED
@@ -98,7 +98,7 @@ $ ship-safe
98
98
  ███████╗██╗ ██╗██╗██████╗ ███████╗ █████╗ ███████╗███████╗
99
99
  ...
100
100
 
101
- v9.2.1 · DeepSeek · ~/my-project
101
+ v9.2.3 · DeepSeek · ~/my-project
102
102
 
103
103
  /scan to find issues · /agent to fix them · /help for more
104
104
 
@@ -113,6 +113,7 @@ shipsafe ›
113
113
  | `/show <n>` | Full detail on finding n |
114
114
  | `/plan <n>` | Preview fix plan for finding n (no writes) |
115
115
  | `/undo [--all]` | Revert the last fix (or all fixes) |
116
+ | `/share` | Publish scan report as a public URL (7 days) |
116
117
  | `/diff` | Show git working-tree diff |
117
118
  | `/provider <name>` | Switch LLM provider mid-session |
118
119
  | `/quit` | Exit (also `Ctrl-D` or `Ctrl-C`) |
@@ -31,6 +31,7 @@ import { rotateCommand } from '../commands/rotate.js';
31
31
  import { agentCommand } from '../commands/agent.js';
32
32
  import { agentFixCommand } from '../commands/agent-fix.js';
33
33
  import { undoCommand } from '../commands/undo.js';
34
+ import { shareCommand } from '../commands/share.js';
34
35
  import { shellCommand } from '../commands/shell.js';
35
36
  import { depsCommand } from '../commands/deps.js';
36
37
  import { scoreCommand } from '../commands/score.js';
@@ -217,6 +218,14 @@ program
217
218
  .option('--dry-run', 'Show what would be reverted without writing anything')
218
219
  .action(undoCommand);
219
220
 
221
+ // -----------------------------------------------------------------------------
222
+ // SHARE COMMAND
223
+ // -----------------------------------------------------------------------------
224
+ program
225
+ .command('share [path]')
226
+ .description('Publish your latest scan report as a public URL (valid 7 days)')
227
+ .action(shareCommand);
228
+
220
229
  // -----------------------------------------------------------------------------
221
230
  // SHELL COMMAND
222
231
  // -----------------------------------------------------------------------------
@@ -499,6 +499,18 @@ export async function auditCommand(targetPath = '.', options = {}) {
499
499
  printComparison(scoringEngine, absolutePath, scoreResult);
500
500
  }
501
501
 
502
+ // ── Upgrade prompt ────────────────────────────────────────────────────
503
+ const criticalCount = filteredFindings.filter(f => f.severity === 'critical').length;
504
+ const highCount = filteredFindings.filter(f => f.severity === 'high').length;
505
+ const fixable = criticalCount + highCount;
506
+ if (fixable > 0 && scoreResult.score < 80) {
507
+ console.log();
508
+ console.log(chalk.yellow(' ┌─────────────────────────────────────────────────────────┐'));
509
+ console.log(chalk.yellow(' │') + chalk.white.bold(` ${fixable} critical/high finding${fixable === 1 ? '' : 's'} — fix them automatically`) + chalk.yellow(' │'));
510
+ console.log(chalk.yellow(' │') + chalk.cyan(' npx ship-safe agent .') + chalk.gray(' · ') + chalk.cyan('shipsafecli.com/pricing') + chalk.yellow(' │'));
511
+ console.log(chalk.yellow(' └─────────────────────────────────────────────────────────┘'));
512
+ }
513
+
502
514
  console.log();
503
515
  console.log(chalk.cyan('═'.repeat(60)));
504
516
  console.log();
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Share Command — Publish a scan report as a public URL
3
+ *
4
+ * Usage:
5
+ * ship-safe share [path]
6
+ *
7
+ * Reads the latest scan from .ship-safe/history.json (or runs a fresh scan),
8
+ * uploads it to shipsafecli.com, and returns a shareable link valid for 7 days.
9
+ */
10
+
11
+ import fs from 'fs';
12
+ import path from 'path';
13
+ import chalk from 'chalk';
14
+ import ora from 'ora';
15
+
16
+ const SHARE_ENDPOINT = 'https://shipsafecli.com/api/share';
17
+
18
+ export async function shareCommand(targetPath = '.', options = {}) {
19
+ const root = path.resolve(targetPath);
20
+
21
+ // ── Load last scan from history ──────────────────────────────────────────
22
+ const historyPath = path.join(root, '.ship-safe', 'history.json');
23
+ let report = null;
24
+
25
+ if (fs.existsSync(historyPath)) {
26
+ try {
27
+ const history = JSON.parse(fs.readFileSync(historyPath, 'utf8'));
28
+ const entries = Array.isArray(history) ? history : [history];
29
+ if (entries.length > 0) report = entries[entries.length - 1];
30
+ } catch {
31
+ // fall through
32
+ }
33
+ }
34
+
35
+ if (!report) {
36
+ console.log(chalk.yellow(' No scan found. Run a scan first:'));
37
+ console.log(chalk.gray(' npx ship-safe audit .'));
38
+ process.exit(1);
39
+ }
40
+
41
+ const spinner = ora({ text: 'Uploading report...', color: 'cyan' }).start();
42
+
43
+ try {
44
+ const res = await fetch(SHARE_ENDPOINT, {
45
+ method: 'POST',
46
+ headers: { 'Content-Type': 'application/json' },
47
+ body: JSON.stringify({
48
+ score: report.score ?? null,
49
+ grade: report.grade ?? null,
50
+ repo: report.rootPath ? path.basename(report.rootPath) : null,
51
+ findings: report.totalFindings ?? 0,
52
+ report,
53
+ }),
54
+ });
55
+
56
+ if (!res.ok) {
57
+ throw new Error(`Server returned ${res.status}`);
58
+ }
59
+
60
+ const { url } = await res.json();
61
+ spinner.succeed(chalk.green('Report shared!'));
62
+ console.log();
63
+ console.log(chalk.white(' Share URL: ') + chalk.cyan.bold(url));
64
+ console.log(chalk.gray(' Link expires in 7 days.'));
65
+ console.log();
66
+ } catch (err) {
67
+ spinner.fail(chalk.red('Failed to share report'));
68
+ console.log(chalk.gray(` ${err.message}`));
69
+ process.exit(1);
70
+ }
71
+ }
@@ -29,6 +29,7 @@ import { autoDetectProvider } from '../providers/llm-provider.js';
29
29
  import { auditCommand } from './audit.js';
30
30
  import { agentFixCommand } from './agent-fix.js';
31
31
  import { undoCommand } from './undo.js';
32
+ import { shareCommand } from './share.js';
32
33
  import * as output from '../utils/output.js';
33
34
 
34
35
  const SEV_RANK = { critical: 4, high: 3, medium: 2, low: 1, info: 0 };
@@ -320,6 +321,11 @@ async function handleSlashCommand(line, state, options) {
320
321
  return true;
321
322
  }
322
323
 
324
+ case 'share': {
325
+ await shareCommand(state.root);
326
+ return true;
327
+ }
328
+
323
329
  case 'provider': {
324
330
  const name = args[0];
325
331
  if (!name) {
@@ -428,6 +434,7 @@ function printHelp() {
428
434
  console.log(' /plan <n> Preview a fix plan for finding <n> (no writes)');
429
435
  console.log(' /agent [--plan-only] Run the interactive fix loop');
430
436
  console.log(' /undo [--all] Revert the last fix (or all)');
437
+ console.log(' /share Publish scan report as a public URL (7 days)');
431
438
  console.log(' /diff [path] Show git working-tree diff');
432
439
  console.log(' /git <args> Pass through to git (status, log, stash, ...)');
433
440
  console.log(' /provider <name> Switch LLM provider');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ship-safe",
3
- "version": "9.2.2",
3
+ "version": "9.2.3",
4
4
  "description": "AI-powered multi-agent security platform. 23 agents scan 80+ attack classes including AI integration supply chain (Vercel-class attacks), Hermes Agent deployments (ASI-01–ASI-10), tool registry poisoning, function-call injection, skill permission drift, and agent attestation. Ship Safe × Hermes Agent.",
5
5
  "main": "cli/index.js",
6
6
  "bin": {