commitshow 0.3.14 → 0.3.16
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 +41 -7
- package/dist/commands/audit.js +8 -1
- package/dist/commands/login.js +119 -10
- package/dist/commands/whoami.js +49 -7
- package/dist/index.js +17 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -87,12 +87,46 @@ Requires **Node 20+**.
|
|
|
87
87
|
|
|
88
88
|
| Command | What it does |
|
|
89
89
|
|---|---|
|
|
90
|
-
| `commitshow audit [target]` | Fetch + render the latest audit, write `.commitshow/audit.md`
|
|
91
|
-
| `commitshow status [target]` | Same render
|
|
90
|
+
| `commitshow audit [target] [--json] [--refresh] [--source=<tag>]` | Fetch + render the latest audit, write `.commitshow/audit.{md,json}` |
|
|
91
|
+
| `commitshow status [target]` | Same render as `audit`, no re-run |
|
|
92
|
+
| `commitshow login [--no-open] [--token <jwt>]` | Device-flow sign-in via browser approval |
|
|
93
|
+
| `commitshow whoami [--logout]` | Print the linked account · `--logout` clears the saved token |
|
|
92
94
|
| `commitshow submit [target]` | Audition a project (coming soon · needs login) |
|
|
93
95
|
| `commitshow install <pack>` | Install a Library artifact (coming soon) |
|
|
94
|
-
|
|
95
|
-
|
|
96
|
+
|
|
97
|
+
### Sign in for higher rate limits
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
npx commitshow@latest login
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Opens `commit.show/cli/link?code=<6-hex>` in your browser. After you
|
|
104
|
+
click Authorize there, the CLI receives a 90-day JWT and saves it to
|
|
105
|
+
`~/.commitshow/config.json` (file mode 0600). Subsequent calls send
|
|
106
|
+
the token in the Authorization header automatically.
|
|
107
|
+
|
|
108
|
+
What changes once signed in:
|
|
109
|
+
|
|
110
|
+
- Per-IP rate cap goes from **20 audits/day** to **50/day**
|
|
111
|
+
- Newly audited preview projects auto-claim ownership (visible at
|
|
112
|
+
[commit.show/me](https://commit.show/me) → MY AUDITS)
|
|
113
|
+
- `commitshow whoami` prints your member id + email
|
|
114
|
+
|
|
115
|
+
Headless / CI? Use `--token <jwt>` to skip the browser handshake.
|
|
116
|
+
|
|
117
|
+
### Telemetry source flag
|
|
118
|
+
|
|
119
|
+
`--source=<tag>` lets you self-report how the call originated:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
npx commitshow audit . --source=claude-code
|
|
123
|
+
COMMITSHOW_SOURCE=cursor npx commitshow audit .
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Common tags: `claude-code` · `cursor` · `gemini-cli` · `codex` ·
|
|
127
|
+
`antigravity` · `production-audit-skill` · any 64-char string. Drops
|
|
128
|
+
into the maintainer's admin breakdown so we can see which agent
|
|
129
|
+
ecosystems are driving installs. Skip the flag to stay anonymous.
|
|
96
130
|
|
|
97
131
|
### Target forms
|
|
98
132
|
|
|
@@ -206,9 +240,9 @@ changes do. Known keys: `project`, `score`, `standing`, `strengths`, `concerns`,
|
|
|
206
240
|
## Roadmap
|
|
207
241
|
|
|
208
242
|
- `0.1` — ✓ read-only audit · status · `--json` · target auto-detect · sidecar files
|
|
209
|
-
- `0.
|
|
210
|
-
- `0.
|
|
211
|
-
- `0.
|
|
243
|
+
- `0.3` — ✓ device-flow login · `--source` telemetry · User-Agent self-report · MCP server (`commitshow-mcp`)
|
|
244
|
+
- `0.4` — `commitshow submit` · `--watch` mode · CI exit-code gate · refresh-token flow
|
|
245
|
+
- `0.5` — `commitshow install <pack>` with {{VARIABLE}} substitution
|
|
212
246
|
|
|
213
247
|
## Links
|
|
214
248
|
|
package/dist/commands/audit.js
CHANGED
|
@@ -11,8 +11,15 @@ export async function audit(args) {
|
|
|
11
11
|
// the COMMITSHOW_SOURCE env var (used by IDE plugins that wrap the
|
|
12
12
|
// CLI) and ultimately empty string. Surfaced in /admin > CLI 사용
|
|
13
13
|
// tab as a distribution chart.
|
|
14
|
+
// --source X (space form): only consume args[idx+1] when --source actually
|
|
15
|
+
// appears. Old code used `args.indexOf('--source') + 1` unguarded, which
|
|
16
|
+
// returns 0 when --source is absent — making the FIRST positional arg
|
|
17
|
+
// (e.g. the target URL) get misread as the source value, then filtered
|
|
18
|
+
// out of `positional` below. Result: target URL silently dropped, CLI
|
|
19
|
+
// falls back to cwd and reports "no git remote".
|
|
20
|
+
const sourceIdx = args.indexOf('--source');
|
|
14
21
|
const sourceFlag = args.find(a => a.startsWith('--source='))?.split('=')[1]
|
|
15
|
-
?? args[
|
|
22
|
+
?? (sourceIdx >= 0 ? args[sourceIdx + 1] : undefined)
|
|
16
23
|
?? process.env.COMMITSHOW_SOURCE
|
|
17
24
|
?? null;
|
|
18
25
|
const positional = args.find(a => !a.startsWith('--') && a !== sourceFlag);
|
package/dist/commands/login.js
CHANGED
|
@@ -1,16 +1,125 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
1
|
+
// commitshow login — device-flow authorization.
|
|
2
|
+
//
|
|
3
|
+
// Default flow:
|
|
4
|
+
// 1. POST /functions/v1/cli-link-init · receive { code, poll_token, verification_url }
|
|
5
|
+
// 2. Print the code + URL · open the URL in the user's browser (unless --no-open)
|
|
6
|
+
// 3. Poll /functions/v1/cli-link-poll every 2s until 'ok' (token returned),
|
|
7
|
+
// 'expired', or timeout (10 min).
|
|
8
|
+
// 4. Save the api_token + member info to ~/.commitshow/config.json
|
|
9
|
+
//
|
|
10
|
+
// --token mode: skip the browser handshake and accept a pre-minted JWT
|
|
11
|
+
// (useful for headless / CI environments).
|
|
12
|
+
//
|
|
13
|
+
// --no-open: don't auto-launch the browser, just print the URL.
|
|
14
|
+
import { readConfig, writeConfig } from '../lib/config.js';
|
|
5
15
|
import { c } from '../lib/colors.js';
|
|
6
|
-
|
|
16
|
+
const POLL_INTERVAL_MS = 2000;
|
|
17
|
+
const POLL_TIMEOUT_MS = 10 * 60 * 1000;
|
|
18
|
+
const DEFAULT_BASE_URL = 'https://tekemubwihsjdzittoqf.supabase.co';
|
|
19
|
+
const DEFAULT_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InRla2VtdWJ3aWhzamR6aXR0b3FmIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzY0MzQ1NzUsImV4cCI6MjA5MjAxMDU3NX0.n2K-3lFVvlXQx-bV9evdNRSQCtG5oC4uQushxB2ja9Y';
|
|
20
|
+
function baseUrl() {
|
|
21
|
+
return readConfig().base_url ?? DEFAULT_BASE_URL;
|
|
22
|
+
}
|
|
23
|
+
function tryOpen(url) {
|
|
24
|
+
// Best-effort cross-platform open. Failure is silent — user still has
|
|
25
|
+
// the URL printed and can copy/paste manually.
|
|
26
|
+
const cmd = process.platform === 'darwin' ? 'open'
|
|
27
|
+
: process.platform === 'win32' ? 'cmd'
|
|
28
|
+
: 'xdg-open';
|
|
29
|
+
const args = process.platform === 'win32' ? ['/c', 'start', '', url] : [url];
|
|
30
|
+
try {
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
32
|
+
const { spawn } = require('node:child_process');
|
|
33
|
+
spawn(cmd, args, { stdio: 'ignore', detached: true }).unref();
|
|
34
|
+
}
|
|
35
|
+
catch { /* ignore */ }
|
|
36
|
+
}
|
|
37
|
+
async function fetchUser(token) {
|
|
38
|
+
try {
|
|
39
|
+
const res = await fetch(`${baseUrl()}/auth/v1/user`, {
|
|
40
|
+
headers: { apikey: DEFAULT_ANON_KEY, Authorization: `Bearer ${token}` },
|
|
41
|
+
});
|
|
42
|
+
if (!res.ok)
|
|
43
|
+
return null;
|
|
44
|
+
const j = await res.json();
|
|
45
|
+
return { id: j.id, email: j.email ?? null };
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export async function login(args) {
|
|
52
|
+
const noOpen = args.includes('--no-open');
|
|
53
|
+
const tokenIdx = args.indexOf('--token');
|
|
54
|
+
const presetToken = tokenIdx >= 0 ? args[tokenIdx + 1] : null;
|
|
55
|
+
// Headless / CI path · skip the browser handshake.
|
|
56
|
+
if (presetToken) {
|
|
57
|
+
const user = await fetchUser(presetToken);
|
|
58
|
+
if (!user) {
|
|
59
|
+
console.error(c.scarlet('✗ Token rejected · invalid or expired.'));
|
|
60
|
+
return 1;
|
|
61
|
+
}
|
|
62
|
+
writeConfig({ ...readConfig(), token: presetToken, member_id: user.id, display_name: user.email ?? undefined });
|
|
63
|
+
console.log(c.gold('✓ Logged in · token saved to ~/.commitshow/config.json'));
|
|
64
|
+
return 0;
|
|
65
|
+
}
|
|
66
|
+
// 1. Init the device-flow.
|
|
67
|
+
let init;
|
|
68
|
+
try {
|
|
69
|
+
const res = await fetch(`${baseUrl()}/functions/v1/cli-link-init`, {
|
|
70
|
+
method: 'POST',
|
|
71
|
+
headers: { apikey: DEFAULT_ANON_KEY, Authorization: `Bearer ${DEFAULT_ANON_KEY}`, 'Content-Type': 'application/json' },
|
|
72
|
+
body: '{}',
|
|
73
|
+
});
|
|
74
|
+
init = await res.json();
|
|
75
|
+
if (!res.ok || !init.code || !init.poll_token) {
|
|
76
|
+
console.error(c.scarlet(`✗ Init failed: ${init.error ?? `HTTP ${res.status}`}`));
|
|
77
|
+
return 1;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
console.error(c.scarlet(`✗ Init network error: ${e?.message ?? e}`));
|
|
82
|
+
return 1;
|
|
83
|
+
}
|
|
84
|
+
console.log('');
|
|
85
|
+
console.log(c.cream(' Authorize commitshow CLI to act on your account.'));
|
|
7
86
|
console.log('');
|
|
8
|
-
console.log(c.
|
|
87
|
+
console.log(` Verification code: ${c.gold(init.code)}`);
|
|
88
|
+
console.log(` Approve at: ${init.verification_url}`);
|
|
9
89
|
console.log('');
|
|
10
|
-
console.log(c.
|
|
11
|
-
console.log(c.muted(' Write commands (submit, re-audit, install) unlock once the device-flow'));
|
|
12
|
-
console.log(c.muted(' endpoint ships — tracked as a V1 item in the roadmap.'));
|
|
90
|
+
console.log(c.dim(' Waiting for approval (10 min timeout)…'));
|
|
13
91
|
console.log('');
|
|
14
|
-
|
|
92
|
+
if (!noOpen && init.verification_url)
|
|
93
|
+
tryOpen(init.verification_url);
|
|
94
|
+
// 2. Poll until approved / expired / timeout.
|
|
95
|
+
const deadline = Date.now() + POLL_TIMEOUT_MS;
|
|
96
|
+
while (Date.now() < deadline) {
|
|
97
|
+
await new Promise(r => setTimeout(r, POLL_INTERVAL_MS));
|
|
98
|
+
try {
|
|
99
|
+
const res = await fetch(`${baseUrl()}/functions/v1/cli-link-poll`, {
|
|
100
|
+
method: 'POST',
|
|
101
|
+
headers: { apikey: DEFAULT_ANON_KEY, Authorization: `Bearer ${DEFAULT_ANON_KEY}`, 'Content-Type': 'application/json' },
|
|
102
|
+
body: JSON.stringify({ poll_token: init.poll_token }),
|
|
103
|
+
});
|
|
104
|
+
const resp = await res.json();
|
|
105
|
+
if (resp.status === 'ok' && resp.api_token) {
|
|
106
|
+
writeConfig({ ...readConfig(), token: resp.api_token, member_id: resp.user_id ?? undefined });
|
|
107
|
+
console.log(c.gold(' ✓ Authorized · token saved to ~/.commitshow/config.json'));
|
|
108
|
+
const user = await fetchUser(resp.api_token);
|
|
109
|
+
if (user?.email)
|
|
110
|
+
writeConfig({ ...readConfig(), display_name: user.email });
|
|
111
|
+
return 0;
|
|
112
|
+
}
|
|
113
|
+
if (resp.status === 'expired' || resp.status === 'consumed') {
|
|
114
|
+
console.error(c.scarlet(` ✗ ${resp.message ?? resp.status}`));
|
|
115
|
+
return 1;
|
|
116
|
+
}
|
|
117
|
+
// status === 'pending' · keep polling.
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// transient · keep polling.
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
console.error(c.scarlet(' ✗ Timed out waiting for approval (10 min). Re-run commitshow login.'));
|
|
15
124
|
return 1;
|
|
16
125
|
}
|
package/dist/commands/whoami.js
CHANGED
|
@@ -1,14 +1,56 @@
|
|
|
1
|
-
import { readConfig } from '../lib/config.js';
|
|
1
|
+
import { readConfig, writeConfig } from '../lib/config.js';
|
|
2
2
|
import { c } from '../lib/colors.js';
|
|
3
|
-
|
|
3
|
+
const DEFAULT_BASE_URL = 'https://tekemubwihsjdzittoqf.supabase.co';
|
|
4
|
+
const DEFAULT_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InRla2VtdWJ3aWhzamR6aXR0b3FmIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzY0MzQ1NzUsImV4cCI6MjA5MjAxMDU3NX0.n2K-3lFVvlXQx-bV9evdNRSQCtG5oC4uQushxB2ja9Y';
|
|
5
|
+
async function verifyToken(token) {
|
|
6
|
+
try {
|
|
7
|
+
const res = await fetch(`${DEFAULT_BASE_URL}/auth/v1/user`, {
|
|
8
|
+
headers: { apikey: DEFAULT_ANON_KEY, Authorization: `Bearer ${token}` },
|
|
9
|
+
});
|
|
10
|
+
if (!res.ok)
|
|
11
|
+
return null;
|
|
12
|
+
const j = await res.json();
|
|
13
|
+
return { id: j.id, email: j.email ?? null };
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export async function whoami(args) {
|
|
4
20
|
const cfg = readConfig();
|
|
5
|
-
|
|
21
|
+
// --logout · convenience flag · clears token from local config.
|
|
22
|
+
if (args.includes('--logout')) {
|
|
23
|
+
if (!cfg.token) {
|
|
24
|
+
console.log(c.dim('Already signed out.'));
|
|
25
|
+
return 0;
|
|
26
|
+
}
|
|
27
|
+
const next = { ...cfg };
|
|
28
|
+
delete next.token;
|
|
29
|
+
delete next.member_id;
|
|
30
|
+
delete next.display_name;
|
|
31
|
+
delete next.refresh_token;
|
|
32
|
+
writeConfig(next);
|
|
33
|
+
console.log(c.gold('✓ Signed out · token cleared from ~/.commitshow/config.json'));
|
|
34
|
+
return 0;
|
|
35
|
+
}
|
|
36
|
+
if (!cfg.token) {
|
|
6
37
|
console.log(c.muted('Not signed in.'));
|
|
7
|
-
console.log(c.dim('
|
|
38
|
+
console.log(c.dim(' Run `commitshow login` to authorize a 90-day API token.'));
|
|
39
|
+
return 1;
|
|
40
|
+
}
|
|
41
|
+
// Verify the saved token is still valid (not expired or revoked).
|
|
42
|
+
const user = await verifyToken(cfg.token);
|
|
43
|
+
if (!user) {
|
|
44
|
+
console.log(c.scarlet('✗ Token rejected (expired or revoked).'));
|
|
45
|
+
console.log(c.dim(' Re-run `commitshow login`.'));
|
|
8
46
|
return 1;
|
|
9
47
|
}
|
|
10
|
-
console.log(
|
|
11
|
-
if (cfg.
|
|
12
|
-
console.log(
|
|
48
|
+
console.log('');
|
|
49
|
+
if (cfg.display_name)
|
|
50
|
+
console.log(` ${c.cream(cfg.display_name)}`);
|
|
51
|
+
if (user.email && user.email !== cfg.display_name)
|
|
52
|
+
console.log(` email: ${user.email}`);
|
|
53
|
+
console.log(` member id: ${user.id}`);
|
|
54
|
+
console.log('');
|
|
13
55
|
return 0;
|
|
14
56
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
1
4
|
import { audit } from './commands/audit.js';
|
|
2
5
|
import { submit } from './commands/submit.js';
|
|
3
6
|
import { install } from './commands/install.js';
|
|
@@ -6,7 +9,20 @@ import { login } from './commands/login.js';
|
|
|
6
9
|
import { whoami } from './commands/whoami.js';
|
|
7
10
|
import { c } from './lib/colors.js';
|
|
8
11
|
import { checkLatestVersion, formatUpdateBanner } from './lib/version-check.js';
|
|
9
|
-
|
|
12
|
+
// Read version from package.json at runtime so a hardcoded constant
|
|
13
|
+
// can't go stale across publishes. (Previous incident: source said
|
|
14
|
+
// '0.2.11' while npm shipped 0.3.x — every binary nagged users to
|
|
15
|
+
// upgrade to a version they were already on.)
|
|
16
|
+
const VERSION = (() => {
|
|
17
|
+
try {
|
|
18
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
19
|
+
const pkg = JSON.parse(readFileSync(join(here, '..', 'package.json'), 'utf8'));
|
|
20
|
+
return pkg.version ?? '0.0.0';
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return '0.0.0';
|
|
24
|
+
}
|
|
25
|
+
})();
|
|
10
26
|
const USAGE = `
|
|
11
27
|
${c.bold(c.gold('commit.show'))} ${c.dim(`v${VERSION}`)} ${c.muted('—')} ${c.cream('audit any vibe-coded project from your terminal.')}
|
|
12
28
|
${c.muted('the')} ${c.gold('walk-on')} ${c.muted('lane: drop in, get scored, leave · no signup, no audition, no league entry.')}
|