@unravel-tech/thing 0.3.0 → 0.4.1

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 (3) hide show
  1. package/README.md +22 -4
  2. package/package.json +10 -3
  3. package/src/index.js +16 -6
package/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # @unravel-tech/thing
2
2
 
3
- CLI for [thing](https://thing.unravel.tech): push, version, and share artifacts HTML pages, Markdown docs, standalone images and PDFs from coding agents.
3
+ CLI for [thing](https://usething.ai): push, version, and share artifacts (HTML pages, Markdown docs, standalone images and PDFs) from coding agents.
4
4
 
5
5
  ```sh
6
6
  npm i -g @unravel-tech/thing
7
- thing login # defaults to https://thing.unravel.tech
7
+ thing login # defaults to https://usething.ai
8
8
  thing push report.html --json
9
9
  thing push notes.md --json # Markdown gets a styled reader view
10
10
  thing push chart.png --json # images and PDFs too
@@ -38,10 +38,28 @@ the overrides set the server picks your default (e.g. the Unravel org for Unrave
38
38
 
39
39
  ## MCP
40
40
 
41
- Any MCP client can push artifacts through your CLI login — register the server as:
41
+ Claude Code, Cursor, Codex, and anything else that speaks Model Context
42
+ Protocol can publish through thing. No terminal login needed: create a token at
43
+ [usething.ai](https://usething.ai) under Settings, then Tokens, and paste this
44
+ into your client's MCP config.
42
45
 
43
46
  ```json
44
- { "command": "thing", "args": ["mcp"] }
47
+ {
48
+ "mcpServers": {
49
+ "thing": {
50
+ "command": "npx",
51
+ "args": ["-y", "@unravel-tech/thing", "mcp"],
52
+ "env": { "THING_TOKEN": "paste-your-token-here" }
53
+ }
54
+ }
55
+ }
45
56
  ```
46
57
 
58
+ The server exposes three tools: `push_artifact` to publish a file or inline
59
+ content and get back a link, `list_artifacts` to see what you have, and
60
+ `whoami` to check which account and team you are pushing to.
61
+
62
+ If you already ran `thing login`, the stored credential is used and
63
+ `THING_TOKEN` can be left out.
64
+
47
65
  Requires Node >= 18 or Bun. No runtime dependencies.
package/package.json CHANGED
@@ -3,8 +3,9 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.3.0",
7
- "description": "CLI for thing: push, version, and share artifacts (HTML, Markdown, images, PDFs) from coding agents.",
6
+ "version": "0.4.1",
7
+ "description": "Artifacts for any coding agent. Publish what Claude Code, Codex, Cursor or Gemini CLI builds to a durable link, with versions and comments on the page.",
8
+ "mcpName": "io.github.unravel-team/thing",
8
9
  "license": "MIT",
9
10
  "repository": {
10
11
  "type": "git",
@@ -24,7 +25,13 @@
24
25
  "html",
25
26
  "cli",
26
27
  "agents",
27
- "claude-code"
28
+ "claude-code",
29
+ "mcp",
30
+ "model-context-protocol",
31
+ "share",
32
+ "publish",
33
+ "codex",
34
+ "cursor"
28
35
  ],
29
36
  "scripts": {
30
37
  "smoke": "bun test/cli-smoke.mjs",
package/src/index.js CHANGED
@@ -5,7 +5,8 @@ import { homedir } from "node:os";
5
5
  import { spawn } from "node:child_process";
6
6
  import { pathToFileURL } from "node:url";
7
7
 
8
- const DEFAULT_SERVER = process.env.THING_SERVER || "https://thing.unravel.tech";
8
+ const DEFAULT_SERVER = process.env.THING_SERVER || "https://usething.ai";
9
+ let clientName = "thing-cli/0.3.0";
9
10
  const VISIBILITIES = new Set(["team", "public"]);
10
11
 
11
12
  class CliError extends Error {
@@ -36,6 +37,7 @@ function projectConfigPath(cwd) {
36
37
 
37
38
  function loadState(cwd, env = process.env) {
38
39
  return {
40
+ env,
39
41
  globalPath: configPath(env),
40
42
  global: readJson(configPath(env)),
41
43
  projectPath: projectConfigPath(cwd),
@@ -87,9 +89,13 @@ function stripSlash(server) {
87
89
  }
88
90
 
89
91
  function context(state, flags = {}) {
92
+ const env = state.env ?? process.env;
90
93
  return {
91
- server: stripSlash(flags.server || state.project.server || state.global.server || DEFAULT_SERVER),
92
- token: flags.token || state.global.token || null,
94
+ // THING_SERVER and THING_TOKEN let an MCP client run this with no prior
95
+ // `thing login`: the whole config is a paste, which is the difference
96
+ // between usable and not for anyone who does not live in a terminal.
97
+ server: stripSlash(flags.server || env.THING_SERVER || state.project.server || state.global.server || DEFAULT_SERVER),
98
+ token: flags.token || env.THING_TOKEN || state.global.token || null,
93
99
  team: flags.team || state.project.team || state.global.activeTeam || null,
94
100
  project: flags.project || state.project.project || state.global.activeProject || null
95
101
  };
@@ -129,6 +135,7 @@ function formatText(value) {
129
135
  async function api(ctx, path, options = {}) {
130
136
  const headers = {
131
137
  Accept: "application/json",
138
+ "X-Thing-Client": clientName,
132
139
  ...(options.body ? { "Content-Type": "application/json" } : {}),
133
140
  ...(ctx.token ? { Authorization: `Bearer ${ctx.token}` } : {}),
134
141
  ...(options.headers || {})
@@ -407,7 +414,7 @@ const MCP_TOOLS = [
407
414
  {
408
415
  name: "push_artifact",
409
416
  description:
410
- "Publish a file as a thing artifact and get a live, shareable URL. Every push creates a new immutable version. Pass either `path` (any supported file: html, md, pdf, png, jpg, gif, webp) or inline `content` with a `filename` (html or md).",
417
+ "Use this whenever the user wants to share, send, show, or publish something you produced: a report, a dashboard, a prototype, a Markdown doc, a PDF, an image. Publishes it to a durable URL that stays live, and returns the link. Pushing the same name again creates a new version at the same link rather than a second link, so prefer reusing a name over inventing one. Pass either `path` (html, md, pdf, png, jpg, gif, webp) or inline `content` with a `filename` (html or md).",
411
418
  inputSchema: {
412
419
  type: "object",
413
420
  properties: {
@@ -423,7 +430,8 @@ const MCP_TOOLS = [
423
430
  },
424
431
  {
425
432
  name: "list_artifacts",
426
- description: "List the artifacts the logged-in user can see, with team, visibility, and title.",
433
+ description:
434
+ "Use this to find a link the user published earlier, or to check whether something is already published before pushing it again. Lists artifacts the signed-in user can see, with team, visibility and title.",
427
435
  inputSchema: {
428
436
  type: "object",
429
437
  properties: {
@@ -434,7 +442,8 @@ const MCP_TOOLS = [
434
442
  },
435
443
  {
436
444
  name: "whoami",
437
- description: "Show the logged-in thing user, the server, and where pushes land by default.",
445
+ description:
446
+ "Use this to confirm the user is signed in and which team will own a new artifact. Shows the account, the server, and where pushes land when no team is named.",
438
447
  inputSchema: { type: "object", properties: {} }
439
448
  }
440
449
  ];
@@ -481,6 +490,7 @@ async function mcpTool(state, parsed, name, args) {
481
490
  }
482
491
 
483
492
  async function mcp(parsed, state, io) {
493
+ clientName = "thing-mcp/0.3.0";
484
494
  const respond = (id, body) => io.stdout.write(`${JSON.stringify({ jsonrpc: "2.0", id, ...body })}\n`);
485
495
  const handle = async (req) => {
486
496
  if (req.method === "initialize") {