Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 1.12.0

Adds the new `value` Live Activity type.

## 1.11.0

This version adds support for Metadata and iOS deep links, and expands Tags support when updating or ending Live Activities.
Expand Down
86 changes: 49 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,33 +158,41 @@ activitysmith push \

Choose the Live Activity type that matches what you want to show:

![Stats Live Activity with six labeled sales metrics](https://cdn.activitysmith.com/features/stats-live-activity.png)
- ![Value Live Activity showing revenue with a growth badge](https://cdn.activitysmith.com/features/value-live-activity.png) **Value**: Show a single value on your Lock Screen, such as revenue, a queue count, or a temperature.

**Stats**: Show up to 8 labeled values on your Lock Screen, from revenue and orders to uptime and conversion.
- ![Stats Live Activity with six labeled sales metrics](https://cdn.activitysmith.com/features/stats-live-activity.png) **Stats**: Show up to 8 labeled values on your Lock Screen, from revenue and orders to uptime and conversion.

![Metrics Live Activity with CPU and memory values](https://cdn.activitysmith.com/features/metrics-live-activity-start.png)
- ![Alert Live Activity showing a customer reactivation update](https://cdn.activitysmith.com/features/alert-live-activity.png) **Alert**: Show status updates with a clear message, badge, and icon. When you add an action button, `color` controls the button tint.

**Metrics**: Track two related values with segmented bars, such as CPU and memory.
- ![Metrics Live Activity with CPU and memory values](https://cdn.activitysmith.com/features/metrics-live-activity-start.png) **Metrics**: Track two related values with segmented bars, such as CPU and memory.

![Segmented Progress Live Activity showing a workflow step](https://cdn.activitysmith.com/features/update-live-activity.png)
- ![Segmented Progress Live Activity showing a workflow step](https://cdn.activitysmith.com/features/update-live-activity.png) **Segmented Progress**: Show progress through a known set of steps, like build, test, deploy, and verify.

**Segmented Progress**: Show progress through a known set of steps, like build, test, deploy, and verify.
- ![Progress Live Activity showing percentage completion](https://cdn.activitysmith.com/features/progress-live-activity.png) **Progress**: Show percentage progress for jobs that move continuously toward completion.

![Progress Live Activity showing percentage completion](https://cdn.activitysmith.com/features/progress-live-activity.png)
- ![Timer Live Activity showing a benchmark run countdown](https://cdn.activitysmith.com/features/timer-live-activity.png) **Timer**: Count down from a duration, or count up from 00:00 while a job runs.

**Progress**: Show percentage progress for jobs that move continuously toward completion.

![Alert Live Activity showing a customer reactivation update](https://cdn.activitysmith.com/features/alert-live-activity.png)
### Start & Update Live Activity

**Alert**: Show status updates with a clear message, badge, and icon. When you add an action button, `color` controls the button tint.
Use a stable `stream_key` to identify the metric, job, deployment, or system you want to keep visible. The first `activity stream` command starts the Live Activity. Later commands with the same `stream_key` update it.

![Timer Live Activity showing a benchmark run countdown](https://cdn.activitysmith.com/features/timer-live-activity.png)
#### Value

**Timer**: Count down from a duration, or count up from 00:00 while a job runs.
![Value Live Activity stream example](https://cdn.activitysmith.com/features/value-live-activity.png)

### Start & Update Live Activity
```bash
activitysmith activity stream revenue-today \
--content-state '{
"title": "Revenue",
"type": "value",
"value": "$1,240",
"color": "green",
"icon": { "symbol": "dollarsign.circle", "color": "green" },
"badge": { "title": "↑ 18%", "color": "purple" }
}'
```

Use a stable `stream_key` to identify the metric, job, deployment, or system you want to keep visible. The first `activity stream` command starts the Live Activity. Later commands with the same `stream_key` update it.
Send a string or number in `value`. Strings keep their formatting, including currency symbols, units, and decimal places.

#### Stats

Expand All @@ -207,6 +215,27 @@ activitysmith activity stream sales-hourly \
}'
```

#### Alert

![Alert Live Activity stream example](https://cdn.activitysmith.com/features/alert-live-activity.png)

```bash
activitysmith activity stream customer-ops \
--content-state '{
"title": "Reactivation",
"message": "Lumen came back after 2 weeks",
"type": "alert",
"icon": {
"symbol": "cloud.sun",
"color": "yellow"
},
"badge": {
"title": "Customer",
"color": "magenta"
}
}'
```

#### Metrics

![Metrics Live Activity stream example](https://cdn.activitysmith.com/features/metrics-live-activity-start.png)
Expand Down Expand Up @@ -253,27 +282,6 @@ activitysmith activity stream search-reindex \
}'
```

#### Alert

![Alert Live Activity stream example](https://cdn.activitysmith.com/features/alert-live-activity.png)

```bash
activitysmith activity stream customer-ops \
--content-state '{
"title": "Reactivation",
"message": "Lumen came back after 2 weeks",
"type": "alert",
"icon": {
"symbol": "cloud.sun",
"color": "yellow"
},
"badge": {
"title": "Customer",
"color": "magenta"
}
}'
```

#### Timer

![Timer Live Activity stream example](https://cdn.activitysmith.com/features/timer-live-activity.png)
Expand Down Expand Up @@ -434,7 +442,7 @@ activitysmith activity stream search-reindex \

Use `--secondary-action` when you want a second button beside the primary `--action`.

The secondary action button is supported for `alert`, `progress`, and `segmented_progress` Live Activities. Both buttons use the same `open_url`, `webhook`, and Apple Shortcut payload shapes.
The secondary action button is supported for `value`, `alert`, `segmented_progress`, and `progress` Live Activities. Both buttons use the same `open_url`, `webhook`, and Apple Shortcut payload shapes.

```bash
activitysmith activity stream agent-approval \
Expand Down Expand Up @@ -601,3 +609,7 @@ The CLI exits non-zero on non-2xx responses and prints the API error body. That
### [NPM Package](https://www.npmjs.com/package/activitysmith-cli)

Install the ActivitySmith CLI from npm

### [Source Code](https://github.com/ActivitySmithHQ/activitysmith-cli)

View the CLI source on GitHub
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "activitysmith-cli",
"version": "1.11.0",
"version": "1.12.0",
"description": "Command-line interface for ActivitySmith. Send Push Notifications, start, update, and end Live Activities, and set App Icon Badge Counts from your terminal.",
"keywords": [
"activitysmith",
Expand Down Expand Up @@ -42,7 +42,7 @@
"test": "node --test"
},
"dependencies": {
"activitysmith": "^1.11.0",
"activitysmith": "^1.12.0",
"commander": "^12.1.0"
}
}
28 changes: 20 additions & 8 deletions src/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -214,9 +214,8 @@ const addContentStateOptions = (command, { includeAutoDismiss, includeAutoDismis
parseNumberOption("percentage")
)
.option(
"--value <number>",
"Content state value",
parseNumberOption("value")
"--value <value>",
"Formatted Value readout, or numeric progress value"
)
.option(
"--upper-limit <number>",
Expand Down Expand Up @@ -388,10 +387,11 @@ const validateContentState = (contentState, mode) => {
normalizedType !== "metrics" &&
normalizedType !== "stats" &&
normalizedType !== "alert" &&
normalizedType !== "timer"
normalizedType !== "timer" &&
normalizedType !== "value"
) {
throw new Error(
"contentState.type must be one of: segmented_progress, progress, metrics, stats, alert, timer"
"contentState.type must be one of: segmented_progress, progress, metrics, stats, alert, timer, value"
);
}

Expand All @@ -416,7 +416,7 @@ const validateContentState = (contentState, mode) => {
}
}

if (hasValue !== hasUpperLimit) {
if (normalizedType !== "value" && hasValue !== hasUpperLimit) {
throw new Error(
"contentState.value and contentState.upperLimit must be provided together"
);
Expand Down Expand Up @@ -450,7 +450,7 @@ const validateContentState = (contentState, mode) => {
}

const hasSegmentedFields = hasNumberOfSteps || hasCurrentStep || hasStepColor;
const hasProgressFields = hasPercentage || hasValue || hasUpperLimit;
const hasProgressFields = hasPercentage || (hasValue && normalizedType !== "value") || hasUpperLimit;
const hasAlertFields = hasMessage || hasIcon || hasBadge;

if (hasIcon) {
Expand Down Expand Up @@ -549,6 +549,16 @@ const validateContentState = (contentState, mode) => {
});
}

if (normalizedType === "value") {
if (!hasValue || !(typeof contentState.value === "string" || Number.isFinite(contentState.value))) {
throw new Error("Value requires contentState.value as a string or finite number");
}
if (hasProgressFields || hasSegmentedFields || hasMetrics || hasTimerFields || hasMessage) {
throw new Error("Do not mix Value with other Live Activity type fields");
}
return;
}

const effectiveType = normalizedType;

if (mode === "start" || mode === "stream") {
Expand Down Expand Up @@ -850,7 +860,9 @@ const buildContentStateFromOptions = (options) => {
}

if (options.value !== undefined) {
contentState.value = options.value;
contentState.value = options.type === "value"
? options.value
: parseNumberOption("value")(options.value);
}

if (options.upperLimit !== undefined) {
Expand Down
22 changes: 22 additions & 0 deletions test/cli.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -308,3 +308,25 @@ test("end-stream rejects conflicting Tags flags", async () => {
const result = await runCli(["activity","end-stream","job","--tags","finished","--clear-tags"]);
assert.notEqual(result.code,0); assert.equal(result.request,null);
});

for (const value of ['$1,240', '0007', '', 0, -12.75]) {
test(`Value preserves ${JSON.stringify(value)} through JSON`, async () => {
const result = await runCli(['activity', 'stream', 'revenue', '--content-state', JSON.stringify({title:'Revenue', type:'value', value})]);
assert.equal(result.code, 0, result.stderr);
assert.equal(result.request.body.content_state.value, value);
});
}
for (const value of ['$1,240', '0007', '0']) {
test(`Value flag preserves ${value}`, async () => {
const result = await runCli(['activity', 'stream', 'revenue', '--title', 'Revenue', '--type', 'value', '--value', value]);
assert.equal(result.code, 0, result.stderr);
assert.equal(result.request.body.content_state.value, value);
});
}
for (const value of [undefined, null, true, {}, []]) {
test(`Value rejects ${JSON.stringify(value)}`, async () => {
const result = await runCli(['activity', 'stream', 'revenue', '--content-state', JSON.stringify({title:'Revenue', type:'value', value})]);
assert.notEqual(result.code, 0);
assert.equal(result.request, null);
});
}
Loading