@unveil/vk 0.2.0 → 0.4.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,104 @@
1
+ # vk
2
+
3
+ Fetch GitHub account activity and analyze it for automation patterns
4
+
5
+ This is the batteries-included wrapper around [`@unveil/identity`](https://github.com/unveil-project/identity). Give it a username and it does the GitHub API calls for you, then hands the result to the scoring engine.
6
+
7
+ It talks to the GitHub REST API with native `fetch` and has no dependencies beyond `@unveil/identity`, so it runs the same in Node, Deno, Bun, the browser and edge runtimes.
8
+
9
+ Both packages are the logic behind [AgentScan](https://agentscan.netlify.app), a tool for analyzing GitHub account behavior to detect potential AI agents and automated activity. The results are indicators, not verdicts.
10
+
11
+ ### Install
12
+
13
+ ```bash
14
+ npm install @unveil/vk
15
+ ```
16
+
17
+ ### Usage
18
+
19
+ ```js
20
+ import { analyze } from "@unveil/vk";
21
+
22
+ const { analysis, eventsCount } = await analyze("github_account_username", {
23
+ token: process.env.GITHUB_TOKEN,
24
+ });
25
+
26
+ console.log(analysis.classification); // "organic" | "mixed" | "automation" | "insufficient-data"
27
+ console.log(analysis.score); // 100 = human, 0 = automation
28
+ console.log(analysis.confidence); // 0-1: how much evidence there was
29
+ console.log(eventsCount); // how many events we actually got back
30
+ ```
31
+
32
+ ### Options
33
+
34
+ ```js
35
+ analyze(username, {
36
+ token: "ghp_...", // GitHub token. Optional, but strongly recommended
37
+ showEvents: true, // return the raw events too. Off by default
38
+ });
39
+ ```
40
+
41
+ `token` is sent as `Authorization: Bearer`. Without it you are on the unauthenticated
42
+ rate limit, which is 60 requests per hour for the whole IP — and each `analyze`
43
+ call spends 4 of them. With a token you get 5000 per hour. The token needs no
44
+ scopes; all the data read here is public.
45
+
46
+ `showEvents` controls whether the raw events come back in the result. They are
47
+ left out by default because 300 GitHub events is a lot of JSON to carry around
48
+ when all you wanted was the score.
49
+
50
+ ### Result
51
+
52
+ ```js
53
+ {
54
+ analysis, // the full IdentifyResult from @unveil/identity
55
+ events, // the raw events, or [] unless showEvents is set
56
+ eventsCount, // always the real count, even when events is empty
57
+ }
58
+ ```
59
+
60
+ `analysis` is whatever `identify()` returns — score, classification, confidence,
61
+ flags, groups, window, timezone and profile. See the
62
+ [identity README](https://github.com/unveil-project/identity#readme) for the full
63
+ shape and for what each of the 53 heuristics looks at.
64
+
65
+ ### How much it fetches
66
+
67
+ `analyze` requests three pages of 100 public events, in parallel. That is both
68
+ the maximum GitHub serves from the public events endpoint and the window
69
+ `@unveil/identity` scores over, so there is nothing to gain from asking for more.
70
+
71
+ Accounts with less activity than that simply return fewer events, and the
72
+ analysis reports lower `confidence` to match.
73
+
74
+ ### Errors
75
+
76
+ `analyze` rejects instead of returning a partial result. A missing user, a
77
+ revoked token or an exhausted rate limit all come back as a plain `Error`
78
+ carrying GitHub's own message, with the `status` and `url` of the failed request
79
+ attached to it:
80
+
81
+ ```js
82
+ try {
83
+ await analyze("ghost");
84
+ } catch (error) {
85
+ if (error.status === 404) {
86
+ // no such account
87
+ }
88
+ }
89
+ ```
90
+
91
+ Network failures reject with whatever `fetch` threw, untouched.
92
+
93
+ ### Which package do I want?
94
+
95
+ Use **`@unveil/vk`** when you have a username and want an answer.
96
+
97
+ Use [**`@unveil/identity`**](https://github.com/unveil-project/identity) when you
98
+ already have the user and events — from your own database, a cache, a webhook, or
99
+ your own authenticated client — and only want the scoring. It has no network
100
+ access and no dependencies on how you got the data.
101
+
102
+ ### Issues and feature requests
103
+
104
+ Please drop an issue if you find something that doesn't work, or have an idea for something that works better.
package/dist/index.d.mts CHANGED
@@ -9,6 +9,11 @@ type AnalyzeReturn = {
9
9
  events: GitHubEvent[];
10
10
  eventsCount: number;
11
11
  };
12
+ declare class GitHubError extends Error {
13
+ readonly status: number;
14
+ readonly url: string;
15
+ constructor(message: string, status: number, url: string);
16
+ }
12
17
  declare function analyze(username: string, options?: AnalyzeOptions): Promise<AnalyzeReturn>;
13
18
  //#endregion
14
- export { AnalyzeOptions, AnalyzeReturn, analyze };
19
+ export { AnalyzeOptions, AnalyzeReturn, GitHubError, analyze };
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{identify as e}from"@unveil/identity";import{Octokit as t}from"octokit";async function n(n,r){let i=new t({auth:r?.token}),{data:a}=await i.rest.users.getByUsername({username:n}),o=Array.from({length:3},(e,t)=>i.rest.activity.listPublicEventsForUser({username:n,per_page:100,page:t+1})),s=(await Promise.all(o)).flatMap(e=>e.data);return{analysis:e({user:a,events:s}),events:r?.showEvents?s:[],eventsCount:s.length}}export{n as analyze};
1
+ import{identify as e}from"@unveil/identity";var t=class extends Error{status;url;constructor(e,t,n){super(e),this.name=`GitHubError`,this.status=t,this.url=n}};async function n(e){try{let t=await e.json();if(t?.message)return t.message}catch{}return`${e.status} ${e.statusText}`}async function r(e,r){let i=`https://api.github.com${e}`,a={Accept:`application/vnd.github+json`,"X-GitHub-Api-Version":`2022-11-28`,"User-Agent":`unveil-vk`};r&&(a.Authorization=`Bearer ${r}`);let o=await fetch(i,{headers:a});if(!o.ok)throw new t(await n(o),o.status,i);return await o.json()}async function i(t,n){let i=n?.token,a=encodeURIComponent(t),o=await r(`/users/${a}`,i),s=Array.from({length:3},(e,t)=>r(`/users/${a}/events/public?per_page=100&page=${t+1}`,i)),c=(await Promise.all(s)).flat();return{analysis:e({user:o,events:c}),events:n?.showEvents?c:[],eventsCount:c.length}}export{t as GitHubError,i as analyze};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@unveil/vk",
3
- "version": "0.2.0",
4
- "description": "Automation-pattern analysis for GitHub accounts",
3
+ "version": "0.4.0",
4
+ "description": "Fetch GitHub account activity and analyze it for automation patterns",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@11.22.0",
7
7
  "exports": {
@@ -43,7 +43,6 @@
43
43
  "vitest": "^4.1.10"
44
44
  },
45
45
  "dependencies": {
46
- "@unveil/identity": "^2.0.2",
47
- "octokit": "^5.0.5"
46
+ "@unveil/identity": "^2.0.2"
48
47
  }
49
48
  }