@launchchair/agent 0.4.3 → 0.4.6
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/AGENTS.md +9 -3
- package/README.md +4 -2
- package/launchchair-cli.mjs +94 -7
- package/local-bridge.mjs +1 -1
- package/mcp-bridge-server.mjs +32 -3
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -111,8 +111,8 @@ The build folder can be an empty scratch folder until Stack Setup creates or rec
|
|
|
111
111
|
|
|
112
112
|
```toml
|
|
113
113
|
[mcp_servers.launchchair]
|
|
114
|
-
command = "
|
|
115
|
-
args = ["mcp-server"]
|
|
114
|
+
command = "/absolute/path/to/node"
|
|
115
|
+
args = ["/absolute/path/to/launchchair-cli.mjs", "mcp-server"]
|
|
116
116
|
required = false
|
|
117
117
|
startup_timeout_sec = 20
|
|
118
118
|
tool_timeout_sec = 3600
|
|
@@ -129,7 +129,7 @@ LC_AGENT_CODEX_CWD = "/absolute/path/to/the-folder-codex-should-build-into"
|
|
|
129
129
|
# LAUNCHCHAIR_AGENT_TOKEN = "<full lc_at token from Settings API Access>"
|
|
130
130
|
```
|
|
131
131
|
|
|
132
|
-
|
|
132
|
+
Prefer `launchchair setup codex --cwd /absolute/path/to/build-folder --write-config` or `launchchair mcp-config codex --ui-fields` so the CLI prints exact local command and argument paths for this machine.
|
|
133
133
|
|
|
134
134
|
## What To Do First In A Fresh Chat
|
|
135
135
|
|
|
@@ -140,6 +140,7 @@ If the desktop app cannot find `launchchair`, run `which launchchair` in Termina
|
|
|
140
140
|
5. If the user clicked Run with agent in LaunchChair, call `launchchair_run_once`.
|
|
141
141
|
6. If the user asks to advance the product workflow, call `launchchair_continue_project`.
|
|
142
142
|
7. If LaunchChair asks for a user-choice bucket such as wedge, pricing, or feature direction, call `launchchair_select_choice`. Do not use generic SPEC_UPDATE apply for user-choice buckets.
|
|
143
|
+
8. When the user asks for validation scores or wants to compare ideas, call `launchchair_get_scorecards`. Report validation scorecards separately from generated direction-option scores.
|
|
143
144
|
|
|
144
145
|
## Consent Behavior
|
|
145
146
|
|
|
@@ -152,6 +153,8 @@ Safe LaunchChair MCP/API actions that should not need another user confirmation:
|
|
|
152
153
|
- continuing a project loop with `launchchair_continue_project`
|
|
153
154
|
- selecting a generated wedge, pricing, or feature direction with `launchchair_select_choice`
|
|
154
155
|
- running/rerunning Product Ideation, Market Validation, Positioning, Pricing, and MVP Blueprint buckets
|
|
156
|
+
- running/rerunning Feature Definition and User & Feature Validation for score-based feature comparison
|
|
157
|
+
- reading Product or Feature validation scorecards with `launchchair_get_scorecards`
|
|
155
158
|
- returning phase synthesis links and selected choices
|
|
156
159
|
|
|
157
160
|
Stop and ask only when:
|
|
@@ -178,9 +181,12 @@ Do not start with `ls`, `rg`, dependency inspection, or nearby repo searches whe
|
|
|
178
181
|
- LaunchChair project creation
|
|
179
182
|
- LaunchChair phase loops
|
|
180
183
|
- score-based reruns such as ICP Pain Score, Market Saturation Score, or Wedge Opportunity Score
|
|
184
|
+
- Feature score-based validation using Feature Pain Score, Pattern Saturation Score, and Feature Opportunity Score
|
|
181
185
|
|
|
182
186
|
Those are LaunchChair project-state tasks, not local repo tasks.
|
|
183
187
|
|
|
188
|
+
For Feature projects, the three Feature validation scorecards evaluate the idea. The two generated wedge-direction scores rank the narrow opportunities inside that idea; they are not the Feature validation scores. User & Feature Validation selects the wedge first, then Feature Blueprint generates and selects exactly two concrete feature directions. Every wedge and Feature direction must be grounded in four evidence pillars: ICP complaint themes, delight themes, substitute behaviors, and competitor gaps. Never collapse those decisions or answer a request for Feature validation scores with direction-option scores alone. When comparing multiple Feature ideas, run each through User & Feature Validation, call `launchchair_get_scorecards` for each project, maximize Feature Pain and Feature Opportunity, minimize Pattern Saturation, and prefer ideas that pass every returned recommended threshold.
|
|
189
|
+
|
|
184
190
|
## When Repo Inspection Is Correct
|
|
185
191
|
|
|
186
192
|
Inspect the repo when the current LaunchChair work is:
|
package/README.md
CHANGED
|
@@ -94,6 +94,8 @@ launchchair setup claude --cwd /absolute/path/to/repo-or-empty-folder --write-co
|
|
|
94
94
|
launchchair setup terminal --cwd /absolute/path/to/repo-or-empty-folder
|
|
95
95
|
launchchair projects
|
|
96
96
|
launchchair projects --query LeafLore
|
|
97
|
+
launchchair scorecards --project-id <project-id>
|
|
98
|
+
launchchair scorecards --project-name LeafLore
|
|
97
99
|
launchchair create --name "SignalLift" --one-liner "Distribution workspace for technical founders" --project-type b2b --save-project
|
|
98
100
|
launchchair continue --stop-phase-key market_validation
|
|
99
101
|
launchchair continue --completion-target "rerun Product Ideation until ICP Pain Score is above 85"
|
|
@@ -143,9 +145,9 @@ Print a Claude Desktop MCP config example:
|
|
|
143
145
|
launchchair mcp-config claude
|
|
144
146
|
```
|
|
145
147
|
|
|
146
|
-
The recommended config launches `launchchair mcp-
|
|
148
|
+
The recommended config launches the MCP server through the exact Node executable and CLI file path generated by `launchchair setup` or `launchchair mcp-config`. That process reads the token saved by `launchchair login`, so Codex and Claude users do not need to paste tokens into MCP config after browser login.
|
|
147
149
|
|
|
148
|
-
Use `LAUNCHCHAIR_AGENT_TOKEN` only as a manual fallback when you skip `launchchair login`, need a per-client token override, or your desktop app cannot read the saved CLI config. If a desktop
|
|
150
|
+
Use `LAUNCHCHAIR_AGENT_TOKEN` only as a manual fallback when you skip `launchchair login`, need a per-client token override, or your desktop app cannot read the saved CLI config. If you use a desktop custom MCP form, run `launchchair mcp-config codex --ui-fields` or `launchchair mcp-config claude --ui-fields` and paste the printed command, each argument, env vars, and working directory exactly as shown.
|
|
149
151
|
|
|
150
152
|
## Local Bridge
|
|
151
153
|
|
package/launchchair-cli.mjs
CHANGED
|
@@ -15,7 +15,7 @@ import path from "node:path";
|
|
|
15
15
|
import process from "node:process";
|
|
16
16
|
import { fileURLToPath } from "node:url";
|
|
17
17
|
|
|
18
|
-
const VERSION = "0.4.
|
|
18
|
+
const VERSION = "0.4.6";
|
|
19
19
|
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
20
20
|
const DEFAULT_BASE_URL = "https://www.launchchair.io";
|
|
21
21
|
const CONFIG_DIR = process.env.LAUNCHCHAIR_CONFIG_DIR?.trim()
|
|
@@ -159,6 +159,8 @@ Commands:
|
|
|
159
159
|
logout Remove the saved local token
|
|
160
160
|
whoami Show the current API identity and token source
|
|
161
161
|
projects [--query name] List projects visible to the token
|
|
162
|
+
scorecards [--project-id ID|--project-name NAME]
|
|
163
|
+
Read Product or Feature validation scorecards
|
|
162
164
|
create --name NAME [options] Create a LaunchChair project
|
|
163
165
|
continue [--project-id ID] Ask LaunchChair to continue the project loop once
|
|
164
166
|
run-once Claim and run one queued LaunchChair job through the local bridge
|
|
@@ -493,6 +495,86 @@ async function listProjects(args) {
|
|
|
493
495
|
});
|
|
494
496
|
}
|
|
495
497
|
|
|
498
|
+
function normalizeProjectName(value) {
|
|
499
|
+
return String(value ?? "")
|
|
500
|
+
.trim()
|
|
501
|
+
.toLowerCase()
|
|
502
|
+
.replace(/\s+/g, " ");
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
async function resolveProjectIdForCommand(args, commandName) {
|
|
506
|
+
const configuredProjectId = args["project-id"] || defaultProjectId();
|
|
507
|
+
if (configuredProjectId) return configuredProjectId;
|
|
508
|
+
|
|
509
|
+
const projectName = String(args["project-name"] ?? args.query ?? "").trim();
|
|
510
|
+
if (!projectName) {
|
|
511
|
+
throw new Error(
|
|
512
|
+
`${commandName} requires --project-id, --project-name, LAUNCHCHAIR_PROJECT_ID, or config set project-id.`,
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const params = new URLSearchParams();
|
|
517
|
+
params.set("query", projectName);
|
|
518
|
+
params.set("limit", "10");
|
|
519
|
+
const body = await api(`/api/agent/v1/projects?${params.toString()}`);
|
|
520
|
+
const projects = Array.isArray(body?.projects) ? body.projects : [];
|
|
521
|
+
const normalizedName = normalizeProjectName(projectName);
|
|
522
|
+
const exactMatches = projects.filter((project) => normalizeProjectName(project?.name) === normalizedName);
|
|
523
|
+
const candidates = exactMatches.length > 0 ? exactMatches : projects;
|
|
524
|
+
|
|
525
|
+
if (candidates.length === 1 && candidates[0]?.id) {
|
|
526
|
+
return candidates[0].id;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if (candidates.length > 1) {
|
|
530
|
+
const labels = candidates
|
|
531
|
+
.map((project) => `${project?.name ?? "Untitled project"} (${project?.id ?? "no id"})`)
|
|
532
|
+
.join(", ");
|
|
533
|
+
throw new Error(`Could not resolve a unique project for "${projectName}". Matching projects: ${labels}.`);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
throw new Error(`Could not find a project named "${projectName}" for this token.`);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
async function showScorecards(args) {
|
|
540
|
+
const projectId = await resolveProjectIdForCommand(args, "scorecards");
|
|
541
|
+
const body = await api(`/api/agent/v1/projects/${encodeURIComponent(projectId)}/scorecards`);
|
|
542
|
+
title(`${body?.projectName ?? "Project"} validation scorecards`);
|
|
543
|
+
const scorecards = Array.isArray(body?.scorecards) ? body.scorecards : [];
|
|
544
|
+
if (!scorecards.length) {
|
|
545
|
+
warn("No validation scorecards returned.");
|
|
546
|
+
}
|
|
547
|
+
scorecards.forEach((scorecard) => {
|
|
548
|
+
const score = typeof scorecard?.score === "number" ? `${scorecard.score}/100` : "needs data";
|
|
549
|
+
const threshold = `${scorecard?.optimization === "minimize" ? "≤" : "≥"}${scorecard?.recommendedThreshold ?? "—"}`;
|
|
550
|
+
const tone = scorecard?.passesRecommendedThreshold === true ? "green" : scorecard?.score === null ? "yellow" : "red";
|
|
551
|
+
row(scorecard?.title ?? scorecard?.id ?? "Score", `${score} · target ${threshold}`, tone);
|
|
552
|
+
if (scorecard?.signalLabel) row("Signal", scorecard.signalLabel, "dim");
|
|
553
|
+
if (scorecard?.explanation) row("Why", scorecard.explanation, "dim");
|
|
554
|
+
if (scorecard?.score === null && scorecard?.insufficientDataMessage) {
|
|
555
|
+
row("Need", scorecard.insufficientDataMessage, "yellow");
|
|
556
|
+
}
|
|
557
|
+
if (scorecard?.score === null && Array.isArray(scorecard?.requiredPhases) && scorecard.requiredPhases.length > 0) {
|
|
558
|
+
row("Required phases", scorecard.requiredPhases.join(", "), "dim");
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
const thresholdMessage = body?.allScoresAvailable === false
|
|
562
|
+
? "missing required phase data"
|
|
563
|
+
: body?.passesRecommendedThresholds
|
|
564
|
+
? "all passed"
|
|
565
|
+
: "needs another validation pass";
|
|
566
|
+
row("Thresholds", thresholdMessage, body?.passesRecommendedThresholds ? "green" : "yellow");
|
|
567
|
+
if (body?.guidance?.distinction) row("Important", body.guidance.distinction, "dim");
|
|
568
|
+
const directions = Array.isArray(body?.directionOptions) ? body.directionOptions : [];
|
|
569
|
+
if (directions.length > 0) {
|
|
570
|
+
print("");
|
|
571
|
+
print(paint("Direction options (not validation scorecards)", "bold"));
|
|
572
|
+
directions.forEach((direction) => {
|
|
573
|
+
row(direction?.selected ? "Selected" : "Option", `${direction?.title ?? direction?.id}: ${direction?.score ?? 0}/100`, direction?.selected ? "cyan" : "dim");
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
496
578
|
async function createProject(args) {
|
|
497
579
|
const name = typeof args.name === "string" ? args.name : "";
|
|
498
580
|
if (!name) {
|
|
@@ -776,9 +858,10 @@ function mcpEnvForTarget(target, options = {}) {
|
|
|
776
858
|
}
|
|
777
859
|
|
|
778
860
|
function mcpServerConfig(target, options = {}) {
|
|
861
|
+
const cliPath = path.join(SCRIPT_DIR, "launchchair-cli.mjs");
|
|
779
862
|
return {
|
|
780
|
-
command:
|
|
781
|
-
args: ["mcp-server"],
|
|
863
|
+
command: process.execPath,
|
|
864
|
+
args: [cliPath, "mcp-server"],
|
|
782
865
|
env: mcpEnvForTarget(target, options),
|
|
783
866
|
};
|
|
784
867
|
}
|
|
@@ -800,9 +883,10 @@ function renderMcpConfig(target, options = {}) {
|
|
|
800
883
|
const envLines = Object.entries(server.env)
|
|
801
884
|
.map(([key, value]) => `${key} = ${tomlString(value)}`)
|
|
802
885
|
.join("\n");
|
|
886
|
+
const argsLine = server.args.map(tomlString).join(", ");
|
|
803
887
|
return `[mcp_servers.launchchair]
|
|
804
|
-
command =
|
|
805
|
-
args = [
|
|
888
|
+
command = ${tomlString(server.command)}
|
|
889
|
+
args = [${argsLine}]
|
|
806
890
|
required = false
|
|
807
891
|
startup_timeout_sec = 20
|
|
808
892
|
tool_timeout_sec = 3600
|
|
@@ -831,7 +915,7 @@ Command to launch
|
|
|
831
915
|
${server.command}
|
|
832
916
|
|
|
833
917
|
Arguments
|
|
834
|
-
|
|
918
|
+
${server.args.map((arg) => ` ${arg}`).join("\n")}
|
|
835
919
|
|
|
836
920
|
Environment variables
|
|
837
921
|
${envLines}
|
|
@@ -1002,6 +1086,8 @@ function installLaunchChairSkill(args) {
|
|
|
1002
1086
|
"For a feature in an existing product, use launchchair_create_feature_project. Never substitute a Product project if that tool is unavailable; report the setup or beta-access issue instead.",
|
|
1003
1087
|
"Use planToBuildBoard=true for planning through the Feature build board.",
|
|
1004
1088
|
"Use fullLoop=true only when the user asks to implement/build the feature; continue agent-run QA and stop for production SQL or a genuine external authorization boundary.",
|
|
1089
|
+
"After Feature validation, call launchchair_get_scorecards. Feature Pain, Pattern Saturation, and Feature Opportunity are the validation scorecards; generated direction scores only rank directions inside the idea.",
|
|
1090
|
+
"When comparing multiple Feature ideas, require evidence across ICP complaint themes, delight themes, substitute behaviors, and competitor gaps; then evaluate each with the scorecards, maximize Feature Pain and Feature Opportunity, minimize Pattern Saturation, and prefer ideas that pass every returned recommended threshold.",
|
|
1005
1091
|
]
|
|
1006
1092
|
: []),
|
|
1007
1093
|
"Do not ask for extra confirmation for ordinary LaunchChair MCP calls when the user already requested the workflow and the token permits it.",
|
|
@@ -1135,7 +1221,7 @@ async function setup(args) {
|
|
|
1135
1221
|
print(`1. Paste the MCP config above into ${details.label}.`);
|
|
1136
1222
|
print(`2. Restart or reload ${details.label} so the MCP server is exposed to fresh chats.`);
|
|
1137
1223
|
}
|
|
1138
|
-
print("3.
|
|
1224
|
+
print("3. Use the generated command and argument fields exactly as printed; setup resolves the local Node and CLI paths for this machine.");
|
|
1139
1225
|
print("4. Keep this bridge running when you want LaunchChair's Run with agent button to work:");
|
|
1140
1226
|
print(` ${paint("launchchair bridge", "cyan")}`);
|
|
1141
1227
|
print("5. Start a fresh agent chat. The agent should discover the LaunchChair skill and MCP server instructions automatically.");
|
|
@@ -1165,6 +1251,7 @@ async function main() {
|
|
|
1165
1251
|
if (command === "logout") return logout();
|
|
1166
1252
|
if (command === "whoami") return whoami();
|
|
1167
1253
|
if (command === "projects") return listProjects(args);
|
|
1254
|
+
if (command === "scorecards") return showScorecards(args);
|
|
1168
1255
|
if (command === "create") return createProject(args);
|
|
1169
1256
|
if (command === "continue") return continueProject(args);
|
|
1170
1257
|
if (command === "run-once") return spawnBridge(["--once", "--verbose"]);
|
package/local-bridge.mjs
CHANGED
|
@@ -17,7 +17,7 @@ import process from "node:process";
|
|
|
17
17
|
import { fileURLToPath } from "node:url";
|
|
18
18
|
import { buildRunnerCommand, parseRunnerCommand } from "./runner-command.mjs";
|
|
19
19
|
|
|
20
|
-
const VERSION = "0.4.
|
|
20
|
+
const VERSION = "0.4.6";
|
|
21
21
|
const CLIENT_TYPES = ["codex", "chatgpt", "claude", "claude_code", "other"];
|
|
22
22
|
const AGENT_SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
23
23
|
const CONFIG_DIR = process.env.LAUNCHCHAIR_CONFIG_DIR?.trim()
|
package/mcp-bridge-server.mjs
CHANGED
|
@@ -19,7 +19,7 @@ import process from "node:process";
|
|
|
19
19
|
import { fileURLToPath } from "node:url";
|
|
20
20
|
import { buildRunnerCommand, parseRunnerCommand } from "./runner-command.mjs";
|
|
21
21
|
|
|
22
|
-
const VERSION = "0.4.
|
|
22
|
+
const VERSION = "0.4.6";
|
|
23
23
|
const SERVER_NAME = "launchchair-agent-mcp";
|
|
24
24
|
const AGENT_SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
25
25
|
const CONFIG_DIR = process.env.LAUNCHCHAIR_CONFIG_DIR?.trim()
|
|
@@ -27,7 +27,7 @@ const CONFIG_DIR = process.env.LAUNCHCHAIR_CONFIG_DIR?.trim()
|
|
|
27
27
|
: path.join(os.homedir() || process.cwd(), ".launchchair");
|
|
28
28
|
const CONFIG_PATH = path.join(CONFIG_DIR, "config.json");
|
|
29
29
|
const BASE_SERVER_INSTRUCTIONS =
|
|
30
|
-
"Use LaunchChair MCP tools—not web browsing—for LaunchChair project discovery, creation, planning, and loops. Call the most specific available tool. If a requested workflow is unavailable, explain the access or setup issue instead of silently substituting another project type or workflow.";
|
|
30
|
+
"Use LaunchChair MCP tools—not web browsing—for LaunchChair project discovery, creation, planning, validation scoring, and loops. Call the most specific available tool. When asked for validation scores, call launchchair_get_scorecards and report those scorecards; do not substitute generated direction-option scores. If a requested workflow is unavailable, explain the access or setup issue instead of silently substituting another project type or workflow.";
|
|
31
31
|
|
|
32
32
|
function nodeScriptCommand(scriptName) {
|
|
33
33
|
return `${JSON.stringify(process.execPath)} ${JSON.stringify(path.join(AGENT_SCRIPT_DIR, scriptName))}`;
|
|
@@ -413,6 +413,14 @@ async function listProjects(args = {}) {
|
|
|
413
413
|
return api(`/api/agent/v1/projects${suffix ? `?${suffix}` : ""}`);
|
|
414
414
|
}
|
|
415
415
|
|
|
416
|
+
async function getScorecards(args = {}) {
|
|
417
|
+
const projectId = await resolveProjectId(args);
|
|
418
|
+
if (!projectId) {
|
|
419
|
+
throw new Error("projectId is required. Pass projectId, set LAUNCHCHAIR_PROJECT_ID, or pass projectName.");
|
|
420
|
+
}
|
|
421
|
+
return api(`/api/agent/v1/projects/${encodeURIComponent(projectId)}/scorecards`);
|
|
422
|
+
}
|
|
423
|
+
|
|
416
424
|
function normalizeProjectName(value) {
|
|
417
425
|
return String(value ?? "")
|
|
418
426
|
.trim()
|
|
@@ -749,6 +757,22 @@ const MCP_TOOLS = [
|
|
|
749
757
|
additionalProperties: false,
|
|
750
758
|
},
|
|
751
759
|
},
|
|
760
|
+
{
|
|
761
|
+
name: "launchchair_get_scorecards",
|
|
762
|
+
description:
|
|
763
|
+
"Read the Product or Feature validation scorecards for a project, including recommended thresholds and direction-option scores kept separate. Use this whenever the user asks about validation scores or wants to compare/rerun ideas until the scorecards are strong.",
|
|
764
|
+
inputSchema: {
|
|
765
|
+
type: "object",
|
|
766
|
+
properties: {
|
|
767
|
+
projectId: { type: "string" },
|
|
768
|
+
projectName: {
|
|
769
|
+
type: "string",
|
|
770
|
+
description: "Optional project name to resolve through launchchair_list_projects when projectId is omitted.",
|
|
771
|
+
},
|
|
772
|
+
},
|
|
773
|
+
additionalProperties: false,
|
|
774
|
+
},
|
|
775
|
+
},
|
|
752
776
|
{
|
|
753
777
|
name: "launchchair_heartbeat",
|
|
754
778
|
description: "Send heartbeat and update connected agent metadata.",
|
|
@@ -918,7 +942,7 @@ const MCP_TOOLS = [
|
|
|
918
942
|
},
|
|
919
943
|
];
|
|
920
944
|
|
|
921
|
-
const READ_ONLY_TOOLS = new Set(["launchchair_status", "launchchair_list_projects"]);
|
|
945
|
+
const READ_ONLY_TOOLS = new Set(["launchchair_status", "launchchair_list_projects", "launchchair_get_scorecards"]);
|
|
922
946
|
|
|
923
947
|
function annotateTool(tool) {
|
|
924
948
|
return {
|
|
@@ -1037,6 +1061,11 @@ async function handleRpc(request) {
|
|
|
1037
1061
|
writeResult(id, textResult(payload));
|
|
1038
1062
|
return;
|
|
1039
1063
|
}
|
|
1064
|
+
if (toolName === "launchchair_get_scorecards") {
|
|
1065
|
+
const payload = await getScorecards(args);
|
|
1066
|
+
writeResult(id, textResult(payload));
|
|
1067
|
+
return;
|
|
1068
|
+
}
|
|
1040
1069
|
if (toolName === "launchchair_heartbeat") {
|
|
1041
1070
|
const payload = await heartbeat(args);
|
|
1042
1071
|
writeResult(id, textResult(payload));
|
package/package.json
CHANGED