@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.
- package/package.descriptor.mjs +39 -18
- package/package.json +2 -2
- package/src/server/pageSupport.js +6 -10
- package/src/server/subcommands/page.js +1 -2
- package/src/server/subcommands/settingsPage.js +1 -2
- package/test/packageDescriptor.test.js +23 -4
- package/test/subcommands.test.js +58 -21
- package/README.md +0 -91
package/package.descriptor.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export default Object.freeze({
|
|
2
2
|
packageVersion: 1,
|
|
3
3
|
packageId: "@jskit-ai/assistant",
|
|
4
|
-
version: "0.1.
|
|
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: "
|
|
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
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
295
|
-
|
|
296
|
-
value:
|
|
297
|
-
|
|
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-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
`
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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,
|
|
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.
|
|
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
|
});
|
package/test/subcommands.test.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
59
|
-
<ShellOutlet
|
|
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, /
|
|
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
|
|
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, /
|
|
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
|
|
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
|
|
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
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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`
|