@foldspace_npm/harness 0.1.9 → 0.1.11
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/CLAUDE.md +235 -0
- package/README.md +23 -4
- package/bin/attach.mjs +48 -17
- package/bin/cli.mjs +29 -1
- package/bin/inject.mjs +65 -49
- package/package.json +3 -2
- package/src/attach-helpers.mjs +32 -0
- package/src/cdp-ownership.mjs +241 -0
- package/src/cli-help.mjs +1 -0
- package/src/cli-registry.mjs +50 -4
- package/src/init.mjs +16 -8
- package/src/upgrade.mjs +469 -0
- package/templates/agent-starter/CLAUDE.md +11 -220
- package/templates/agent-starter/README.md +1 -1
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
import { createCdpRequestManager } from "./cdp-request-manager.mjs";
|
|
6
|
+
|
|
7
|
+
export const SENTINEL_FILE = ".foldspace-sentinel";
|
|
8
|
+
|
|
9
|
+
export function chromeProfileDir(root) {
|
|
10
|
+
return path.join(root, ".foldspace-dev", "chrome-profile");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function sentinelPath(profileDir) {
|
|
14
|
+
return path.join(profileDir, SENTINEL_FILE);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function present(value) {
|
|
18
|
+
if (value == null) return null;
|
|
19
|
+
const text = String(value).trim();
|
|
20
|
+
return text ? text : null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function resolvedPath(filePath) {
|
|
24
|
+
try {
|
|
25
|
+
return fs.realpathSync(filePath);
|
|
26
|
+
} catch {
|
|
27
|
+
return path.resolve(filePath);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function writeLaunchSentinel(profileDir) {
|
|
32
|
+
const token = crypto.randomBytes(16).toString("hex");
|
|
33
|
+
fs.mkdirSync(profileDir, { recursive: true });
|
|
34
|
+
fs.writeFileSync(sentinelPath(profileDir), `${token}\n`, "utf8");
|
|
35
|
+
return token;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function readLaunchSentinel(profileDir) {
|
|
39
|
+
try {
|
|
40
|
+
return present(fs.readFileSync(sentinelPath(profileDir), "utf8"));
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function verifyLaunchSentinel({ profileDir, token } = {}) {
|
|
47
|
+
const onDisk = readLaunchSentinel(profileDir);
|
|
48
|
+
const expected = present(token);
|
|
49
|
+
if (!expected || !onDisk || onDisk !== expected) {
|
|
50
|
+
return { ok: false, reason: "sentinel_mismatch" };
|
|
51
|
+
}
|
|
52
|
+
return { ok: true };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function parseChromeVersionText(text) {
|
|
56
|
+
const body = String(text || "");
|
|
57
|
+
const profilePath = present(body.match(/Profile Path\s+(.+)/i)?.[1]);
|
|
58
|
+
const commandLine = present(
|
|
59
|
+
body.match(/Command Line\s+(.+?)(?=\n[A-Z][A-Za-z ]+\s|\n{2}|$)/s)?.[1],
|
|
60
|
+
);
|
|
61
|
+
const userDataMatch = commandLine?.match(
|
|
62
|
+
/--user-data-dir(?:=|\s+)(?:"([^"]+)"|(\S+))/,
|
|
63
|
+
);
|
|
64
|
+
return {
|
|
65
|
+
profilePath,
|
|
66
|
+
commandLine,
|
|
67
|
+
userDataDir: present(userDataMatch?.[1] || userDataMatch?.[2]),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function reportedProfileMatches(profileDir, reported = {}) {
|
|
72
|
+
const expected = resolvedPath(profileDir);
|
|
73
|
+
if (reported.userDataDir) {
|
|
74
|
+
return resolvedPath(reported.userDataDir) === expected;
|
|
75
|
+
}
|
|
76
|
+
if (reported.profilePath) {
|
|
77
|
+
return resolvedPath(path.dirname(reported.profilePath)) === expected;
|
|
78
|
+
}
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function verifyCdpOwnership({ profileDir, token, reported } = {}) {
|
|
83
|
+
const sentinel = verifyLaunchSentinel({ profileDir, token });
|
|
84
|
+
if (!sentinel.ok) return sentinel;
|
|
85
|
+
if (!reportedProfileMatches(profileDir, reported)) {
|
|
86
|
+
return { ok: false, reason: "profile_mismatch" };
|
|
87
|
+
}
|
|
88
|
+
return { ok: true };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function ownershipErrorMessage(
|
|
92
|
+
reason,
|
|
93
|
+
{ port, profileDir, root } = {},
|
|
94
|
+
) {
|
|
95
|
+
const profile =
|
|
96
|
+
root && profileDir
|
|
97
|
+
? path.relative(root, profileDir) || profileDir
|
|
98
|
+
: profileDir || ".foldspace-dev/chrome-profile";
|
|
99
|
+
const hints = {
|
|
100
|
+
sentinel_mismatch:
|
|
101
|
+
"Launch sentinel is missing or does not match. Run foldspace inject again.",
|
|
102
|
+
profile_mismatch: `The browser on :${port} is using a different user-data-dir than ${profile}.`,
|
|
103
|
+
cdp_inspect_failed: `Could not read the profile path from Chrome on :${port}.`,
|
|
104
|
+
cdp_not_ready: `Chrome did not expose CDP on :${port}.`,
|
|
105
|
+
};
|
|
106
|
+
return (
|
|
107
|
+
`Chrome on :${port} is not the Foldspace profile.\n` +
|
|
108
|
+
` ${hints[reason] || reason}\n` +
|
|
109
|
+
` Close the other debugging Chrome, or pass --port <free-port>.`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export async function fetchCdpVersion(port) {
|
|
114
|
+
const response = await fetch(`http://127.0.0.1:${port}/json/version`);
|
|
115
|
+
if (!response.ok) {
|
|
116
|
+
throw new Error(`CDP /json/version returned ${response.status}`);
|
|
117
|
+
}
|
|
118
|
+
return response.json();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function openCdpSocket(webSocketDebuggerUrl) {
|
|
122
|
+
const cdp = createCdpRequestManager({ timeoutMs: 8_000 });
|
|
123
|
+
const ws = new WebSocket(webSocketDebuggerUrl);
|
|
124
|
+
await new Promise((resolve, reject) => {
|
|
125
|
+
ws.addEventListener("open", resolve);
|
|
126
|
+
ws.addEventListener("error", reject);
|
|
127
|
+
});
|
|
128
|
+
ws.addEventListener("message", (event) => {
|
|
129
|
+
cdp.handleMessage(JSON.parse(event.data));
|
|
130
|
+
});
|
|
131
|
+
return {
|
|
132
|
+
ws,
|
|
133
|
+
call: (method, params, sessionId) => cdp.call(ws, method, params, sessionId),
|
|
134
|
+
close() {
|
|
135
|
+
cdp.failAll();
|
|
136
|
+
ws.close();
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export async function inspectCdpUserDataDir(webSocketDebuggerUrl) {
|
|
142
|
+
const session = await openCdpSocket(webSocketDebuggerUrl);
|
|
143
|
+
try {
|
|
144
|
+
const { targetId } = await session.call("Target.createTarget", {
|
|
145
|
+
url: "chrome://version/",
|
|
146
|
+
});
|
|
147
|
+
const attached = await session.call("Target.attachToTarget", {
|
|
148
|
+
targetId,
|
|
149
|
+
flatten: true,
|
|
150
|
+
});
|
|
151
|
+
const sessionId = attached.sessionId;
|
|
152
|
+
await session.call("Runtime.enable", {}, sessionId);
|
|
153
|
+
await session.call("Page.enable", {}, sessionId);
|
|
154
|
+
|
|
155
|
+
let text = "";
|
|
156
|
+
for (let attempt = 0; attempt < 20; attempt++) {
|
|
157
|
+
const evaluation = await session.call(
|
|
158
|
+
"Runtime.evaluate",
|
|
159
|
+
{
|
|
160
|
+
expression: "document.body ? document.body.innerText : ''",
|
|
161
|
+
returnByValue: true,
|
|
162
|
+
},
|
|
163
|
+
sessionId,
|
|
164
|
+
);
|
|
165
|
+
text = evaluation?.result?.value || "";
|
|
166
|
+
if (/Profile Path|Command Line/i.test(text)) break;
|
|
167
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
await session.call("Target.closeTarget", { targetId });
|
|
172
|
+
} catch {
|
|
173
|
+
// Best-effort: ownership can still be decided from the text we got.
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const reported = parseChromeVersionText(text);
|
|
177
|
+
if (!reported.userDataDir && !reported.profilePath) {
|
|
178
|
+
throw new Error("chrome://version did not expose a profile path");
|
|
179
|
+
}
|
|
180
|
+
return reported;
|
|
181
|
+
} finally {
|
|
182
|
+
session.close();
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export async function assertOwnedCdp({
|
|
187
|
+
profileDir,
|
|
188
|
+
port,
|
|
189
|
+
token,
|
|
190
|
+
fetchVersion = fetchCdpVersion,
|
|
191
|
+
inspectUserDataDir = inspectCdpUserDataDir,
|
|
192
|
+
} = {}) {
|
|
193
|
+
const sentinel = verifyLaunchSentinel({ profileDir, token });
|
|
194
|
+
if (!sentinel.ok) return sentinel;
|
|
195
|
+
|
|
196
|
+
let version;
|
|
197
|
+
try {
|
|
198
|
+
version = await fetchVersion(port);
|
|
199
|
+
} catch {
|
|
200
|
+
return { ok: false, reason: "cdp_not_ready", version: null };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
let reported;
|
|
204
|
+
try {
|
|
205
|
+
reported = await inspectUserDataDir(version.webSocketDebuggerUrl);
|
|
206
|
+
} catch {
|
|
207
|
+
return { ok: false, reason: "cdp_inspect_failed", version };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const result = verifyCdpOwnership({ profileDir, token, reported });
|
|
211
|
+
return { ...result, version, reported };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export async function waitForOwnedCdp({
|
|
215
|
+
profileDir,
|
|
216
|
+
port,
|
|
217
|
+
token,
|
|
218
|
+
timeoutMs = 20_000,
|
|
219
|
+
fetchVersion = fetchCdpVersion,
|
|
220
|
+
inspectUserDataDir = inspectCdpUserDataDir,
|
|
221
|
+
now = Date.now,
|
|
222
|
+
sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
223
|
+
} = {}) {
|
|
224
|
+
const started = now();
|
|
225
|
+
let last = { ok: false, reason: "cdp_not_ready", version: null };
|
|
226
|
+
while (now() - started < timeoutMs) {
|
|
227
|
+
last = await assertOwnedCdp({
|
|
228
|
+
profileDir,
|
|
229
|
+
port,
|
|
230
|
+
token,
|
|
231
|
+
fetchVersion,
|
|
232
|
+
inspectUserDataDir,
|
|
233
|
+
});
|
|
234
|
+
if (last.ok) return last;
|
|
235
|
+
if (last.reason !== "cdp_not_ready" && last.reason !== "cdp_inspect_failed") {
|
|
236
|
+
return last;
|
|
237
|
+
}
|
|
238
|
+
await sleep(250);
|
|
239
|
+
}
|
|
240
|
+
return last;
|
|
241
|
+
}
|
package/src/cli-help.mjs
CHANGED
|
@@ -46,6 +46,7 @@ export function renderGeneralHelp(registry) {
|
|
|
46
46
|
" foldspace help --json Print the machine-readable CLI contract",
|
|
47
47
|
" foldspace help <command> --json Print one command contract",
|
|
48
48
|
" foldspace --version Show package and protocol versions",
|
|
49
|
+
" foldspace upgrade --check Compare the installed pin to npm latest",
|
|
49
50
|
"",
|
|
50
51
|
"attach loads local actions and observes the normal agent experience.",
|
|
51
52
|
"deploy is a separate remote publication step.",
|
package/src/cli-registry.mjs
CHANGED
|
@@ -21,7 +21,7 @@ export const CLI_COMMANDS = Object.freeze([
|
|
|
21
21
|
group: "start",
|
|
22
22
|
summary: "Create a configured Foldspace actions project",
|
|
23
23
|
usage:
|
|
24
|
-
"foldspace init [<directory>] [--product-id <id>] [--agent-key <key>] [--domain <host>] [--name <display-name>]",
|
|
24
|
+
"foldspace init [<directory>] [--product-id <id>] [--agent-key <key>] [--app-domain <host>] [--name <display-name>]",
|
|
25
25
|
risk: "local-write",
|
|
26
26
|
environment: "node",
|
|
27
27
|
environmentVariables: [],
|
|
@@ -41,8 +41,9 @@ export const CLI_COMMANDS = Object.freeze([
|
|
|
41
41
|
required: "non-interactive",
|
|
42
42
|
deprecatedAliases: ["--agent-api-name"],
|
|
43
43
|
}),
|
|
44
|
-
value("--domain", "host", "
|
|
44
|
+
value("--app-domain", "host", "Live app hostname or HTTP(S) URL", {
|
|
45
45
|
required: "non-interactive",
|
|
46
|
+
deprecatedAliases: ["--domain"],
|
|
46
47
|
}),
|
|
47
48
|
value("--name", "display-name", "Display name", {
|
|
48
49
|
default: "directory name",
|
|
@@ -51,7 +52,7 @@ export const CLI_COMMANDS = Object.freeze([
|
|
|
51
52
|
prerequisites: [
|
|
52
53
|
"Node 20 or newer",
|
|
53
54
|
"A target directory that does not already exist",
|
|
54
|
-
"Non-interactive use requires directory, product ID, Agent Key, and domain",
|
|
55
|
+
"Non-interactive use requires directory, product ID, Agent Key, and app domain",
|
|
55
56
|
],
|
|
56
57
|
effects: ["Creates a new local project; never initializes Git"],
|
|
57
58
|
next: [
|
|
@@ -61,6 +62,48 @@ export const CLI_COMMANDS = Object.freeze([
|
|
|
61
62
|
"Run foldspace attach",
|
|
62
63
|
],
|
|
63
64
|
}),
|
|
65
|
+
Object.freeze({
|
|
66
|
+
name: "upgrade",
|
|
67
|
+
entry: null,
|
|
68
|
+
group: "start",
|
|
69
|
+
summary: "Check for a newer harness and update the exact pin after asking",
|
|
70
|
+
usage:
|
|
71
|
+
"foldspace upgrade [--check | --yes] [--refresh-instructions]",
|
|
72
|
+
risk: "local-write",
|
|
73
|
+
environment: "node",
|
|
74
|
+
environmentVariables: [
|
|
75
|
+
"FOLDSPACE_PROJECT_DIR",
|
|
76
|
+
"FOLDSPACE_SKIP_UPDATE_CHECK",
|
|
77
|
+
],
|
|
78
|
+
capabilities: ["project.upgrade"],
|
|
79
|
+
positionals: [],
|
|
80
|
+
options: [
|
|
81
|
+
flag("--check", "Compare versions and print JSON; never install"),
|
|
82
|
+
flag(
|
|
83
|
+
"--yes",
|
|
84
|
+
"Install the latest exact pin after the user has already agreed",
|
|
85
|
+
),
|
|
86
|
+
flag(
|
|
87
|
+
"--refresh-instructions",
|
|
88
|
+
"Replace CLAUDE.md with an import of the package instructions",
|
|
89
|
+
),
|
|
90
|
+
],
|
|
91
|
+
prerequisites: [
|
|
92
|
+
"A consumer project with package.json",
|
|
93
|
+
"An exact @foldspace_npm/harness dependency unless only refreshing instructions",
|
|
94
|
+
],
|
|
95
|
+
effects: [
|
|
96
|
+
"Without --yes, writes nothing except an optional update cache",
|
|
97
|
+
"With --yes, rewrites the exact harness pin and runs npm install --ignore-scripts",
|
|
98
|
+
"Does not rebuild dist/index.js or deploy",
|
|
99
|
+
"--refresh-instructions copies CLAUDE.md to CLAUDE.md.bak then writes the stub",
|
|
100
|
+
],
|
|
101
|
+
next: [
|
|
102
|
+
"If outdated, ask the user, then run foldspace upgrade --yes",
|
|
103
|
+
"Run npm run build after a pin bump",
|
|
104
|
+
"Deploy only if product users should receive the rebuilt runtime",
|
|
105
|
+
],
|
|
106
|
+
}),
|
|
64
107
|
Object.freeze({
|
|
65
108
|
name: "build",
|
|
66
109
|
entry: "build-cli.mjs",
|
|
@@ -194,7 +237,7 @@ export const CLI_COMMANDS = Object.freeze([
|
|
|
194
237
|
value(
|
|
195
238
|
"--port",
|
|
196
239
|
"number",
|
|
197
|
-
"CDP port: CLI, CDP_PORT,
|
|
240
|
+
"CDP port: CLI, CDP_PORT, or the port inject recorded",
|
|
198
241
|
),
|
|
199
242
|
value("--agent", "api-name", "Override the configured agent API name"),
|
|
200
243
|
flag(
|
|
@@ -217,6 +260,7 @@ export const CLI_COMMANDS = Object.freeze([
|
|
|
217
260
|
],
|
|
218
261
|
effects: [
|
|
219
262
|
"May reload and instrument matching target pages",
|
|
263
|
+
"Refuses a Chrome that is not the profile inject launched",
|
|
220
264
|
"Test mode is enabled unless --no-test-mode is passed",
|
|
221
265
|
"Never directly invokes an action handler",
|
|
222
266
|
"An empty local action registry is valid; named actions are not required",
|
|
@@ -271,6 +315,7 @@ export const CLI_COMMANDS = Object.freeze([
|
|
|
271
315
|
|
|
272
316
|
export const CAPABILITY_CATALOGUE = Object.freeze([
|
|
273
317
|
["project.scaffold", "Create a new configured project on local disk"],
|
|
318
|
+
["project.upgrade", "Update the consuming project's exact harness pin"],
|
|
274
319
|
["artifact.build", "Build the portable dist/index.js action artifact"],
|
|
275
320
|
["browser.launch", "Launch an isolated local Chrome profile"],
|
|
276
321
|
["browser.cdp", "Connect to local Chrome through CDP"],
|
|
@@ -341,6 +386,7 @@ export function createCliRegistry({ packageName, packageVersion }) {
|
|
|
341
386
|
"The harness bin alias is equivalent to foldspace.",
|
|
342
387
|
"attach diagnostics are attach-internal; interpret them from the lifecycle log, not as CLI commands.",
|
|
343
388
|
"Coding agents should run attach --daemon; foreground attach is for humans watching the terminal.",
|
|
389
|
+
"If update.outdated is true, ask the user before foldspace upgrade --yes.",
|
|
344
390
|
],
|
|
345
391
|
};
|
|
346
392
|
}
|
package/src/init.mjs
CHANGED
|
@@ -13,6 +13,7 @@ const allowedFlags = new Set([
|
|
|
13
13
|
"product-id",
|
|
14
14
|
"agent-key",
|
|
15
15
|
"agent-api-name",
|
|
16
|
+
"app-domain",
|
|
16
17
|
"domain",
|
|
17
18
|
]);
|
|
18
19
|
const tokenPattern = /\{\{([A-Z0-9_]+)\}\}/g;
|
|
@@ -86,13 +87,18 @@ export function parseInitArgs(argv) {
|
|
|
86
87
|
"Use --agent-key only; --agent-api-name is its deprecated alias.",
|
|
87
88
|
);
|
|
88
89
|
}
|
|
90
|
+
if (flags["app-domain"] && flags.domain) {
|
|
91
|
+
throw new Error(
|
|
92
|
+
"Use --app-domain only; --domain is its deprecated alias.",
|
|
93
|
+
);
|
|
94
|
+
}
|
|
89
95
|
|
|
90
96
|
return {
|
|
91
97
|
directory: positional[0] || null,
|
|
92
98
|
displayName: flags.name || null,
|
|
93
99
|
productId: flags["product-id"] || null,
|
|
94
100
|
agentApiName: flags["agent-key"] || flags["agent-api-name"] || null,
|
|
95
|
-
domain: flags.domain || null,
|
|
101
|
+
domain: flags["app-domain"] || flags.domain || null,
|
|
96
102
|
};
|
|
97
103
|
}
|
|
98
104
|
|
|
@@ -101,7 +107,7 @@ function missingInitFields(parsed) {
|
|
|
101
107
|
if (!parsed.directory) missing.push("directory");
|
|
102
108
|
if (!parsed.productId) missing.push("product-id");
|
|
103
109
|
if (!parsed.agentApiName) missing.push("agent-key");
|
|
104
|
-
if (!parsed.domain) missing.push("domain");
|
|
110
|
+
if (!parsed.domain) missing.push("app-domain");
|
|
105
111
|
return missing;
|
|
106
112
|
}
|
|
107
113
|
|
|
@@ -134,14 +140,14 @@ function validateProductId(value) {
|
|
|
134
140
|
function normalizeTarget(value) {
|
|
135
141
|
const raw = value.trim();
|
|
136
142
|
if (!raw || raw.includes("*")) {
|
|
137
|
-
throw new Error("
|
|
143
|
+
throw new Error("App domain must be a concrete hostname without a wildcard.");
|
|
138
144
|
}
|
|
139
145
|
|
|
140
146
|
let url;
|
|
141
147
|
try {
|
|
142
148
|
url = new URL(raw.includes("://") ? raw : `https://${raw}`);
|
|
143
149
|
} catch {
|
|
144
|
-
throw new Error(`Invalid domain: ${value}`);
|
|
150
|
+
throw new Error(`Invalid app domain: ${value}`);
|
|
145
151
|
}
|
|
146
152
|
|
|
147
153
|
if (
|
|
@@ -153,12 +159,12 @@ function normalizeTarget(value) {
|
|
|
153
159
|
url.search ||
|
|
154
160
|
url.hash
|
|
155
161
|
) {
|
|
156
|
-
throw new Error("
|
|
162
|
+
throw new Error("App domain must contain only an HTTP(S) hostname, without credentials, a port, path, query, or fragment.");
|
|
157
163
|
}
|
|
158
164
|
|
|
159
165
|
const domain = url.hostname.toLowerCase().replace(/\.$/, "");
|
|
160
166
|
if (!domain || domain.includes("..")) {
|
|
161
|
-
throw new Error(`Invalid domain: ${value}`);
|
|
167
|
+
throw new Error(`Invalid app domain: ${value}`);
|
|
162
168
|
}
|
|
163
169
|
|
|
164
170
|
return {
|
|
@@ -227,7 +233,7 @@ async function promptForMissingFields(parsed, options = {}) {
|
|
|
227
233
|
|
|
228
234
|
if (!next.domain) {
|
|
229
235
|
next.domain = await askUntilValid(
|
|
230
|
-
"
|
|
236
|
+
"App domain",
|
|
231
237
|
(value) => {
|
|
232
238
|
normalizeTarget(value);
|
|
233
239
|
return value.trim();
|
|
@@ -255,7 +261,7 @@ function finalizeInitConfig(parsed) {
|
|
|
255
261
|
for (const [key, label] of [
|
|
256
262
|
["productId", "product-id"],
|
|
257
263
|
["agentApiName", "agent-key"],
|
|
258
|
-
["domain", "domain"],
|
|
264
|
+
["domain", "app-domain"],
|
|
259
265
|
]) {
|
|
260
266
|
if (!parsed[key]) {
|
|
261
267
|
throw new Error(`Missing required option: --${label}\nUsage: ${initUsage}`);
|
|
@@ -304,7 +310,9 @@ function createTemplateValues(config, harnessVersion) {
|
|
|
304
310
|
DISPLAY_NAME: displayName,
|
|
305
311
|
PACKAGE_NAME: packageName,
|
|
306
312
|
PACKAGE_DESCRIPTION_JSON: JSON.stringify(`Foldspace browser actions for ${displayName}.`),
|
|
313
|
+
PRODUCT_ID: productId,
|
|
307
314
|
PRODUCT_ID_JSON: JSON.stringify(productId),
|
|
315
|
+
AGENT_API_NAME: agentApiName,
|
|
308
316
|
AGENT_API_NAME_JSON: JSON.stringify(agentApiName),
|
|
309
317
|
APP_DOMAIN: target.domain,
|
|
310
318
|
APP_DOMAIN_JSON: JSON.stringify(target.domain),
|