@jskit-ai/assistant 0.1.41 → 0.1.43

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.
@@ -1,7 +1,7 @@
1
1
  export default Object.freeze({
2
2
  packageVersion: 1,
3
3
  packageId: "@jskit-ai/assistant",
4
- version: "0.1.41",
4
+ version: "0.1.43",
5
5
  kind: "generator",
6
6
  description: "Install assistant runtime/config for one surface and scaffold assistant pages at explicit target files.",
7
7
  options: {
@@ -19,8 +19,7 @@ export default Object.freeze({
19
19
  inputType: "text",
20
20
  validationType: "enabled-surface-id",
21
21
  defaultValue: "",
22
- promptLabel: "Settings surface",
23
- promptHint: "Enabled settings host surface id used by assistant setup.",
22
+ promptLabel: "Which enabled surface should host the assistant settings UI?",
24
23
  helpHint: "Surface that hosts the settings UI for the selected assistant runtime."
25
24
  },
26
25
  "config-scope": {
@@ -50,7 +49,7 @@ export default Object.freeze({
50
49
  inputType: "text",
51
50
  defaultValue: "",
52
51
  promptLabel: "Link placement",
53
- promptHint: "Optional host:position target for the generated page link placement."
52
+ promptHint: "Optional target for the generated page link placement (format: host:position)."
54
53
  },
55
54
  "link-component-token": {
56
55
  required: false,
@@ -69,7 +68,7 @@ export default Object.freeze({
69
68
  "ai-config-prefix": {
70
69
  required: false,
71
70
  inputType: "text",
72
- defaultValue: "",
71
+ defaultFromOptionTemplate: "${option:surface|snake|upper}_ASSISTANT",
73
72
  promptLabel: "AI config prefix",
74
73
  promptHint: "Optional env/config prefix override. Defaults to <SURFACE>_ASSISTANT."
75
74
  },
@@ -87,8 +86,7 @@ export default Object.freeze({
87
86
  promptHint: "Leave empty to keep the assistant disabled until you add a key."
88
87
  },
89
88
  "ai-base-url": {
90
- required: true,
91
- allowEmpty: true,
89
+ required: false,
92
90
  defaultValue: "",
93
91
  promptLabel: "AI base URL",
94
92
  promptHint: "Optional provider-compatible base URL override."
@@ -160,6 +158,7 @@ export default Object.freeze({
160
158
  ]
161
159
  },
162
160
  page: {
161
+ requiresShellWeb: true,
163
162
  entrypoint: "src/server/subcommands/page.js",
164
163
  export: "runGeneratorSubcommand",
165
164
  description: "Create an assistant runtime page at an explicit target file relative to src/pages/.",
@@ -196,6 +195,7 @@ export default Object.freeze({
196
195
  ]
197
196
  },
198
197
  "settings-page": {
198
+ requiresShellWeb: true,
199
199
  entrypoint: "src/server/subcommands/settingsPage.js",
200
200
  export: "runGeneratorSubcommand",
201
201
  description: "Create an assistant settings page at an explicit target file relative to src/pages/.",
@@ -289,19 +289,40 @@ export default Object.freeze({
289
289
  }
290
290
  },
291
291
  {
292
- op: "append-text",
293
292
  file: ".env",
294
- position: "bottom",
295
- skipIfContains: "__ASSISTANT_AI_CONFIG_PREFIX___AI_PROVIDER=",
296
- value:
297
- "\n__ASSISTANT_AI_CONFIG_PREFIX___AI_PROVIDER=${option:ai-provider}\n__ASSISTANT_AI_CONFIG_PREFIX___AI_API_KEY=${option:ai-api-key}\n__ASSISTANT_AI_CONFIG_PREFIX___AI_BASE_URL=${option:ai-base-url}\n__ASSISTANT_AI_CONFIG_PREFIX___AI_TIMEOUT_MS=${option:ai-timeout-ms}\n",
298
- reason: "Append assistant AI env defaults for the generated surface prefix.",
293
+ op: "upsert-env",
294
+ key: "${option:ai-config-prefix}_AI_PROVIDER",
295
+ value: "${option:ai-provider}",
296
+ reason: "Configure the assistant AI provider for the selected surface.",
299
297
  category: "runtime-config",
300
- id: "assistant-ai-prefixed-env",
301
- templateContext: {
302
- entrypoint: "src/server/buildTemplateContext.js",
303
- export: "buildTemplateContext"
304
- }
298
+ id: "assistant-ai-provider"
299
+ },
300
+ {
301
+ file: ".env",
302
+ op: "upsert-env",
303
+ key: "${option:ai-config-prefix}_AI_API_KEY",
304
+ value: "${option:ai-api-key}",
305
+ reason: "Configure the assistant AI API key for the selected surface.",
306
+ category: "runtime-config",
307
+ id: "assistant-ai-api-key"
308
+ },
309
+ {
310
+ file: ".env",
311
+ op: "upsert-env",
312
+ key: "${option:ai-config-prefix}_AI_BASE_URL",
313
+ value: "${option:ai-base-url}",
314
+ reason: "Configure the optional assistant AI base URL override for the selected surface.",
315
+ category: "runtime-config",
316
+ id: "assistant-ai-base-url"
317
+ },
318
+ {
319
+ file: ".env",
320
+ op: "upsert-env",
321
+ key: "${option:ai-config-prefix}_AI_TIMEOUT_MS",
322
+ value: "${option:ai-timeout-ms}",
323
+ reason: "Configure the assistant AI timeout for the selected surface.",
324
+ category: "runtime-config",
325
+ id: "assistant-ai-timeout-ms"
305
326
  }
306
327
  ]
307
328
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/assistant",
3
- "version": "0.1.41",
3
+ "version": "0.1.43",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "test": "node --test"
@@ -9,6 +9,6 @@
9
9
  "./server/buildTemplateContext": "./src/server/buildTemplateContext.js"
10
10
  },
11
11
  "dependencies": {
12
- "@jskit-ai/kernel": "0.1.32"
12
+ "@jskit-ai/kernel": "0.1.34"
13
13
  }
14
14
  }
@@ -56,11 +56,11 @@ async function resolveAssistantPageGenerationContext({
56
56
  return Object.freeze({
57
57
  pageTarget,
58
58
  pageLabel: normalizeText(options?.name) || pageTarget.defaultName,
59
- linkPlacementHost: String(linkTarget.placementTarget?.host || ""),
60
- linkPlacementPosition: String(linkTarget.placementTarget?.position || ""),
59
+ linkPlacementTarget: String(linkTarget.placementTarget?.id || ""),
61
60
  linkComponentToken: String(linkTarget.componentToken || ""),
62
61
  linkWorkspaceSuffix: pageTarget.routeUrlSuffix,
63
62
  linkNonWorkspaceSuffix: pageTarget.routeUrlSuffix,
63
+ linkWhenLine: String(linkTarget.whenLine || ""),
64
64
  linkToPropLine: resolveLinkToPropLine(linkTarget.linkTo)
65
65
  });
66
66
  }
@@ -75,8 +75,7 @@ function renderAssistantPageLinkPlacementBlock({
75
75
  "{\n" +
76
76
  " addPlacement({\n" +
77
77
  ` id: "${String(pageTarget?.placementId || "")}",\n` +
78
- ` host: "${String(generationContext?.linkPlacementHost || "")}",\n` +
79
- ` position: "${String(generationContext?.linkPlacementPosition || "")}",\n` +
78
+ ` target: "${String(generationContext?.linkPlacementTarget || "")}",\n` +
80
79
  ` surfaces: ["${String(pageTarget?.surfaceId || "")}"],\n` +
81
80
  " order: 155,\n" +
82
81
  ` componentToken: "${String(generationContext?.linkComponentToken || "")}",\n` +
@@ -86,7 +85,7 @@ function renderAssistantPageLinkPlacementBlock({
86
85
  ` workspaceSuffix: "${String(generationContext?.linkWorkspaceSuffix || "")}",\n` +
87
86
  ` nonWorkspaceSuffix: "${String(generationContext?.linkNonWorkspaceSuffix || "")}",\n` +
88
87
  `${String(generationContext?.linkToPropLine || "")} },\n` +
89
- " when: ({ auth }) => Boolean(auth?.authenticated)\n" +
88
+ `${String(generationContext?.linkWhenLine || "")}` +
90
89
  " });\n" +
91
90
  "}\n"
92
91
  );
@@ -94,7 +93,7 @@ function renderAssistantPageLinkPlacementBlock({
94
93
 
95
94
  function renderAssistantPageSummary(
96
95
  pageTarget = {},
97
- { pageAlreadyExisted = false, pageOverwritten = false, placementChanged = false } = {}
96
+ { pageAlreadyExisted = false, pageOverwritten = false } = {}
98
97
  ) {
99
98
  if (!pageAlreadyExisted) {
100
99
  return `Generated assistant page "${String(pageTarget?.routeUrlSuffix || "")}".`;
@@ -102,10 +101,7 @@ function renderAssistantPageSummary(
102
101
  if (pageOverwritten) {
103
102
  return `Regenerated assistant page "${String(pageTarget?.routeUrlSuffix || "")}".`;
104
103
  }
105
- if (placementChanged) {
106
- return `Updated assistant page link placement for "${String(pageTarget?.routeUrlSuffix || "")}".`;
107
- }
108
- return `Assistant page "${String(pageTarget?.routeUrlSuffix || "")}" is already up to date.`;
104
+ return `Generated assistant page "${String(pageTarget?.routeUrlSuffix || "")}".`;
109
105
  }
110
106
 
111
107
  export {
@@ -96,8 +96,7 @@ async function runGeneratorSubcommand({
96
96
  touchedFiles: [...touchedFiles].sort((left, right) => left.localeCompare(right)),
97
97
  summary: renderAssistantPageSummary(pageTarget, {
98
98
  pageAlreadyExisted,
99
- pageOverwritten: pageAlreadyExisted && forceOverwrite,
100
- placementChanged: placementApplied.changed
99
+ pageOverwritten: pageAlreadyExisted && forceOverwrite
101
100
  })
102
101
  };
103
102
  }
@@ -100,8 +100,7 @@ async function runGeneratorSubcommand({
100
100
  touchedFiles: [...touchedFiles].sort((left, right) => left.localeCompare(right)),
101
101
  summary: renderAssistantPageSummary(pageTarget, {
102
102
  pageAlreadyExisted,
103
- pageOverwritten: pageAlreadyExisted && forceOverwrite,
104
- placementChanged: placementApplied.changed
103
+ pageOverwritten: pageAlreadyExisted && forceOverwrite
105
104
  })
106
105
  };
107
106
  }
@@ -15,8 +15,13 @@ test("assistant descriptor exposes setup as the primary command and still depend
15
15
  assert.equal(descriptor.options?.["settings-surface"]?.required, true);
16
16
  assert.equal(descriptor.options?.["settings-surface"]?.validationType, "enabled-surface-id");
17
17
  assert.equal(descriptor.options?.["config-scope"]?.defaultValue, "global");
18
+ assert.equal(
19
+ descriptor.options?.["ai-config-prefix"]?.defaultFromOptionTemplate,
20
+ "${option:surface|snake|upper}_ASSISTANT"
21
+ );
18
22
  assert.equal(descriptor.options?.name?.required, false);
19
23
  assert.equal(descriptor.options?.["link-placement"]?.required, false);
24
+ assert.equal(descriptor.options?.["ai-base-url"]?.required, false);
20
25
  assert.deepEqual(descriptor.dependsOn, ["@jskit-ai/assistant-runtime"]);
21
26
  });
22
27
 
@@ -34,16 +39,30 @@ test("assistant descriptor defines explicit page subcommands and setup-only muta
34
39
  assert.equal(fileMutations.length, 0);
35
40
  });
36
41
 
37
- test("assistant descriptor appends only setup config and env entries", () => {
42
+ test("assistant descriptor appends setup config and upserts assistant env entries", () => {
38
43
  const publicConfig = findTextMutation("assistant-public-surface-config");
39
44
  const serverConfig = findTextMutation("assistant-server-surface-config");
40
- const envBlock = findTextMutation("assistant-ai-prefixed-env");
45
+ const envProvider = findTextMutation("assistant-ai-provider");
46
+ const envApiKey = findTextMutation("assistant-ai-api-key");
47
+ const envBaseUrl = findTextMutation("assistant-ai-base-url");
48
+ const envTimeout = findTextMutation("assistant-ai-timeout-ms");
41
49
  const textMutations = Array.isArray(descriptor?.mutations?.text) ? descriptor.mutations.text : [];
42
50
 
43
- assert.equal(textMutations.length, 3);
51
+ assert.equal(textMutations.length, 6);
44
52
  assert.match(String(publicConfig?.value || ""), /config\.assistantSurfaces\.\$\{option:surface\|lower\} = \{/);
45
53
  assert.match(String(publicConfig?.value || ""), /settingsSurfaceId: "__ASSISTANT_SETTINGS_SURFACE_ID__"/);
46
54
  assert.match(String(serverConfig?.value || ""), /config\.assistantServer\.\$\{option:surface\|lower\} = \{/);
47
55
  assert.match(String(serverConfig?.value || ""), /aiConfigPrefix: "__ASSISTANT_AI_CONFIG_PREFIX__"/);
48
- assert.match(String(envBlock?.value || ""), /__ASSISTANT_AI_CONFIG_PREFIX___AI_PROVIDER=\$\{option:ai-provider\}/);
56
+ assert.equal(envProvider?.op, "upsert-env");
57
+ assert.equal(envProvider?.key, "${option:ai-config-prefix}_AI_PROVIDER");
58
+ assert.equal(envProvider?.value, "${option:ai-provider}");
59
+ assert.equal(envApiKey?.op, "upsert-env");
60
+ assert.equal(envApiKey?.key, "${option:ai-config-prefix}_AI_API_KEY");
61
+ assert.equal(envApiKey?.value, "${option:ai-api-key}");
62
+ assert.equal(envBaseUrl?.op, "upsert-env");
63
+ assert.equal(envBaseUrl?.key, "${option:ai-config-prefix}_AI_BASE_URL");
64
+ assert.equal(envBaseUrl?.value, "${option:ai-base-url}");
65
+ assert.equal(envTimeout?.op, "upsert-env");
66
+ assert.equal(envTimeout?.key, "${option:ai-config-prefix}_AI_TIMEOUT_MS");
67
+ assert.equal(envTimeout?.value, "${option:ai-timeout-ms}");
49
68
  });
@@ -25,11 +25,12 @@ function toPagePath(targetFile = "") {
25
25
  return path.join("src/pages", targetFile);
26
26
  }
27
27
 
28
- async function writeAppFixture(appRoot) {
28
+ async function writeAppFixture(appRoot, { configSource = "" } = {}) {
29
29
  await writeFileInApp(
30
30
  appRoot,
31
31
  "config/public.js",
32
- `export const config = {
32
+ configSource ||
33
+ `export const config = {
33
34
  surfaceDefinitions: {
34
35
  admin: {
35
36
  id: "admin",
@@ -55,8 +56,8 @@ async function writeAppFixture(appRoot) {
55
56
  "src/components/ShellLayout.vue",
56
57
  `<template>
57
58
  <div>
58
- <ShellOutlet host="shell-layout" position="primary-menu" default />
59
- <ShellOutlet host="shell-layout" position="top-right" />
59
+ <ShellOutlet target="shell-layout:primary-menu" default />
60
+ <ShellOutlet target="shell-layout:top-right" />
60
61
  </div>
61
62
  </template>
62
63
  `
@@ -87,6 +88,7 @@ test("assistant page subcommand creates a runtime page at an explicit target fil
87
88
  });
88
89
 
89
90
  assert.deepEqual(result.touchedFiles, [toPagePath(targetFile), "src/placement.js"]);
91
+ assert.equal(result.summary, 'Generated assistant page "/ops/copilot".');
90
92
 
91
93
  const pageSource = await readFile(path.join(appRoot, toPagePath(targetFile)), "utf8");
92
94
  assert.match(pageSource, /<AssistantSurfaceClientElement surface-id="admin" \/>/);
@@ -94,8 +96,7 @@ test("assistant page subcommand creates a runtime page at an explicit target fil
94
96
  const placementSource = await readFile(path.join(appRoot, "src/placement.js"), "utf8");
95
97
  assert.match(placementSource, /jskit:assistant\.page\.link:admin:\/ops\/copilot/);
96
98
  assert.match(placementSource, /id: "ui-generator\.page\.admin\.ops\.copilot\.link"/);
97
- assert.match(placementSource, /host: "shell-layout"/);
98
- assert.match(placementSource, /position: "primary-menu"/);
99
+ assert.match(placementSource, /target: "shell-layout:primary-menu"/);
99
100
  assert.match(placementSource, /label: "Copilot"/);
100
101
  assert.match(placementSource, /workspaceSuffix: "\/ops\/copilot"/);
101
102
  });
@@ -110,7 +111,7 @@ test("assistant settings-page subcommand uses the target assistant surface and i
110
111
  `<template>
111
112
  <SectionContainerShell>
112
113
  <template #tabs>
113
- <ShellOutlet host="admin-settings" position="sub-pages" />
114
+ <ShellOutlet target="admin-settings:sub-pages" />
114
115
  </template>
115
116
  <RouterView />
116
117
  </SectionContainerShell>
@@ -129,20 +130,55 @@ test("assistant settings-page subcommand uses the target assistant surface and i
129
130
  });
130
131
 
131
132
  assert.deepEqual(result.touchedFiles, [toPagePath(targetFile), "src/placement.js"]);
133
+ assert.equal(result.summary, 'Generated assistant page "/settings/assistant".');
132
134
 
133
135
  const pageSource = await readFile(path.join(appRoot, toPagePath(targetFile)), "utf8");
134
136
  assert.match(pageSource, /<AssistantSettingsClientElement target-surface-id="console" \/>/);
135
137
 
136
138
  const placementSource = await readFile(path.join(appRoot, "src/placement.js"), "utf8");
137
139
  assert.match(placementSource, /jskit:assistant\.settings-page\.link:admin:\/settings\/assistant:console/);
138
- assert.match(placementSource, /host: "admin-settings"/);
139
- assert.match(placementSource, /position: "sub-pages"/);
140
+ assert.match(placementSource, /target: "admin-settings:sub-pages"/);
140
141
  assert.match(placementSource, /componentToken: "local\.main\.ui\.tab-link-item"/);
141
142
  assert.match(placementSource, /to: "\.\/assistant"/);
142
143
  assert.match(placementSource, /label: "Assistant"/);
143
144
  });
144
145
  });
145
146
 
147
+ test("assistant page subcommand omits the auth guard for a public surface link", async () => {
148
+ await withTempApp(async (appRoot) => {
149
+ await writeAppFixture(appRoot, {
150
+ configSource: `export const config = {
151
+ surfaceAccessPolicies: {
152
+ public: {}
153
+ },
154
+ surfaceDefinitions: {
155
+ home: {
156
+ id: "home",
157
+ pagesRoot: "home",
158
+ enabled: true,
159
+ requiresWorkspace: false,
160
+ accessPolicyId: "public"
161
+ }
162
+ },
163
+ assistantSurfaces: {}
164
+ };
165
+ `
166
+ });
167
+
168
+ const targetFile = "home/assistant/index.vue";
169
+ await runPageSubcommand({
170
+ appRoot,
171
+ subcommand: "page",
172
+ args: [targetFile],
173
+ options: {}
174
+ });
175
+
176
+ const placementSource = await readFile(path.join(appRoot, "src", "placement.js"), "utf8");
177
+ assert.match(placementSource, /id: "ui-generator\.page\.home\.assistant\.link"/);
178
+ assert.doesNotMatch(placementSource, /when: \(\{ auth \}\) => Boolean\(auth\?\.authenticated\)/);
179
+ });
180
+ });
181
+
146
182
  test("assistant page subcommand refuses to overwrite an existing user-owned page", async () => {
147
183
  await withTempApp(async (appRoot) => {
148
184
  await writeAppFixture(appRoot);
@@ -211,7 +247,7 @@ test("assistant settings-page subcommand overwrites an existing page when --forc
211
247
  `<template>
212
248
  <SectionContainerShell>
213
249
  <template #tabs>
214
- <ShellOutlet host="admin-settings" position="sub-pages" />
250
+ <ShellOutlet target="admin-settings:sub-pages" />
215
251
  </template>
216
252
  <RouterView />
217
253
  </SectionContainerShell>
@@ -265,19 +301,20 @@ test("assistant settings-page subcommand requires the target assistant surface o
265
301
  });
266
302
  });
267
303
 
268
- test("assistant page subcommand rejects target files with a src/pages prefix", async () => {
304
+ test("assistant page subcommand accepts target files with a src/pages prefix", async () => {
269
305
  await withTempApp(async (appRoot) => {
270
306
  await writeAppFixture(appRoot);
271
307
 
272
- await assert.rejects(
273
- () =>
274
- runPageSubcommand({
275
- appRoot,
276
- subcommand: "page",
277
- args: ["src/pages/w/[workspaceSlug]/admin/assistant/index.vue"],
278
- options: {}
279
- }),
280
- /must be relative to src\/pages\/, without the src\/pages\/ prefix/
281
- );
308
+ const targetFile = "src/pages/w/[workspaceSlug]/admin/assistant/index.vue";
309
+ const result = await runPageSubcommand({
310
+ appRoot,
311
+ subcommand: "page",
312
+ args: [targetFile],
313
+ options: {}
314
+ });
315
+
316
+ assert.deepEqual(result.touchedFiles, [targetFile, "src/placement.js"]);
317
+ const pageSource = await readFile(path.join(appRoot, targetFile), "utf8");
318
+ assert.match(pageSource, /<AssistantSurfaceClientElement surface-id="admin" \/>/);
282
319
  });
283
320
  });
package/README.md DELETED
@@ -1,91 +0,0 @@
1
- # @jskit-ai/assistant
2
-
3
- Install assistant runtime/config for one surface, then scaffold assistant pages at explicit page files.
4
-
5
- ## Mental Model
6
-
7
- `@jskit-ai/assistant` is the generator package.
8
-
9
- - `setup` installs the assistant runtime/config/env wiring.
10
- - `page <target-file>` creates the assistant runtime page anywhere relative to `src/pages/...`.
11
- - `settings-page <target-file>` creates the assistant settings page anywhere relative to `src/pages/...`.
12
-
13
- There is no separate server command and no separate client command.
14
-
15
- The layering is:
16
-
17
- - `@jskit-ai/assistant`: generator package with `setup`, `page`, and `settings-page`
18
- - `@jskit-ai/assistant-runtime`: runtime package installed by `setup`
19
- - `@jskit-ai/assistant-core`: lower-level provider/client library pulled in by `assistant-runtime`
20
-
21
- So the normal flow is:
22
-
23
- - run `setup`
24
- - optionally create `page`
25
- - optionally create `settings-page`
26
-
27
- Page placement follows the same file-driven model as `@jskit-ai/ui-generator`.
28
-
29
- - The surface comes from where the page file lives.
30
- - The route URL comes from that file path.
31
- - The default page-link placement comes from the nearest real parent subpages host when there is one.
32
-
33
- Only one assistant can be installed per target surface. That rule is enforced by `setup`, which writes the per-surface config entries.
34
-
35
- ## Setup
36
-
37
- ```bash
38
- npx jskit generate @jskit-ai/assistant setup \
39
- --surface admin \
40
- --settings-surface console \
41
- --config-scope global \
42
- --ai-provider openai \
43
- --ai-api-key "$OPENAI_API_KEY" \
44
- --ai-base-url "" \
45
- --ai-timeout-ms 120000
46
- ```
47
-
48
- `setup` does not create pages.
49
-
50
- It only registers:
51
-
52
- - `config.assistantSurfaces.<surface>`
53
- - `config.assistantServer.<surface>`
54
- - prefixed AI env defaults in `.env`
55
- - the `@jskit-ai/assistant-runtime` dependency chain, including `@jskit-ai/assistant-core`
56
-
57
- ## Runtime Page
58
-
59
- ```bash
60
- npx jskit generate @jskit-ai/assistant page \
61
- admin/ops/copilot/index.vue
62
- ```
63
-
64
- The page surface id comes from the target file path.
65
-
66
- Optional page-link overrides:
67
-
68
- - `--name`
69
- - `--link-placement`
70
- - `--link-component-token`
71
- - `--link-to`
72
-
73
- ## Settings Page
74
-
75
- ```bash
76
- npx jskit generate @jskit-ai/assistant settings-page \
77
- admin/settings/index/assistant/index.vue \
78
- --surface admin
79
- ```
80
-
81
- For `settings-page`, the page location and the assistant target surface are separate concerns:
82
-
83
- - `<target-file>` decides where the settings page lives and how its page link is placed
84
- - `--surface` decides which assistant surface the settings page edits
85
-
86
- The same optional page-link overrides are supported:
87
-
88
- - `--name`
89
- - `--link-placement`
90
- - `--link-component-token`
91
- - `--link-to`