@pitcher/cli-scripts 2.20.0 → 2.21.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 +3 -4
- package/dist/src/commands/sharedLinks.d.ts +0 -2
- package/dist/src/commands/sharedLinks.js +10 -20
- package/dist/src/mcp/prompts/analytics.js +3 -0
- package/dist/src/mcp/resources/analyticsCatalog.d.ts +1 -1
- package/dist/src/mcp/resources/analyticsCatalog.js +4 -0
- package/dist/src/mcp/tools/sharedLinks.js +4 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
> Pitcher CLI Tools provide a suite of command-line utilities to streamline your workflow. For the best experience, we recommend installing the tools globally using `npm install -g @pitcher/cli-scripts`.
|
|
4
4
|
|
|
5
|
+
Releases are published to npm automatically: a merge to `main` that bumps `version` in `package.json` publishes that version, and a merge without a bump publishes nothing (`.github/workflows/cli-scripts-publish.yml`; release flow in `CLAUDE.md`).
|
|
6
|
+
|
|
5
7
|
## Help
|
|
6
8
|
|
|
7
9
|
```bash
|
|
@@ -223,15 +225,12 @@ p canvases get 01CANVASID... --api-key myapikey
|
|
|
223
225
|
|
|
224
226
|
## To run the `shared-links` command:
|
|
225
227
|
|
|
226
|
-
Create and read share links for canvases
|
|
228
|
+
Create and read share links for canvases (returns the `short_url`).
|
|
227
229
|
|
|
228
230
|
```bash
|
|
229
231
|
# Create a share link for a canvas
|
|
230
232
|
p shared-links create --canvas-id 01CANVASID... --api-key myapikey
|
|
231
233
|
|
|
232
|
-
# Create a share link for a file
|
|
233
|
-
p shared-links create --file-id 01FILEID... --api-key myapikey
|
|
234
|
-
|
|
235
234
|
# Fetch the existing share link for a canvas
|
|
236
235
|
p shared-links get --canvas-id 01CANVASID... --api-key myapikey
|
|
237
236
|
```
|
|
@@ -7,10 +7,8 @@ export interface SharedLinksDeps {
|
|
|
7
7
|
}
|
|
8
8
|
export declare function runSharedLinksCreate(opts: CommonOpts & {
|
|
9
9
|
canvasId?: string;
|
|
10
|
-
fileId?: string;
|
|
11
10
|
}, deps?: SharedLinksDeps): Promise<Envelope<unknown>>;
|
|
12
11
|
export declare function runSharedLinksGet(opts: CommonOpts & {
|
|
13
12
|
canvasId?: string;
|
|
14
|
-
fileId?: string;
|
|
15
13
|
}, deps?: SharedLinksDeps): Promise<Envelope<unknown>>;
|
|
16
14
|
export declare function createSharedLinksCommand(): Command;
|
|
@@ -4,22 +4,17 @@ import { ok, fail } from '../core/envelope.js';
|
|
|
4
4
|
import { createCliError } from '../core/errors.js';
|
|
5
5
|
import { emitEnvelope } from '../core/output.js';
|
|
6
6
|
import { withAuthFlags } from './withAuth.js';
|
|
7
|
-
/**
|
|
8
|
-
function
|
|
9
|
-
if (opts.canvasId
|
|
10
|
-
throw createCliError('VALIDATION', '
|
|
11
|
-
|
|
12
|
-
return 'canvas';
|
|
13
|
-
if (opts.fileId)
|
|
14
|
-
return 'file';
|
|
15
|
-
throw createCliError('VALIDATION', 'pass exactly one of --canvas-id or --file-id');
|
|
7
|
+
/** Only canvases can be shared. */
|
|
8
|
+
function requireCanvasId(opts) {
|
|
9
|
+
if (!opts.canvasId)
|
|
10
|
+
throw createCliError('VALIDATION', '--canvas-id is required');
|
|
11
|
+
return opts.canvasId;
|
|
16
12
|
}
|
|
17
13
|
export async function runSharedLinksCreate(opts, deps = {}) {
|
|
18
14
|
try {
|
|
19
|
-
const
|
|
15
|
+
const canvasId = requireCanvasId(opts);
|
|
20
16
|
const client = clientFor(opts, deps);
|
|
21
|
-
const
|
|
22
|
-
const res = await client.post('/api/v1/shared-links/', body);
|
|
17
|
+
const res = await client.post('/api/v1/shared-links/', { canvas_id: canvasId });
|
|
23
18
|
return ok(res.data);
|
|
24
19
|
}
|
|
25
20
|
catch (e) {
|
|
@@ -28,13 +23,10 @@ export async function runSharedLinksCreate(opts, deps = {}) {
|
|
|
28
23
|
}
|
|
29
24
|
export async function runSharedLinksGet(opts, deps = {}) {
|
|
30
25
|
try {
|
|
31
|
-
const
|
|
26
|
+
const canvasId = requireCanvasId(opts);
|
|
32
27
|
const client = clientFor(opts, deps);
|
|
33
28
|
// encodeURIComponent so an id containing `/` or `..` can't retarget the request path
|
|
34
|
-
const
|
|
35
|
-
? `/api/v1/shared-links/canvas/${encodeURIComponent(opts.canvasId)}`
|
|
36
|
-
: `/api/v1/shared-links/file/${encodeURIComponent(opts.fileId)}`;
|
|
37
|
-
const res = await client.get(path);
|
|
29
|
+
const res = await client.get(`/api/v1/shared-links/canvas/${encodeURIComponent(canvasId)}`);
|
|
38
30
|
return ok(res.data);
|
|
39
31
|
}
|
|
40
32
|
catch (e) {
|
|
@@ -42,17 +34,15 @@ export async function runSharedLinksGet(opts, deps = {}) {
|
|
|
42
34
|
}
|
|
43
35
|
}
|
|
44
36
|
export function createSharedLinksCommand() {
|
|
45
|
-
const cmd = new Command('shared-links').description('create and read share links for canvases
|
|
37
|
+
const cmd = new Command('shared-links').description('create and read share links for canvases');
|
|
46
38
|
withAuthFlags(cmd.command('create'))
|
|
47
39
|
.option('--canvas-id <id>', 'Canvas to share')
|
|
48
|
-
.option('--file-id <id>', 'File to share')
|
|
49
40
|
.action(async (options, command) => {
|
|
50
41
|
const globals = command.optsWithGlobals();
|
|
51
42
|
emitEnvelope(await runSharedLinksCreate({ ...options, quiet: globals.quiet }), globals);
|
|
52
43
|
});
|
|
53
44
|
withAuthFlags(cmd.command('get'))
|
|
54
45
|
.option('--canvas-id <id>', 'Canvas whose link to fetch')
|
|
55
|
-
.option('--file-id <id>', 'File whose link to fetch')
|
|
56
46
|
.action(async (options, command) => {
|
|
57
47
|
const globals = command.optsWithGlobals();
|
|
58
48
|
emitEnvelope(await runSharedLinksGet({ ...options, quiet: globals.quiet }), globals);
|
|
@@ -79,6 +79,9 @@ export const analyticsPrompts = [
|
|
|
79
79
|
'1. Call `pitcher_analytics_viewers` with the `canvasId` to list viewers and their per-viewer engagement.',
|
|
80
80
|
'2. To drill into a specific viewer, call `pitcher_analytics_sessions` with the same `canvasId` plus that viewer’s ' +
|
|
81
81
|
'`userEmail` (both come from step 1) to get their raw session event timeline.',
|
|
82
|
+
' A session is one `Entered` of the queried id itself (canvas or section), paired by `event_pair_id`; ' +
|
|
83
|
+
'an `Entered` with an empty `event_pair_id` counts as one session on its own. ' +
|
|
84
|
+
'Sections listed by `pitcher_analytics_canvases` are valid `canvasId` inputs for both tools.',
|
|
82
85
|
'3. Summarize who viewed, how much they engaged, and — if you drilled in — what that viewer did. Works with a member key.',
|
|
83
86
|
].join('\n');
|
|
84
87
|
return [{ role: 'user', content: { type: 'text', text } }];
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { ResourceDescriptor } from './types.js';
|
|
2
|
-
export declare const ANALYTICS_CATALOG = "# Pitcher analytics catalog\n\nTen read-only verbs, each an MCP tool named `pitcher_analytics_<verb>`. Pick the verb that matches the\nquestion, then call its tool. All are read-only; org scope + auth come from the server's API key (never a\ntool input) \u2014 you only pass `instanceId` (and, where noted, `canvasId`/`userEmail`).\n\n## Key tiers\n- **org-admin key required:** org, popularity, users, files, canvases\n- **instance-admin or org-admin:** instance\n- **member key OK (access follows the key user's real role):** kpi, engagement, viewers, sessions\n\n## Time model\n- **Rollup (no window):** org, instance \u2014 lifetime/current totals.\n- **Date-range, 30-day default:** kpi (activity types need dates), engagement, popularity, `users` with\n `stats: true`, files, canvases \u2014 omit dates to get the last 30 days. (`users` without `stats` is a\n full roster with no window.)\n- **Date-range, server default window:** viewers, sessions \u2014 omit dates and the service picks the window.\n\n## Verbs\n\n| Verb (tool) | Returns | Time | Key | Example question |\n|---|---|---|---|---|\n| `org` | Organization-wide rollup totals (Django) | rollup | org-admin | \"How is the whole org doing overall?\" |\n| `instance` | Single-instance rollup totals (Django) | rollup | instance-admin | \"Totals for this one workspace?\" |\n| `kpi` | KPI metrics for an instance (members, active users, \u2026) | date-range | member | \"How many active users last month?\" |\n| `engagement` | Per-DSR/content engagement rows (capped list + summary) | date-range | member | \"Which content is being engaged with?\" |\n| `popularity` | Most-viewed canvases, ranked (view-analysis) | date-range | org-admin | \"Top 10 most-viewed decks this quarter?\" |\n| `users` | User roster, or active-user stats with `stats: true` | roster: none / stats: date-range | org-admin | \"Who are my active users this month?\" |\n| `files` | File usage analytics (views/opens/engagement per file) | date-range | org-admin | \"Which files get opened the most?\" |\n| `canvases` | Per-section canvas engagement list | date-range | org-admin | \"How are my canvas sections performing?\" |\n| `viewers` | Who viewed a specific DSR canvas (per-viewer engagement) | date-range | member | \"Who viewed this DSR, and how much?\" |\n| `sessions` | One viewer's session event timeline for a DSR canvas (raw) | date-range | member | \"What did this viewer do inside the DSR?\" |\n\n## Shaping\nRow-heavy verbs (engagement, popularity, users, files, canvases, viewers) return a capped envelope:\n`summary` + `rows` (default 20, max 100 \u2014 raise with `limit`) + `total_count`/`returned`/`has_more`,\nand a `truncated` note when more rows exist. `kpi` and `sessions` return the raw payload (a KPI scalar set\nand an uncapped event timeline, respectively) \u2014 never truncated.\n\n## DSR drill-down\nFor \"who viewed X and what did they do\": call `viewers` (with `canvasId`) to list viewers, then `sessions`\n(with the same `canvasId` + the viewer's `userEmail`) to see that viewer's event timeline.\n\n## Notes\n- `popularity` maps to the canvases view-analysis endpoint (there is no separate popularity route).\n- Results can include user identities/emails \u2014 treat as customer data and handle per policy.\n";
|
|
2
|
+
export declare const ANALYTICS_CATALOG = "# Pitcher analytics catalog\n\nTen read-only verbs, each an MCP tool named `pitcher_analytics_<verb>`. Pick the verb that matches the\nquestion, then call its tool. All are read-only; org scope + auth come from the server's API key (never a\ntool input) \u2014 you only pass `instanceId` (and, where noted, `canvasId`/`userEmail`).\n\n## Key tiers\n- **org-admin key required:** org, popularity, users, files, canvases\n- **instance-admin or org-admin:** instance\n- **member key OK (access follows the key user's real role):** kpi, engagement, viewers, sessions\n\n## Time model\n- **Rollup (no window):** org, instance \u2014 lifetime/current totals.\n- **Date-range, 30-day default:** kpi (activity types need dates), engagement, popularity, `users` with\n `stats: true`, files, canvases \u2014 omit dates to get the last 30 days. (`users` without `stats` is a\n full roster with no window.)\n- **Date-range, server default window:** viewers, sessions \u2014 omit dates and the service picks the window.\n\n## Verbs\n\n| Verb (tool) | Returns | Time | Key | Example question |\n|---|---|---|---|---|\n| `org` | Organization-wide rollup totals (Django) | rollup | org-admin | \"How is the whole org doing overall?\" |\n| `instance` | Single-instance rollup totals (Django) | rollup | instance-admin | \"Totals for this one workspace?\" |\n| `kpi` | KPI metrics for an instance (members, active users, \u2026) | date-range | member | \"How many active users last month?\" |\n| `engagement` | Per-DSR/content engagement rows (capped list + summary) | date-range | member | \"Which content is being engaged with?\" |\n| `popularity` | Most-viewed canvases, ranked (view-analysis) | date-range | org-admin | \"Top 10 most-viewed decks this quarter?\" |\n| `users` | User roster, or active-user stats with `stats: true` | roster: none / stats: date-range | org-admin | \"Who are my active users this month?\" |\n| `files` | File usage analytics (views/opens/engagement per file) | date-range | org-admin | \"Which files get opened the most?\" |\n| `canvases` | Per-section canvas engagement list | date-range | org-admin | \"How are my canvas sections performing?\" |\n| `viewers` | Who viewed a specific DSR canvas (per-viewer engagement) | date-range | member | \"Who viewed this DSR, and how much?\" |\n| `sessions` | One viewer's session event timeline for a DSR canvas (raw) | date-range | member | \"What did this viewer do inside the DSR?\" |\n\n## Shaping\nRow-heavy verbs (engagement, popularity, users, files, canvases, viewers) return a capped envelope:\n`summary` + `rows` (default 20, max 100 \u2014 raise with `limit`) + `total_count`/`returned`/`has_more`,\nand a `truncated` note when more rows exist. `kpi` and `sessions` return the raw payload (a KPI scalar set\nand an uncapped event timeline, respectively) \u2014 never truncated.\n\n## DSR drill-down\nFor \"who viewed X and what did they do\": call `viewers` (with `canvasId`) to list viewers, then `sessions`\n(with the same `canvasId` + the viewer's `userEmail`) to see that viewer's event timeline.\nA session is one `Entered` of the queried id itself (a canvas or a section), paired by `event_pair_id`; an\n`Entered` with an empty `event_pair_id` counts as one session on its own.\n`session_count` is counted the same way as the overview's `total_sessions`. `canvases` lists sections, and their\nids are valid `canvasId` inputs for `viewers` and `sessions`.\n\n## Notes\n- `popularity` maps to the canvases view-analysis endpoint (there is no separate popularity route).\n- Results can include user identities/emails \u2014 treat as customer data and handle per policy.\n";
|
|
3
3
|
export declare const analyticsCatalogResource: ResourceDescriptor;
|
|
@@ -45,6 +45,10 @@ and an uncapped event timeline, respectively) — never truncated.
|
|
|
45
45
|
## DSR drill-down
|
|
46
46
|
For "who viewed X and what did they do": call \`viewers\` (with \`canvasId\`) to list viewers, then \`sessions\`
|
|
47
47
|
(with the same \`canvasId\` + the viewer's \`userEmail\`) to see that viewer's event timeline.
|
|
48
|
+
A session is one \`Entered\` of the queried id itself (a canvas or a section), paired by \`event_pair_id\`; an
|
|
49
|
+
\`Entered\` with an empty \`event_pair_id\` counts as one session on its own.
|
|
50
|
+
\`session_count\` is counted the same way as the overview's \`total_sessions\`. \`canvases\` lists sections, and their
|
|
51
|
+
ids are valid \`canvasId\` inputs for \`viewers\` and \`sessions\`.
|
|
48
52
|
|
|
49
53
|
## Notes
|
|
50
54
|
- \`popularity\` maps to the canvases view-analysis endpoint (there is no separate popularity route).
|
|
@@ -4,10 +4,9 @@ export const sharedLinksTools = [
|
|
|
4
4
|
{
|
|
5
5
|
name: 'pitcher_shared_links_create',
|
|
6
6
|
title: 'Create a shared link',
|
|
7
|
-
description: 'Create a share link (short_url) for a canvas
|
|
7
|
+
description: 'Create a share link (short_url) for a canvas. Only canvases can be shared. Additive.',
|
|
8
8
|
inputShape: {
|
|
9
|
-
canvasId: z.string().
|
|
10
|
-
fileId: z.string().optional().describe('File to share (exactly one of canvasId/fileId is required)'),
|
|
9
|
+
canvasId: z.string().describe('Canvas to share'),
|
|
11
10
|
},
|
|
12
11
|
run: runSharedLinksCreate,
|
|
13
12
|
readOnly: false,
|
|
@@ -16,10 +15,9 @@ export const sharedLinksTools = [
|
|
|
16
15
|
{
|
|
17
16
|
name: 'pitcher_shared_links_get',
|
|
18
17
|
title: 'Get a shared link',
|
|
19
|
-
description: 'Retrieve the existing share link for a canvas
|
|
18
|
+
description: 'Retrieve the existing share link for a canvas.',
|
|
20
19
|
inputShape: {
|
|
21
|
-
canvasId: z.string().
|
|
22
|
-
fileId: z.string().optional().describe('File whose link to fetch (exactly one of canvasId/fileId is required)'),
|
|
20
|
+
canvasId: z.string().describe('Canvas whose link to fetch'),
|
|
23
21
|
},
|
|
24
22
|
run: runSharedLinksGet,
|
|
25
23
|
readOnly: true,
|