@teemtape/cli 0.0.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 +72 -0
- package/dist/index.js +450 -0
- package/dist/index.js.map +1 -0
- package/package.json +31 -0
package/README.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# @teemtape/cli
|
|
2
|
+
|
|
3
|
+
The teemtape command-line client. List stocks and post **anonymous notes** from
|
|
4
|
+
the terminal — the same notes that appear in the web/mobile note popups. Built so
|
|
5
|
+
AI agents (and power users) can collaborate on the same watchlist as end users.
|
|
6
|
+
|
|
7
|
+
> Not a trading tool. Quotes are delayed ~1 minute and informational only.
|
|
8
|
+
|
|
9
|
+
Built with [Commander.js](https://github.com/tj/commander.js) on Node + TypeScript,
|
|
10
|
+
bundled with [tsup](https://tsup.egoist.dev/). It talks to the same Worker API as
|
|
11
|
+
the web app via the shared [`@teemtape/api-client`](../api-client) package.
|
|
12
|
+
|
|
13
|
+
## Try it locally (with the mock API)
|
|
14
|
+
|
|
15
|
+
From the repo root:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install
|
|
19
|
+
npm run build
|
|
20
|
+
|
|
21
|
+
# terminal 1 — start the in-memory mock API (http://localhost:8787)
|
|
22
|
+
npm run mock
|
|
23
|
+
|
|
24
|
+
# terminal 2 — run the CLI against it
|
|
25
|
+
node packages/cli/dist/index.js list \
|
|
26
|
+
--token 6f1ed002ab5595859014ebf0951522d9
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The mock seeds a watchlist (token `6f1ed002ab5595859014ebf0951522d9`) so `list`
|
|
30
|
+
and `notes` work immediately. Once it's published you'll be able to run it as
|
|
31
|
+
`teemtape …` (via `npx teemtape` or a global install).
|
|
32
|
+
|
|
33
|
+
## Commands
|
|
34
|
+
|
|
35
|
+
| Command | What it does |
|
|
36
|
+
| ------- | ------------ |
|
|
37
|
+
| `teemtape init` | Create a new anonymous watchlist; saves the token to your config file |
|
|
38
|
+
| `teemtape list [--symbols A,B]` | Delayed quotes for your watchlist (or specific symbols) |
|
|
39
|
+
| `teemtape add <SYMBOL>` | Add a symbol to your watchlist |
|
|
40
|
+
| `teemtape notes <SYMBOL>` | Read the anonymous note thread for a symbol |
|
|
41
|
+
| `teemtape note <SYMBOL> -m "…"` | Post an anonymous note (tagged `source: cli`) |
|
|
42
|
+
| `teemtape share` | Print your shareable watchlist link |
|
|
43
|
+
| `teemtape config` | Show resolved config (token masked) |
|
|
44
|
+
|
|
45
|
+
### Global flags
|
|
46
|
+
|
|
47
|
+
- `--api-url <url>` — Worker API base URL
|
|
48
|
+
- `--token <token>` — watchlist token
|
|
49
|
+
- `--web-url <url>` — web base URL for share links
|
|
50
|
+
- `--json` — machine-readable JSON output (handy for agents)
|
|
51
|
+
|
|
52
|
+
## Configuration
|
|
53
|
+
|
|
54
|
+
Resolved with precedence: **CLI flags > env vars > config file > defaults**.
|
|
55
|
+
|
|
56
|
+
| Setting | Flag | Env var | Default |
|
|
57
|
+
| ------- | ---- | ------- | ------- |
|
|
58
|
+
| API URL | `--api-url` | `TEEMTAPE_API_URL` | `http://localhost:8787` |
|
|
59
|
+
| Token | `--token` | `TEEMTAPE_TOKEN` | (none) |
|
|
60
|
+
| Web URL | `--web-url` | `TEEMTAPE_WEB_URL` | `https://teemtape.app` |
|
|
61
|
+
|
|
62
|
+
The config file lives at `~/.config/teemtape/config.json` (or
|
|
63
|
+
`$XDG_CONFIG_HOME/teemtape/config.json`) and is written with `0600` perms. The
|
|
64
|
+
watchlist token is never printed in full (it's masked in `config`).
|
|
65
|
+
|
|
66
|
+
## Develop
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npm run build --workspace @teemtape/cli # bundle to dist/
|
|
70
|
+
npm run dev --workspace @teemtape/cli # watch mode
|
|
71
|
+
npm run test --workspace @teemtape/cli # config + binary tests
|
|
72
|
+
```
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// ../api-client/dist/index.js
|
|
4
|
+
var ApiError = class extends Error {
|
|
5
|
+
status;
|
|
6
|
+
body;
|
|
7
|
+
constructor(status, message, body) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = "ApiError";
|
|
10
|
+
this.status = status;
|
|
11
|
+
this.body = body;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
var TeemtapeClient = class {
|
|
15
|
+
baseUrl;
|
|
16
|
+
token;
|
|
17
|
+
fetchImpl;
|
|
18
|
+
constructor(options) {
|
|
19
|
+
this.baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
20
|
+
this.token = options.token;
|
|
21
|
+
this.fetchImpl = options.fetch ?? globalThis.fetch;
|
|
22
|
+
if (typeof this.fetchImpl !== "function") {
|
|
23
|
+
throw new Error("No fetch implementation available (need Node 18+ or pass options.fetch).");
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/** Delayed quotes for the given symbols. */
|
|
27
|
+
async getQuotes(symbols) {
|
|
28
|
+
const query = encodeURIComponent(symbols.join(","));
|
|
29
|
+
return this.request(`/api/quotes?symbols=${query}`);
|
|
30
|
+
}
|
|
31
|
+
/** Paginated SEC symbol catalog with optional search and sort. */
|
|
32
|
+
async listSymbols(params = {}) {
|
|
33
|
+
const search = new URLSearchParams();
|
|
34
|
+
if (params.offset !== void 0) search.set("offset", String(params.offset));
|
|
35
|
+
if (params.limit !== void 0) search.set("limit", String(params.limit));
|
|
36
|
+
if (params.sort) search.set("sort", params.sort);
|
|
37
|
+
if (params.q) search.set("q", params.q);
|
|
38
|
+
if (params.symbol) search.set("symbol", params.symbol);
|
|
39
|
+
if (params.name) search.set("name", params.name);
|
|
40
|
+
const qs = search.toString();
|
|
41
|
+
return this.request(`/api/symbols${qs ? `?${qs}` : ""}`);
|
|
42
|
+
}
|
|
43
|
+
/** Create a new anonymous watchlist and return its MD5 token. */
|
|
44
|
+
async createWatchlist() {
|
|
45
|
+
return this.request(`/api/watchlists`, { method: "POST" });
|
|
46
|
+
}
|
|
47
|
+
/** Fetch a watchlist (symbols + metadata) by token. */
|
|
48
|
+
async getWatchlist(token = this.requireToken()) {
|
|
49
|
+
return this.request(`/api/w/${token}`);
|
|
50
|
+
}
|
|
51
|
+
/** Add a symbol to a watchlist. */
|
|
52
|
+
async addSymbol(symbol, token = this.requireToken()) {
|
|
53
|
+
return this.request(`/api/w/${token}/symbols`, {
|
|
54
|
+
method: "POST",
|
|
55
|
+
body: JSON.stringify({ symbol: symbol.toUpperCase() })
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/** Notes for a symbol on a watchlist. */
|
|
59
|
+
async getNotes(symbol, token = this.requireToken()) {
|
|
60
|
+
const query = encodeURIComponent(symbol.toUpperCase());
|
|
61
|
+
return this.request(`/api/w/${token}/notes?symbol=${query}`);
|
|
62
|
+
}
|
|
63
|
+
/** Post an anonymous note to a symbol. */
|
|
64
|
+
async addNote(input, token = this.requireToken()) {
|
|
65
|
+
return this.request(`/api/w/${token}/notes`, {
|
|
66
|
+
method: "POST",
|
|
67
|
+
body: JSON.stringify({ ...input, symbol: input.symbol.toUpperCase() })
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
requireToken() {
|
|
71
|
+
if (!this.token) {
|
|
72
|
+
throw new Error("A watchlist token is required. Pass --token or set TEEMTAPE_TOKEN.");
|
|
73
|
+
}
|
|
74
|
+
return this.token;
|
|
75
|
+
}
|
|
76
|
+
async request(path, init = {}) {
|
|
77
|
+
const headers = new Headers(init.headers);
|
|
78
|
+
if (init.body && !headers.has("content-type")) {
|
|
79
|
+
headers.set("content-type", "application/json");
|
|
80
|
+
}
|
|
81
|
+
headers.set("accept", "application/json");
|
|
82
|
+
const res = await this.fetchImpl(`${this.baseUrl}${path}`, { ...init, headers });
|
|
83
|
+
const text = await res.text();
|
|
84
|
+
const data = text ? safeJsonParse(text) : void 0;
|
|
85
|
+
if (!res.ok) {
|
|
86
|
+
const message = isRecord(data) && typeof data.error === "string" && data.error || `Request to ${path} failed with ${res.status}`;
|
|
87
|
+
throw new ApiError(res.status, message, data);
|
|
88
|
+
}
|
|
89
|
+
return data;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
function safeJsonParse(text) {
|
|
93
|
+
try {
|
|
94
|
+
return JSON.parse(text);
|
|
95
|
+
} catch {
|
|
96
|
+
return text;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function isRecord(value) {
|
|
100
|
+
return typeof value === "object" && value !== null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// src/index.ts
|
|
104
|
+
import { Command } from "commander";
|
|
105
|
+
|
|
106
|
+
// src/output.ts
|
|
107
|
+
var useColor = Boolean(process.stdout.isTTY) && !process.env.NO_COLOR;
|
|
108
|
+
var codes = {
|
|
109
|
+
reset: "\x1B[0m",
|
|
110
|
+
dim: "\x1B[2m",
|
|
111
|
+
bold: "\x1B[1m",
|
|
112
|
+
green: "\x1B[32m",
|
|
113
|
+
red: "\x1B[31m",
|
|
114
|
+
yellow: "\x1B[33m",
|
|
115
|
+
cyan: "\x1B[36m"
|
|
116
|
+
};
|
|
117
|
+
function paint(code, text) {
|
|
118
|
+
return useColor ? `${code}${text}${codes.reset}` : text;
|
|
119
|
+
}
|
|
120
|
+
var c = {
|
|
121
|
+
dim: (t) => paint(codes.dim, t),
|
|
122
|
+
bold: (t) => paint(codes.bold, t),
|
|
123
|
+
green: (t) => paint(codes.green, t),
|
|
124
|
+
red: (t) => paint(codes.red, t),
|
|
125
|
+
yellow: (t) => paint(codes.yellow, t),
|
|
126
|
+
cyan: (t) => paint(codes.cyan, t)
|
|
127
|
+
};
|
|
128
|
+
function printJson(value) {
|
|
129
|
+
process.stdout.write(`${JSON.stringify(value, null, 2)}
|
|
130
|
+
`);
|
|
131
|
+
}
|
|
132
|
+
function money(n) {
|
|
133
|
+
return `$${n.toFixed(2)}`;
|
|
134
|
+
}
|
|
135
|
+
function signed(n) {
|
|
136
|
+
return `${n >= 0 ? "+" : ""}${n.toFixed(2)}`;
|
|
137
|
+
}
|
|
138
|
+
function pad(text, width) {
|
|
139
|
+
return text.length >= width ? text : text + " ".repeat(width - text.length);
|
|
140
|
+
}
|
|
141
|
+
function padStart(text, width) {
|
|
142
|
+
return text.length >= width ? text : " ".repeat(width - text.length) + text;
|
|
143
|
+
}
|
|
144
|
+
function printQuotesTable(quotes, delayedSeconds, source) {
|
|
145
|
+
if (quotes.length === 0) {
|
|
146
|
+
process.stdout.write(c.dim("No symbols on this watchlist yet. Add one with `teemtape add <SYMBOL>`.\n"));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const header = `${pad("SYMBOL", 8)}${pad("COMPANY", 26)}${padStart("LAST", 10)} ${padStart("CHANGE", 18)}`;
|
|
150
|
+
process.stdout.write(`${c.dim(header)}
|
|
151
|
+
`);
|
|
152
|
+
for (const q of quotes) {
|
|
153
|
+
const changeText = `${signed(q.change)} (${signed(q.pct)}%)`;
|
|
154
|
+
const colored = q.change >= 0 ? c.green(changeText) : c.red(changeText);
|
|
155
|
+
const row = pad(q.symbol, 8) + pad(truncate(q.name, 25), 26) + padStart(money(q.price), 10) + " " + padStartColored(colored, changeText, 18);
|
|
156
|
+
process.stdout.write(`${row}
|
|
157
|
+
`);
|
|
158
|
+
}
|
|
159
|
+
const mins = Math.round(delayedSeconds / 60);
|
|
160
|
+
process.stdout.write(
|
|
161
|
+
`
|
|
162
|
+
${c.dim(`# prices delayed ~${mins} min \xB7 source: ${source}`)}
|
|
163
|
+
`
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
function printNotes(symbol, notes) {
|
|
167
|
+
process.stdout.write(`${c.bold(symbol)} ${c.dim("notes")}
|
|
168
|
+
`);
|
|
169
|
+
process.stdout.write(`${c.dim("\u2500".repeat(44))}
|
|
170
|
+
`);
|
|
171
|
+
if (notes.length === 0) {
|
|
172
|
+
process.stdout.write(`${c.dim("No notes yet. Add one with `teemtape note " + symbol + ' --message "\u2026"`.')}
|
|
173
|
+
`);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
for (const n of notes) {
|
|
177
|
+
const src = n.source === "cli" ? c.yellow(`(cli, ${rel(n.createdAt)})`) : c.dim(`(web, ${rel(n.createdAt)})`);
|
|
178
|
+
process.stdout.write(`${c.cyan(n.author)} ${src}
|
|
179
|
+
`);
|
|
180
|
+
process.stdout.write(` ${n.body}
|
|
181
|
+
`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function truncate(text, max) {
|
|
185
|
+
return text.length <= max ? text : `${text.slice(0, max - 1)}\u2026`;
|
|
186
|
+
}
|
|
187
|
+
function padStartColored(colored, plain, width) {
|
|
188
|
+
const padding = Math.max(0, width - plain.length);
|
|
189
|
+
return " ".repeat(padding) + colored;
|
|
190
|
+
}
|
|
191
|
+
function rel(iso) {
|
|
192
|
+
const then = new Date(iso).getTime();
|
|
193
|
+
if (Number.isNaN(then)) return iso;
|
|
194
|
+
const diffMs = Date.now() - then;
|
|
195
|
+
const mins = Math.round(diffMs / 6e4);
|
|
196
|
+
if (mins < 1) return "just now";
|
|
197
|
+
if (mins < 60) return `${mins}m ago`;
|
|
198
|
+
const hours = Math.round(mins / 60);
|
|
199
|
+
if (hours < 24) return `${hours}h ago`;
|
|
200
|
+
return `${Math.round(hours / 24)}d ago`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// src/commands/add.ts
|
|
204
|
+
async function addCommand(ctx, symbol) {
|
|
205
|
+
const watchlist = await ctx.client.addSymbol(symbol);
|
|
206
|
+
if (ctx.json) {
|
|
207
|
+
printJson(watchlist);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
process.stdout.write(`${c.green("\u2713")} Added ${c.bold(symbol.toUpperCase())}
|
|
211
|
+
`);
|
|
212
|
+
process.stdout.write(` ${c.dim(`watchlist: ${watchlist.symbols.join(", ") || "(empty)"}`)}
|
|
213
|
+
`);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// src/config.ts
|
|
217
|
+
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
218
|
+
import { homedir } from "os";
|
|
219
|
+
import { dirname, join } from "path";
|
|
220
|
+
var DEFAULTS = {
|
|
221
|
+
apiUrl: "http://localhost:8787",
|
|
222
|
+
webUrl: "https://teemtape.app"
|
|
223
|
+
};
|
|
224
|
+
function configFilePath() {
|
|
225
|
+
const base = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
|
226
|
+
return join(base, "teemtape", "config.json");
|
|
227
|
+
}
|
|
228
|
+
function readConfigFile() {
|
|
229
|
+
try {
|
|
230
|
+
return JSON.parse(readFileSync(configFilePath(), "utf8"));
|
|
231
|
+
} catch {
|
|
232
|
+
return {};
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
function resolveConfig(flags = {}) {
|
|
236
|
+
const file = readConfigFile();
|
|
237
|
+
const env = {
|
|
238
|
+
apiUrl: process.env.TEEMTAPE_API_URL,
|
|
239
|
+
webUrl: process.env.TEEMTAPE_WEB_URL,
|
|
240
|
+
token: process.env.TEEMTAPE_TOKEN
|
|
241
|
+
};
|
|
242
|
+
return {
|
|
243
|
+
apiUrl: flags.apiUrl ?? env.apiUrl ?? file.apiUrl ?? DEFAULTS.apiUrl,
|
|
244
|
+
webUrl: flags.webUrl ?? env.webUrl ?? file.webUrl ?? DEFAULTS.webUrl,
|
|
245
|
+
token: flags.token ?? env.token ?? file.token
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
function saveConfig(patch) {
|
|
249
|
+
const path = configFilePath();
|
|
250
|
+
const merged = { ...readConfigFile(), ...patch };
|
|
251
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
252
|
+
writeFileSync(path, `${JSON.stringify(merged, null, 2)}
|
|
253
|
+
`, { mode: 384 });
|
|
254
|
+
return path;
|
|
255
|
+
}
|
|
256
|
+
function maskToken(token) {
|
|
257
|
+
if (!token) return "(none)";
|
|
258
|
+
if (token.length <= 8) return "****";
|
|
259
|
+
return `${token.slice(0, 6)}\u2026${token.slice(-2)}`;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// src/commands/config.ts
|
|
263
|
+
async function configCommand(ctx) {
|
|
264
|
+
const view = {
|
|
265
|
+
apiUrl: ctx.config.apiUrl,
|
|
266
|
+
webUrl: ctx.config.webUrl,
|
|
267
|
+
token: maskToken(ctx.config.token),
|
|
268
|
+
configFile: configFilePath()
|
|
269
|
+
};
|
|
270
|
+
if (ctx.json) {
|
|
271
|
+
printJson(view);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
process.stdout.write(`${c.bold("teemtape config")}
|
|
275
|
+
`);
|
|
276
|
+
process.stdout.write(` api url : ${view.apiUrl}
|
|
277
|
+
`);
|
|
278
|
+
process.stdout.write(` web url : ${view.webUrl}
|
|
279
|
+
`);
|
|
280
|
+
process.stdout.write(` token : ${view.token}
|
|
281
|
+
`);
|
|
282
|
+
process.stdout.write(` ${c.dim(`config file: ${view.configFile}`)}
|
|
283
|
+
`);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// src/commands/init.ts
|
|
287
|
+
async function initCommand(ctx) {
|
|
288
|
+
const watchlist = await ctx.client.createWatchlist();
|
|
289
|
+
const path = saveConfig({ token: watchlist.token });
|
|
290
|
+
const url = `${ctx.config.webUrl.replace(/\/$/, "")}/w/${watchlist.token}`;
|
|
291
|
+
if (ctx.json) {
|
|
292
|
+
printJson({ token: watchlist.token, url, configPath: path });
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
process.stdout.write(`${c.green("\u2713")} Created a new anonymous watchlist
|
|
296
|
+
`);
|
|
297
|
+
process.stdout.write(` ${c.dim(`token saved to ${path}`)}
|
|
298
|
+
`);
|
|
299
|
+
process.stdout.write(` ${c.dim("share link:")} ${url}
|
|
300
|
+
`);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// src/commands/list.ts
|
|
304
|
+
async function listCommand(ctx, opts) {
|
|
305
|
+
let symbols;
|
|
306
|
+
if (opts.symbols) {
|
|
307
|
+
symbols = splitSymbols(opts.symbols);
|
|
308
|
+
} else {
|
|
309
|
+
const watchlist = await ctx.client.getWatchlist();
|
|
310
|
+
symbols = watchlist.symbols;
|
|
311
|
+
}
|
|
312
|
+
if (symbols.length === 0) {
|
|
313
|
+
if (ctx.json) {
|
|
314
|
+
printJson({ quotes: [], delayedSeconds: 0, source: "none" });
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
process.stdout.write("No symbols to show. Add one with `teemtape add <SYMBOL>`.\n");
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
const res = await ctx.client.getQuotes(symbols);
|
|
321
|
+
if (ctx.json) {
|
|
322
|
+
printJson(res);
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
printQuotesTable(res.quotes, res.delayedSeconds, res.source);
|
|
326
|
+
}
|
|
327
|
+
function splitSymbols(raw) {
|
|
328
|
+
return raw.split(",").map((s) => s.trim().toUpperCase()).filter(Boolean);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// src/commands/note.ts
|
|
332
|
+
async function noteCommand(ctx, symbol, opts) {
|
|
333
|
+
const body = opts.message?.trim();
|
|
334
|
+
if (!body) {
|
|
335
|
+
throw new Error('A note message is required. Use --message "your note".');
|
|
336
|
+
}
|
|
337
|
+
const note = await ctx.client.addNote({ symbol, body, source: "cli" });
|
|
338
|
+
if (ctx.json) {
|
|
339
|
+
printJson(note);
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
process.stdout.write(`${c.green("\u2713")} Note posted to ${c.bold(note.symbol)} as ${c.cyan(note.author)}
|
|
343
|
+
`);
|
|
344
|
+
process.stdout.write(` ${c.dim(`id: ${note.id} \xB7 visible in web & mobile note popups`)}
|
|
345
|
+
`);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// src/commands/notes.ts
|
|
349
|
+
async function notesCommand(ctx, symbol) {
|
|
350
|
+
const res = await ctx.client.getNotes(symbol);
|
|
351
|
+
if (ctx.json) {
|
|
352
|
+
printJson(res);
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
printNotes(res.symbol, res.notes);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// src/commands/share.ts
|
|
359
|
+
async function shareCommand(ctx) {
|
|
360
|
+
const token = ctx.config.token;
|
|
361
|
+
if (!token) {
|
|
362
|
+
throw new Error("No watchlist token set. Run `teemtape init` to create one, or pass --token.");
|
|
363
|
+
}
|
|
364
|
+
const url = `${ctx.config.webUrl.replace(/\/$/, "")}/w/${token}`;
|
|
365
|
+
if (ctx.json) {
|
|
366
|
+
printJson({ url, token });
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
process.stdout.write(`${c.dim("# your anonymous watchlist link (share with anyone):")}
|
|
370
|
+
`);
|
|
371
|
+
process.stdout.write(`${url}
|
|
372
|
+
`);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// src/context.ts
|
|
376
|
+
function createContext(flags) {
|
|
377
|
+
const config = resolveConfig(flags);
|
|
378
|
+
const client = new TeemtapeClient({ baseUrl: config.apiUrl, token: config.token });
|
|
379
|
+
return { config, json: Boolean(flags.json), client };
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// src/index.ts
|
|
383
|
+
var program = new Command();
|
|
384
|
+
program.name("teemtape").description("teemtape \u2014 list stocks and post anonymous notes from the terminal.").version("0.0.0").option("--api-url <url>", "Worker API base URL (env: TEEMTAPE_API_URL)").option("--token <token>", "watchlist token (env: TEEMTAPE_TOKEN)").option("--web-url <url>", "web app base URL used for share links (env: TEEMTAPE_WEB_URL)").option("--json", "output machine-readable JSON (handy for agents)").showHelpAfterError();
|
|
385
|
+
function globalsOf(command) {
|
|
386
|
+
const o = command.optsWithGlobals();
|
|
387
|
+
return {
|
|
388
|
+
apiUrl: o.apiUrl,
|
|
389
|
+
token: o.token,
|
|
390
|
+
webUrl: o.webUrl,
|
|
391
|
+
json: Boolean(o.json)
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
async function run(command, fn) {
|
|
395
|
+
try {
|
|
396
|
+
const ctx = createContext(globalsOf(command));
|
|
397
|
+
await fn(ctx);
|
|
398
|
+
} catch (err) {
|
|
399
|
+
handleError(err);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
function fail(message) {
|
|
403
|
+
process.stderr.write(`${c.red("error:")} ${message}
|
|
404
|
+
`);
|
|
405
|
+
process.exitCode = 1;
|
|
406
|
+
}
|
|
407
|
+
function handleError(err) {
|
|
408
|
+
if (err instanceof ApiError) {
|
|
409
|
+
fail(`${err.message} (HTTP ${err.status})`);
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
const cause = err instanceof Error ? err.cause : void 0;
|
|
413
|
+
if (cause?.code === "ECONNREFUSED" || cause?.code === "ENOTFOUND") {
|
|
414
|
+
fail("could not reach the API. Is it running? For local testing run `npm run mock` in another terminal.");
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
fail(err instanceof Error ? err.message : String(err));
|
|
418
|
+
}
|
|
419
|
+
program.command("init").description("create a new anonymous watchlist and save its token locally").action(async (_opts, command) => run(command, (ctx) => initCommand(ctx)));
|
|
420
|
+
program.command("list").description("show delayed quotes for your watchlist (or --symbols)").option("-s, --symbols <list>", "comma-separated symbols to show instead of the watchlist").action(
|
|
421
|
+
async (opts, command) => run(command, (ctx) => listCommand(ctx, opts))
|
|
422
|
+
);
|
|
423
|
+
program.command("add").argument("<symbol>", "ticker symbol, e.g. AAPL").description("add a symbol to your watchlist").action(
|
|
424
|
+
async (symbol, _opts, command) => run(command, (ctx) => addCommand(ctx, symbol))
|
|
425
|
+
);
|
|
426
|
+
program.command("notes").argument("<symbol>", "ticker symbol, e.g. AAPL").description("read the anonymous note thread for a symbol").action(
|
|
427
|
+
async (symbol, _opts, command) => run(command, (ctx) => notesCommand(ctx, symbol))
|
|
428
|
+
);
|
|
429
|
+
program.command("note").argument("<symbol>", "ticker symbol, e.g. AAPL").requiredOption("-m, --message <text>", "the note to post").description("post an anonymous note to a symbol (tagged as source: cli)").action(
|
|
430
|
+
async (symbol, opts, command) => run(command, (ctx) => noteCommand(ctx, symbol, opts))
|
|
431
|
+
);
|
|
432
|
+
program.command("share").description("print your anonymous shareable watchlist link").action(async (_opts, command) => run(command, (ctx) => shareCommand(ctx)));
|
|
433
|
+
program.command("config").description("show the resolved configuration (token masked)").action(async (_opts, command) => run(command, (ctx) => configCommand(ctx)));
|
|
434
|
+
program.addHelpText(
|
|
435
|
+
"after",
|
|
436
|
+
`
|
|
437
|
+
Examples:
|
|
438
|
+
$ teemtape --api-url http://localhost:8787 init
|
|
439
|
+
$ teemtape add NVDA
|
|
440
|
+
$ teemtape list
|
|
441
|
+
$ teemtape notes NVDA
|
|
442
|
+
$ teemtape note AAPL --message "Earnings call scheduled."
|
|
443
|
+
$ teemtape share
|
|
444
|
+
$ teemtape list --json # machine-readable output for agents
|
|
445
|
+
|
|
446
|
+
Config precedence: CLI flags > env vars > ~/.config/teemtape/config.json > defaults
|
|
447
|
+
`
|
|
448
|
+
);
|
|
449
|
+
program.parseAsync().catch(handleError);
|
|
450
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../api-client/src/client.ts","../src/index.ts","../src/output.ts","../src/commands/add.ts","../src/config.ts","../src/commands/config.ts","../src/commands/init.ts","../src/commands/list.ts","../src/commands/note.ts","../src/commands/notes.ts","../src/commands/share.ts","../src/context.ts"],"sourcesContent":["import type {\n CreateNoteInput,\n Note,\n NotesResponse,\n QuotesResponse,\n SymbolsListResponse,\n Watchlist,\n} from \"./types.js\";\n\nexport interface ApiClientOptions {\n /** Base URL of the Worker API, e.g. http://localhost:8787 */\n baseUrl: string;\n /** Optional default watchlist token used by watchlist/note calls. */\n token?: string;\n /** Injectable fetch (defaults to global fetch); handy for tests. */\n fetch?: typeof fetch;\n}\n\n/** Thrown for any non-2xx API response. */\nexport class ApiError extends Error {\n readonly status: number;\n readonly body: unknown;\n\n constructor(status: number, message: string, body: unknown) {\n super(message);\n this.name = \"ApiError\";\n this.status = status;\n this.body = body;\n }\n}\n\n/**\n * Minimal typed client for the teemtape Worker API. The same contract is reused\n * by the CLI now and the web/native apps later (see docs/cli-options.md).\n */\nexport class TeemtapeClient {\n private readonly baseUrl: string;\n private readonly token?: string;\n private readonly fetchImpl: typeof fetch;\n\n constructor(options: ApiClientOptions) {\n this.baseUrl = options.baseUrl.replace(/\\/$/, \"\");\n this.token = options.token;\n this.fetchImpl = options.fetch ?? globalThis.fetch;\n if (typeof this.fetchImpl !== \"function\") {\n throw new Error(\"No fetch implementation available (need Node 18+ or pass options.fetch).\");\n }\n }\n\n /** Delayed quotes for the given symbols. */\n async getQuotes(symbols: string[]): Promise<QuotesResponse> {\n const query = encodeURIComponent(symbols.join(\",\"));\n return this.request<QuotesResponse>(`/api/quotes?symbols=${query}`);\n }\n\n /** Paginated SEC symbol catalog with optional search and sort. */\n async listSymbols(params: {\n offset?: number;\n limit?: number;\n sort?: \"ticker\" | \"title\";\n q?: string;\n symbol?: string;\n name?: string;\n } = {}): Promise<SymbolsListResponse> {\n const search = new URLSearchParams();\n if (params.offset !== undefined) search.set(\"offset\", String(params.offset));\n if (params.limit !== undefined) search.set(\"limit\", String(params.limit));\n if (params.sort) search.set(\"sort\", params.sort);\n if (params.q) search.set(\"q\", params.q);\n if (params.symbol) search.set(\"symbol\", params.symbol);\n if (params.name) search.set(\"name\", params.name);\n const qs = search.toString();\n return this.request<SymbolsListResponse>(`/api/symbols${qs ? `?${qs}` : \"\"}`);\n }\n\n /** Create a new anonymous watchlist and return its MD5 token. */\n async createWatchlist(): Promise<Watchlist> {\n return this.request<Watchlist>(`/api/watchlists`, { method: \"POST\" });\n }\n\n /** Fetch a watchlist (symbols + metadata) by token. */\n async getWatchlist(token = this.requireToken()): Promise<Watchlist> {\n return this.request<Watchlist>(`/api/w/${token}`);\n }\n\n /** Add a symbol to a watchlist. */\n async addSymbol(symbol: string, token = this.requireToken()): Promise<Watchlist> {\n return this.request<Watchlist>(`/api/w/${token}/symbols`, {\n method: \"POST\",\n body: JSON.stringify({ symbol: symbol.toUpperCase() }),\n });\n }\n\n /** Notes for a symbol on a watchlist. */\n async getNotes(symbol: string, token = this.requireToken()): Promise<NotesResponse> {\n const query = encodeURIComponent(symbol.toUpperCase());\n return this.request<NotesResponse>(`/api/w/${token}/notes?symbol=${query}`);\n }\n\n /** Post an anonymous note to a symbol. */\n async addNote(input: CreateNoteInput, token = this.requireToken()): Promise<Note> {\n return this.request<Note>(`/api/w/${token}/notes`, {\n method: \"POST\",\n body: JSON.stringify({ ...input, symbol: input.symbol.toUpperCase() }),\n });\n }\n\n private requireToken(): string {\n if (!this.token) {\n throw new Error(\"A watchlist token is required. Pass --token or set TEEMTAPE_TOKEN.\");\n }\n return this.token;\n }\n\n private async request<T>(path: string, init: RequestInit = {}): Promise<T> {\n const headers = new Headers(init.headers);\n if (init.body && !headers.has(\"content-type\")) {\n headers.set(\"content-type\", \"application/json\");\n }\n headers.set(\"accept\", \"application/json\");\n\n const res = await this.fetchImpl(`${this.baseUrl}${path}`, { ...init, headers });\n const text = await res.text();\n const data = text ? safeJsonParse(text) : undefined;\n\n if (!res.ok) {\n const message =\n (isRecord(data) && typeof data.error === \"string\" && data.error) ||\n `Request to ${path} failed with ${res.status}`;\n throw new ApiError(res.status, message, data);\n }\n return data as T;\n }\n}\n\nfunction safeJsonParse(text: string): unknown {\n try {\n return JSON.parse(text);\n } catch {\n return text;\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n","#!/usr/bin/env node\nimport { ApiError } from \"@teemtape/api-client\";\nimport { Command } from \"commander\";\nimport { addCommand } from \"./commands/add.js\";\nimport { configCommand } from \"./commands/config.js\";\nimport { initCommand } from \"./commands/init.js\";\nimport { listCommand } from \"./commands/list.js\";\nimport { noteCommand } from \"./commands/note.js\";\nimport { notesCommand } from \"./commands/notes.js\";\nimport { shareCommand } from \"./commands/share.js\";\nimport { createContext, type Context, type GlobalFlags } from \"./context.js\";\nimport { c } from \"./output.js\";\n\nconst program = new Command();\n\nprogram\n .name(\"teemtape\")\n .description(\"teemtape — list stocks and post anonymous notes from the terminal.\")\n .version(\"0.0.0\")\n .option(\"--api-url <url>\", \"Worker API base URL (env: TEEMTAPE_API_URL)\")\n .option(\"--token <token>\", \"watchlist token (env: TEEMTAPE_TOKEN)\")\n .option(\"--web-url <url>\", \"web app base URL used for share links (env: TEEMTAPE_WEB_URL)\")\n .option(\"--json\", \"output machine-readable JSON (handy for agents)\")\n .showHelpAfterError();\n\nfunction globalsOf(command: Command): GlobalFlags {\n const o = command.optsWithGlobals();\n return {\n apiUrl: o.apiUrl as string | undefined,\n token: o.token as string | undefined,\n webUrl: o.webUrl as string | undefined,\n json: Boolean(o.json),\n };\n}\n\nasync function run(command: Command, fn: (ctx: Context) => Promise<void>): Promise<void> {\n try {\n const ctx = createContext(globalsOf(command));\n await fn(ctx);\n } catch (err) {\n handleError(err);\n }\n}\n\nfunction fail(message: string): void {\n process.stderr.write(`${c.red(\"error:\")} ${message}\\n`);\n process.exitCode = 1;\n}\n\nfunction handleError(err: unknown): void {\n if (err instanceof ApiError) {\n fail(`${err.message} (HTTP ${err.status})`);\n return;\n }\n const cause = err instanceof Error ? (err.cause as { code?: string } | undefined) : undefined;\n if (cause?.code === \"ECONNREFUSED\" || cause?.code === \"ENOTFOUND\") {\n fail(\"could not reach the API. Is it running? For local testing run `npm run mock` in another terminal.\");\n return;\n }\n fail(err instanceof Error ? err.message : String(err));\n}\n\nprogram\n .command(\"init\")\n .description(\"create a new anonymous watchlist and save its token locally\")\n .action(async (_opts, command: Command) => run(command, (ctx) => initCommand(ctx)));\n\nprogram\n .command(\"list\")\n .description(\"show delayed quotes for your watchlist (or --symbols)\")\n .option(\"-s, --symbols <list>\", \"comma-separated symbols to show instead of the watchlist\")\n .action(async (opts: { symbols?: string }, command: Command) =>\n run(command, (ctx) => listCommand(ctx, opts)),\n );\n\nprogram\n .command(\"add\")\n .argument(\"<symbol>\", \"ticker symbol, e.g. AAPL\")\n .description(\"add a symbol to your watchlist\")\n .action(async (symbol: string, _opts, command: Command) =>\n run(command, (ctx) => addCommand(ctx, symbol)),\n );\n\nprogram\n .command(\"notes\")\n .argument(\"<symbol>\", \"ticker symbol, e.g. AAPL\")\n .description(\"read the anonymous note thread for a symbol\")\n .action(async (symbol: string, _opts, command: Command) =>\n run(command, (ctx) => notesCommand(ctx, symbol)),\n );\n\nprogram\n .command(\"note\")\n .argument(\"<symbol>\", \"ticker symbol, e.g. AAPL\")\n .requiredOption(\"-m, --message <text>\", \"the note to post\")\n .description(\"post an anonymous note to a symbol (tagged as source: cli)\")\n .action(async (symbol: string, opts: { message?: string }, command: Command) =>\n run(command, (ctx) => noteCommand(ctx, symbol, opts)),\n );\n\nprogram\n .command(\"share\")\n .description(\"print your anonymous shareable watchlist link\")\n .action(async (_opts, command: Command) => run(command, (ctx) => shareCommand(ctx)));\n\nprogram\n .command(\"config\")\n .description(\"show the resolved configuration (token masked)\")\n .action(async (_opts, command: Command) => run(command, (ctx) => configCommand(ctx)));\n\nprogram.addHelpText(\n \"after\",\n `\nExamples:\n $ teemtape --api-url http://localhost:8787 init\n $ teemtape add NVDA\n $ teemtape list\n $ teemtape notes NVDA\n $ teemtape note AAPL --message \"Earnings call scheduled.\"\n $ teemtape share\n $ teemtape list --json # machine-readable output for agents\n\nConfig precedence: CLI flags > env vars > ~/.config/teemtape/config.json > defaults\n`,\n);\n\nprogram.parseAsync().catch(handleError);\n","import type { Note, Quote } from \"@teemtape/api-client\";\n\nconst useColor = Boolean(process.stdout.isTTY) && !process.env.NO_COLOR;\n\nconst codes = {\n reset: \"\\x1b[0m\",\n dim: \"\\x1b[2m\",\n bold: \"\\x1b[1m\",\n green: \"\\x1b[32m\",\n red: \"\\x1b[31m\",\n yellow: \"\\x1b[33m\",\n cyan: \"\\x1b[36m\",\n} as const;\n\nfunction paint(code: string, text: string): string {\n return useColor ? `${code}${text}${codes.reset}` : text;\n}\n\nexport const c = {\n dim: (t: string) => paint(codes.dim, t),\n bold: (t: string) => paint(codes.bold, t),\n green: (t: string) => paint(codes.green, t),\n red: (t: string) => paint(codes.red, t),\n yellow: (t: string) => paint(codes.yellow, t),\n cyan: (t: string) => paint(codes.cyan, t),\n};\n\nexport function printJson(value: unknown): void {\n process.stdout.write(`${JSON.stringify(value, null, 2)}\\n`);\n}\n\nfunction money(n: number): string {\n return `$${n.toFixed(2)}`;\n}\n\nfunction signed(n: number): string {\n return `${n >= 0 ? \"+\" : \"\"}${n.toFixed(2)}`;\n}\n\nfunction pad(text: string, width: number): string {\n return text.length >= width ? text : text + \" \".repeat(width - text.length);\n}\n\nfunction padStart(text: string, width: number): string {\n return text.length >= width ? text : \" \".repeat(width - text.length) + text;\n}\n\n/** Render the watchlist quotes as an aligned table. */\nexport function printQuotesTable(quotes: Quote[], delayedSeconds: number, source: string): void {\n if (quotes.length === 0) {\n process.stdout.write(c.dim(\"No symbols on this watchlist yet. Add one with `teemtape add <SYMBOL>`.\\n\"));\n return;\n }\n\n const header = `${pad(\"SYMBOL\", 8)}${pad(\"COMPANY\", 26)}${padStart(\"LAST\", 10)} ${padStart(\"CHANGE\", 18)}`;\n process.stdout.write(`${c.dim(header)}\\n`);\n\n for (const q of quotes) {\n const changeText = `${signed(q.change)} (${signed(q.pct)}%)`;\n const colored = q.change >= 0 ? c.green(changeText) : c.red(changeText);\n const row =\n pad(q.symbol, 8) +\n pad(truncate(q.name, 25), 26) +\n padStart(money(q.price), 10) +\n \" \" +\n padStartColored(colored, changeText, 18);\n process.stdout.write(`${row}\\n`);\n }\n\n const mins = Math.round(delayedSeconds / 60);\n process.stdout.write(\n `\\n${c.dim(`# prices delayed ~${mins} min · source: ${source}`)}\\n`,\n );\n}\n\n/** Render a note thread for a symbol. */\nexport function printNotes(symbol: string, notes: Note[]): void {\n process.stdout.write(`${c.bold(symbol)} ${c.dim(\"notes\")}\\n`);\n process.stdout.write(`${c.dim(\"─\".repeat(44))}\\n`);\n if (notes.length === 0) {\n process.stdout.write(`${c.dim(\"No notes yet. Add one with `teemtape note \" + symbol + ' --message \"…\"`.')}\\n`);\n return;\n }\n for (const n of notes) {\n const src = n.source === \"cli\" ? c.yellow(`(cli, ${rel(n.createdAt)})`) : c.dim(`(web, ${rel(n.createdAt)})`);\n process.stdout.write(`${c.cyan(n.author)} ${src}\\n`);\n process.stdout.write(` ${n.body}\\n`);\n }\n}\n\nfunction truncate(text: string, max: number): string {\n return text.length <= max ? text : `${text.slice(0, max - 1)}…`;\n}\n\n// Right-pad accounting for invisible ANSI codes by padding the plain text width.\nfunction padStartColored(colored: string, plain: string, width: number): string {\n const padding = Math.max(0, width - plain.length);\n return \" \".repeat(padding) + colored;\n}\n\nfunction rel(iso: string): string {\n const then = new Date(iso).getTime();\n if (Number.isNaN(then)) return iso;\n const diffMs = Date.now() - then;\n const mins = Math.round(diffMs / 60000);\n if (mins < 1) return \"just now\";\n if (mins < 60) return `${mins}m ago`;\n const hours = Math.round(mins / 60);\n if (hours < 24) return `${hours}h ago`;\n return `${Math.round(hours / 24)}d ago`;\n}\n","import type { Context } from \"../context.js\";\nimport { c, printJson } from \"../output.js\";\n\n/** `teemtape add <SYMBOL>` — add a symbol to the watchlist. */\nexport async function addCommand(ctx: Context, symbol: string): Promise<void> {\n const watchlist = await ctx.client.addSymbol(symbol);\n if (ctx.json) {\n printJson(watchlist);\n return;\n }\n process.stdout.write(`${c.green(\"✓\")} Added ${c.bold(symbol.toUpperCase())}\\n`);\n process.stdout.write(` ${c.dim(`watchlist: ${watchlist.symbols.join(\", \") || \"(empty)\"}`)}\\n`);\n}\n","import { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\n\n/** Resolved runtime configuration for a CLI invocation. */\nexport interface ResolvedConfig {\n apiUrl: string;\n webUrl: string;\n token?: string;\n}\n\nexport interface ConfigFlags {\n apiUrl?: string;\n webUrl?: string;\n token?: string;\n}\n\nconst DEFAULTS = {\n apiUrl: \"http://localhost:8787\",\n webUrl: \"https://teemtape.app\",\n};\n\n/** Path to the persisted config file (XDG-aware, falls back to ~/.config). */\nexport function configFilePath(): string {\n const base = process.env.XDG_CONFIG_HOME || join(homedir(), \".config\");\n return join(base, \"teemtape\", \"config.json\");\n}\n\ninterface StoredConfig {\n apiUrl?: string;\n webUrl?: string;\n token?: string;\n}\n\nfunction readConfigFile(): StoredConfig {\n try {\n return JSON.parse(readFileSync(configFilePath(), \"utf8\")) as StoredConfig;\n } catch {\n return {};\n }\n}\n\n/**\n * Resolve config with precedence: CLI flags > environment > config file > defaults.\n * (See docs/cli-options.md.)\n */\nexport function resolveConfig(flags: ConfigFlags = {}): ResolvedConfig {\n const file = readConfigFile();\n const env = {\n apiUrl: process.env.TEEMTAPE_API_URL,\n webUrl: process.env.TEEMTAPE_WEB_URL,\n token: process.env.TEEMTAPE_TOKEN,\n };\n\n return {\n apiUrl: flags.apiUrl ?? env.apiUrl ?? file.apiUrl ?? DEFAULTS.apiUrl,\n webUrl: flags.webUrl ?? env.webUrl ?? file.webUrl ?? DEFAULTS.webUrl,\n token: flags.token ?? env.token ?? file.token,\n };\n}\n\n/** Persist values to the config file (merges with existing). */\nexport function saveConfig(patch: StoredConfig): string {\n const path = configFilePath();\n const merged = { ...readConfigFile(), ...patch };\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, `${JSON.stringify(merged, null, 2)}\\n`, { mode: 0o600 });\n return path;\n}\n\n/** Mask a token for display so it never gets fully printed/logged. */\nexport function maskToken(token?: string): string {\n if (!token) return \"(none)\";\n if (token.length <= 8) return \"****\";\n return `${token.slice(0, 6)}…${token.slice(-2)}`;\n}\n","import type { Context } from \"../context.js\";\nimport { configFilePath, maskToken } from \"../config.js\";\nimport { c, printJson } from \"../output.js\";\n\n/** `teemtape config` — show the resolved configuration (token masked). */\nexport async function configCommand(ctx: Context): Promise<void> {\n const view = {\n apiUrl: ctx.config.apiUrl,\n webUrl: ctx.config.webUrl,\n token: maskToken(ctx.config.token),\n configFile: configFilePath(),\n };\n if (ctx.json) {\n printJson(view);\n return;\n }\n process.stdout.write(`${c.bold(\"teemtape config\")}\\n`);\n process.stdout.write(` api url : ${view.apiUrl}\\n`);\n process.stdout.write(` web url : ${view.webUrl}\\n`);\n process.stdout.write(` token : ${view.token}\\n`);\n process.stdout.write(` ${c.dim(`config file: ${view.configFile}`)}\\n`);\n}\n","import type { Context } from \"../context.js\";\nimport { saveConfig } from \"../config.js\";\nimport { c, printJson } from \"../output.js\";\n\n/** `teemtape init` — create a new anonymous watchlist and save its token locally. */\nexport async function initCommand(ctx: Context): Promise<void> {\n const watchlist = await ctx.client.createWatchlist();\n const path = saveConfig({ token: watchlist.token });\n const url = `${ctx.config.webUrl.replace(/\\/$/, \"\")}/w/${watchlist.token}`;\n\n if (ctx.json) {\n printJson({ token: watchlist.token, url, configPath: path });\n return;\n }\n process.stdout.write(`${c.green(\"✓\")} Created a new anonymous watchlist\\n`);\n process.stdout.write(` ${c.dim(`token saved to ${path}`)}\\n`);\n process.stdout.write(` ${c.dim(\"share link:\")} ${url}\\n`);\n}\n","import type { Context } from \"../context.js\";\nimport { printJson, printQuotesTable } from \"../output.js\";\n\nexport interface ListOptions {\n symbols?: string;\n}\n\n/** `teemtape list` — show delayed quotes for the watchlist (or --symbols). */\nexport async function listCommand(ctx: Context, opts: ListOptions): Promise<void> {\n let symbols: string[];\n if (opts.symbols) {\n symbols = splitSymbols(opts.symbols);\n } else {\n const watchlist = await ctx.client.getWatchlist();\n symbols = watchlist.symbols;\n }\n\n if (symbols.length === 0) {\n if (ctx.json) {\n printJson({ quotes: [], delayedSeconds: 0, source: \"none\" });\n return;\n }\n process.stdout.write(\"No symbols to show. Add one with `teemtape add <SYMBOL>`.\\n\");\n return;\n }\n\n const res = await ctx.client.getQuotes(symbols);\n if (ctx.json) {\n printJson(res);\n return;\n }\n printQuotesTable(res.quotes, res.delayedSeconds, res.source);\n}\n\nfunction splitSymbols(raw: string): string[] {\n return raw\n .split(\",\")\n .map((s) => s.trim().toUpperCase())\n .filter(Boolean);\n}\n","import type { Context } from \"../context.js\";\nimport { c, printJson } from \"../output.js\";\n\nexport interface NoteOptions {\n message?: string;\n}\n\n/** `teemtape note <SYMBOL> --message \"…\"` — post an anonymous note (source: cli). */\nexport async function noteCommand(ctx: Context, symbol: string, opts: NoteOptions): Promise<void> {\n const body = opts.message?.trim();\n if (!body) {\n throw new Error(\"A note message is required. Use --message \\\"your note\\\".\");\n }\n\n const note = await ctx.client.addNote({ symbol, body, source: \"cli\" });\n if (ctx.json) {\n printJson(note);\n return;\n }\n process.stdout.write(`${c.green(\"✓\")} Note posted to ${c.bold(note.symbol)} as ${c.cyan(note.author)}\\n`);\n process.stdout.write(` ${c.dim(`id: ${note.id} · visible in web & mobile note popups`)}\\n`);\n}\n","import type { Context } from \"../context.js\";\nimport { printJson, printNotes } from \"../output.js\";\n\n/** `teemtape notes <SYMBOL>` — read the note thread for a symbol. */\nexport async function notesCommand(ctx: Context, symbol: string): Promise<void> {\n const res = await ctx.client.getNotes(symbol);\n if (ctx.json) {\n printJson(res);\n return;\n }\n printNotes(res.symbol, res.notes);\n}\n","import type { Context } from \"../context.js\";\nimport { c, printJson } from \"../output.js\";\n\n/** `teemtape share` — print the anonymous shareable watchlist link. */\nexport async function shareCommand(ctx: Context): Promise<void> {\n const token = ctx.config.token;\n if (!token) {\n throw new Error(\"No watchlist token set. Run `teemtape init` to create one, or pass --token.\");\n }\n const url = `${ctx.config.webUrl.replace(/\\/$/, \"\")}/w/${token}`;\n if (ctx.json) {\n printJson({ url, token });\n return;\n }\n process.stdout.write(`${c.dim(\"# your anonymous watchlist link (share with anyone):\")}\\n`);\n process.stdout.write(`${url}\\n`);\n}\n","import { TeemtapeClient } from \"@teemtape/api-client\";\nimport { resolveConfig, type ConfigFlags, type ResolvedConfig } from \"./config.js\";\n\nexport interface GlobalFlags extends ConfigFlags {\n json?: boolean;\n}\n\nexport interface Context {\n config: ResolvedConfig;\n json: boolean;\n client: TeemtapeClient;\n}\n\n/** Build the per-invocation context (resolved config + a ready API client). */\nexport function createContext(flags: GlobalFlags): Context {\n const config = resolveConfig(flags);\n const client = new TeemtapeClient({ baseUrl: config.apiUrl, token: config.token });\n return { config, json: Boolean(flags.json), client };\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@teemtape/cli",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "teemtape CLI — list stocks and post anonymous notes from the terminal.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"teemtape": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsup",
|
|
15
|
+
"dev": "tsup --watch",
|
|
16
|
+
"test": "node --test",
|
|
17
|
+
"start": "node dist/index.js"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@teemtape/api-client": "*",
|
|
21
|
+
"commander": "^12.1.0"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/node": "^20.12.0",
|
|
25
|
+
"tsup": "^8.0.0",
|
|
26
|
+
"typescript": "^5.4.0"
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
}
|
|
31
|
+
}
|