Commit 7ccb38b
Add
* Add `productive run` command for JS/TS script execution
Adds a new `productive run` command (alias: `productive script`) that
executes a JavaScript or TypeScript script with a pre-configured Productive
SDK client injected automatically.
- Reads credentials from the usual sources (keychain → config file → env vars → CLI flags)
- Writes a temporary bootstrap wrapper (.mjs) that imports the SDK via an
absolute file:// URL — no extra npm install required in the user's project
- Supports two authoring patterns:
- Pattern A: `export default async function ({ client, output, args }) {}`
- Pattern B: globals (`productive`, `output`, `args`)
- TypeScript files (.ts, .mts) use Node.js built-in type stripping via
--experimental-strip-types + --experimental-transform-types — no tsx/ts-node needed
- Forwards the subprocess exit code; cleans up the temp wrapper after execution
New packages/cli/src/script/ subpackage:
- types.ts: ScriptContext, ScriptOutput, ScriptSpinner interfaces
- output.ts: ScriptOutput implementation (table, json, csv, logging, spinner)
- wrapper.ts: bootstrap wrapper generator
- index.ts: public API for @studiometa/productive-cli/script subpath export
New packages/cli/src/commands/run/:
- handlers.ts: scriptRun, isTypeScriptFile, waitForProcess (exported for testability)
- command.ts: handleRunCommand entry point
- help.ts, index.ts
Updated: cli.ts, package.json, vite.config.ts, SKILL.md, CHANGELOG.md
Co-authored-by: Claude <claude@anthropic.com>
* Add parsed `flags` to ScriptContext in `productive run`
Scripts now receive a `flags` object alongside `args` in both pattern A
and pattern B. The flag parser handles: positionals, --flag, --flag value,
--flag=value, --no-flag (→ false), -f, -f value, repeated flags (→ array),
negative numbers as values, and the -- end-of-flags separator.
- `packages/cli/src/script/args.ts` — new `parseScriptArgs()` function
- `packages/cli/src/script/args.test.ts` — 24 tests for the parser
- `ScriptContext` gains a `flags: ParsedFlags` field
- Wrapper bootstrap uses `parseScriptArgs` and passes `flags` to the
default export and exposes it as a global for pattern B scripts
- Help text updated with flags example and global listing
Co-authored-by: Claude <claude@anthropic.com>
* Add wrap-style `output.spinner(msg, asyncFn)` overload
The handle form (`output.spinner(msg)`) is unchanged.
A new overload accepts an async function: the spinner starts
automatically, stops on resolution, and shows a fail message
on rejection before re-throwing. The task return value is
passed through so callers can `await` it directly.
// before (manual)
const sp = output.spinner('Loading…');
const data = await fetch();
sp.stop();
// after (wrap form)
const data = await output.spinner('Loading…', () => fetch());
- 5 new tests cover the wrap form (resolve, reject, pass-through)
- Help text and SKILL.md updated with the new overload
Co-authored-by: Claude <claude@anthropic.com>
* Enable source maps in `productive run` subprocess for accurate stack traces
Add `--enable-source-maps` to the Node.js args spawned by `productive run`.
This flag instructs Node to use source maps when formatting error stack
traces, so lines reference the original source (TypeScript or source-mapped
JS) rather than the stripped or transpiled output.
With `--experimental-strip-types` Node uses SWC internally to strip types
and generates inline source maps. Without `--enable-source-maps` those maps
are ignored and the stack trace shows the post-strip line numbers.
The flag is always added (for both .ts and .js scripts) so that .js files
with external .map files also benefit.
Co-authored-by: Claude <claude@anthropic.com>
* Add `--dry-run` flag to `productive run`
When `productive run --dry-run ./script.ts` is used, mutating HTTP
requests (POST, PATCH, PUT, DELETE) are intercepted via a custom
`globalThis.fetch` wrapper and recorded instead of executed. Read-only
requests (GET, HEAD) still pass through so the script can fetch real
data it needs. After the script completes, a summary table of the
recorded calls is printed.
Implementation:
- `packages/cli/src/script/dry-run.ts` — new `createDryRunFetch()` and
`printDryRunSummary()` helpers; 21 tests in `dry-run.test.ts`
- `handlers.ts` strips `--dry-run` from rawArgs and sets
`PRODUCTIVE_DRY_RUN=1` in the subprocess environment
- `wrapper.ts` monkey-patches `globalThis.fetch` when the env var is set
and calls `printDryRunSummary` after the script finishes
- Help text and SKILL.md updated with `--dry-run` option and example
Co-authored-by: Claude <claude@anthropic.com>
* Add `export const meta` convention and `productive run --list`
Scripts can now export a `ScriptMeta` object to declare their name,
description, and usage. This metadata is picked up by the new
`productive run --list` command, which scans a directory (defaults to
`./scripts`) and prints an annotated index of all script files found.
// In a script file:
export const meta: ScriptMeta = {
name: 'Weekly Report',
description: 'Summarise time entries for the past week.',
usage: '--from <date> --to <date>',
};
// Discover scripts:
productive run --list
productive run --list ./automation
Implementation:
- `packages/cli/src/script/meta.ts` — `ScriptMeta` interface
- `packages/cli/src/commands/run/list.ts` — `discoverScripts()`,
`extractMetaFromSource()` (regex, no execution), `printScriptList()`,
and `scriptList()` entry point; 16 tests in `list.test.ts`
- `command.ts` — detects `--list` in allArgs and delegates to `scriptList`;
does not forward to `scriptRun` when listing
- `ScriptMeta` exported from the `@studiometa/productive-cli/script` subpath
- Help text and SKILL.md updated with meta example, `--list` option, and
discovery examples
Co-authored-by: Claude <claude@anthropic.com>
* Update CHANGELOG for productive run improvements
Co-authored-by: Claude <claude@anthropic.com>
* Fix script examples to use .all() instead of .list() for pagination
SDK .list() returns a single-page Promise (no .toArray()); .all() returns
an AsyncPaginatedIterator with .toArray() for collecting all pages.
Also fix FlattenResource attribute access: use p.name, not p.attributes.name —
SDK types merge id/type/attributes flat onto the top-level object.
Co-authored-by: Claude <claude@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Suppress Node.js ExperimentalWarning in script subprocess
Set NODE_NO_WARNINGS=1 in the child process env to hide banners like:
(node:XXXX) ExperimentalWarning: Transform Types is an experimental feature
Only affects the spawned subprocess — the parent CLI process is unaffected.
Co-authored-by: Claude <claude@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add defineMeta() and createScript() helpers for script authoring
Identity functions that provide full type inference without explicit
annotations — similar to Vue's defineComponent pattern.
import { defineMeta, createScript } from '@studiometa/productive-cli/script';
export const meta = defineMeta({ name: 'My Report' });
export default createScript(async ({ client, output, flags }) => {
const tasks = await client.tasks.all().toArray();
output.table(tasks.map((t) => ({ id: t.id, title: t.title })));
});
Also updates help text and SKILL.md to show the new recommended pattern,
and fixes stale p.attributes.name references (SDK types are flat).
Co-authored-by: Claude <claude@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add examples/ folder and fix --list to use dynamic import for meta
Replace regex-based meta extraction with a single-subprocess dynamic import:
spawns node with --experimental-strip-types --input-type=module, imports all
discovered scripts in one pass, and reads their exported meta values. Handles
defineMeta(), plain literals, and type annotations equally because they are
all just module exports — no parsing heuristics needed. Falls back to {} per
file on import errors (syntax errors, missing deps).
Also fix productive run --list <dir> when dir is passed: the global arg parser
consumed --list <dir> as options.list='<dir>' so allArgs.includes('--list')
never fired. Now checks options.list first, then falls back to allArgs scan.
Add 4 example scripts in packages/cli/examples/:
- my-tasks.ts — open tasks assigned to current user, with --overdue-only
- time-report.ts — time entries for a date range, table/csv/json output
- log-time.ts — log a time entry, dry-run friendly
- project-list.ts — active projects with --search and --format
Co-authored-by: Claude <claude@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix lint warnings in list.ts and add meta.test.ts
- Rename Promise callback param resolve→finish to avoid no-shadow
with node:path's resolve import
- Refactor try/catch to assign result then call finish() once,
eliminating the no-multiple-resolved false positive
- Add ScriptMeta smoke tests (meta.ts is a pure interface)
Co-authored-by: Claude <claude@anthropic.com>
---------
Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>productive run command for JS/TS script execution (#173)1 parent 29b70f7 commit 7ccb38b
34 files changed
Lines changed: 3129 additions & 3 deletions
File tree
- packages/cli
- examples
- skills
- src
- commands/run
- script
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
10 | 29 | | |
11 | 30 | | |
12 | 31 | | |
| |||
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
31 | 31 | | |
32 | 32 | | |
33 | 33 | | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
34 | 38 | | |
35 | 39 | | |
36 | 40 | | |
| |||
45 | 49 | | |
46 | 50 | | |
47 | 51 | | |
48 | | - | |
| 52 | + | |
| 53 | + | |
49 | 54 | | |
50 | 55 | | |
51 | 56 | | |
| |||
0 commit comments