@askjo/camoufox-browser 1.0.2 → 1.0.5
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.json +39 -1
- package/plugin.ts +228 -189
- package/.env.bak +0 -4
- package/.github/workflows/deploy.yml +0 -21
- package/AGENTS.md +0 -153
- package/experimental/chromium/Dockerfile +0 -35
- package/experimental/chromium/README.md +0 -47
- package/experimental/chromium/run.sh +0 -24
- package/experimental/chromium/server.js +0 -812
- package/fly.toml +0 -29
- package/jest.config.js +0 -41
- package/tests/e2e/concurrency.test.js +0 -103
- package/tests/e2e/formSubmission.test.js +0 -129
- package/tests/e2e/macroNavigation.test.js +0 -92
- package/tests/e2e/navigation.test.js +0 -128
- package/tests/e2e/scroll.test.js +0 -81
- package/tests/e2e/snapshotLinks.test.js +0 -141
- package/tests/e2e/tabLifecycle.test.js +0 -149
- package/tests/e2e/typingEnter.test.js +0 -147
- package/tests/helpers/client.js +0 -222
- package/tests/helpers/startJoBrowser.js +0 -95
- package/tests/helpers/testSite.js +0 -238
- package/tests/live/googleSearch.test.js +0 -93
- package/tests/live/macroExpansion.test.js +0 -132
- package/tests/unit/macros.test.js +0 -123
package/package.json
CHANGED
|
@@ -1,8 +1,46 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askjo/camoufox-browser",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "Headless browser automation server and OpenClaw plugin for AI agents - anti-detection, element refs, and session isolation",
|
|
5
5
|
"main": "server-camoufox.js",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Jo Inc <hello@askjo.ai>",
|
|
8
|
+
"homepage": "https://github.com/jo-inc/camoufox-browser#readme",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/jo-inc/camoufox-browser.git"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/jo-inc/camoufox-browser/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"browser",
|
|
18
|
+
"automation",
|
|
19
|
+
"headless",
|
|
20
|
+
"scraping",
|
|
21
|
+
"camoufox",
|
|
22
|
+
"anti-detection",
|
|
23
|
+
"ai-agent",
|
|
24
|
+
"openclaw",
|
|
25
|
+
"clawdbot",
|
|
26
|
+
"moltbot",
|
|
27
|
+
"playwright",
|
|
28
|
+
"firefox"
|
|
29
|
+
],
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=18"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"server-camoufox.js",
|
|
35
|
+
"lib/",
|
|
36
|
+
"plugin.ts",
|
|
37
|
+
"openclaw.plugin.json",
|
|
38
|
+
"run-camoufox.sh",
|
|
39
|
+
"Dockerfile.camoufox",
|
|
40
|
+
"SKILL.md",
|
|
41
|
+
"README.md",
|
|
42
|
+
"LICENSE"
|
|
43
|
+
],
|
|
6
44
|
"openclaw": {
|
|
7
45
|
"extensions": ["plugin.ts"]
|
|
8
46
|
},
|
package/plugin.ts
CHANGED
|
@@ -13,14 +13,20 @@ interface PluginConfig {
|
|
|
13
13
|
autoStart?: boolean;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
interface ToolResult {
|
|
17
|
+
content: Array<{ type: string; text: string }>;
|
|
18
|
+
}
|
|
19
|
+
|
|
16
20
|
interface PluginApi {
|
|
17
|
-
registerTool: (
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
registerTool: (
|
|
22
|
+
tool: {
|
|
23
|
+
name: string;
|
|
24
|
+
description: string;
|
|
25
|
+
parameters: object;
|
|
26
|
+
execute: (id: string, params: Record<string, unknown>) => Promise<ToolResult>;
|
|
27
|
+
},
|
|
28
|
+
options?: { optional?: boolean }
|
|
29
|
+
) => void;
|
|
24
30
|
registerCommand: (cmd: {
|
|
25
31
|
name: string;
|
|
26
32
|
description: string;
|
|
@@ -55,218 +61,251 @@ async function fetchApi(
|
|
|
55
61
|
return res.json();
|
|
56
62
|
}
|
|
57
63
|
|
|
64
|
+
function toToolResult(data: unknown): ToolResult {
|
|
65
|
+
return {
|
|
66
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
58
70
|
export default function register(api: PluginApi) {
|
|
59
71
|
const baseUrl = api.config.url || "http://localhost:9377";
|
|
60
72
|
|
|
61
|
-
api.registerTool(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
73
|
+
api.registerTool(
|
|
74
|
+
{
|
|
75
|
+
name: "camoufox_create_tab",
|
|
76
|
+
description:
|
|
77
|
+
"Create a new browser tab. Returns tabId for subsequent operations.",
|
|
78
|
+
parameters: {
|
|
79
|
+
type: "object",
|
|
80
|
+
properties: {
|
|
81
|
+
userId: { type: "string", description: "User identifier for session isolation" },
|
|
82
|
+
listItemId: { type: "string", description: "Conversation/task identifier for tab grouping" },
|
|
83
|
+
url: { type: "string", description: "Initial URL to navigate to" },
|
|
84
|
+
},
|
|
85
|
+
required: ["userId", "url"],
|
|
86
|
+
},
|
|
87
|
+
async execute(_id, params) {
|
|
88
|
+
const result = await fetchApi(baseUrl, "/tabs", {
|
|
89
|
+
method: "POST",
|
|
90
|
+
body: JSON.stringify(params),
|
|
91
|
+
});
|
|
92
|
+
return toToolResult(result);
|
|
71
93
|
},
|
|
72
|
-
required: ["userId", "url"],
|
|
73
|
-
},
|
|
74
|
-
optional: true,
|
|
75
|
-
handler: async (params) => {
|
|
76
|
-
return fetchApi(baseUrl, "/tabs", {
|
|
77
|
-
method: "POST",
|
|
78
|
-
body: JSON.stringify(params),
|
|
79
|
-
});
|
|
80
94
|
},
|
|
81
|
-
|
|
95
|
+
{ optional: true }
|
|
96
|
+
);
|
|
82
97
|
|
|
83
|
-
api.registerTool(
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
98
|
+
api.registerTool(
|
|
99
|
+
{
|
|
100
|
+
name: "camoufox_snapshot",
|
|
101
|
+
description:
|
|
102
|
+
"Get accessibility snapshot of a page with element refs (e1, e2, etc.) for interaction.",
|
|
103
|
+
parameters: {
|
|
104
|
+
type: "object",
|
|
105
|
+
properties: {
|
|
106
|
+
tabId: { type: "string", description: "Tab identifier" },
|
|
107
|
+
userId: { type: "string", description: "User identifier" },
|
|
108
|
+
},
|
|
109
|
+
required: ["tabId", "userId"],
|
|
110
|
+
},
|
|
111
|
+
async execute(_id, params) {
|
|
112
|
+
const { tabId, userId } = params as { tabId: string; userId: string };
|
|
113
|
+
const result = await fetchApi(baseUrl, `/tabs/${tabId}/snapshot?userId=${userId}`);
|
|
114
|
+
return toToolResult(result);
|
|
92
115
|
},
|
|
93
|
-
required: ["tabId", "userId"],
|
|
94
|
-
},
|
|
95
|
-
optional: true,
|
|
96
|
-
handler: async (params) => {
|
|
97
|
-
const { tabId, userId } = params as { tabId: string; userId: string };
|
|
98
|
-
return fetchApi(baseUrl, `/tabs/${tabId}/snapshot?userId=${userId}`);
|
|
99
116
|
},
|
|
100
|
-
|
|
117
|
+
{ optional: true }
|
|
118
|
+
);
|
|
101
119
|
|
|
102
|
-
api.registerTool(
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
120
|
+
api.registerTool(
|
|
121
|
+
{
|
|
122
|
+
name: "camoufox_click",
|
|
123
|
+
description: "Click an element by ref (e.g., e1) or CSS selector.",
|
|
124
|
+
parameters: {
|
|
125
|
+
type: "object",
|
|
126
|
+
properties: {
|
|
127
|
+
tabId: { type: "string", description: "Tab identifier" },
|
|
128
|
+
userId: { type: "string", description: "User identifier" },
|
|
129
|
+
ref: { type: "string", description: "Element ref from snapshot (e.g., e1)" },
|
|
130
|
+
selector: { type: "string", description: "CSS selector (alternative to ref)" },
|
|
131
|
+
},
|
|
132
|
+
required: ["tabId", "userId"],
|
|
133
|
+
},
|
|
134
|
+
async execute(_id, params) {
|
|
135
|
+
const { tabId, ...body } = params as { tabId: string } & Record<string, unknown>;
|
|
136
|
+
const result = await fetchApi(baseUrl, `/tabs/${tabId}/click`, {
|
|
137
|
+
method: "POST",
|
|
138
|
+
body: JSON.stringify(body),
|
|
139
|
+
});
|
|
140
|
+
return toToolResult(result);
|
|
112
141
|
},
|
|
113
|
-
required: ["tabId", "userId"],
|
|
114
|
-
},
|
|
115
|
-
optional: true,
|
|
116
|
-
handler: async (params) => {
|
|
117
|
-
const { tabId, ...body } = params as { tabId: string } & Record<string, unknown>;
|
|
118
|
-
return fetchApi(baseUrl, `/tabs/${tabId}/click`, {
|
|
119
|
-
method: "POST",
|
|
120
|
-
body: JSON.stringify(body),
|
|
121
|
-
});
|
|
122
142
|
},
|
|
123
|
-
|
|
143
|
+
{ optional: true }
|
|
144
|
+
);
|
|
124
145
|
|
|
125
|
-
api.registerTool(
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
146
|
+
api.registerTool(
|
|
147
|
+
{
|
|
148
|
+
name: "camoufox_type",
|
|
149
|
+
description: "Type text into an element.",
|
|
150
|
+
parameters: {
|
|
151
|
+
type: "object",
|
|
152
|
+
properties: {
|
|
153
|
+
tabId: { type: "string", description: "Tab identifier" },
|
|
154
|
+
userId: { type: "string", description: "User identifier" },
|
|
155
|
+
ref: { type: "string", description: "Element ref from snapshot (e.g., e2)" },
|
|
156
|
+
selector: { type: "string", description: "CSS selector (alternative to ref)" },
|
|
157
|
+
text: { type: "string", description: "Text to type" },
|
|
158
|
+
pressEnter: { type: "boolean", description: "Press Enter after typing" },
|
|
159
|
+
},
|
|
160
|
+
required: ["tabId", "userId", "text"],
|
|
161
|
+
},
|
|
162
|
+
async execute(_id, params) {
|
|
163
|
+
const { tabId, ...body } = params as { tabId: string } & Record<string, unknown>;
|
|
164
|
+
const result = await fetchApi(baseUrl, `/tabs/${tabId}/type`, {
|
|
165
|
+
method: "POST",
|
|
166
|
+
body: JSON.stringify(body),
|
|
167
|
+
});
|
|
168
|
+
return toToolResult(result);
|
|
137
169
|
},
|
|
138
|
-
required: ["tabId", "userId", "text"],
|
|
139
|
-
},
|
|
140
|
-
optional: true,
|
|
141
|
-
handler: async (params) => {
|
|
142
|
-
const { tabId, ...body } = params as { tabId: string } & Record<string, unknown>;
|
|
143
|
-
return fetchApi(baseUrl, `/tabs/${tabId}/type`, {
|
|
144
|
-
method: "POST",
|
|
145
|
-
body: JSON.stringify(body),
|
|
146
|
-
});
|
|
147
170
|
},
|
|
148
|
-
|
|
171
|
+
{ optional: true }
|
|
172
|
+
);
|
|
149
173
|
|
|
150
|
-
api.registerTool(
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
174
|
+
api.registerTool(
|
|
175
|
+
{
|
|
176
|
+
name: "camoufox_navigate",
|
|
177
|
+
description:
|
|
178
|
+
"Navigate to a URL or use a search macro (@google_search, @youtube_search, etc.).",
|
|
179
|
+
parameters: {
|
|
180
|
+
type: "object",
|
|
181
|
+
properties: {
|
|
182
|
+
tabId: { type: "string", description: "Tab identifier" },
|
|
183
|
+
userId: { type: "string", description: "User identifier" },
|
|
184
|
+
url: { type: "string", description: "URL to navigate to" },
|
|
185
|
+
macro: {
|
|
186
|
+
type: "string",
|
|
187
|
+
description: "Search macro (e.g., @google_search, @youtube_search)",
|
|
188
|
+
enum: [
|
|
189
|
+
"@google_search",
|
|
190
|
+
"@youtube_search",
|
|
191
|
+
"@amazon_search",
|
|
192
|
+
"@reddit_search",
|
|
193
|
+
"@wikipedia_search",
|
|
194
|
+
"@twitter_search",
|
|
195
|
+
"@yelp_search",
|
|
196
|
+
"@spotify_search",
|
|
197
|
+
"@netflix_search",
|
|
198
|
+
"@linkedin_search",
|
|
199
|
+
"@instagram_search",
|
|
200
|
+
"@tiktok_search",
|
|
201
|
+
"@twitch_search",
|
|
202
|
+
],
|
|
203
|
+
},
|
|
204
|
+
query: { type: "string", description: "Search query (when using macro)" },
|
|
178
205
|
},
|
|
179
|
-
|
|
206
|
+
required: ["tabId", "userId"],
|
|
207
|
+
},
|
|
208
|
+
async execute(_id, params) {
|
|
209
|
+
const { tabId, ...body } = params as { tabId: string } & Record<string, unknown>;
|
|
210
|
+
const result = await fetchApi(baseUrl, `/tabs/${tabId}/navigate`, {
|
|
211
|
+
method: "POST",
|
|
212
|
+
body: JSON.stringify(body),
|
|
213
|
+
});
|
|
214
|
+
return toToolResult(result);
|
|
180
215
|
},
|
|
181
|
-
required: ["tabId", "userId"],
|
|
182
|
-
},
|
|
183
|
-
optional: true,
|
|
184
|
-
handler: async (params) => {
|
|
185
|
-
const { tabId, ...body } = params as { tabId: string } & Record<string, unknown>;
|
|
186
|
-
return fetchApi(baseUrl, `/tabs/${tabId}/navigate`, {
|
|
187
|
-
method: "POST",
|
|
188
|
-
body: JSON.stringify(body),
|
|
189
|
-
});
|
|
190
216
|
},
|
|
191
|
-
|
|
217
|
+
{ optional: true }
|
|
218
|
+
);
|
|
192
219
|
|
|
193
|
-
api.registerTool(
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
220
|
+
api.registerTool(
|
|
221
|
+
{
|
|
222
|
+
name: "camoufox_scroll",
|
|
223
|
+
description: "Scroll the page.",
|
|
224
|
+
parameters: {
|
|
225
|
+
type: "object",
|
|
226
|
+
properties: {
|
|
227
|
+
tabId: { type: "string", description: "Tab identifier" },
|
|
228
|
+
userId: { type: "string", description: "User identifier" },
|
|
229
|
+
direction: { type: "string", enum: ["up", "down", "left", "right"] },
|
|
230
|
+
amount: { type: "number", description: "Pixels to scroll" },
|
|
231
|
+
},
|
|
232
|
+
required: ["tabId", "userId", "direction"],
|
|
233
|
+
},
|
|
234
|
+
async execute(_id, params) {
|
|
235
|
+
const { tabId, ...body } = params as { tabId: string } & Record<string, unknown>;
|
|
236
|
+
const result = await fetchApi(baseUrl, `/tabs/${tabId}/scroll`, {
|
|
237
|
+
method: "POST",
|
|
238
|
+
body: JSON.stringify(body),
|
|
239
|
+
});
|
|
240
|
+
return toToolResult(result);
|
|
203
241
|
},
|
|
204
|
-
required: ["tabId", "userId", "direction"],
|
|
205
|
-
},
|
|
206
|
-
optional: true,
|
|
207
|
-
handler: async (params) => {
|
|
208
|
-
const { tabId, ...body } = params as { tabId: string } & Record<string, unknown>;
|
|
209
|
-
return fetchApi(baseUrl, `/tabs/${tabId}/scroll`, {
|
|
210
|
-
method: "POST",
|
|
211
|
-
body: JSON.stringify(body),
|
|
212
|
-
});
|
|
213
242
|
},
|
|
214
|
-
|
|
243
|
+
{ optional: true }
|
|
244
|
+
);
|
|
215
245
|
|
|
216
|
-
api.registerTool(
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
246
|
+
api.registerTool(
|
|
247
|
+
{
|
|
248
|
+
name: "camoufox_screenshot",
|
|
249
|
+
description: "Take a screenshot of the current page.",
|
|
250
|
+
parameters: {
|
|
251
|
+
type: "object",
|
|
252
|
+
properties: {
|
|
253
|
+
tabId: { type: "string", description: "Tab identifier" },
|
|
254
|
+
userId: { type: "string", description: "User identifier" },
|
|
255
|
+
},
|
|
256
|
+
required: ["tabId", "userId"],
|
|
257
|
+
},
|
|
258
|
+
async execute(_id, params) {
|
|
259
|
+
const { tabId, userId } = params as { tabId: string; userId: string };
|
|
260
|
+
const result = await fetchApi(baseUrl, `/tabs/${tabId}/screenshot?userId=${userId}`);
|
|
261
|
+
return toToolResult(result);
|
|
224
262
|
},
|
|
225
|
-
required: ["tabId", "userId"],
|
|
226
|
-
},
|
|
227
|
-
optional: true,
|
|
228
|
-
handler: async (params) => {
|
|
229
|
-
const { tabId, userId } = params as { tabId: string; userId: string };
|
|
230
|
-
return fetchApi(baseUrl, `/tabs/${tabId}/screenshot?userId=${userId}`);
|
|
231
263
|
},
|
|
232
|
-
|
|
264
|
+
{ optional: true }
|
|
265
|
+
);
|
|
233
266
|
|
|
234
|
-
api.registerTool(
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
267
|
+
api.registerTool(
|
|
268
|
+
{
|
|
269
|
+
name: "camoufox_close_tab",
|
|
270
|
+
description: "Close a browser tab.",
|
|
271
|
+
parameters: {
|
|
272
|
+
type: "object",
|
|
273
|
+
properties: {
|
|
274
|
+
tabId: { type: "string", description: "Tab identifier" },
|
|
275
|
+
userId: { type: "string", description: "User identifier" },
|
|
276
|
+
},
|
|
277
|
+
required: ["tabId", "userId"],
|
|
278
|
+
},
|
|
279
|
+
async execute(_id, params) {
|
|
280
|
+
const { tabId, userId } = params as { tabId: string; userId: string };
|
|
281
|
+
const result = await fetchApi(baseUrl, `/tabs/${tabId}?userId=${userId}`, {
|
|
282
|
+
method: "DELETE",
|
|
283
|
+
});
|
|
284
|
+
return toToolResult(result);
|
|
242
285
|
},
|
|
243
|
-
required: ["tabId", "userId"],
|
|
244
|
-
},
|
|
245
|
-
optional: true,
|
|
246
|
-
handler: async (params) => {
|
|
247
|
-
const { tabId, userId } = params as { tabId: string; userId: string };
|
|
248
|
-
return fetchApi(baseUrl, `/tabs/${tabId}?userId=${userId}`, {
|
|
249
|
-
method: "DELETE",
|
|
250
|
-
});
|
|
251
286
|
},
|
|
252
|
-
|
|
287
|
+
{ optional: true }
|
|
288
|
+
);
|
|
253
289
|
|
|
254
|
-
api.registerTool(
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
290
|
+
api.registerTool(
|
|
291
|
+
{
|
|
292
|
+
name: "camoufox_list_tabs",
|
|
293
|
+
description: "List all open tabs for a user.",
|
|
294
|
+
parameters: {
|
|
295
|
+
type: "object",
|
|
296
|
+
properties: {
|
|
297
|
+
userId: { type: "string", description: "User identifier" },
|
|
298
|
+
},
|
|
299
|
+
required: ["userId"],
|
|
300
|
+
},
|
|
301
|
+
async execute(_id, params) {
|
|
302
|
+
const { userId } = params as { userId: string };
|
|
303
|
+
const result = await fetchApi(baseUrl, `/tabs?userId=${userId}`);
|
|
304
|
+
return toToolResult(result);
|
|
261
305
|
},
|
|
262
|
-
required: ["userId"],
|
|
263
|
-
},
|
|
264
|
-
optional: true,
|
|
265
|
-
handler: async (params) => {
|
|
266
|
-
const { userId } = params as { userId: string };
|
|
267
|
-
return fetchApi(baseUrl, `/tabs?userId=${userId}`);
|
|
268
306
|
},
|
|
269
|
-
|
|
307
|
+
{ optional: true }
|
|
308
|
+
);
|
|
270
309
|
|
|
271
310
|
api.registerCommand({
|
|
272
311
|
name: "camoufox",
|
package/.env.bak
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
name: Deploy to Fly.io
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches:
|
|
6
|
-
- master
|
|
7
|
-
- main
|
|
8
|
-
|
|
9
|
-
jobs:
|
|
10
|
-
deploy:
|
|
11
|
-
name: Deploy to Fly.io
|
|
12
|
-
runs-on: ubuntu-latest
|
|
13
|
-
concurrency: deploy-group
|
|
14
|
-
steps:
|
|
15
|
-
- uses: actions/checkout@v4
|
|
16
|
-
|
|
17
|
-
- uses: superfly/flyctl-actions/setup-flyctl@master
|
|
18
|
-
|
|
19
|
-
- run: flyctl deploy --remote-only
|
|
20
|
-
env:
|
|
21
|
-
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
|