Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ integration-test:
nix run .#star-integration-tests

test-emacs:
$(EMACS) -Q --batch -L . -l client-test.el -f ert-run-tests-batch-and-exit
$(EMACS) -Q --batch -L . -l client-test.el -l starintel-workbench-test.el -f ert-run-tests-batch-and-exit

images:
nix build .#star-server-image .#couchdb-image .#clouseau-image .#rabbitmq-image
Expand Down
8 changes: 6 additions & 2 deletions client.el
Original file line number Diff line number Diff line change
Expand Up @@ -219,11 +219,15 @@ scrub error text."
(defun starintel-api-error-message (err)
"Return the redacted message of a starintel-api error ERR.
ERR is the condition data as captured by `condition-case' or
`should-error': (SYMBOL . DATA)."
`should-error': (SYMBOL . DATA). Plain string data, as signaled by
ordinary `user-error' calls at the UI boundary, is redacted too."
(let ((data (cdr err)))
(cond
((and (listp data) data (plist-member (car data) :message))
((and (listp data) data (listp (car data))
(plist-member (car data) :message))
(plist-get (car data) :message))
((and (listp data) (stringp (car data)))
(starintel-api--redact (car data)))
((stringp data) (starintel-api--redact data))
(t (starintel-api--redact (format "%S" data))))))

Expand Down
88 changes: 88 additions & 0 deletions docs/emacs-workbench-inventory.org
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#+title: Emacs OSINT workbench: implementation inventory
#+options: toc:2

This inventory records what the StarIntel Emacs client implements today, what
the current server (v0.9 family) exposes, and where the workbench gaps are.
It was written by inspecting =client.el=, =client-test.el=,
=docs/http-api-docs.org=, =source/frontends/http-capabilities.lisp= and
=source/frontends/http-contract-routes.lisp= at commit
=e99aadd= (master, 2026-08).

* Current client implementation

** Modern contract layer (=starintel-api-*=, =client.el=)

- Async-capable transport boundary with a pluggable
=starintel-api-transport-function=; default is a bounded url.el transport.
- Typed error taxonomy (=starintel-api-timeout-error=,
=starintel-api-http-error=, ...) with secret redaction.
- Correlation IDs and =X-Request-Timeout-Ms= deadlines on every request.
- Capability discovery against =GET /api/v1/capabilities= with cache and
=legacy_routes= compatibility gating.
- Contract operations: =starintel-api-health=, =starintel-api-server-info=,
=starintel-api-search=, =starintel-api-get-document=.

** Legacy layer (requires =request=, soft dependency)

- Callback-style wrappers for server info, health, documents, targets,
documents creation, and the CouchDB view routes
(=starintel-hosts-by-ip=, =starintel-messages-by-user=, ...).
- Document construction through the =starintel-doc= package.

** Presentation layer (=starintel-ui-*=)

- One shared =special-mode= buffer =*StarIntel*= with static text rendering.
- =starintel-status= renders server info, health and capabilities.
- =starintel-search= renders search rows as plain lines (not actionable).
- =starintel-document= renders the raw JSON document.

** Existing modes

- No major modes. The shared buffer is =special-mode= only.

* Server surface available today (v0.9 family)

| Surface | Route | Status |
|---------+-------+--------|
| Capabilities | =GET /api/v1/capabilities= | Active, public |
| Server info | =GET /= | Active: =server=, =version=, =doc_spec_version=, =default-dataset=, =event_log=, =openapi=, =client_manifest= |
| Health | =GET /health= | Active |
| Stats | =GET /api/v1/stats= | Active: =documents.total=, =documents.by_dtype=, =targets.total= |
| Search | =GET /api/v1/search= and legacy =GET /search= | Active: =q= (<=512), =limit= (1..50), =bookmark=; CouchDB FTS =rows= + =bookmark= |
| OpenAPI / manifest | =GET /openapi.json=, =GET /client-manifest.json= | Active |
| Auth | =POST /auth/login=, =/auth/bootstrap=, =/auth/context=, user/credential lifecycle | Active (registry operations) |
| Documents | =POST /new/document/:dtype=, =POST /documents/bulk=, =GET/PUT/DELETE /document/:id= | Active (legacy-marked), strict 0.9 validation |
| Targets | =POST /new/target/:actor=, =GET /targets/:actor= | Active (legacy-marked, non-strict adapter) |
| View queries | =/documents/.../by-*= bounded pagination | Active |
| Target leases | =features.target_leases= | =false=: not advertised; client must treat as unavailable |
| Streams/events | =features.streams= | =false=: no event stream yet |
| Actors/jobs | (none) | Not exposed over HTTP; local Sento actors and Rabbit only |
| Graph/path queries | (none) | Not exposed; relations are documents of dtype =relation= |

* Gap analysis: workbench features missing from the client

1. Server profiles and current-server abstraction (multiple deployments,
=auth-source=, server identity in references).
2. Stable object identity and =star://= URIs; no link handling, no Org
integration.
3. Generic object buffer (typed rendering, actions) - today only raw JSON.
4. Actionable search results buffer (tabulated list, marking, pagination).
5. Person/organization/target dedicated views (render 0.9 =data= fields).
6. Relation/graph traversal (read from =relation= documents).
7. Ingest commands over the validated =POST /new/document/:dtype= boundary.
8. Query workbench, saved queries, history.
9. Timeline assembled from object timestamps (=dateAdded=, =dateUpdated=,
extension timestamps).
10. Provenance surfacing from =extensions.star_server= (exists in schema).
11. Actor/job workbench: server exposes nothing yet; must render as
"under development" without breaking the workbench.
12. Live updates: =features.streams= is false; nothing to integrate yet.
13. Bookmarks/recent objects, investigation context.

* Principles honored going forward

- Server-authoritative; client stores references only.
- Capability-gated: missing features render as unavailable, never crash.
- No scraping/actor logic in Emacs; the workbench is a cockpit.
- No heavy frameworks: tabulated-list, buttons, text properties, soft
=transient= and =org= dependencies only.
101 changes: 101 additions & 0 deletions docs/emacs-workbench-plan.org
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#+title: Emacs OSINT workbench: execution plan
#+options: toc:2

Long-horizon plan for evolving =client.el= into a native Emacs OSINT
workbench. The workbench is a cockpit over the StarIntel server; the server
stays authoritative. Update the status table in the same commit as behavior
changes.

* Dependency order

1. transport/auth/server profiles <- foundation slice (this PR)
2. object/URI model <- foundation slice (this PR)
3. generic object views <- foundation slice (this PR)
4. search + documents + entities <- foundation slice (this PR)
5. targets
6. investigations + Org integration
7. graph (relation traversal)
8. actors/jobs (blocked: server exposes nothing)
9. ingest
10. query/timeline/provenance
11. dashboard/workbench polish

* Module layout (additive; client.el keeps transport + legacy + compat UI)

| File | Responsibility |
|------+----------------|
| =starintel-server.el= | Server profiles, current server, =auth-source=, switch/status |
| =starintel-uri.el= | =star://= parse/format/open, Org link registration |
| =starintel-object.el= | Object identity, dtype field maps, titles, object buffer mode |
| =starintel-search.el= | Search results tabulated-list buffer, marking, pagination |
| =starintel-ui.el= | Workbench package =starintel-ui=, entry =M-x starintel=, soft transient menu |

* star:// URI grammar

: star://SERVER/KIND/ID

- =SERVER=: server profile name; empty means the current server.
- =KIND=: =document=, =person=, =org=, =target=, =relation=, =search=, ...
open set; unknown kinds still parse and open generically.
- =ID=: remainder of the string (IDs may contain =/=); percent-decoded.

Examples: =star://local/document/01JABC...=, =star:///person/01J...=,
=star://remote/search/alice example.com=.

* Server profile model

Profiles live in =starintel-servers= as plists:

#+begin_src elisp
(setq starintel-servers
'((local :url "http://127.0.0.1:5000"
:auth-source (:host "starintel-local" :user "api"))))
#+end_src

