@somewhere-tech/cli 0.12.9 → 0.14.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.
Files changed (70) hide show
  1. package/README.md +59 -1
  2. package/bin/swpm.js +9 -0
  3. package/bin/swpx.js +9 -0
  4. package/dist/commands/auth.js +5 -5
  5. package/dist/commands/auth.js.map +1 -1
  6. package/dist/commands/db.js +1 -1
  7. package/dist/commands/db.js.map +1 -1
  8. package/dist/commands/deploy.js +1 -1
  9. package/dist/commands/deploy.js.map +1 -1
  10. package/dist/commands/dev.js +2 -2
  11. package/dist/commands/dev.js.map +1 -1
  12. package/dist/commands/init.js +7 -7
  13. package/dist/commands/init.js.map +1 -1
  14. package/dist/commands/mcp.js +9 -8
  15. package/dist/commands/mcp.js.map +1 -1
  16. package/dist/commands/open.js +1 -1
  17. package/dist/commands/open.js.map +1 -1
  18. package/dist/commands/project.js +1 -1
  19. package/dist/commands/project.js.map +1 -1
  20. package/dist/commands/promote.js +1 -1
  21. package/dist/commands/promote.js.map +1 -1
  22. package/dist/commands/pull.js +1 -1
  23. package/dist/commands/pull.js.map +1 -1
  24. package/dist/commands/rollback.js +1 -1
  25. package/dist/commands/rollback.js.map +1 -1
  26. package/dist/commands/swpx.js +43 -0
  27. package/dist/commands/swpx.js.map +1 -0
  28. package/dist/commands/typecheck.js +1 -1
  29. package/dist/commands/typecheck.js.map +1 -1
  30. package/dist/index.js +6 -0
  31. package/dist/index.js.map +1 -1
  32. package/dist/lib/auth.js +8 -3
  33. package/dist/lib/auth.js.map +1 -1
  34. package/dist/lib/client.js +57 -1
  35. package/dist/lib/client.js.map +1 -1
  36. package/dist/lib/config.js +37 -19
  37. package/dist/lib/config.js.map +1 -1
  38. package/dist/lib/device-login.js +4 -1
  39. package/dist/lib/device-login.js.map +1 -1
  40. package/dist/lib/open.js +25 -0
  41. package/dist/lib/open.js.map +1 -0
  42. package/dist/lib/output.js +11 -7
  43. package/dist/lib/output.js.map +1 -1
  44. package/dist/lib/spinner.js +85 -0
  45. package/dist/lib/spinner.js.map +1 -0
  46. package/dist/swpm-bin.js +11 -0
  47. package/dist/swpm-bin.js.map +1 -0
  48. package/dist/swpx/check.js +52 -0
  49. package/dist/swpx/check.js.map +1 -0
  50. package/dist/swpx/registry.js +68 -0
  51. package/dist/swpx/registry.js.map +1 -0
  52. package/dist/swpx/render.js +308 -0
  53. package/dist/swpx/render.js.map +1 -0
  54. package/dist/swpx/run-common.js +23 -0
  55. package/dist/swpx/run-common.js.map +1 -0
  56. package/dist/swpx/run-swpm.js +87 -0
  57. package/dist/swpx/run-swpm.js.map +1 -0
  58. package/dist/swpx/run-swpx.js +54 -0
  59. package/dist/swpx/run-swpx.js.map +1 -0
  60. package/dist/swpx/spawn.js +30 -0
  61. package/dist/swpx/spawn.js.map +1 -0
  62. package/dist/swpx/tree.js +101 -0
  63. package/dist/swpx/tree.js.map +1 -0
  64. package/dist/swpx/types.js +14 -0
  65. package/dist/swpx/types.js.map +1 -0
  66. package/dist/swpx/verdict-client.js +109 -0
  67. package/dist/swpx/verdict-client.js.map +1 -0
  68. package/dist/swpx-bin.js +20 -0
  69. package/dist/swpx-bin.js.map +1 -0
  70. package/package.json +6 -5
