@haoxdong/marquee-cli 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/LICENSE.md +44 -0
- package/README.md +20 -0
- package/dist/cli.js +134 -0
- package/package.json +44 -0
- package/scripts/install-skill.mjs +94 -0
- package/skills/marquee/SKILL.md +100 -0
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@haoxdong/marquee-cli",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"license": "SEE LICENSE IN LICENSE.md",
|
|
5
|
+
"repository": "https://github.com/haoxdong/marquee-cli.git",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=20.16.0 <21 || >=22.3.0"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"marquee": "dist/cli.js"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist/cli.js",
|
|
15
|
+
"skills",
|
|
16
|
+
"scripts",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public",
|
|
21
|
+
"registry": "https://registry.npmjs.org"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"clean": "node -e \"const fs = require('node:fs'); for (const path of ['dist', 'tsconfig.tsbuildinfo']) fs.rmSync(path, { recursive: true, force: true })\"",
|
|
25
|
+
"build": "npm run clean && esbuild cli/main.ts --bundle --platform=node --format=esm --minify --external:agent-browser --banner:js=\"import{createRequire}from'node:module';const require=createRequire(import.meta.url);\" --outfile=dist/cli.js && node -e \"require('node:fs').chmodSync('dist/cli.js', 0o755)\"",
|
|
26
|
+
"build:coverage": "npm run clean && tsc -p tsconfig.json && node -e \"const fs = require('node:fs'); fs.writeFileSync('dist/cli.js', ['#!/usr/bin/env node', \\\"import './cli/main.js';\\\", ''].join('\\n')); fs.chmodSync('dist/cli.js', 0o755)\"",
|
|
27
|
+
"postinstall": "node scripts/install-skill.mjs",
|
|
28
|
+
"prepack": "npm run build",
|
|
29
|
+
"test": "vitest run",
|
|
30
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"agent-browser": "0.27.0",
|
|
34
|
+
"commander": "^14.0.0",
|
|
35
|
+
"proper-lockfile": "^4.1.2"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/node": "^22.13.10",
|
|
39
|
+
"@types/proper-lockfile": "^4.1.4",
|
|
40
|
+
"esbuild": "^0.27.4",
|
|
41
|
+
"typescript": "^5.8.2",
|
|
42
|
+
"vitest": "^3.2.4"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cpSync,
|
|
3
|
+
existsSync,
|
|
4
|
+
lstatSync,
|
|
5
|
+
mkdirSync,
|
|
6
|
+
readFileSync,
|
|
7
|
+
readlinkSync,
|
|
8
|
+
realpathSync,
|
|
9
|
+
rmSync,
|
|
10
|
+
statSync,
|
|
11
|
+
writeFileSync,
|
|
12
|
+
} from 'node:fs';
|
|
13
|
+
import { homedir } from 'node:os';
|
|
14
|
+
import { dirname, resolve } from 'node:path';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
|
|
17
|
+
const source = fileURLToPath(new URL('../skills/marquee', import.meta.url));
|
|
18
|
+
const ownerMarker = '.marquee-cli-owner';
|
|
19
|
+
const owner = '@haoxdong/marquee-cli';
|
|
20
|
+
const packageSkillSuffix = '/node_modules/@haoxdong/marquee-cli/skills/marquee';
|
|
21
|
+
const home = process.env.HOME ?? homedir();
|
|
22
|
+
|
|
23
|
+
const agents = [
|
|
24
|
+
{
|
|
25
|
+
name: 'claude',
|
|
26
|
+
configDir: process.env.CLAUDE_CONFIG_DIR ?? resolve(home, '.claude'),
|
|
27
|
+
installWhenMissing: true,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: 'codex',
|
|
31
|
+
configDir: resolve(home, '.agents'),
|
|
32
|
+
},
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
function selectedAgentNames() {
|
|
36
|
+
const configured = process.env.MARQUEE_SKILL_TARGETS;
|
|
37
|
+
if (configured === undefined) return undefined;
|
|
38
|
+
return new Set(configured.split(',').map((name) => name.trim()).filter(Boolean));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function packageOwns(target, installed) {
|
|
42
|
+
if (installed.isSymbolicLink()) {
|
|
43
|
+
try {
|
|
44
|
+
if (realpathSync(target) === realpathSync(source)) return true;
|
|
45
|
+
} catch {
|
|
46
|
+
// A package uninstall or npx cache cleanup can leave our old link broken.
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
const linkedPath = resolve(dirname(target), readlinkSync(target)).replaceAll('\\', '/');
|
|
50
|
+
return linkedPath.endsWith(packageSkillSuffix);
|
|
51
|
+
} catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (!installed.isDirectory()) return false;
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
return readFileSync(resolve(target, ownerMarker), 'utf8').trim() === owner;
|
|
59
|
+
} catch (error) {
|
|
60
|
+
if (error?.code === 'ENOENT') return false;
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function install(agent) {
|
|
66
|
+
const configDirExists = existsSync(agent.configDir);
|
|
67
|
+
if (!configDirExists && !agent.installWhenMissing) return;
|
|
68
|
+
if (configDirExists && !statSync(agent.configDir).isDirectory()) return;
|
|
69
|
+
|
|
70
|
+
const target = resolve(agent.configDir, 'skills/marquee');
|
|
71
|
+
let installed;
|
|
72
|
+
try {
|
|
73
|
+
installed = lstatSync(target);
|
|
74
|
+
} catch (error) {
|
|
75
|
+
if (error?.code !== 'ENOENT') throw error;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (installed && !packageOwns(target, installed)) {
|
|
79
|
+
console.warn(`Skipping marquee skill installation: ${target} already exists.`);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (installed) {
|
|
84
|
+
rmSync(target, { recursive: true, force: true });
|
|
85
|
+
}
|
|
86
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
87
|
+
cpSync(source, target, { recursive: true, force: true });
|
|
88
|
+
writeFileSync(resolve(target, ownerMarker), `${owner}\n`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const selected = selectedAgentNames();
|
|
92
|
+
for (const agent of agents) {
|
|
93
|
+
if (!selected || selected.has(agent.name)) install(agent);
|
|
94
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: marquee
|
|
3
|
+
description: Use when querying Marquee for market data, research content, or page interaction via the CLI.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Response format
|
|
7
|
+
|
|
8
|
+
**Authoritative.** State conclusions as facts. No hedging ("it seems", "appears to"). No emoji.
|
|
9
|
+
|
|
10
|
+
**Direct.** Answer the question asked, nothing adjacent. Tables over paragraphs for structured data.
|
|
11
|
+
|
|
12
|
+
**Concise.** Numbers over narrative. One sentence where one suffices; a table cell where a sentence would pad. Never restate the question. Most answers fit in 1-3 flat sentences — stop there. Only when genuinely multi-part:
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
### Summary
|
|
16
|
+
Bottom line in 1-2 sentences.
|
|
17
|
+
|
|
18
|
+
### More Details
|
|
19
|
+
Supporting data, tables, and analysis.
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Use exactly these two headers — no other sections, no renaming.
|
|
23
|
+
|
|
24
|
+
**Counterweight.** Close with what could break the thesis — the key risk or the condition under which the narrative flips.
|
|
25
|
+
|
|
26
|
+
**Citations.** Every factual claim gets a source URL — no exceptions, including bullets and table cells: `…widened 12bp [[1]](https://marquee.gs.com/s/markets/…)`. Never surface refs (`@s1`, `@wN`, `@wN.data`) — internal plumbing.
|
|
27
|
+
|
|
28
|
+
## Core loop
|
|
29
|
+
|
|
30
|
+
**Chain** refs — every command emits refs, the next consumes them.
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
marquee marketview search "query" # → @s1 with widget refs
|
|
34
|
+
marquee load @s1.w1 # → @wN, @wN.data
|
|
35
|
+
marquee eval @wN.data "data.filter(…)" # extract from flat rows
|
|
36
|
+
marquee expand @wN.data --tail 5 # peek at rows without code
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Asset-level: `marquee marketview dashboard TICKER` → `@dN`, then `load @dN.w1`.
|
|
40
|
+
|
|
41
|
+
Go straight to the best command; search only when genuinely ambiguous.
|
|
42
|
+
|
|
43
|
+
## Refs
|
|
44
|
+
|
|
45
|
+
Use the exact ref from the latest output — never hardcode. The global registry accumulates across commands.
|
|
46
|
+
|
|
47
|
+
| Target | Shape | When |
|
|
48
|
+
|--------|-------|------|
|
|
49
|
+
| `@wN.data` | flat rows | row values — the data itself |
|
|
50
|
+
| `@wN` | full payload | widget config, axis labels, param metadata |
|
|
51
|
+
| `@s1` | search payload | filter/rank results before loading |
|
|
52
|
+
|
|
53
|
+
### Changing parameters
|
|
54
|
+
|
|
55
|
+
Load output shows each param's ref and verb (`[@name, select|fill|check]`):
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
marquee select @wN.universe Latam # enum
|
|
59
|
+
marquee fill @wN.pricingDate "2026-01-15" # date/string
|
|
60
|
+
marquee check @wN.includeInternal # boolean toggle
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Content
|
|
64
|
+
|
|
65
|
+
Research articles and market commentary:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
marquee content search "query" # → @s1 with @s1.cmN / @s1.crN
|
|
69
|
+
marquee content markets <uuid|url> # TFC → @cmN
|
|
70
|
+
marquee content research <uuid|url> # GIR → @crN
|
|
71
|
+
marquee content research <uuid> --date YYYY-MM-DD # GIR by UUID + date
|
|
72
|
+
marquee load @s1.cm1 # load from search ref
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Paging: `next @cmN` / `prev @cmN`; full body: `expand @cmN`.
|
|
76
|
+
|
|
77
|
+
## Execution
|
|
78
|
+
|
|
79
|
+
Separate Bash tool calls run **sequentially**. All independent commands — searches, loads, evals — go in one Bash call with `&` + `wait`.
|
|
80
|
+
|
|
81
|
+
**Hedge searches**: the search engine is noisy — run 2-3 query variations in parallel (`&` + `wait`) to increase hit rate.
|
|
82
|
+
|
|
83
|
+
Search queries: precise, API-ready — include asset class (`EURUSD vol FX`), expand shorthand (`JPY` not `yen`), canonical terms (`risk reversal skew`).
|
|
84
|
+
|
|
85
|
+
## Browser fallback
|
|
86
|
+
|
|
87
|
+
When structured commands can't get the data:
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
marquee browser open <url> # navigate
|
|
91
|
+
marquee browser snapshot # → @e1, @e2, …
|
|
92
|
+
marquee browser click @e1 # click
|
|
93
|
+
marquee browser fill @e2 "value" # clear + type
|
|
94
|
+
marquee browser get text @e1 # read text
|
|
95
|
+
marquee browser wait --load networkidle # wait for page
|
|
96
|
+
marquee browser network requests --type xhr,fetch # list API calls
|
|
97
|
+
marquee browser network request <id> # request detail
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Snapshot → interact → re-snapshot. Use `network requests` for API debugging.
|