Activating a profile sets the API layer base URL and token resolution,
clears the capability cache, and stamps every object reference with the
profile name.

* Secret handling

- Preferred: =:auth-source= in a profile. Credentials resolve per
request through =auth-source-search=; nothing is cached or written
by the client. Use an encrypted =~/.authinfo.gpg=:

#+begin_src text
machine starintel-remote login api password star_sk_v1_...
#+end_src

- =:auth-source= accepts a host string (login defaults to =api=) or
=(:host HOST :user USER)=.
- A session =:token= keeps the secret in memory for the session only
and is never persisted through Customize.
- Bearer tokens never appear in URLs; error text and messages are
redacted by the API layer against the active token. Secrets come from =auth-source=, never plain config.

* Status

| Slice | Status | Evidence |
|-------+--------+----------|
| 1. inventory + plan | done | this file + emacs-workbench-inventory.org |
| 2. profiles | done | starintel-server.el + ERT |
| 3. URI model | done | starintel-uri.el + ERT |
| 4. object identity + buffer | done | starintel-object.el + ERT |
| 5. search buffer | done | starintel-search.el + ERT |
| 6. targets workbench | pending | view routes + =/targets/:actor= exist server-side |
| 7. investigations/org | pending | after object model proven in use |
| 8. graph traversal | pending | relation documents |
| 9. actors/jobs | blocked | server feature absent; render unavailable |
| 10. ingest | pending | POST /new/document/:dtype |
| 11. query/timeline/provenance | pending | |
| 12. dashboard polish | pending | after real features land |

Live validation against the development deployment (127.0.0.1:5000):
capabilities discovery, search round-trip, stats, and typed timeout
handling verified. Authenticated document fetch and target routes are
pending validation with a local API credential.

* TDD gate

=make test-emacs= runs all ERT suites (client + workbench) with the fake
transport. New behavior lands with red/green evidence in commit history.
29 changes: 29 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,33 @@ PY
starServer = star-server-bin;
};

# Native Emacs OSINT workbench (package name starintel-ui, entry
# command M-x starintel). Only runtime elisp is compiled: test
# files stay out of the installed site-lisp. Autoloads are
# generated so M-x can discover the interactive commands.
starintel-ui-src = pkgs.runCommand "starintel-ui-src" { } ''
mkdir -p $out
cp ${./client.el} $out/client.el
cp ${./starintel-server.el} $out/starintel-server.el
cp ${./starintel-uri.el} $out/starintel-uri.el
cp ${./starintel-object.el} $out/starintel-object.el
cp ${./starintel-search.el} $out/starintel-search.el
cp ${./starintel-ui.el} $out/starintel-ui.el
'';

starintel-ui = pkgs.emacsPackages.trivialBuild {
pname = "starintel-ui";
version = "2.0.0";
src = starintel-ui-src;
postInstall = ''
cd "$out/share/emacs/site-lisp"
${pkgs.emacs}/bin/emacs --batch \
--eval '(progn
(require (quote package))
(package-generate-autoloads "starintel-ui" "."))'
'';
};

in {
packages.${system} = {
default = star-server-bin;
Expand All @@ -512,6 +539,8 @@ PY
container-images = containerImages.allImages;
load-images = containerImages.loadImages;

starintel-ui = starintel-ui;

star-cli = pkgs.stdenv.mkDerivation {
pname = "star-cli";
version = "0.1.0";
Expand Down
Loading
Loading