stik-cli 0.1.0 → 0.2.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 CHANGED
@@ -1,34 +1,191 @@
1
1
  # stik
2
2
 
3
- > CLI-first sticky notes + idea capture, warm-tone terminal UI.
3
+ **A passive watcher that tells you, in plain English, what's on your network — and taps you on the shoulder when something new shows up.**
4
4
 
5
- Two commands over one local SQLite store — everything lives in your terminal, nothing leaves your machine.
5
+ [![CI](https://github.com/adamsjack711-ux/stik-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/adamsjack711-ux/stik-cli/actions/workflows/ci.yml)
6
+ [![Go 1.26+](https://img.shields.io/badge/go-1.26+-00ADD8?logo=go&logoColor=white)](https://go.dev)
7
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
6
8
 
7
- - **`note`** — sticky-note CRUD: titles, bodies, tags, due dates, colors, pin/archive.
8
- - **`stik`** drop a markdown idea into a project's `ideas/` folder, with recent-location picking.
9
+ <p align="center">
10
+ <img src="https://raw.githubusercontent.com/adamsjack711-ux/stik-cli/main/demo/stik.gif" alt="stik noticing a new device join the network and naming it" width="760">
11
+ </p>
12
+
13
+ > **nmap** tells you what's on your network *right now*.
14
+ > **stik** remembers what's *normal*, and tells you when that changes.
15
+
16
+ Every other tool in this space — Wireshark, nmap, tcpdump — hands you **evidence** and leaves you to draw the conclusion. Nobody knows what `a4:83:e7:2f:11:0c` is. Everybody knows what *"Dylan's iPhone"* is. stik exists to produce that second sentence.
17
+
18
+ ```
19
+ ⚠ A new device joined 2 minutes ago.
20
+ Amazon device, first seen 17:34.
21
+ Its network-card maker is Amazon; it hasn't announced a name.
22
+ Run `stik devices` to see it, or `stik name` to label it.
23
+ ```
24
+
25
+ ---
9
26
 
10
27
  ## Install
11
28
 
12
- ```sh
13
- npm install -g stik-cli
29
+ stik is a single Go binary. It uses `libpcap` for capture (bundled on macOS; `libpcap0.8` ships on most Linux desktops).
30
+
31
+ ```bash
32
+ # from source (needs Go 1.26+ and libpcap headers) — installs a binary named `stik`
33
+ go install github.com/adamsjack711-ux/stik-cli/cmd/stik@latest
34
+
35
+ # or clone + make
36
+ git clone https://github.com/adamsjack711-ux/stik-cli
37
+ cd stik-cli && make install # builds and installs to /usr/local/bin
14
38
  ```
15
39
 
16
- This installs prebuilt native binaries (the CLIs are written in Rust).
40
+ On Debian/Ubuntu, building from source needs the pcap headers: `sudo apt install libpcap-dev`.
17
41
 
18
- ## Usage
42
+ Packet capture requires elevated privileges. On macOS you can grant your user access to the BPF devices once (Wireshark's *ChmodBPF* helper does this) instead of running as root; otherwise run stik with `sudo`.
19
43
 
20
- ```sh
21
- note add "ship v0.1" --tag release --due "in 2d" --color blue
22
- note ls
23
- note show <id>
44
+ ---
24
45
 
25
- stik # interactive idea capture
46
+ ## 30-second quickstart
47
+
48
+ **First run walks you through your network, one device at a time.** This is the whole idea: it builds the baseline of "normal", and it gets you to actually *look* at what's connected — usually for the first time.
49
+
50
+ ```
51
+ $ stik
52
+
53
+ 👋 Welcome to stik. Let's learn what's on your network.
54
+ Listening passively for 10s — stik only ever listens, it never sends traffic.
55
+
56
+ Found 8 devices on your network. Let's figure out what they are.
57
+
58
+ 1/8 Apple iPhone — "Dylans-iPhone"
59
+ Its network-card maker is Apple.
60
+ Is this yours? [Y/n/skip] y
61
+ name it: my phone
62
+ ✓ saved as "my phone"
63
+
64
+ 2/8 Amazon device — no hostname
65
+ Its network-card maker is Amazon; it hasn't announced a name.
66
+ Is this yours? [Y/n/skip] y
67
+ name it: kitchen echo
68
+ ✓ saved as "kitchen echo"
69
+ ...
26
70
  ```
27
71
 
28
- ## Platform support
72
+ After that, `stik` is a one-line glance — and usually boring. **Boring is the feature.**
73
+
74
+ ```
75
+ $ stik
76
+ ✓ Everything looks normal. 8 known devices.
77
+ ```
78
+
79
+ Leave the watcher running in the background, and you get a **desktop notification** the moment something unrecognized joins:
80
+
81
+ ```bash
82
+ stik daemon
83
+ ```
84
+
85
+ Because nobody watches a TUI all day. The alert comes to you.
86
+
87
+ ---
88
+
89
+ ## Commands
90
+
91
+ | Command | What it does |
92
+ |---|---|
93
+ | `stik` | Status: is anything new? (runs the setup wizard on first use) |
94
+ | `stik devices` | List every device in plain terms (`--verbose` for MACs & details) |
95
+ | `stik watch` | Live view; new devices highlight as they appear |
96
+ | `stik daemon` | Background watcher; fires a desktop notification on a new device |
97
+ | `stik name <who>` | Name a device — match by name, hostname, IP, or MAC |
98
+ | `stik forget <who>` | Remove a device from the registry |
99
+
100
+ ```
101
+ $ stik devices
102
+ Known (4)
103
+ • my phone (Apple iPhone)
104
+ last seen 2 minutes ago · dylans-iphone
105
+ • living room TV (Apple TV)
106
+ last seen just now · apple-tv
107
+ • work laptop (Apple MacBook)
108
+ last seen 5 minutes ago · dylans-macbook
109
+ • the router (TP-Link device)
110
+ last seen just now
111
+ ```
112
+
113
+ ---
114
+
115
+ ## What stik can — and can't — see
116
+
117
+ stik is deliberately narrow, and says so up front:
29
118
 
30
- Currently **macOS (Apple Silicon)** only that's the prebuilt binary bundled in this package. Other platforms can build from source with cargo and set `STIK_BIN` / `NOTE_BIN` to the built binaries.
119
+ - **Passive only.** It *listens*; it never transmits. No ARP scanning, no ARP spoofing, no port scanning. This is a hard line.
120
+ - **Broadcast/multicast only.** On a switched network you physically cannot see other devices' unicast traffic — the switch doesn't forward it to your port. stik reads only the protocols every device on the LAN legitimately broadcasts: **ARP**, **mDNS** (5353), and **DHCP** (67/68).
121
+ - **For networks you own.** Point it at your own home or lab network.
122
+
123
+ That narrowness is the honest shape of the problem, not a limitation stik is hiding. See [Design notes](#design-notes) for why.
124
+
125
+ ---
126
+
127
+ ## How stik identifies a device
128
+
129
+ Three broadcast protocols, combined into one sentence:
130
+
131
+ - **ARP** → the MAC↔IP pairing. The ground truth of who's on the wire.
132
+ - **mDNS** → hostnames. Apple and many IoT devices announce themselves constantly (`Dylans-iPhone.local`) — free, high-quality identity.
133
+ - **DHCP** → the *set and order* of requested options is a fingerprint that often reveals the OS even when nothing else does, plus a vendor-class string like `android-dhcp-14`.
134
+ - **OUI** → the first three bytes of the MAC map to a manufacturer, via the **embedded IEEE registry** (no network fetch — stik works on a network it doesn't trust).
135
+
136
+ Then it writes the verdict: *"Apple iPhone"*, *"Amazon device"*, *"unknown device (Espressif — likely IoT)"*, or — when a phone is using a randomized address — *"device with a private address"*.
137
+
138
+ ---
139
+
140
+ ## Design notes
141
+
142
+ The interesting decisions, and why they went the way they did.
143
+
144
+ ### Verdicts, not packets
145
+
146
+ The prime directive: **output the conclusion, not the evidence.** A tool that prints `unrecognized OUI a4:83:e7` has made the human do the work. stik prints *"a device we don't recognize."* Raw MACs, IPs, and DHCP fingerprints exist, but they live behind `--verbose`. If a design choice makes the output more technically complete but less humanly legible, it's the wrong choice.
147
+
148
+ ### Passive, and broadcast-only — the limit stik refuses to cross
149
+
150
+ On a switched network, one host cannot see another host's unicast traffic; the switch simply doesn't deliver it. The only ways around that are **port mirroring** (needs switch access you usually don't have) or **ARP spoofing** — telling every device you're the router so their traffic flows through you. Spoofing is an *attack* technique. stik will not do it.
151
+
152
+ So stik confines itself to what any device on the LAN can legitimately hear: broadcast and multicast. That's a real limit, and stik states it plainly rather than overclaiming. Explaining the boundary you *chose not to cross* is the honest way to build a tool like this.
153
+
154
+ ### Why DHCP option ordering fingerprints an OS
155
+
156
+ When a device joins a network it sends a DHCP request containing a **Parameter Request List** (option 55): the specific options it wants, *in a specific order*. That order is baked into each OS's DHCP client and barely changes between versions — so `1,3,6,15,26,28,51,58,59,43` says "Android" and `1,121,3,6,15,119,252,95,44,46` says "Apple". stik preserves the order exactly, because the order is the signal. (Option 60, the vendor class, is an even more direct hint when a device sends one.)
157
+
158
+ ### Randomized MACs, and why naive vendor lookup lies
159
+
160
+ Modern phones rotate their MAC address per network to resist tracking. A randomized MAC has an invented prefix, so an OUI lookup will either fail or — worse — *coincidentally match some real vendor and confidently report the wrong thing.* stik checks the **locally-administered bit** (`0x02` of the first octet) first. If it's set, stik doesn't trust the OUI at all and says *"device with a private address"* — unless mDNS gave a real name, in which case the name wins. Getting this right is the difference between a demo and a tool.
161
+
162
+ ### One storage module
163
+
164
+ Everything stik persists lives in a single JSON file (`~/.stik/devices.json`), and exactly one package (`internal/store`) ever touches it. Writes are **atomic** — a sibling temp file is written and then `rename`d over the target — so a crash or a second process can never leave a half-written, unparseable registry behind. A corrupt file is backed up and the registry starts fresh rather than wedging the tool. The store's interface is tiny on purpose: a SQLite backend could replace it without a single command changing.
165
+
166
+ ### Embedded OUI table, no runtime fetch
167
+
168
+ The IEEE manufacturer database (~40,000 prefixes) is compressed and embedded in the binary at build time. stik never phones home to identify a device — which matters, because you might be pointing it at a network you don't trust.
169
+
170
+ ---
171
+
172
+ ## Development
173
+
174
+ ```bash
175
+ make build # build ./stik
176
+ make test # go test ./...
177
+ make vet # go vet
178
+ make oui # regenerate the embedded IEEE OUI table
179
+ ```
180
+
181
+ The dissectors are tested against **real serialized packet bytes** (ARP, mDNS, DHCP) rather than mocks — the same frames libpcap would hand them. Lane-free logic like OUI lookup, randomized-MAC detection, known-vs-new, atomic writes, and corrupt-store recovery all have focused unit tests.
182
+
183
+ Record the demo GIF with [vhs](https://github.com/charmbracelet/vhs):
184
+
185
+ ```bash
186
+ bash demo/record.sh
187
+ ```
31
188
 
32
189
  ## License
33
190
 
34
- MIT
191
+ MIT — see [LICENSE](LICENSE). Built for networks you own.
package/bin/stik.js CHANGED
File without changes
Binary file
package/lib/launch.js CHANGED
@@ -1,5 +1,9 @@
1
1
  'use strict';
2
2
 
3
+ // Thin Node shim: locate the bundled Go binary for this platform (or a
4
+ // STIK_BIN override) and exec it, passing through argv, stdio, and exit code.
5
+ // The npm package is just a delivery vehicle for the compiled `stik` binary.
6
+
3
7
  const { spawnSync } = require('child_process');
4
8
  const path = require('path');
5
9
  const fs = require('fs');
@@ -30,10 +34,16 @@ function launch(name) {
30
34
  `${name}: no prebuilt binary for ${process.platform}-${process.arch}.`
31
35
  );
32
36
  console.error(
33
- 'stik currently ships macOS arm64 binaries. On other platforms, build'
37
+ 'stik currently ships a macOS arm64 binary. On other platforms, build from'
38
+ );
39
+ console.error(
40
+ 'source (needs Go 1.26+ and libpcap):'
41
+ );
42
+ console.error(
43
+ ' go install github.com/adamsjack711-ux/stik-cli/cmd/stik@latest'
34
44
  );
35
45
  console.error(
36
- `from source with cargo and point ${name.toUpperCase()}_BIN at the binary.`
46
+ `then point ${name.toUpperCase()}_BIN at the resulting binary.`
37
47
  );
38
48
  process.exit(1);
39
49
  }
package/package.json CHANGED
@@ -1,10 +1,31 @@
1
1
  {
2
2
  "name": "stik-cli",
3
- "version": "0.1.0",
4
- "description": "CLI-first sticky notes + idea capture with a warm-tone terminal UI. Ships the `note` and `stik` commands.",
3
+ "version": "0.2.0",
4
+ "description": "Passive network watcher tells you in plain English what's on your network, and taps you on the shoulder when something new shows up.",
5
+ "keywords": [
6
+ "network",
7
+ "security",
8
+ "arp",
9
+ "mdns",
10
+ "dhcp",
11
+ "passive",
12
+ "monitoring",
13
+ "devices",
14
+ "lan",
15
+ "cli"
16
+ ],
17
+ "license": "MIT",
18
+ "author": "Jack Adams-Lovell",
19
+ "homepage": "https://github.com/adamsjack711-ux/stik-cli#readme",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/adamsjack711-ux/stik-cli.git"
23
+ },
24
+ "bugs": {
25
+ "url": "https://github.com/adamsjack711-ux/stik-cli/issues"
26
+ },
5
27
  "bin": {
6
- "stik": "bin/stik.js",
7
- "note": "bin/note.js"
28
+ "stik": "bin/stik.js"
8
29
  },
9
30
  "files": [
10
31
  "bin",
@@ -20,21 +41,9 @@
20
41
  "arm64"
21
42
  ],
22
43
  "engines": {
23
- "node": ">=18"
44
+ "node": ">=16"
24
45
  },
25
- "repository": {
26
- "type": "git",
27
- "url": "git+https://github.com/adamsjack711-ux/stik.git"
28
- },
29
- "keywords": [
30
- "sticky-notes",
31
- "notes",
32
- "cli",
33
- "ideas",
34
- "capture",
35
- "sqlite",
36
- "terminal"
37
- ],
38
- "author": "Jack Adams-Lovell",
39
- "license": "MIT"
46
+ "scripts": {
47
+ "build": "./build.sh"
48
+ }
40
49
  }
package/bin/note.js DELETED
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- require('../lib/launch')('note');
Binary file