@vellumai/assistant 0.4.43 → 0.4.44
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/ARCHITECTURE.md +13 -14
- package/README.md +11 -12
- package/docs/architecture/integrations.md +75 -93
- package/package.json +1 -1
- package/src/__tests__/approval-routes-http.test.ts +0 -2
- package/src/__tests__/bundled-asset.test.ts +1 -1
- package/src/__tests__/checker.test.ts +31 -28
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +6 -6
- package/src/__tests__/credential-security-invariants.test.ts +2 -1
- package/src/__tests__/error-handler-friendly-messages.test.ts +46 -0
- package/src/__tests__/managed-twitter-guardrails.test.ts +5 -1
- package/src/__tests__/onboarding-template-contract.test.ts +0 -10
- package/src/__tests__/provider-fail-open-selection.test.ts +12 -2
- package/src/__tests__/send-endpoint-busy.test.ts +0 -3
- package/src/__tests__/session-confirmation-signals.test.ts +7 -45
- package/src/__tests__/starter-task-flow.test.ts +9 -19
- package/src/__tests__/system-prompt.test.ts +3 -4
- package/src/__tests__/trust-store.test.ts +4 -4
- package/src/__tests__/twitter-platform-proxy-client.test.ts +43 -18
- package/src/cli/commands/amazon/index.ts +4 -39
- package/src/cli/commands/amazon/session.ts +18 -26
- package/src/cli/commands/twitter/__tests__/cli-read-routing.test.ts +58 -196
- package/src/cli/commands/twitter/__tests__/cli-routing.test.ts +26 -186
- package/src/cli/commands/twitter/__tests__/oauth-client.test.ts +1 -47
- package/src/cli/commands/twitter/index.ts +95 -835
- package/src/cli/commands/twitter/oauth-client.ts +1 -35
- package/src/cli/commands/twitter/router.ts +70 -115
- package/src/cli/commands/twitter/types.ts +30 -0
- package/src/cli/reference.ts +2 -2
- package/src/config/bundled-skills/amazon/SKILL.md +0 -1
- package/src/config/bundled-skills/app-builder/SKILL.md +0 -6
- package/src/config/bundled-skills/app-builder/TOOLS.json +0 -4
- package/src/config/bundled-skills/doordash/SKILL.md +0 -1
- package/src/config/bundled-skills/doordash/__tests__/doordash-session.test.ts +1 -82
- package/src/config/bundled-skills/doordash/doordash-cli.ts +17 -28
- package/src/config/bundled-skills/doordash/lib/session.ts +21 -17
- package/src/config/bundled-skills/twitter/SKILL.md +53 -166
- package/src/config/feature-flag-registry.json +8 -0
- package/src/daemon/handlers/session-history.ts +41 -9
- package/src/daemon/lifecycle.ts +4 -17
- package/src/daemon/message-types/apps.ts +0 -25
- package/src/daemon/message-types/integrations.ts +1 -7
- package/src/daemon/message-types/sessions.ts +6 -1
- package/src/daemon/message-types/surfaces.ts +2 -0
- package/src/daemon/ride-shotgun-handler.ts +33 -1
- package/src/daemon/seed-files.ts +3 -27
- package/src/daemon/server.ts +2 -18
- package/src/daemon/session-agent-loop-handlers.ts +24 -2
- package/src/daemon/session-runtime-assembly.ts +0 -7
- package/src/daemon/session-surfaces.ts +185 -33
- package/src/daemon/session.ts +2 -28
- package/src/memory/app-store.ts +0 -18
- package/src/memory/schema/infrastructure.ts +0 -8
- package/src/permissions/defaults.ts +3 -3
- package/src/prompts/system-prompt.ts +4 -5
- package/src/prompts/templates/BOOTSTRAP.md +0 -3
- package/src/providers/registry.ts +2 -4
- package/src/runtime/auth/__tests__/guard-tests.test.ts +1 -0
- package/src/runtime/auth/__tests__/scopes.test.ts +2 -1
- package/src/runtime/auth/route-policy.ts +0 -4
- package/src/runtime/auth/scopes.ts +1 -0
- package/src/runtime/auth/token-service.ts +1 -1
- package/src/runtime/http-types.ts +10 -0
- package/src/runtime/middleware/error-handler.ts +14 -1
- package/src/runtime/routes/app-management-routes.ts +61 -64
- package/src/runtime/routes/brain-graph/brain-graph.html +1845 -0
- package/src/runtime/routes/brain-graph-routes.ts +4 -42
- package/src/runtime/routes/conversation-routes.ts +9 -6
- package/src/runtime/routes/diagnostics-routes.ts +91 -14
- package/src/runtime/routes/settings-routes.ts +3 -93
- package/src/tools/AGENTS.md +38 -0
- package/src/tools/apps/executors.ts +0 -6
- package/src/tools/document/editor-template.ts +10 -8
- package/src/twitter/platform-proxy-client.ts +6 -3
- package/src/util/errors.ts +12 -0
- package/src/__tests__/home-base-bootstrap.test.ts +0 -84
- package/src/__tests__/prebuilt-home-base-seed.test.ts +0 -79
- package/src/cli/commands/twitter/__tests__/cli-error-shaping.test.ts +0 -265
- package/src/cli/commands/twitter/client.ts +0 -989
- package/src/cli/commands/twitter/session.ts +0 -121
- package/src/home-base/app-link-store.ts +0 -78
- package/src/home-base/bootstrap.ts +0 -74
- package/src/home-base/prebuilt/brain-graph.html +0 -1483
- package/src/home-base/prebuilt/index.html +0 -702
- package/src/home-base/prebuilt/seed-metadata.json +0 -21
- package/src/home-base/prebuilt/seed.ts +0 -122
- package/src/home-base/prebuilt-home-base-updater.ts +0 -36
- package/src/util/cookie-session.ts +0 -98
|
@@ -2,42 +2,17 @@
|
|
|
2
2
|
* OAuth-backed Twitter API client.
|
|
3
3
|
*
|
|
4
4
|
* Accepts an OAuth2 Bearer token as a parameter and uses it to execute
|
|
5
|
-
* Twitter API v2 operations directly
|
|
6
|
-
* Currently supports post and reply; all other operations fall back to the
|
|
7
|
-
* browser-based CDP client.
|
|
5
|
+
* Twitter API v2 operations directly (post and reply).
|
|
8
6
|
*/
|
|
9
7
|
|
|
10
8
|
const TWITTER_API_BASE = "https://api.x.com/2";
|
|
11
9
|
|
|
12
|
-
/** Operations that the OAuth client can handle natively. */
|
|
13
|
-
const SUPPORTED_OPERATIONS = new Set(["post", "reply"]);
|
|
14
|
-
|
|
15
10
|
export interface OAuthPostResult {
|
|
16
11
|
tweetId: string;
|
|
17
12
|
text: string;
|
|
18
13
|
url?: string;
|
|
19
14
|
}
|
|
20
15
|
|
|
21
|
-
export interface OAuthOperationError {
|
|
22
|
-
message: string;
|
|
23
|
-
suggestFallback: boolean;
|
|
24
|
-
fallbackPath: "browser";
|
|
25
|
-
operation: string;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export class UnsupportedOAuthOperationError extends Error {
|
|
29
|
-
public readonly suggestFallback = true;
|
|
30
|
-
public readonly fallbackPath = "browser" as const;
|
|
31
|
-
public readonly operation: string;
|
|
32
|
-
constructor(operation: string) {
|
|
33
|
-
super(
|
|
34
|
-
`The "${operation}" operation is not available via the OAuth API. Use the browser path instead.`,
|
|
35
|
-
);
|
|
36
|
-
this.name = "UnsupportedOAuthOperationError";
|
|
37
|
-
this.operation = operation;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
16
|
/**
|
|
42
17
|
* Post a tweet (or reply) using OAuth2 Bearer token authentication.
|
|
43
18
|
*
|
|
@@ -83,12 +58,3 @@ export async function oauthPostTweet(
|
|
|
83
58
|
export function oauthIsAvailable(oauthToken: string | undefined): boolean {
|
|
84
59
|
return oauthToken != null && oauthToken.length > 0;
|
|
85
60
|
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Check whether a given operation is supported via the OAuth API path.
|
|
89
|
-
* Only `post` and `reply` are currently supported; everything else
|
|
90
|
-
* (timeline, search, bookmarks, etc.) requires the browser path.
|
|
91
|
-
*/
|
|
92
|
-
export function oauthSupportsOperation(operation: string): boolean {
|
|
93
|
-
return SUPPORTED_OPERATIONS.has(operation);
|
|
94
|
-
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* Selects managed proxy
|
|
2
|
+
* Mode router for Twitter operations.
|
|
3
|
+
* Selects managed proxy or OAuth path based on the caller-provided integration mode.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import {
|
|
@@ -11,47 +11,27 @@ import {
|
|
|
11
11
|
searchRecentTweets as managedSearchRecentTweets,
|
|
12
12
|
TwitterProxyError,
|
|
13
13
|
} from "../../../twitter/platform-proxy-client.js";
|
|
14
|
-
import
|
|
15
|
-
import {
|
|
16
|
-
getTweetDetail as browserGetTweetDetail,
|
|
17
|
-
getUserByScreenName as browserGetUserByScreenName,
|
|
18
|
-
getUserTweets as browserGetUserTweets,
|
|
19
|
-
postTweet as browserPostTweet,
|
|
20
|
-
searchTweets as browserSearchTweets,
|
|
21
|
-
SessionExpiredError,
|
|
22
|
-
} from "./client.js";
|
|
23
|
-
import {
|
|
24
|
-
oauthIsAvailable,
|
|
25
|
-
oauthPostTweet,
|
|
26
|
-
oauthSupportsOperation,
|
|
27
|
-
} from "./oauth-client.js";
|
|
14
|
+
import { oauthIsAvailable, oauthPostTweet } from "./oauth-client.js";
|
|
15
|
+
import type { PostTweetResult, TweetEntry, UserInfo } from "./types.js";
|
|
28
16
|
|
|
29
|
-
export type
|
|
17
|
+
export type TwitterMode = "oauth" | "managed";
|
|
30
18
|
|
|
31
19
|
export interface RoutedResult<T> {
|
|
32
20
|
result: T;
|
|
33
|
-
pathUsed:
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export interface RoutedError {
|
|
37
|
-
message: string;
|
|
38
|
-
pathUsed: TwitterStrategy;
|
|
39
|
-
suggestAlternative?: TwitterStrategy;
|
|
40
|
-
alternativeSetupHint?: string;
|
|
21
|
+
pathUsed: TwitterMode;
|
|
41
22
|
}
|
|
42
23
|
|
|
43
24
|
export async function routedPostTweet(
|
|
44
25
|
text: string,
|
|
45
26
|
opts: {
|
|
46
27
|
inReplyToTweetId?: string;
|
|
47
|
-
|
|
28
|
+
mode: TwitterMode;
|
|
48
29
|
oauthToken?: string;
|
|
49
30
|
},
|
|
50
31
|
): Promise<RoutedResult<PostTweetResult>> {
|
|
51
|
-
const
|
|
52
|
-
const operation = opts.inReplyToTweetId ? "reply" : "post";
|
|
32
|
+
const mode = opts.mode;
|
|
53
33
|
|
|
54
|
-
if (
|
|
34
|
+
if (mode === "managed") {
|
|
55
35
|
// Route through platform proxy — the platform holds the OAuth credentials
|
|
56
36
|
try {
|
|
57
37
|
const response = await managedPostTweet(text, {
|
|
@@ -91,16 +71,15 @@ export async function routedPostTweet(
|
|
|
91
71
|
}
|
|
92
72
|
}
|
|
93
73
|
|
|
94
|
-
if (
|
|
74
|
+
if (mode === "oauth") {
|
|
95
75
|
// User explicitly wants OAuth
|
|
96
76
|
if (!oauthIsAvailable(opts.oauthToken)) {
|
|
97
77
|
throw Object.assign(
|
|
98
78
|
new Error(
|
|
99
|
-
"OAuth is not configured.
|
|
79
|
+
"OAuth is not configured. Connect your X developer credentials to set up OAuth.",
|
|
100
80
|
),
|
|
101
81
|
{
|
|
102
82
|
pathUsed: "oauth" as const,
|
|
103
|
-
suggestAlternative: "browser" as const,
|
|
104
83
|
},
|
|
105
84
|
);
|
|
106
85
|
}
|
|
@@ -118,68 +97,9 @@ export async function routedPostTweet(
|
|
|
118
97
|
};
|
|
119
98
|
}
|
|
120
99
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const result = await browserPostTweet(text, {
|
|
125
|
-
inReplyToTweetId: opts.inReplyToTweetId,
|
|
126
|
-
});
|
|
127
|
-
return { result, pathUsed: "browser" };
|
|
128
|
-
} catch (err) {
|
|
129
|
-
if (err instanceof SessionExpiredError) {
|
|
130
|
-
throw Object.assign(err, {
|
|
131
|
-
pathUsed: "browser" as const,
|
|
132
|
-
suggestAlternative: "oauth" as const,
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
throw err;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// auto strategy: try OAuth first if available and supported, fallback to browser
|
|
140
|
-
let oauthError: Error | undefined;
|
|
141
|
-
if (oauthIsAvailable(opts.oauthToken) && oauthSupportsOperation(operation)) {
|
|
142
|
-
try {
|
|
143
|
-
const result = await oauthPostTweet(text, {
|
|
144
|
-
inReplyToTweetId: opts.inReplyToTweetId,
|
|
145
|
-
oauthToken: opts.oauthToken!,
|
|
146
|
-
});
|
|
147
|
-
return {
|
|
148
|
-
result: {
|
|
149
|
-
tweetId: result.tweetId,
|
|
150
|
-
text: result.text,
|
|
151
|
-
url: result.url ?? `https://x.com/i/status/${result.tweetId}`,
|
|
152
|
-
},
|
|
153
|
-
pathUsed: "oauth",
|
|
154
|
-
};
|
|
155
|
-
} catch (err) {
|
|
156
|
-
oauthError = err instanceof Error ? err : new Error(String(err));
|
|
157
|
-
// Fall through to browser
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Fallback to browser
|
|
162
|
-
try {
|
|
163
|
-
const result = await browserPostTweet(text, {
|
|
164
|
-
inReplyToTweetId: opts.inReplyToTweetId,
|
|
165
|
-
});
|
|
166
|
-
return { result, pathUsed: "browser" };
|
|
167
|
-
} catch (err) {
|
|
168
|
-
if (err instanceof SessionExpiredError) {
|
|
169
|
-
throw Object.assign(err, {
|
|
170
|
-
pathUsed: "auto" as const,
|
|
171
|
-
oauthError: oauthError?.message,
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
if (oauthError) {
|
|
175
|
-
const browserError = err instanceof Error ? err : new Error(String(err));
|
|
176
|
-
throw Object.assign(browserError, {
|
|
177
|
-
pathUsed: "auto" as const,
|
|
178
|
-
oauthError: oauthError.message,
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
throw err;
|
|
182
|
-
}
|
|
100
|
+
// Exhaustive check — should never reach here
|
|
101
|
+
const _exhaustive: never = mode;
|
|
102
|
+
throw new Error(`Unknown mode: ${_exhaustive}`);
|
|
183
103
|
}
|
|
184
104
|
|
|
185
105
|
// ---------------------------------------------------------------------------
|
|
@@ -188,13 +108,13 @@ export async function routedPostTweet(
|
|
|
188
108
|
|
|
189
109
|
/**
|
|
190
110
|
* Look up a user by screen name.
|
|
191
|
-
* Managed mode uses GET /2/users/by/username/:username
|
|
111
|
+
* Managed mode uses GET /2/users/by/username/:username.
|
|
192
112
|
*/
|
|
193
113
|
export async function routedGetUserByScreenName(
|
|
194
114
|
screenName: string,
|
|
195
|
-
opts: {
|
|
115
|
+
opts: { mode: TwitterMode },
|
|
196
116
|
): Promise<RoutedResult<UserInfo>> {
|
|
197
|
-
if (opts.
|
|
117
|
+
if (opts.mode === "managed") {
|
|
198
118
|
try {
|
|
199
119
|
const response = await managedGetUserByUsername(screenName, {
|
|
200
120
|
"user.fields": "id,name,username",
|
|
@@ -228,21 +148,29 @@ export async function routedGetUserByScreenName(
|
|
|
228
148
|
}
|
|
229
149
|
}
|
|
230
150
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
151
|
+
if (opts.mode === "oauth") {
|
|
152
|
+
throw Object.assign(
|
|
153
|
+
new Error(
|
|
154
|
+
"Read operations are not supported via OAuth. Use managed mode for read access.",
|
|
155
|
+
),
|
|
156
|
+
{ pathUsed: "oauth" as const },
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const _exhaustive: never = opts.mode;
|
|
161
|
+
throw new Error(`Unknown mode: ${_exhaustive}`);
|
|
234
162
|
}
|
|
235
163
|
|
|
236
164
|
/**
|
|
237
165
|
* Fetch a user's recent tweets.
|
|
238
|
-
* Managed mode uses GET /2/users/:id/tweets
|
|
166
|
+
* Managed mode uses GET /2/users/:id/tweets.
|
|
239
167
|
*/
|
|
240
168
|
export async function routedGetUserTweets(
|
|
241
169
|
userId: string,
|
|
242
170
|
count: number,
|
|
243
|
-
opts: {
|
|
171
|
+
opts: { mode: TwitterMode },
|
|
244
172
|
): Promise<RoutedResult<TweetEntry[]>> {
|
|
245
|
-
if (opts.
|
|
173
|
+
if (opts.mode === "managed") {
|
|
246
174
|
try {
|
|
247
175
|
const response = await managedGetUserTweets(userId, {
|
|
248
176
|
max_results: String(Math.min(count, 100)),
|
|
@@ -269,19 +197,28 @@ export async function routedGetUserTweets(
|
|
|
269
197
|
}
|
|
270
198
|
}
|
|
271
199
|
|
|
272
|
-
|
|
273
|
-
|
|
200
|
+
if (opts.mode === "oauth") {
|
|
201
|
+
throw Object.assign(
|
|
202
|
+
new Error(
|
|
203
|
+
"Read operations are not supported via OAuth. Use managed mode for read access.",
|
|
204
|
+
),
|
|
205
|
+
{ pathUsed: "oauth" as const },
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const _exhaustive: never = opts.mode;
|
|
210
|
+
throw new Error(`Unknown mode: ${_exhaustive}`);
|
|
274
211
|
}
|
|
275
212
|
|
|
276
213
|
/**
|
|
277
214
|
* Fetch a single tweet by ID.
|
|
278
|
-
* Managed mode uses GET /2/tweets/:id
|
|
215
|
+
* Managed mode uses GET /2/tweets/:id.
|
|
279
216
|
*/
|
|
280
217
|
export async function routedGetTweetDetail(
|
|
281
218
|
tweetId: string,
|
|
282
|
-
opts: {
|
|
219
|
+
opts: { mode: TwitterMode },
|
|
283
220
|
): Promise<RoutedResult<TweetEntry[]>> {
|
|
284
|
-
if (opts.
|
|
221
|
+
if (opts.mode === "managed") {
|
|
285
222
|
try {
|
|
286
223
|
const response = await managedGetTweet(tweetId, {
|
|
287
224
|
"tweet.fields": "id,text,created_at,author_id,conversation_id",
|
|
@@ -340,20 +277,29 @@ export async function routedGetTweetDetail(
|
|
|
340
277
|
}
|
|
341
278
|
}
|
|
342
279
|
|
|
343
|
-
|
|
344
|
-
|
|
280
|
+
if (opts.mode === "oauth") {
|
|
281
|
+
throw Object.assign(
|
|
282
|
+
new Error(
|
|
283
|
+
"Read operations are not supported via OAuth. Use managed mode for read access.",
|
|
284
|
+
),
|
|
285
|
+
{ pathUsed: "oauth" as const },
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const _exhaustive: never = opts.mode;
|
|
290
|
+
throw new Error(`Unknown mode: ${_exhaustive}`);
|
|
345
291
|
}
|
|
346
292
|
|
|
347
293
|
/**
|
|
348
294
|
* Search tweets.
|
|
349
|
-
* Managed mode uses GET /2/tweets/search/recent
|
|
295
|
+
* Managed mode uses GET /2/tweets/search/recent.
|
|
350
296
|
*/
|
|
351
297
|
export async function routedSearchTweets(
|
|
352
298
|
query: string,
|
|
353
299
|
product: "Top" | "Latest" | "People" | "Media",
|
|
354
|
-
opts: {
|
|
300
|
+
opts: { mode: TwitterMode },
|
|
355
301
|
): Promise<RoutedResult<TweetEntry[]>> {
|
|
356
|
-
if (opts.
|
|
302
|
+
if (opts.mode === "managed") {
|
|
357
303
|
if (product === "People" || product === "Media") {
|
|
358
304
|
throw Object.assign(
|
|
359
305
|
new Error(
|
|
@@ -391,6 +337,15 @@ export async function routedSearchTweets(
|
|
|
391
337
|
}
|
|
392
338
|
}
|
|
393
339
|
|
|
394
|
-
|
|
395
|
-
|
|
340
|
+
if (opts.mode === "oauth") {
|
|
341
|
+
throw Object.assign(
|
|
342
|
+
new Error(
|
|
343
|
+
"Read operations are not supported via OAuth. Use managed mode for read access.",
|
|
344
|
+
),
|
|
345
|
+
{ pathUsed: "oauth" as const },
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const _exhaustive: never = opts.mode;
|
|
350
|
+
throw new Error(`Unknown mode: ${_exhaustive}`);
|
|
396
351
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public types shared across the Twitter CLI module.
|
|
3
|
+
* Extracted from the former browser CDP client.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface PostTweetResult {
|
|
7
|
+
tweetId: string;
|
|
8
|
+
text: string;
|
|
9
|
+
url: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface UserInfo {
|
|
13
|
+
userId: string;
|
|
14
|
+
screenName: string;
|
|
15
|
+
name: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface TweetEntry {
|
|
19
|
+
tweetId: string;
|
|
20
|
+
text: string;
|
|
21
|
+
url: string;
|
|
22
|
+
createdAt: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface NotificationEntry {
|
|
26
|
+
id: string;
|
|
27
|
+
message: string;
|
|
28
|
+
timestamp: string;
|
|
29
|
+
url?: string;
|
|
30
|
+
}
|
package/src/cli/reference.ts
CHANGED
|
@@ -24,7 +24,7 @@ Commands:
|
|
|
24
24
|
email [options] Email operations (provider-agnostic)
|
|
25
25
|
contacts [options] Manage and query the contact graph
|
|
26
26
|
channel-verification-sessions [options] Manage channel verification sessions
|
|
27
|
-
amazon [options] Shop on Amazon and Amazon Fresh. Requires
|
|
27
|
+
amazon [options] Shop on Amazon and Amazon Fresh. Requires an active session (use "refresh" to authenticate).
|
|
28
28
|
autonomy [options] View and configure autonomy tiers
|
|
29
29
|
completions <shell> Generate shell completion script (e.g. assistant completions bash >> ~/.bashrc)
|
|
30
30
|
notifications [options] Send and inspect notifications through the unified notification router
|
|
@@ -32,7 +32,7 @@ Commands:
|
|
|
32
32
|
oauth [options] Manage OAuth tokens for connected integrations
|
|
33
33
|
skills Browse and install skills from the Vellum catalog
|
|
34
34
|
browser Browser automation, extension relay, and Chrome CDP management
|
|
35
|
-
x|twitter [options] Post on X and manage connections. Supports
|
|
35
|
+
x|twitter [options] Post on X and manage connections. Supports managed (platform proxy) and OAuth (official API) paths.
|
|
36
36
|
map [options] <domain> Auto-navigate a domain and produce a deduplicated API map. Launches Chrome with CDP, starts a Ride Shotgun learn session, then analyzes captured network traffic.
|
|
37
37
|
sequence [options] Manage email sequences
|
|
38
38
|
`;
|
|
@@ -86,7 +86,6 @@ Session capture (`assistant amazon refresh`) and session checks (`assistant amaz
|
|
|
86
86
|
```
|
|
87
87
|
assistant amazon status --json # Check if logged in
|
|
88
88
|
assistant amazon refresh --json # Capture fresh session via Ride Shotgun
|
|
89
|
-
assistant amazon login --recording <path> # Import session from a recording file
|
|
90
89
|
assistant amazon logout # Clear session
|
|
91
90
|
|
|
92
91
|
assistant amazon search "<query>" [--fresh] [--limit <n>] --json
|
|
@@ -533,12 +533,6 @@ When the user wants to triage or bulk-act on items, generate an interactive UI w
|
|
|
533
533
|
4. Execute tools, update UI with `ui_update`, show feedback via `widgets.toast()`
|
|
534
534
|
5. Use `window.vellum.confirm()` for destructive actions
|
|
535
535
|
|
|
536
|
-
## Home Base
|
|
537
|
-
|
|
538
|
-
Home Base starts from a prebuilt scaffold. When updating, preserve required task-lane anchors and apply changes through `app_file_edit` or `app_file_write`.
|
|
539
|
-
|
|
540
|
-
Home Base buttons send prefilled natural-language prompts through `vellum.sendAction`. Treat these as normal user messages.
|
|
541
|
-
|
|
542
536
|
## External Links
|
|
543
537
|
|
|
544
538
|
Use `vellum.openLink(url, metadata)` to make items clickable. Construct deep-link URLs when possible. Include `metadata.provider` and `metadata.type` for context.
|
|
@@ -36,10 +36,6 @@
|
|
|
36
36
|
"type": "boolean",
|
|
37
37
|
"description": "When true (default), an inline preview card is shown in chat after creation. The app is NOT automatically opened in a workspace panel \u2014 users can open it explicitly via the 'Open App' button on the inline card."
|
|
38
38
|
},
|
|
39
|
-
"set_as_home_base": {
|
|
40
|
-
"type": "boolean",
|
|
41
|
-
"description": "Link this app as the user's Home Base dashboard. Defaults to false. When true, this app becomes the default Home Base shown on launch."
|
|
42
|
-
},
|
|
43
39
|
"preview": {
|
|
44
40
|
"type": "object",
|
|
45
41
|
"description": "Inline preview card shown in chat after creation. Always include this so users see a compact summary of what was built. Required fields: title.",
|
|
@@ -125,7 +125,6 @@ doordash cart add --store-id <id> --menu-id <id> --item-id <id> --item-name "Lat
|
|
|
125
125
|
```
|
|
126
126
|
doordash status --json # Check if logged in
|
|
127
127
|
doordash refresh --json # Capture fresh session via Ride Shotgun (auto-stops after login)
|
|
128
|
-
doordash login --recording <path> # Import session from a recording file manually
|
|
129
128
|
doordash logout --json # Clear session
|
|
130
129
|
doordash search "<query>" --json # Search restaurants
|
|
131
130
|
doordash menu <storeId> --json # Get store menu (auto-detects retail stores)
|
|
@@ -1,20 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { tmpdir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
5
2
|
|
|
6
3
|
import {
|
|
7
4
|
type DoorDashSession,
|
|
8
5
|
getCookieHeader,
|
|
9
6
|
getCsrfToken,
|
|
10
|
-
importFromRecording,
|
|
11
7
|
} from "../lib/session.js";
|
|
12
8
|
|
|
13
|
-
// Override VELLUM_DATA_DIR to use a temp directory during tests.
|
|
14
|
-
// session.ts reads process.env.VELLUM_DATA_DIR directly.
|
|
15
|
-
const TEST_DIR = join(tmpdir(), `vellum-dd-test-${process.pid}`);
|
|
16
|
-
let originalDataDir: string | undefined;
|
|
17
|
-
|
|
18
9
|
function makeCookie(
|
|
19
10
|
name: string,
|
|
20
11
|
value: string,
|
|
@@ -81,75 +72,3 @@ describe("DoorDash session helpers", () => {
|
|
|
81
72
|
});
|
|
82
73
|
});
|
|
83
74
|
});
|
|
84
|
-
|
|
85
|
-
describe("DoorDash session persistence", () => {
|
|
86
|
-
// These tests exercise the real loadSession/saveSession/clearSession
|
|
87
|
-
// by pointing VELLUM_DATA_DIR at a temp directory and testing via
|
|
88
|
-
// importFromRecording which exercises save+load.
|
|
89
|
-
|
|
90
|
-
beforeEach(() => {
|
|
91
|
-
originalDataDir = process.env.VELLUM_DATA_DIR;
|
|
92
|
-
process.env.VELLUM_DATA_DIR = TEST_DIR;
|
|
93
|
-
mkdirSync(TEST_DIR, { recursive: true });
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
afterEach(() => {
|
|
97
|
-
if (originalDataDir === undefined) {
|
|
98
|
-
delete process.env.VELLUM_DATA_DIR;
|
|
99
|
-
} else {
|
|
100
|
-
process.env.VELLUM_DATA_DIR = originalDataDir;
|
|
101
|
-
}
|
|
102
|
-
if (existsSync(TEST_DIR)) {
|
|
103
|
-
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
describe("importFromRecording", () => {
|
|
108
|
-
it("throws when the recording file does not exist", () => {
|
|
109
|
-
expect(() => importFromRecording("/nonexistent/recording.json")).toThrow(
|
|
110
|
-
"Recording not found",
|
|
111
|
-
);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it("throws when the recording contains no cookies", () => {
|
|
115
|
-
const recordingPath = join(TEST_DIR, "empty-recording.json");
|
|
116
|
-
writeFileSync(
|
|
117
|
-
recordingPath,
|
|
118
|
-
JSON.stringify({
|
|
119
|
-
id: "rec-empty",
|
|
120
|
-
startedAt: 0,
|
|
121
|
-
endedAt: 1,
|
|
122
|
-
targetDomain: "doordash.com",
|
|
123
|
-
networkEntries: [],
|
|
124
|
-
cookies: [],
|
|
125
|
-
observations: [],
|
|
126
|
-
}),
|
|
127
|
-
);
|
|
128
|
-
expect(() => importFromRecording(recordingPath)).toThrow(
|
|
129
|
-
"Recording contains no cookies",
|
|
130
|
-
);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it("successfully imports a recording with cookies", () => {
|
|
134
|
-
const recordingPath = join(TEST_DIR, "valid-recording.json");
|
|
135
|
-
writeFileSync(
|
|
136
|
-
recordingPath,
|
|
137
|
-
JSON.stringify({
|
|
138
|
-
id: "rec-valid",
|
|
139
|
-
startedAt: 0,
|
|
140
|
-
endedAt: 1,
|
|
141
|
-
targetDomain: "doordash.com",
|
|
142
|
-
networkEntries: [],
|
|
143
|
-
cookies: [makeCookie("session_id", "xyz")],
|
|
144
|
-
observations: [],
|
|
145
|
-
}),
|
|
146
|
-
);
|
|
147
|
-
const session = importFromRecording(recordingPath);
|
|
148
|
-
expect(session.cookies).toHaveLength(1);
|
|
149
|
-
expect(session.cookies[0].name).toBe("session_id");
|
|
150
|
-
expect(session.cookies[0].value).toBe("xyz");
|
|
151
|
-
expect(session.recordingId).toBe("rec-valid");
|
|
152
|
-
expect(session.importedAt).toBeTruthy();
|
|
153
|
-
});
|
|
154
|
-
});
|
|
155
|
-
});
|
|
@@ -33,11 +33,16 @@ import {
|
|
|
33
33
|
import { extractQueries, saveQueries } from "./lib/query-extractor.js";
|
|
34
34
|
import {
|
|
35
35
|
clearSession,
|
|
36
|
-
|
|
36
|
+
importFromCredentialStore,
|
|
37
37
|
loadSession,
|
|
38
38
|
} from "./lib/session.js";
|
|
39
39
|
import { NetworkRecorder } from "./lib/shared/network-recorder.js";
|
|
40
|
-
import {
|
|
40
|
+
import {
|
|
41
|
+
buildDaemonUrl,
|
|
42
|
+
getDataDir,
|
|
43
|
+
getHttpPort,
|
|
44
|
+
readHttpToken,
|
|
45
|
+
} from "./lib/shared/platform.js";
|
|
41
46
|
import { loadRecording, saveRecording } from "./lib/shared/recording-store.js";
|
|
42
47
|
import type { SessionRecording } from "./lib/shared/recording-types.js";
|
|
43
48
|
|
|
@@ -94,27 +99,10 @@ export function registerDoordashCommand(program: Command): void {
|
|
|
94
99
|
const dd = program
|
|
95
100
|
.command("doordash")
|
|
96
101
|
.description(
|
|
97
|
-
|
|
102
|
+
'Order food from DoorDash. Requires an active session (use "refresh" to authenticate).',
|
|
98
103
|
)
|
|
99
104
|
.option("--json", "Machine-readable JSON output");
|
|
100
105
|
|
|
101
|
-
// =========================================================================
|
|
102
|
-
// login — import session from a recording
|
|
103
|
-
// =========================================================================
|
|
104
|
-
dd.command("login")
|
|
105
|
-
.description("Import a DoorDash session from a Ride Shotgun recording")
|
|
106
|
-
.requiredOption("--recording <path>", "Path to the recording JSON file")
|
|
107
|
-
.action(async (opts: { recording: string }, cmd: Command) => {
|
|
108
|
-
await run(cmd, async () => {
|
|
109
|
-
const session = importFromRecording(opts.recording);
|
|
110
|
-
return {
|
|
111
|
-
message: "Session imported successfully",
|
|
112
|
-
cookieCount: session.cookies.length,
|
|
113
|
-
recordingId: session.recordingId,
|
|
114
|
-
};
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
|
|
118
106
|
// =========================================================================
|
|
119
107
|
// logout — clear saved session
|
|
120
108
|
// =========================================================================
|
|
@@ -148,13 +136,13 @@ export function registerDoordashCommand(program: Command): void {
|
|
|
148
136
|
}
|
|
149
137
|
|
|
150
138
|
const result = await startLearnSession(duration);
|
|
151
|
-
if (result.
|
|
152
|
-
const session =
|
|
139
|
+
if (result.recordingId) {
|
|
140
|
+
const session = await importFromCredentialStore("doordash.com");
|
|
153
141
|
|
|
154
142
|
// Also extract and save captured queries for self-healing
|
|
155
143
|
let queriesCaptured = 0;
|
|
156
144
|
try {
|
|
157
|
-
const recording = loadRecording(result.recordingId
|
|
145
|
+
const recording = loadRecording(result.recordingId);
|
|
158
146
|
if (recording) {
|
|
159
147
|
const queries = extractQueries(recording);
|
|
160
148
|
if (queries.length > 0) {
|
|
@@ -188,7 +176,7 @@ export function registerDoordashCommand(program: Command): void {
|
|
|
188
176
|
output(
|
|
189
177
|
{
|
|
190
178
|
ok: false,
|
|
191
|
-
error: "Recording completed but no recording
|
|
179
|
+
error: "Recording completed but no recording ID returned",
|
|
192
180
|
recordingId: result.recordingId,
|
|
193
181
|
},
|
|
194
182
|
json,
|
|
@@ -1020,7 +1008,10 @@ async function startLearnSession(
|
|
|
1020
1008
|
// Poll session status to detect failures early
|
|
1021
1009
|
try {
|
|
1022
1010
|
const fetchAbort = AbortSignal.timeout(10_000);
|
|
1023
|
-
const statusRes = await fetch(statusUrl, {
|
|
1011
|
+
const statusRes = await fetch(statusUrl, {
|
|
1012
|
+
headers,
|
|
1013
|
+
signal: fetchAbort,
|
|
1014
|
+
});
|
|
1024
1015
|
if (statusRes.ok) {
|
|
1025
1016
|
const status = (await statusRes.json()) as {
|
|
1026
1017
|
status: string;
|
|
@@ -1073,9 +1064,7 @@ async function startLearnSession(
|
|
|
1073
1064
|
|
|
1074
1065
|
// Completed but no recordingId — cannot correlate
|
|
1075
1066
|
reject(
|
|
1076
|
-
new Error(
|
|
1077
|
-
"Learn session completed but no recording was saved.",
|
|
1078
|
-
),
|
|
1067
|
+
new Error("Learn session completed but no recording was saved."),
|
|
1079
1068
|
);
|
|
1080
1069
|
return;
|
|
1081
1070
|
}
|