@@ -0,0 +1,54 @@
1
+ /** `swpx <package> [args...]` — the verdict gate in front of `npx`.
2
+ *
3
+ * resolve → verdict → act:
4
+ * verified → print the green one-liner, delegate to real npx.
5
+ * unverified/suspicious → print the evidence block, STOP (exit 1). The user's
6
+ * documented override is to run `npx` directly, per
7
+ * the landing page ("your choice, not ours") — so we
8
+ * deliberately do not prompt or take a --yes flag
9
+ * (which would also collide with npx -y).
10
+ * blocked → print the red block, hard refuse (exit 1, no run).
11
+ * can't resolve / verdict unreachable → fall back to npx unchecked. We are a
12
+ * gate, not a wall: our outage must not stop the user.
13
+ *
14
+ * All verdict messaging goes to STDERR so swpx is transparent on stdout — the
15
+ * executed program owns stdout. */
16
+ import { parseSpec } from './registry.js';
17
+ import { decide, renderVerdict } from './render.js';
18
+ import { bindDeps, msg } from './run-common.js';
19
+ import { dim } from '../lib/output.js';
20
+ export async function runSwpx(args, deps = {}) {
21
+ const d = bindDeps(deps);
22
+ // The package is the first non-flag arg, so `swpx -y create-next-app` works.
23
+ const pkgArg = args.find((a) => !a.startsWith('-'));
24
+ if (!pkgArg) {
25
+ d.errLog('Usage: swpx <package> [args...]');
26
+ return { exitCode: 1, action: 'stopped' };
27
+ }
28
+ const spec = parseSpec(pkgArg);
29
+ let version;
30
+ try {
31
+ version = await d.resolveVersion(spec.name, spec.version);
32
+ }
33
+ catch (err) {
34
+ d.errLog(dim(`swpx: couldn't resolve ${spec.name} (${msg(err)}) — running npx unchecked`));
35
+ return { exitCode: await d.runReal('npx', args), action: 'fallback' };
36
+ }
37
+ let verdict;
38
+ try {
39
+ verdict = await d.getVerdict(spec.name, version);
40
+ }
41
+ catch {
42
+ d.errLog(dim('swpx: verdict service unavailable — running npx unchecked'));
43
+ return { exitCode: await d.runReal('npx', args), action: 'fallback' };
44
+ }
45
+ const action = decide(verdict);
46
+ for (const l of renderVerdict(verdict))
47
+ d.errLog(l);
48
+ if (action === 'run') {
49
+ return { exitCode: await d.runReal('npx', args), action: 'ran' };
50
+ }
51
+ // unverified/suspicious and blocked both stop swpx; only the exit reason differs.
52
+ return { exitCode: 1, action: action === 'block' ? 'blocked' : 'stopped' };
53
+ }
54
+ //# sourceMappingURL=run-swpx.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-swpx.js","sourceRoot":"","sources":["../../src/swpx/run-swpx.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;oCAcoC;AAEpC,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAgB,MAAM,iBAAiB,CAAC;AAC9D,OAAO,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAC;AAOvC,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,IAAc,EAAE,OAAgB,EAAE;IAC9D,MAAM,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IAEzB,6EAA6E;IAC7E,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;IACpD,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,CAAC,CAAC,MAAM,CAAC,iCAAiC,CAAC,CAAC;QAC5C,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IAC5C,CAAC;IACD,MAAM,IAAI,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;IAE/B,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IAC5D,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,0BAA0B,IAAI,CAAC,IAAI,KAAK,GAAG,CAAC,GAAG,CAAC,2BAA2B,CAAC,CAAC,CAAC;QAC3F,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IACxE,CAAC;IAED,IAAI,OAAO,CAAC;IACZ,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACnD,CAAC;IAAC,MAAM,CAAC;QACP,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,2DAA2D,CAAC,CAAC,CAAC;QAC3E,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IACxE,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAC/B,KAAK,MAAM,CAAC,IAAI,aAAa,CAAC,OAAO,CAAC;QAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACpD,IAAI,MAAM,KAAK,KAAK,EAAE,CAAC;QACrB,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IACnE,CAAC;IACD,kFAAkF;IAClF,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC;AAC7E,CAAC"}
@@ -0,0 +1,30 @@
1
+ /** Shell out to the REAL npx / npm. swpx/swpm are wrappers, never replacements:
2
+ * once a verdict says "go", the genuine tool runs with the user's exact args,
3
+ * inheriting stdio so its output is untouched (we keep our own messaging on
4
+ * stderr). The child's exit code becomes ours. A launch failure (tool not on
5
+ * PATH) resolves to 127 rather than throwing — the caller already printed the
6
+ * verdict; a missing npm is the user's environment, not a swpx error. */
7
+ import { spawn } from 'node:child_process';
8
+ import { dim } from '../lib/output.js';
9
+ export function runReal(cmd, args) {
10
+ // SWPX_DRY_RUN: show the decision + what WOULD run, without executing. Useful
11
+ // for "what would swpx do?" and for testing the gate end-to-end offline.
12
+ if (process.env.SWPX_DRY_RUN) {
13
+ console.error(dim(`swpx: [dry-run] would run: ${cmd} ${args.join(' ')}`));
14
+ return Promise.resolve(0);
15
+ }
16
+ return new Promise((resolve) => {
17
+ const child = spawn(cmd, args, {
18
+ stdio: 'inherit',
19
+ // On Windows npx/npm are .cmd shims that need a shell; on POSIX we spawn
20
+ // the binary directly (no shell → no quoting/injection surface).
21
+ shell: process.platform === 'win32',
22
+ });
23
+ child.on('error', (err) => {
24
+ console.error(dim(`swpx: failed to launch ${cmd} — ${err.message}`));
25
+ resolve(127);
26
+ });
27
+ child.on('exit', (code, signal) => resolve(signal ? 1 : code ?? 0));
28
+ });
29
+ }
30
+ //# sourceMappingURL=spawn.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spawn.js","sourceRoot":"","sources":["../../src/swpx/spawn.ts"],"names":[],"mappings":"AAAA;;;;;0EAK0E;AAE1E,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAC;AAEvC,MAAM,UAAU,OAAO,CAAC,GAAkB,EAAE,IAAc;IACxD,8EAA8E;IAC9E,yEAAyE;IACzE,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;QAC7B,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,8BAA8B,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QAC1E,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAC5B,CAAC;IACD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE;YAC7B,KAAK,EAAE,SAAS;YAChB,yEAAyE;YACzE,iEAAiE;YACjE,KAAK,EAAE,OAAO,CAAC,QAAQ,KAAK,OAAO;SACpC,CAAC,CAAC;QACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YACxB,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,0BAA0B,GAAG,MAAM,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;YACrE,OAAO,CAAC,GAAG,CAAC,CAAC;QACf,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC;IACtE,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,101 @@
1
+ /** Resolve a project's dependency tree for `swpm install`.
2
+ *
3
+ * When a lockfile is present it IS the resolved tree — the exact versions npm
4
+ * would install — so we read it directly (no registry round-trips, no guessing
5
+ * ranges). Without a lockfile we fall back to the direct deps in package.json
6
+ * and let the caller resolve their ranges. Parsing is split from fs reads so
7
+ * the lockfile/manifest parsers are unit-tested against fixture strings. */
8
+ import { existsSync, readFileSync } from 'node:fs';
9
+ import { join } from 'node:path';
10
+ export function parsePackageJsonDeps(pkgJson) {
11
+ let obj;
12
+ try {
13
+ obj = JSON.parse(pkgJson);
14
+ }
15
+ catch {
16
+ return { directNames: [], ranges: {} };
17
+ }
18
+ const ranges = {};
19
+ const o = obj;
20
+ for (const field of ['dependencies', 'devDependencies', 'optionalDependencies']) {
21
+ const d = o?.[field];
22
+ if (d && typeof d === 'object') {
23
+ for (const [k, v] of Object.entries(d)) {
24
+ if (typeof v === 'string')
25
+ ranges[k] = v;
26
+ }
27
+ }
28
+ }
29
+ return { directNames: Object.keys(ranges), ranges };
30
+ }
31
+ /** Extract every concrete package@version from a package-lock.json /
32
+ * npm-shrinkwrap.json string. Handles lockfile v2/v3 (`packages` keyed by
33
+ * node_modules paths) and the older v1 (`dependencies` tree). Deduped. */
34
+ export function parseLockfile(lock) {
35
+ let obj;
36
+ try {
37
+ obj = JSON.parse(lock);
38
+ }
39
+ catch {
40
+ return [];
41
+ }
42
+ const out = [];
43
+ const seen = new Set();
44
+ const add = (name, version) => {
45
+ if (!name || typeof version !== 'string')
46
+ return;
47
+ const id = `${name}@${version}`;
48
+ if (seen.has(id))
49
+ return;
50
+ seen.add(id);
51
+ out.push({ package: name, version });
52
+ };
53
+ const packages = obj.packages;
54
+ if (packages && typeof packages === 'object') {
55
+ for (const [key, val] of Object.entries(packages)) {
56
+ if (!key)
57
+ continue; // "" is the root project
58
+ const idx = key.lastIndexOf('node_modules/');
59
+ if (idx === -1)
60
+ continue;
61
+ if (val?.link)
62
+ continue; // workspace symlink, not a real install
63
+ add(key.slice(idx + 'node_modules/'.length), val?.version);
64
+ }
65
+ return out;
66
+ }
67
+ // lockfile v1: recursive `dependencies` tree.
68
+ const walk = (deps) => {
69
+ if (!deps || typeof deps !== 'object')
70
+ return;
71
+ for (const [name, info] of Object.entries(deps)) {
72
+ add(name, info?.version);
73
+ walk(info?.dependencies);
74
+ }
75
+ };
76
+ walk(obj.dependencies);
77
+ return out;
78
+ }
79
+ /** Read package.json + the first lockfile we find in `dir`. Missing files yield
80
+ * an empty-ish tree rather than throwing — the caller decides what to do with
81
+ * no deps. */
82
+ export function readTree(dir) {
83
+ let directNames = [];
84
+ let ranges = {};
85
+ const pkgPath = join(dir, 'package.json');
86
+ if (existsSync(pkgPath)) {
87
+ const parsed = parsePackageJsonDeps(readFileSync(pkgPath, 'utf-8'));
88
+ directNames = parsed.directNames;
89
+ ranges = parsed.ranges;
90
+ }
91
+ let locked = [];
92
+ for (const lockName of ['package-lock.json', 'npm-shrinkwrap.json']) {
93
+ const lockPath = join(dir, lockName);
94
+ if (existsSync(lockPath)) {
95
+ locked = parseLockfile(readFileSync(lockPath, 'utf-8'));
96
+ break;
97
+ }
98
+ }
99
+ return { directNames, ranges, locked };
100
+ }
101
+ //# sourceMappingURL=tree.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tree.js","sourceRoot":"","sources":["../../src/swpx/tree.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /** Shared types for the swpx / swpm verdict layer.
2
+ *
3
+ * The verdict API lives on the `npm` platform project (npm.somewhere.tech),
4
+ * NOT the /v1 control plane. It is unauthenticated (free, no login) — the CLI
5
+ * hits it with a plain fetch, independent of `somewhere login`. See
6
+ * verdict-client.ts.
7
+ *
8
+ * The `Verdict` shape mirrors the D1 `verdicts` row (tsk_f30faf55) plus the
9
+ * live MAL advisory check, which is NOT cached (a version can be retroactively
10
+ * flagged). The `--json` output is a stable PROJECTION of this (toJsonVerdict)
11
+ * — that projection is the public contract shown on npm.somewhere.tech, so it
12
+ * is intentionally narrower than the internal row. */
13
+ export {};
14
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/swpx/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;uDAWuD"}
@@ -0,0 +1,109 @@
1
+ /** Client for the verdict API hosted on the `npm` platform project.
2
+ *
3
+ * This is NOT the /v1 control plane and NOT the runner — it's the public,
4
+ * unauthenticated verdict service at npm.somewhere.tech (FAQ: "Free, no
5
+ * login"). So this client deliberately does not touch ApiClient or the stored
6
+ * developer key. Override the host for staging/tests via SWPX_VERDICT_URL.
7
+ *
8
+ * Reachability is a product guarantee, inverted: if the verdict API is down,
9
+ * the user must still get normal npm behaviour ("a gate, not a wall"). So every
10
+ * failure here raises VerdictUnavailable and the command layer falls back to
11
+ * the real npx/npm. We never block on our own outage. */
12
+ const VERDICT_BASE = process.env.SWPX_VERDICT_URL?.replace(/\/$/, '') || 'https://npm.somewhere.tech';
13
+ /** Short by design: the verdict check sits in front of every `npx`, so a slow
14
+ * or unreachable service must fail fast to fallback, not stall the user. */
15
+ const VERDICT_TIMEOUT_MS = Number(process.env.SWPX_VERDICT_TIMEOUT_MS) || 4000;
16
+ const defaultFetch = (url, init) => fetch(url, init);
17
+ /** Raised whenever the verdict couldn't be obtained — network error, timeout,
18
+ * non-2xx, or unparseable body. The command layer treats it as "fall back to
19
+ * the real tool", never as "block". */
20
+ export class VerdictUnavailable extends Error {
21
+ constructor(message) {
22
+ super(message);
23
+ this.name = 'VerdictUnavailable';
24
+ }
25
+ }
26
+ /** Accept either a bare verdict object or a `{ ok, data }` envelope. */
27
+ function unwrap(body) {
28
+ if (body && typeof body === 'object' && 'ok' in body) {
29
+ const env = body;
30
+ if (env.ok === true)
31
+ return env.data;
32
+ throw new VerdictUnavailable(env.message || env.error || 'verdict API returned ok:false');
33
+ }
34
+ return body;
35
+ }
36
+ function timeoutSignal() {
37
+ try {
38
+ return AbortSignal.timeout(VERDICT_TIMEOUT_MS);
39
+ }
40
+ catch {
41
+ return undefined; // very old runtime — let the request run without our timeout
42
+ }
43
+ }
44
+ /** Look up one package@version. Throws VerdictUnavailable on any failure. */
45
+ export async function getVerdict(name, version, fetchImpl = defaultFetch) {
46
+ const url = `${VERDICT_BASE}/api/verdict/${encodeURIComponent(name)}/${encodeURIComponent(version)}`;
47
+ let res;
48
+ try {
49
+ res = await fetchImpl(url, { headers: { Accept: 'application/json' }, signal: timeoutSignal() });
50
+ }
51
+ catch (err) {
52
+ throw new VerdictUnavailable(err instanceof Error ? err.message : String(err));
53
+ }
54
+ if (!res.ok)
55
+ throw new VerdictUnavailable(`verdict API returned ${res.status}`);
56
+ let body;
57
+ try {
58
+ body = await res.json();
59
+ }
60
+ catch {
61
+ throw new VerdictUnavailable('verdict API returned a non-JSON body');
62
+ }
63
+ const v = unwrap(body);
64
+ if (!v || typeof v !== 'object' || typeof v.verdict !== 'string') {
65
+ throw new VerdictUnavailable('verdict API returned an unexpected shape');
66
+ }
67
+ // Trust the caller's resolved coordinates over whatever the row echoes back.
68
+ return { ...v, package: name, version };
69
+ }
70
+ /** Batch-check a resolved tree. Throws VerdictUnavailable on any failure so the
71
+ * caller can fall back to a plain `npm install`. The returned array is aligned
72
+ * to the input order; any row the service omits is dropped (caller treats a
73
+ * missing row as "unknown", not "verified"). */
74
+ export async function getVerdictBatch(pkgs, fetchImpl = defaultFetch) {
75
+ if (pkgs.length === 0)
76
+ return [];
77
+ const url = `${VERDICT_BASE}/api/verdict/batch`;
78
+ let res;
79
+ try {
80
+ res = await fetchImpl(url, {
81
+ method: 'POST',
82
+ headers: { Accept: 'application/json', 'Content-Type': 'application/json' },
83
+ body: JSON.stringify({ packages: pkgs }),
84
+ signal: timeoutSignal(),
85
+ });
86
+ }
87
+ catch (err) {
88
+ throw new VerdictUnavailable(err instanceof Error ? err.message : String(err));
89
+ }
90
+ if (!res.ok)
91
+ throw new VerdictUnavailable(`verdict API returned ${res.status}`);
92
+ let body;
93
+ try {
94
+ body = await res.json();
95
+ }
96
+ catch {
97
+ throw new VerdictUnavailable('verdict API returned a non-JSON body');
98
+ }
99
+ const data = unwrap(body);
100
+ const results = Array.isArray(data)
101
+ ? data
102
+ : data?.results;
103
+ if (!Array.isArray(results)) {
104
+ throw new VerdictUnavailable('verdict API batch returned an unexpected shape');
105
+ }
106
+ return results.filter((v) => !!v && typeof v === 'object' && typeof v.verdict === 'string');
107
+ }
108
+ export const verdictBaseUrl = VERDICT_BASE;
109
+ //# sourceMappingURL=verdict-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verdict-client.js","sourceRoot":"","sources":["../../src/swpx/verdict-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;0DAU0D;AAK1D,MAAM,YAAY,GAChB,OAAO,CAAC,GAAG,CAAC,gBAAgB,EAAE,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,4BAA4B,CAAC;AAEnF;6EAC6E;AAC7E,MAAM,kBAAkB,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,IAAI,IAAI,CAAC;AAE/E,MAAM,YAAY,GAAc,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAC5C,KAAK,CAAC,GAAG,EAAE,IAAmB,CAAsC,CAAC;AAEvE;;wCAEwC;AACxC,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAC3C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;IACnC,CAAC;CACF;AAED,wEAAwE;AACxE,SAAS,MAAM,CAAC,IAAa;IAC3B,IAAI,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC;QACrD,MAAM,GAAG,GAAG,IAA0E,CAAC;QACvF,IAAI,GAAG,CAAC,EAAE,KAAK,IAAI;YAAE,OAAO,GAAG,CAAC,IAAI,CAAC;QACrC,MAAM,IAAI,kBAAkB,CAAC,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC,KAAK,IAAI,+BAA+B,CAAC,CAAC;IAC5F,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,aAAa;IACpB,IAAI,CAAC;QACH,OAAO,WAAW,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACjD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC,CAAC,6DAA6D;IACjF,CAAC;AACH,CAAC;AAED,6EAA6E;AAC7E,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,IAAY,EACZ,OAAe,EACf,YAAuB,YAAY;IAEnC,MAAM,GAAG,GAAG,GAAG,YAAY,gBAAgB,kBAAkB,CAAC,IAAI,CAAC,IAAI,kBAAkB,CAAC,OAAO,CAAC,EAAE,CAAC;IACrG,IAAI,GAAkB,CAAC;IACvB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,SAAS,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE,EAAE,MAAM,EAAE,aAAa,EAAE,EAAE,CAAC,CAAC;IACnG,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,kBAAkB,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IACjF,CAAC;IACD,IAAI,CAAC,GAAG,CAAC,EAAE;QAAE,MAAM,IAAI,kBAAkB,CAAC,wBAAwB,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;IAChF,IAAI,IAAa,CAAC;IAClB,IAAI,CAAC;QACH,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,kBAAkB,CAAC,sCAAsC,CAAC,CAAC;IACvE,CAAC;IACD,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAwB,CAAC;IAC9C,IAAI,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAQ,CAAa,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;QAC9E,MAAM,IAAI,kBAAkB,CAAC,0CAA0C,CAAC,CAAC;IAC3E,CAAC;IACD,6EAA6E;IAC7E,OAAO,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;AAC1C,CAAC;AAED;;;iDAGiD;AACjD,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,IAAiD,EACjD,YAAuB,YAAY;IAEnC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IACjC,MAAM,GAAG,GAAG,GAAG,YAAY,oBAAoB,CAAC;IAChD,IAAI,GAAkB,CAAC;IACvB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,SAAS,CAAC,GAAG,EAAE;YACzB,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC3E,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;YACxC,MAAM,EAAE,aAAa,EAAE;SACxB,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,kBAAkB,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IACjF,CAAC;IACD,IAAI,CAAC,GAAG,CAAC,EAAE;QAAE,MAAM,IAAI,kBAAkB,CAAC,wBAAwB,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;IAChF,IAAI,IAAa,CAAC;IAClB,IAAI,CAAC;QACH,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,kBAAkB,CAAC,sCAAsC,CAAC,CAAC;IACvE,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IAC1B,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;QACjC,CAAC,CAAC,IAAI;QACN,CAAC,CAAE,IAA8B,EAAE,OAAO,CAAC;IAC7C,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,kBAAkB,CAAC,gDAAgD,CAAC,CAAC;IACjF,CAAC;IACD,OAAO,OAAO,CAAC,MAAM,CACnB,CAAC,CAAC,EAAgB,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAQ,CAAa,CAAC,OAAO,KAAK,QAAQ,CAChG,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,cAAc,GAAG,YAAY,CAAC"}
@@ -0,0 +1,20 @@
1
+ /** Entry for the standalone `swpx` binary (and `somewhere npx`).
2
+ *
3
+ * `swpx check <pkg> [--json]` inspects without running; anything else is a
4
+ * package to run through the verdict gate. No commander here — a wrapper must
5
+ * pass the user's flags through to the target package untouched, so we read
6
+ * raw argv. `main` is side-effect-free (deps injectable) so it's unit-tested;
7
+ * bin/swpx.js does the process.exit. */
8
+ import { runSwpx } from './swpx/run-swpx.js';
9
+ import { runCheck } from './swpx/check.js';
10
+ export async function main(argv, deps = {}) {
11
+ if (argv[0] === 'check') {
12
+ const rest = argv.slice(1);
13
+ const json = rest.includes('--json');
14
+ const pkg = rest.find((a) => !a.startsWith('-'));
15
+ return runCheck(pkg, { json }, deps);
16
+ }
17
+ const { exitCode } = await runSwpx(argv, deps);
18
+ return exitCode;
19
+ }
20
+ //# sourceMappingURL=swpx-bin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"swpx-bin.js","sourceRoot":"","sources":["../src/swpx-bin.ts"],"names":[],"mappings":"AAAA;;;;;;yCAMyC;AAEzC,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAG3C,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,IAAc,EAAE,OAAgB,EAAE;IAC3D,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,OAAO,EAAE,CAAC;QACxB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC3B,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;QACjD,OAAO,QAAQ,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,CAAC,CAAC;IACvC,CAAC;IACD,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC/C,OAAO,QAAQ,CAAC;AAClB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@somewhere-tech/cli",
3
- "version": "0.12.9",
3
+ "version": "0.14.0",
4
4
  "description": "CLI for somewhere.tech — auth, projects, deploy, pull, promote, db, logs, env, MCP bridge.",
5
5
  "keywords": [
6
6
  "somewhere",
@@ -22,7 +22,9 @@
22
22
  "type": "module",
23
23
  "bin": {
24
24
  "somewhere": "bin/somewhere.js",
25
- "sw": "bin/somewhere.js"
25
+ "sw": "bin/somewhere.js",
26
+ "swpx": "bin/swpx.js",
27
+ "swpm": "bin/swpm.js"
26
28
  },
27
29
  "files": [
28
30
  "bin",
@@ -44,17 +46,16 @@
44
46
  },
45
47
  "dependencies": {
46
48
  "@modelcontextprotocol/sdk": "^1.29.0",
47
- "chalk": "^5.3.0",
48
49
  "chokidar": "^4.0.3",
49
50
  "commander": "^12.0.0",
50
- "open": "^10.0.0",
51
- "ora": "^8.0.0",
52
51
  "prompts": "^2.4.2",
52
+ "semver": "^7.6.3",
53
53
  "undici": "^8.4.1"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@types/node": "^22.19.21",
57
57
  "@types/prompts": "^2.4.0",
58
+ "@types/semver": "^7.5.8",
58
59
  "typescript": "^5.4.0"
59
60
  }
60
61
  }