assistant-ui 0.0.120 → 0.0.122
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/README.md +60 -1
- package/dist/commands/add.d.ts.map +1 -1
- package/dist/commands/add.js +1 -2
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/cloud.d.ts +25 -0
- package/dist/commands/cloud.d.ts.map +1 -0
- package/dist/commands/cloud.js +194 -0
- package/dist/commands/cloud.js.map +1 -0
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +10 -2
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/info.js +2 -2
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +1 -2
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/mcp.d.ts.map +1 -1
- package/dist/commands/mcp.js +95 -23
- package/dist/commands/mcp.js.map +1 -1
- package/dist/lib/agent-skill.d.ts +2 -2
- package/dist/lib/agent-skill.d.ts.map +1 -1
- package/dist/lib/agent-skill.js +10 -28
- package/dist/lib/agent-skill.js.map +1 -1
- package/dist/lib/cloud-abort.d.ts +2 -0
- package/dist/lib/cloud-abort.d.ts.map +1 -0
- package/dist/lib/cloud-abort.js +13 -0
- package/dist/lib/cloud-abort.js.map +1 -0
- package/dist/lib/cloud-auth.d.ts +28 -0
- package/dist/lib/cloud-auth.d.ts.map +1 -0
- package/dist/lib/cloud-auth.js +169 -0
- package/dist/lib/cloud-auth.js.map +1 -0
- package/dist/lib/cloud-config.d.ts +21 -0
- package/dist/lib/cloud-config.d.ts.map +1 -0
- package/dist/lib/cloud-config.js +76 -0
- package/dist/lib/cloud-config.js.map +1 -0
- package/dist/lib/cloud-provision.d.ts +20 -0
- package/dist/lib/cloud-provision.d.ts.map +1 -0
- package/dist/lib/cloud-provision.js +55 -0
- package/dist/lib/cloud-provision.js.map +1 -0
- package/dist/lib/cloud-setup-login.d.ts +9 -0
- package/dist/lib/cloud-setup-login.d.ts.map +1 -0
- package/dist/lib/cloud-setup-login.js +137 -0
- package/dist/lib/cloud-setup-login.js.map +1 -0
- package/dist/lib/cloud-url.d.ts +2 -0
- package/dist/lib/cloud-url.d.ts.map +1 -0
- package/dist/lib/cloud-url.js +14 -0
- package/dist/lib/cloud-url.js.map +1 -0
- package/dist/lib/create-project.d.ts +0 -9
- package/dist/lib/create-project.d.ts.map +1 -1
- package/dist/lib/create-project.js +15 -46
- package/dist/lib/create-project.js.map +1 -1
- package/dist/lib/transform.d.ts +0 -4
- package/dist/lib/transform.d.ts.map +1 -1
- package/dist/lib/transform.js +8 -17
- package/dist/lib/transform.js.map +1 -1
- package/dist/lib/utils/download.d.ts +5 -0
- package/dist/lib/utils/download.d.ts.map +1 -0
- package/dist/lib/utils/download.js +41 -0
- package/dist/lib/utils/download.js.map +1 -0
- package/dist/lib/utils/file-scanner.d.ts +0 -1
- package/dist/lib/utils/file-scanner.d.ts.map +1 -1
- package/dist/lib/utils/file-scanner.js +1 -16
- package/dist/lib/utils/file-scanner.js.map +1 -1
- package/dist/lib/utils/package-manager.d.ts +7 -0
- package/dist/lib/utils/package-manager.d.ts.map +1 -1
- package/dist/lib/utils/package-manager.js +15 -1
- package/dist/lib/utils/package-manager.js.map +1 -1
- package/dist/program.d.ts.map +1 -1
- package/dist/program.js +2 -1
- package/dist/program.js.map +1 -1
- package/package.json +12 -8
- package/src/commands/add.ts +2 -1
- package/src/commands/cloud.test.ts +238 -0
- package/src/commands/cloud.ts +455 -0
- package/src/commands/create.ts +13 -3
- package/src/commands/init.ts +5 -2
- package/src/commands/mcp.ts +223 -56
- package/src/lib/agent-skill.ts +18 -44
- package/src/lib/cloud-abort.ts +12 -0
- package/src/lib/cloud-auth.test.ts +500 -0
- package/src/lib/cloud-auth.ts +273 -0
- package/src/lib/cloud-config.test.ts +82 -0
- package/src/lib/cloud-config.ts +138 -0
- package/src/lib/cloud-provision.test.ts +132 -0
- package/src/lib/cloud-provision.ts +93 -0
- package/src/lib/cloud-setup-login.test.ts +361 -0
- package/src/lib/cloud-setup-login.ts +240 -0
- package/src/lib/cloud-url.ts +19 -0
- package/src/lib/create-project.test.ts +0 -17
- package/src/lib/create-project.ts +33 -98
- package/src/lib/transform-files.test.ts +1 -3
- package/src/lib/transform.ts +6 -20
- package/src/lib/utils/download.ts +57 -0
- package/src/lib/utils/file-scanner.ts +0 -26
- package/src/lib/utils/package-manager.ts +26 -0
- package/src/lib/utils/registry.test.ts +0 -6
- package/src/program.ts +2 -0
- package/templates/cloud-harness/.assistant-ui/template.json +3 -0
- package/templates/cloud-harness/.env.example +1 -0
- package/templates/cloud-harness/.gitignore +42 -0
- package/templates/cloud-harness/README.md +28 -0
- package/templates/cloud-harness/app/api/chat/route.ts +14 -0
- package/templates/cloud-harness/app/api/credential/route.ts +14 -0
- package/templates/cloud-harness/app/assistant.tsx +83 -0
- package/templates/cloud-harness/app/favicon.ico +0 -0
- package/templates/cloud-harness/app/globals.css +139 -0
- package/templates/cloud-harness/app/layout.tsx +37 -0
- package/templates/cloud-harness/app/page.tsx +5 -0
- package/templates/cloud-harness/components/assistant-ui/elements/attachment.aui.tsx +279 -0
- package/templates/cloud-harness/components/assistant-ui/elements/collapsible-root.tsx +19 -0
- package/templates/cloud-harness/components/assistant-ui/elements/markdown-text.tsx +238 -0
- package/templates/cloud-harness/components/assistant-ui/elements/thread.aui.tsx +521 -0
- package/templates/cloud-harness/components/assistant-ui/elements/tool-fallback.aui.tsx +1321 -0
- package/templates/cloud-harness/components/assistant-ui/elements/tooltip-icon-button.tsx +49 -0
- package/templates/cloud-harness/components/ui/avatar.tsx +109 -0
- package/templates/cloud-harness/components/ui/button.tsx +58 -0
- package/templates/cloud-harness/components/ui/collapsible.tsx +21 -0
- package/templates/cloud-harness/components/ui/dialog.tsx +159 -0
- package/templates/cloud-harness/components/ui/textarea.tsx +18 -0
- package/templates/cloud-harness/components/ui/tooltip.tsx +69 -0
- package/templates/cloud-harness/components.json +24 -0
- package/templates/cloud-harness/hooks/use-attachment-src.ts +46 -0
- package/templates/cloud-harness/hooks/use-copy-to-clipboard.ts +54 -0
- package/templates/cloud-harness/lib/utils.ts +1 -0
- package/templates/cloud-harness/next.config.ts +6 -0
- package/templates/cloud-harness/package.json +51 -0
- package/templates/cloud-harness/postcss.config.mjs +5 -0
- package/templates/cloud-harness/tsconfig.json +33 -0
- package/templates/cloud-harness/vendor/README.md +13 -0
- package/templates/cloud-harness/vendor/assistant-ui-react-harness-sdk-0.0.2.tgz +0 -0
- package/templates/cloud-harness/vendor/harness-sdk-0.3.1.tgz +0 -0
- package/templates/cloud-harness/vendor/provenance.json +38 -0
- package/templates/cloud-harness/vendor/statewire-0.19.3.tgz +0 -0
- /package/{LICENSE → templates/cloud-harness/vendor/LICENSE} +0 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const validateCloudUrl = (value: string): string => {
|
|
2
|
+
const url = new URL(value);
|
|
3
|
+
if (
|
|
4
|
+
url.username ||
|
|
5
|
+
url.password ||
|
|
6
|
+
url.search ||
|
|
7
|
+
url.hash ||
|
|
8
|
+
(url.protocol !== "https:" &&
|
|
9
|
+
!(
|
|
10
|
+
url.protocol === "http:" &&
|
|
11
|
+
["localhost", "127.0.0.1", "[::1]"].includes(url.hostname)
|
|
12
|
+
))
|
|
13
|
+
) {
|
|
14
|
+
throw new Error(
|
|
15
|
+
"Cloud, accounts and setup URLs must use HTTPS or HTTP localhost for development.",
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
return value.replace(/\/+$/, "");
|
|
19
|
+
};
|
|
@@ -24,23 +24,6 @@ describe("reconcileAssistantUIImportLayout", () => {
|
|
|
24
24
|
const read = (file: string) =>
|
|
25
25
|
fs.readFileSync(path.join(projectDir, file), "utf8");
|
|
26
26
|
|
|
27
|
-
it("rewrites a legacy import when only the elements layout exists", async () => {
|
|
28
|
-
write(
|
|
29
|
-
"app/page.tsx",
|
|
30
|
-
'import { Thread } from "@/components/assistant-ui/thread";\n' +
|
|
31
|
-
'import { ThreadList } from "@/components/assistant-ui/thread-list";\n',
|
|
32
|
-
);
|
|
33
|
-
write("components/assistant-ui/elements/thread.aui.tsx", "export {};");
|
|
34
|
-
write("components/assistant-ui/elements/thread-list.aui.tsx", "export {};");
|
|
35
|
-
|
|
36
|
-
await reconcileAssistantUIImportLayout(projectDir);
|
|
37
|
-
|
|
38
|
-
expect(read("app/page.tsx")).toBe(
|
|
39
|
-
'import { Thread } from "@/components/assistant-ui/elements/thread.aui";\n' +
|
|
40
|
-
'import { ThreadList } from "@/components/assistant-ui/elements/thread-list.aui";\n',
|
|
41
|
-
);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
27
|
it("rewrites module declarations without changing import-like source text", async () => {
|
|
45
28
|
write(
|
|
46
29
|
"app/page.tsx",
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
import { downloadTemplate } from "giget";
|
|
5
4
|
import {
|
|
6
5
|
parse as parseJsonc,
|
|
7
6
|
printParseErrorCode,
|
|
@@ -9,26 +8,14 @@ import {
|
|
|
9
8
|
} from "jsonc-parser";
|
|
10
9
|
import { logger } from "./utils/logger";
|
|
11
10
|
import { runSpawn, SpawnExitError, SpawnSignalError } from "./run-spawn";
|
|
12
|
-
import { type PackageManagerName } from "./utils/package-manager";
|
|
11
|
+
import { dlxCommand, type PackageManagerName } from "./utils/package-manager";
|
|
12
|
+
import { resolveGitHubAuthToken, withStagedDownload } from "./utils/download";
|
|
13
13
|
import { readProjectFiles } from "./utils/file-scanner";
|
|
14
14
|
import {
|
|
15
15
|
detectRegistryPlatform,
|
|
16
16
|
resolveRegistryItemUrl,
|
|
17
17
|
} from "./utils/registry";
|
|
18
18
|
|
|
19
|
-
export function dlxCommand(pm: PackageManagerName): [string, string[]] {
|
|
20
|
-
switch (pm) {
|
|
21
|
-
case "pnpm":
|
|
22
|
-
return ["pnpm", ["dlx"]];
|
|
23
|
-
case "yarn":
|
|
24
|
-
return ["yarn", ["dlx"]];
|
|
25
|
-
case "bun":
|
|
26
|
-
return ["bunx", []];
|
|
27
|
-
case "npm":
|
|
28
|
-
return ["npx", ["--yes"]];
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
19
|
export interface TransformOptions {
|
|
33
20
|
hasLocalComponents: boolean;
|
|
34
21
|
skipInstall?: boolean;
|
|
@@ -56,26 +43,6 @@ const LOCAL_PROJECT_ARTIFACT_GLOB_IGNORES = LOCAL_PROJECT_ARTIFACT_DIRS.map(
|
|
|
56
43
|
(dir) => `**/${dir}/**`,
|
|
57
44
|
);
|
|
58
45
|
|
|
59
|
-
export function resolvePackageManager(opts: {
|
|
60
|
-
useNpm?: boolean;
|
|
61
|
-
usePnpm?: boolean;
|
|
62
|
-
useYarn?: boolean;
|
|
63
|
-
useBun?: boolean;
|
|
64
|
-
}): PackageManagerName | undefined {
|
|
65
|
-
if (opts.useNpm) return "npm";
|
|
66
|
-
if (opts.usePnpm) return "pnpm";
|
|
67
|
-
if (opts.useYarn) return "yarn";
|
|
68
|
-
if (opts.useBun) return "bun";
|
|
69
|
-
return undefined;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export function resolveGitHubAuthToken(): string | undefined {
|
|
73
|
-
const token =
|
|
74
|
-
process.env.GIGET_AUTH ?? process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN;
|
|
75
|
-
const trimmed = token?.trim();
|
|
76
|
-
return trimmed || undefined;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
46
|
function toBearerAuthHeader(token: string): string {
|
|
80
47
|
return token.toLowerCase().startsWith("bearer ") ? token : `Bearer ${token}`;
|
|
81
48
|
}
|
|
@@ -97,7 +64,6 @@ export async function resolveLatestReleaseRef(): Promise<string | undefined> {
|
|
|
97
64
|
}
|
|
98
65
|
}
|
|
99
66
|
|
|
100
|
-
export const DOWNLOAD_TIMEOUT_MS = 30_000;
|
|
101
67
|
const pendingDownloadCleanups = new Set<() => void>();
|
|
102
68
|
|
|
103
69
|
export function cleanupPendingProjectDownloads(): void {
|
|
@@ -117,16 +83,9 @@ export async function downloadProject(
|
|
|
117
83
|
? `gh:assistant-ui/assistant-ui/${repoPath}#${ref}`
|
|
118
84
|
: `gh:assistant-ui/assistant-ui/${repoPath}`;
|
|
119
85
|
|
|
120
|
-
// Suppress giget's debug output. The `debug` package (used by the upgrade
|
|
121
|
-
// command) sets process.env.DEBUG at module-load time, and giget logs to
|
|
122
|
-
// console.debug whenever that env var is truthy — even for unrelated
|
|
123
|
-
// namespaces. Temporarily unsetting it targets the root cause.
|
|
124
|
-
const origDebug = process.env.DEBUG;
|
|
125
|
-
delete process.env.DEBUG;
|
|
126
86
|
let destinationCreated = false;
|
|
127
87
|
let stagingDir: string | undefined;
|
|
128
|
-
let
|
|
129
|
-
let downloadFinished = false;
|
|
88
|
+
let downloadStarted = false;
|
|
130
89
|
let downloadCommitted = false;
|
|
131
90
|
let cleanupRequested = false;
|
|
132
91
|
const removeCleanupListeners = () => {
|
|
@@ -183,62 +142,38 @@ export async function downloadProject(
|
|
|
183
142
|
process.once("exit", cleanupOnExit);
|
|
184
143
|
pendingDownloadCleanups.add(cleanupOnExit);
|
|
185
144
|
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
silent: true,
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
)
|
|
204
|
-
)
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
destinationCreated = !fs.existsSync(destDir);
|
|
213
|
-
await fs.promises.mkdir(destDir, { recursive: true });
|
|
214
|
-
for (const entry of await fs.promises.readdir(stagingDir)) {
|
|
215
|
-
const target = path.join(destDir, entry);
|
|
216
|
-
const sourceEntry = path.join(stagingDir, entry);
|
|
217
|
-
await fs.promises.rm(target, { recursive: true, force: true });
|
|
218
|
-
try {
|
|
219
|
-
await fs.promises.rename(sourceEntry, target);
|
|
220
|
-
} catch (error) {
|
|
221
|
-
if ((error as NodeJS.ErrnoException).code !== "EXDEV") throw error;
|
|
222
|
-
await fs.promises.cp(sourceEntry, target, {
|
|
223
|
-
recursive: true,
|
|
224
|
-
force: true,
|
|
225
|
-
});
|
|
226
|
-
await fs.promises.rm(sourceEntry, { recursive: true, force: true });
|
|
145
|
+
const currentStagingDir = stagingDir;
|
|
146
|
+
downloadStarted = true;
|
|
147
|
+
await withStagedDownload(
|
|
148
|
+
source,
|
|
149
|
+
{ dir: currentStagingDir, force: true, silent: true },
|
|
150
|
+
"Download timed out. This may be due to GitHub rate limiting or a network issue. Try again in a few minutes.",
|
|
151
|
+
removeStagingDir,
|
|
152
|
+
async (download) => {
|
|
153
|
+
await download;
|
|
154
|
+
if (cleanupRequested) throw new Error("Download was interrupted.");
|
|
155
|
+
destinationCreated = !fs.existsSync(destDir);
|
|
156
|
+
await fs.promises.mkdir(destDir, { recursive: true });
|
|
157
|
+
for (const entry of await fs.promises.readdir(currentStagingDir)) {
|
|
158
|
+
const target = path.join(destDir, entry);
|
|
159
|
+
const sourceEntry = path.join(currentStagingDir, entry);
|
|
160
|
+
await fs.promises.rm(target, { recursive: true, force: true });
|
|
161
|
+
try {
|
|
162
|
+
await fs.promises.rename(sourceEntry, target);
|
|
163
|
+
} catch (error) {
|
|
164
|
+
if ((error as NodeJS.ErrnoException).code !== "EXDEV") throw error;
|
|
165
|
+
await fs.promises.cp(sourceEntry, target, {
|
|
166
|
+
recursive: true,
|
|
167
|
+
force: true,
|
|
168
|
+
});
|
|
169
|
+
await fs.promises.rm(sourceEntry, { recursive: true, force: true });
|
|
170
|
+
}
|
|
227
171
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
clearTimeout(timer!);
|
|
232
|
-
}
|
|
172
|
+
downloadCommitted = true;
|
|
173
|
+
},
|
|
174
|
+
);
|
|
233
175
|
} finally {
|
|
234
|
-
if (!
|
|
235
|
-
await removeStagingDir();
|
|
236
|
-
} else {
|
|
237
|
-
void downloadPromise.then(removeStagingDir, removeStagingDir);
|
|
238
|
-
}
|
|
239
|
-
if (origDebug !== undefined) {
|
|
240
|
-
process.env.DEBUG = origDebug;
|
|
241
|
-
}
|
|
176
|
+
if (!downloadStarted) await removeStagingDir();
|
|
242
177
|
}
|
|
243
178
|
}
|
|
244
179
|
|
|
@@ -2,7 +2,7 @@ import { describe, expect, it, onTestFinished } from "vitest";
|
|
|
2
2
|
import { mkdirSync, mkdtempSync, writeFileSync, rmSync } from "node:fs";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { dirname, join, relative } from "node:path";
|
|
5
|
-
import { getRelevantFiles
|
|
5
|
+
import { getRelevantFiles } from "./transform";
|
|
6
6
|
|
|
7
7
|
const fixture = () => {
|
|
8
8
|
const directory = mkdtempSync(join(tmpdir(), "aui-transform-files-"));
|
|
@@ -26,7 +26,6 @@ describe("codemod file discovery", () => {
|
|
|
26
26
|
const { write } = fixture();
|
|
27
27
|
const file = write(`my app.${extension}`);
|
|
28
28
|
expect(getRelevantFiles(file)).toEqual([file]);
|
|
29
|
-
expect(countFilesToProcess(file)).toBe(1);
|
|
30
29
|
expect(getRelevantFiles(relative(process.cwd(), file))).toEqual([file]);
|
|
31
30
|
},
|
|
32
31
|
);
|
|
@@ -54,7 +53,6 @@ describe("codemod file discovery", () => {
|
|
|
54
53
|
const { write } = fixture();
|
|
55
54
|
const file = write("app.tsx", source);
|
|
56
55
|
expect(getRelevantFiles(file)).toEqual([file]);
|
|
57
|
-
expect(countFilesToProcess(file)).toBe(1);
|
|
58
56
|
});
|
|
59
57
|
|
|
60
58
|
it("skips unsupported explicit files", () => {
|
package/src/lib/transform.ts
CHANGED
|
@@ -3,8 +3,8 @@ import path from "node:path";
|
|
|
3
3
|
import type { TransformOptions } from "./transform-options";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import * as fs from "node:fs";
|
|
6
|
-
import { sync as globSync } from "glob";
|
|
7
6
|
import { runSpawnCapture, SpawnExitError, SpawnSignalError } from "./run-spawn";
|
|
7
|
+
import { readProjectFiles } from "./utils/file-scanner";
|
|
8
8
|
|
|
9
9
|
const log = debug("codemod:transform");
|
|
10
10
|
|
|
@@ -22,7 +22,8 @@ export function getRelevantFiles(source: string): string[] {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
const pattern = "**/*.{js,jsx,ts,tsx}";
|
|
25
|
-
const
|
|
25
|
+
const relevantFiles: string[] = [];
|
|
26
|
+
for (const { fullPath, content } of readProjectFiles(pattern, {
|
|
26
27
|
cwd: target,
|
|
27
28
|
ignore: [
|
|
28
29
|
"**/node_modules/**",
|
|
@@ -31,27 +32,12 @@ export function getRelevantFiles(source: string): string[] {
|
|
|
31
32
|
"**/*.min.js",
|
|
32
33
|
"**/*.bundle.js",
|
|
33
34
|
],
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
try {
|
|
38
|
-
const content = fs.readFileSync(file, "utf8");
|
|
39
|
-
return content.includes("assistant-ui");
|
|
40
|
-
} catch {
|
|
41
|
-
return false;
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
|
|
35
|
+
})) {
|
|
36
|
+
if (content.includes("assistant-ui")) relevantFiles.push(fullPath);
|
|
37
|
+
}
|
|
45
38
|
return relevantFiles;
|
|
46
39
|
}
|
|
47
40
|
|
|
48
|
-
/**
|
|
49
|
-
* Counts the number of files that need to be processed
|
|
50
|
-
*/
|
|
51
|
-
export function countFilesToProcess(source: string): number {
|
|
52
|
-
return getRelevantFiles(source).length;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
41
|
function buildCommand(
|
|
56
42
|
codemodPath: string,
|
|
57
43
|
targetFiles: string[],
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { downloadTemplate } from "giget";
|
|
2
|
+
|
|
3
|
+
export const DOWNLOAD_TIMEOUT_MS = 30_000;
|
|
4
|
+
|
|
5
|
+
export function resolveGitHubAuthToken(): string | undefined {
|
|
6
|
+
const token =
|
|
7
|
+
process.env.GIGET_AUTH ?? process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN;
|
|
8
|
+
const trimmed = token?.trim();
|
|
9
|
+
return trimmed || undefined;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function withStagedDownload<T>(
|
|
13
|
+
source: string,
|
|
14
|
+
options: NonNullable<Parameters<typeof downloadTemplate>[1]>,
|
|
15
|
+
timeoutMessage: string,
|
|
16
|
+
cleanup: () => void | Promise<void>,
|
|
17
|
+
consumeDownload: (download: Promise<unknown>) => Promise<T>,
|
|
18
|
+
): Promise<T> {
|
|
19
|
+
// giget logs to console.debug whenever DEBUG is set, which the `debug`
|
|
20
|
+
// package does at module load for an unrelated namespace.
|
|
21
|
+
const origDebug = process.env.DEBUG;
|
|
22
|
+
delete process.env.DEBUG;
|
|
23
|
+
let download: Promise<unknown> | undefined;
|
|
24
|
+
let settled = false;
|
|
25
|
+
try {
|
|
26
|
+
const authToken = resolveGitHubAuthToken();
|
|
27
|
+
download = downloadTemplate(source, {
|
|
28
|
+
...options,
|
|
29
|
+
...(authToken ? { auth: authToken } : {}),
|
|
30
|
+
}).finally(() => {
|
|
31
|
+
settled = true;
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
let timer: ReturnType<typeof setTimeout>;
|
|
35
|
+
const timeout = new Promise<never>((_, reject) => {
|
|
36
|
+
timer = setTimeout(
|
|
37
|
+
() => reject(new Error(timeoutMessage)),
|
|
38
|
+
DOWNLOAD_TIMEOUT_MS,
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
try {
|
|
42
|
+
return await consumeDownload(Promise.race([download, timeout]));
|
|
43
|
+
} finally {
|
|
44
|
+
clearTimeout(timer!);
|
|
45
|
+
}
|
|
46
|
+
} finally {
|
|
47
|
+
try {
|
|
48
|
+
if (!download || settled) {
|
|
49
|
+
await cleanup();
|
|
50
|
+
} else {
|
|
51
|
+
void download.then(cleanup, cleanup);
|
|
52
|
+
}
|
|
53
|
+
} finally {
|
|
54
|
+
if (origDebug !== undefined) process.env.DEBUG = origDebug;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -49,29 +49,3 @@ export function scanForImport(
|
|
|
49
49
|
|
|
50
50
|
return false;
|
|
51
51
|
}
|
|
52
|
-
|
|
53
|
-
export function getFilesContaining(
|
|
54
|
-
searchString: string,
|
|
55
|
-
options: ScanOptions = {},
|
|
56
|
-
): string[] {
|
|
57
|
-
const cwd = options.cwd || process.cwd();
|
|
58
|
-
const pattern = options.pattern || "**/*.{js,jsx,ts,tsx}";
|
|
59
|
-
const ignore = options.ignore || [
|
|
60
|
-
"**/node_modules/**",
|
|
61
|
-
"**/dist/**",
|
|
62
|
-
"**/build/**",
|
|
63
|
-
];
|
|
64
|
-
|
|
65
|
-
const result: string[] = [];
|
|
66
|
-
|
|
67
|
-
for (const { fullPath, content } of readProjectFiles(pattern, {
|
|
68
|
-
cwd,
|
|
69
|
-
ignore,
|
|
70
|
-
})) {
|
|
71
|
-
if (content.includes(searchString)) {
|
|
72
|
-
result.push(fullPath);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return result;
|
|
77
|
-
}
|
|
@@ -7,6 +7,32 @@ import { runSpawn, SpawnSignalError } from "../run-spawn";
|
|
|
7
7
|
|
|
8
8
|
export type PackageManagerName = "npm" | "pnpm" | "yarn" | "bun";
|
|
9
9
|
|
|
10
|
+
export function dlxCommand(pm: PackageManagerName): [string, string[]] {
|
|
11
|
+
switch (pm) {
|
|
12
|
+
case "pnpm":
|
|
13
|
+
return ["pnpm", ["dlx"]];
|
|
14
|
+
case "yarn":
|
|
15
|
+
return ["yarn", ["dlx"]];
|
|
16
|
+
case "bun":
|
|
17
|
+
return ["bunx", []];
|
|
18
|
+
case "npm":
|
|
19
|
+
return ["npx", ["--yes"]];
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function resolvePackageManager(opts: {
|
|
24
|
+
useNpm?: boolean;
|
|
25
|
+
usePnpm?: boolean;
|
|
26
|
+
useYarn?: boolean;
|
|
27
|
+
useBun?: boolean;
|
|
28
|
+
}): PackageManagerName | undefined {
|
|
29
|
+
if (opts.useNpm) return "npm";
|
|
30
|
+
if (opts.usePnpm) return "pnpm";
|
|
31
|
+
if (opts.useYarn) return "yarn";
|
|
32
|
+
if (opts.useBun) return "bun";
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
10
36
|
export function askQuestion(query: string): Promise<string> {
|
|
11
37
|
return new Promise((resolve) => {
|
|
12
38
|
// A stream only reaches EOF once, so a run that already consumed stdin gets
|
|
@@ -144,12 +144,6 @@ describe("resolveRegistryItemUrl", () => {
|
|
|
144
144
|
);
|
|
145
145
|
});
|
|
146
146
|
|
|
147
|
-
it("uses the base URL for an explicit undefined style", () => {
|
|
148
|
-
expect(resolveRegistryItemUrl("thread", undefined)).toBe(
|
|
149
|
-
"https://r.assistant-ui.com/base/thread.json",
|
|
150
|
-
);
|
|
151
|
-
});
|
|
152
|
-
|
|
153
147
|
it("keeps the style inside a single path segment", () => {
|
|
154
148
|
expect(resolveRegistryItemUrl("thread", "base-nova?preview=1")).toBe(
|
|
155
149
|
"https://r.assistant-ui.com/styles/base-nova%3Fpreview%3D1/thread.json",
|
package/src/program.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { mcp } from "./commands/mcp";
|
|
|
8
8
|
import { agent } from "./commands/agent";
|
|
9
9
|
import { info } from "./commands/info";
|
|
10
10
|
import { doctor } from "./commands/doctor";
|
|
11
|
+
import { cloud } from "./commands/cloud";
|
|
11
12
|
|
|
12
13
|
export function buildProgram() {
|
|
13
14
|
return new Command()
|
|
@@ -21,6 +22,7 @@ export function buildProgram() {
|
|
|
21
22
|
.addCommand(upgradeCommand)
|
|
22
23
|
.addCommand(update)
|
|
23
24
|
.addCommand(agent)
|
|
25
|
+
.addCommand(cloud)
|
|
24
26
|
.addCommand(info)
|
|
25
27
|
.addCommand(doctor);
|
|
26
28
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
OPENAI_API_KEY=
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
|
2
|
+
|
|
3
|
+
# dependencies
|
|
4
|
+
/node_modules
|
|
5
|
+
/.pnp
|
|
6
|
+
.pnp.*
|
|
7
|
+
.yarn/*
|
|
8
|
+
!.yarn/patches
|
|
9
|
+
!.yarn/plugins
|
|
10
|
+
!.yarn/releases
|
|
11
|
+
!.yarn/versions
|
|
12
|
+
|
|
13
|
+
# testing
|
|
14
|
+
/coverage
|
|
15
|
+
|
|
16
|
+
# next.js
|
|
17
|
+
/.next/
|
|
18
|
+
/out/
|
|
19
|
+
|
|
20
|
+
# production
|
|
21
|
+
/build
|
|
22
|
+
|
|
23
|
+
# misc
|
|
24
|
+
.DS_Store
|
|
25
|
+
*.pem
|
|
26
|
+
|
|
27
|
+
# debug
|
|
28
|
+
npm-debug.log*
|
|
29
|
+
yarn-debug.log*
|
|
30
|
+
yarn-error.log*
|
|
31
|
+
.pnpm-debug.log*
|
|
32
|
+
|
|
33
|
+
# env files (can opt-in for committing if needed)
|
|
34
|
+
.env*
|
|
35
|
+
!.env.example
|
|
36
|
+
|
|
37
|
+
# vercel
|
|
38
|
+
.vercel
|
|
39
|
+
|
|
40
|
+
# typescript
|
|
41
|
+
*.tsbuildinfo
|
|
42
|
+
next-env.d.ts
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Shared cloud chat
|
|
2
|
+
|
|
3
|
+
This Next.js starter connects assistant-ui to a hosted harness. Browser clients
|
|
4
|
+
on the same URL share conversation state, replies, and cancellation through
|
|
5
|
+
`HarnessCloudThreadList`.
|
|
6
|
+
|
|
7
|
+
Run `npx assistant-ui@latest cloud setup .` from this app to sign in and configure a project and harness.
|
|
8
|
+
Add your `OPENAI_API_KEY` to `.env.local`, install dependencies, and run `npm run dev`.
|
|
9
|
+
Open the app in two browser windows, or use **Share this chat** to copy its link.
|
|
10
|
+
Send a message from either window; both show the same reply as it streams.
|
|
11
|
+
Reload either window to restore the conversation.
|
|
12
|
+
|
|
13
|
+
The URL fragment selects the thread. A fresh app uses `main` in the
|
|
14
|
+
configured shared workspace. The server mints short-lived browser credentials for the
|
|
15
|
+
shared `hackathon` user. `ASSISTANT_API_KEY` and `OPENAI_API_KEY` stay in the
|
|
16
|
+
server environment. This starter admits visitors as the same shared user.
|
|
17
|
+
Both `/api/credential` and `/api/chat` admit public visitors in this hackathon
|
|
18
|
+
starter. Add your application authentication to both routes before deploying a
|
|
19
|
+
private workspace.
|
|
20
|
+
|
|
21
|
+
During local development, the existing harness runtime serves model requests
|
|
22
|
+
through the browser's localhost tunnel. Keep a browser connected while a run
|
|
23
|
+
is active. For deployment, add the application's HTTPS `/api/chat` endpoint to
|
|
24
|
+
the harness's allowed endpoints.
|
|
25
|
+
|
|
26
|
+
The harness runtimes are bundled as local package tarballs while their public
|
|
27
|
+
packages are in preview. See [vendor/README.md](vendor/README.md) for their source
|
|
28
|
+
revision, license, and reproduction command.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { openai } from "@ai-sdk/openai";
|
|
2
|
+
import { convertToModelMessages, streamText, type UIMessage } from "ai";
|
|
3
|
+
|
|
4
|
+
export const maxDuration = 30;
|
|
5
|
+
|
|
6
|
+
export const POST = async (request: Request) => {
|
|
7
|
+
const { messages } = (await request.json()) as { messages: UIMessage[] };
|
|
8
|
+
const result = streamText({
|
|
9
|
+
abortSignal: request.signal,
|
|
10
|
+
model: openai.responses("gpt-6-luna"),
|
|
11
|
+
messages: await convertToModelMessages(messages),
|
|
12
|
+
});
|
|
13
|
+
return result.toUIMessageStreamResponse();
|
|
14
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { AssistantCloud } from "assistant-cloud";
|
|
2
|
+
|
|
3
|
+
export const GET = async () => {
|
|
4
|
+
const cloud = new AssistantCloud({
|
|
5
|
+
baseUrl: process.env.NEXT_PUBLIC_ASSISTANT_BASE_URL!,
|
|
6
|
+
apiKey: process.env.ASSISTANT_API_KEY!,
|
|
7
|
+
workspaceId: process.env.NEXT_PUBLIC_ASSISTANT_WORKSPACE_ID!,
|
|
8
|
+
userId: "hackathon",
|
|
9
|
+
});
|
|
10
|
+
const credential = await cloud.auth.tokens.create();
|
|
11
|
+
return Response.json(credential, {
|
|
12
|
+
headers: { "Cache-Control": "no-store" },
|
|
13
|
+
});
|
|
14
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { AuiConfig, AuiProvider } from "@assistant-ui/react";
|
|
4
|
+
import { HarnessCloudThreadList } from "@assistant-ui/react-harness-sdk";
|
|
5
|
+
import { useSyncExternalStore } from "react";
|
|
6
|
+
import { Thread } from "@/components/assistant-ui/elements/thread.aui";
|
|
7
|
+
import { Button } from "@/components/ui/button";
|
|
8
|
+
import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard";
|
|
9
|
+
|
|
10
|
+
const credential = async () => {
|
|
11
|
+
const response = await fetch("/api/credential", { cache: "no-store" });
|
|
12
|
+
if (!response.ok) throw new Error(await response.text());
|
|
13
|
+
const { token } = (await response.json()) as { token: string };
|
|
14
|
+
return token;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const ShareThread = () => {
|
|
18
|
+
const { isCopied, copyToClipboard } = useCopyToClipboard();
|
|
19
|
+
return (
|
|
20
|
+
<Button
|
|
21
|
+
variant="outline"
|
|
22
|
+
size="sm"
|
|
23
|
+
onClick={() => copyToClipboard(window.location.href)}
|
|
24
|
+
>
|
|
25
|
+
{isCopied ? "Link copied" : "Share this chat"}
|
|
26
|
+
</Button>
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const ConnectedAssistant = ({ threadId }: { threadId: string }) => {
|
|
31
|
+
const config = AuiConfig({
|
|
32
|
+
threads: HarnessCloudThreadList({
|
|
33
|
+
url: "/api/chat",
|
|
34
|
+
origin: process.env.NEXT_PUBLIC_ASSISTANT_HARNESS_URL!,
|
|
35
|
+
workspaceId: process.env.NEXT_PUBLIC_ASSISTANT_WORKSPACE_ID!,
|
|
36
|
+
credential,
|
|
37
|
+
threadId,
|
|
38
|
+
onThreadIdChange: (threadId) => {
|
|
39
|
+
window.location.hash = threadId ?? crypto.randomUUID();
|
|
40
|
+
},
|
|
41
|
+
}),
|
|
42
|
+
});
|
|
43
|
+
return (
|
|
44
|
+
<AuiProvider config={config}>
|
|
45
|
+
<div className="flex h-dvh flex-col">
|
|
46
|
+
<header className="flex items-center justify-between border-b px-6 py-3">
|
|
47
|
+
<div>
|
|
48
|
+
<h1 className="font-semibold">Shared chat</h1>
|
|
49
|
+
<p className="text-muted-foreground text-sm">
|
|
50
|
+
Open the same link in another browser to chat together.
|
|
51
|
+
</p>
|
|
52
|
+
</div>
|
|
53
|
+
<ShareThread />
|
|
54
|
+
</header>
|
|
55
|
+
<div className="min-h-0 flex-1">
|
|
56
|
+
<Thread />
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
</AuiProvider>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const Assistant = () => {
|
|
64
|
+
const threadId = useSyncExternalStore(
|
|
65
|
+
subscribeToHash,
|
|
66
|
+
currentThreadId,
|
|
67
|
+
() => null,
|
|
68
|
+
);
|
|
69
|
+
if (threadId === null)
|
|
70
|
+
return (
|
|
71
|
+
<p className="p-6" role="status">
|
|
72
|
+
Connecting to your chat…
|
|
73
|
+
</p>
|
|
74
|
+
);
|
|
75
|
+
return <ConnectedAssistant threadId={threadId} />;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const currentThreadId = () => window.location.hash.slice(1) || "main";
|
|
79
|
+
|
|
80
|
+
const subscribeToHash = (onChange: () => void) => {
|
|
81
|
+
window.addEventListener("hashchange", onChange);
|
|
82
|
+
return () => window.removeEventListener("hashchange", onChange);
|
|
83
|
+
};
|
|
Binary file
|