alphamilk 0.0.1
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/dist/cli.js +424 -0
- package/package.json +31 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// src/cli.ts
|
|
3
|
+
import { Command, Options, Args } from "@effect/cli";
|
|
4
|
+
import { NodeContext, NodeRuntime } from "@effect/platform-node";
|
|
5
|
+
import { FetchHttpClient } from "@effect/platform";
|
|
6
|
+
import { Console as Console2, Effect as Effect3, Layer, Logger, LogLevel } from "effect";
|
|
7
|
+
|
|
8
|
+
// src/config.ts
|
|
9
|
+
import * as fs from "node:fs";
|
|
10
|
+
import * as path from "node:path";
|
|
11
|
+
import * as os from "node:os";
|
|
12
|
+
var CONFIG_DIR = path.join(os.homedir(), ".config", "alphamilk");
|
|
13
|
+
var SESSION_FILE = path.join(CONFIG_DIR, "session.json");
|
|
14
|
+
function ensureConfigDir() {
|
|
15
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
16
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function saveSession(config) {
|
|
20
|
+
ensureConfigDir();
|
|
21
|
+
fs.writeFileSync(SESSION_FILE, JSON.stringify(config, null, 2));
|
|
22
|
+
}
|
|
23
|
+
function loadSession() {
|
|
24
|
+
if (!fs.existsSync(SESSION_FILE)) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const raw = fs.readFileSync(SESSION_FILE, "utf-8");
|
|
29
|
+
const parsed = JSON.parse(raw);
|
|
30
|
+
if (new Date(parsed.expiresAt) < /* @__PURE__ */ new Date()) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
return parsed;
|
|
34
|
+
} catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function clearSession() {
|
|
39
|
+
if (fs.existsSync(SESSION_FILE)) {
|
|
40
|
+
fs.unlinkSync(SESSION_FILE);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// src/api.ts
|
|
45
|
+
import { Effect, Data } from "effect";
|
|
46
|
+
import { HttpClient, HttpClientRequest } from "@effect/platform";
|
|
47
|
+
var CLI_VERSION = "0.0.1";
|
|
48
|
+
var ApiError = class extends Data.TaggedError("ApiError") {
|
|
49
|
+
};
|
|
50
|
+
var apiGet = (baseUrl, path3, params) => Effect.gen(function* () {
|
|
51
|
+
const client = yield* HttpClient.HttpClient;
|
|
52
|
+
const url = new URL(path3, baseUrl);
|
|
53
|
+
if (params) {
|
|
54
|
+
for (const [k, v] of Object.entries(params)) {
|
|
55
|
+
if (v !== void 0 && v !== "") {
|
|
56
|
+
url.searchParams.set(k, v);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const request = HttpClientRequest.get(url.toString()).pipe(
|
|
61
|
+
HttpClientRequest.setHeader("X-AlphaMilk-Client", "cli"),
|
|
62
|
+
HttpClientRequest.setHeader("X-AlphaMilk-CLI-Version", CLI_VERSION)
|
|
63
|
+
);
|
|
64
|
+
const response = yield* client.execute(request).pipe(Effect.scoped);
|
|
65
|
+
const body = yield* response.text;
|
|
66
|
+
if (response.status >= 400) {
|
|
67
|
+
return yield* Effect.fail(new ApiError({ statusCode: response.status, message: body }));
|
|
68
|
+
}
|
|
69
|
+
return body;
|
|
70
|
+
});
|
|
71
|
+
var apiPost = (baseUrl, path3, body) => Effect.gen(function* () {
|
|
72
|
+
const client = yield* HttpClient.HttpClient;
|
|
73
|
+
const url = new URL(path3, baseUrl);
|
|
74
|
+
const request = HttpClientRequest.post(url.toString()).pipe(
|
|
75
|
+
HttpClientRequest.setHeader("X-AlphaMilk-Client", "cli"),
|
|
76
|
+
HttpClientRequest.setHeader("X-AlphaMilk-CLI-Version", CLI_VERSION),
|
|
77
|
+
HttpClientRequest.bodyUnsafeJson(body)
|
|
78
|
+
);
|
|
79
|
+
const response = yield* client.execute(request).pipe(Effect.scoped);
|
|
80
|
+
const responseBody = yield* response.text;
|
|
81
|
+
if (response.status >= 400) {
|
|
82
|
+
return yield* Effect.fail(new ApiError({ statusCode: response.status, message: responseBody }));
|
|
83
|
+
}
|
|
84
|
+
return responseBody;
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// src/helpers.ts
|
|
88
|
+
import { Console, Effect as Effect2 } from "effect";
|
|
89
|
+
import * as fs2 from "node:fs";
|
|
90
|
+
import * as path2 from "node:path";
|
|
91
|
+
var parseSessionId = (markdown) => {
|
|
92
|
+
const match = markdown.match(/\*\*Session ID\*\*\s*\|\s*`([^`]+)`/);
|
|
93
|
+
return match ? match[1] : null;
|
|
94
|
+
};
|
|
95
|
+
var parseExpiresAt = (markdown) => {
|
|
96
|
+
const match = markdown.match(/\*\*Expires\*\*\s*\|\s*(\S+)/);
|
|
97
|
+
return match ? match[1] : null;
|
|
98
|
+
};
|
|
99
|
+
var readReportFile = (filePath) => Effect2.gen(function* () {
|
|
100
|
+
const resolved = path2.resolve(filePath);
|
|
101
|
+
if (!fs2.existsSync(resolved)) {
|
|
102
|
+
yield* Console.error(
|
|
103
|
+
`File not found: ${resolved}
|
|
104
|
+
|
|
105
|
+
Check the path and try again. Use an absolute path or a path relative to your current directory.
|
|
106
|
+
|
|
107
|
+
Example:
|
|
108
|
+
alphamilk report save --name "My Report" --file ./report.md
|
|
109
|
+
alphamilk report save --name "My Report" --file /tmp/report.md`
|
|
110
|
+
);
|
|
111
|
+
return yield* Effect2.die("File not found");
|
|
112
|
+
}
|
|
113
|
+
let fileContent;
|
|
114
|
+
try {
|
|
115
|
+
fileContent = fs2.readFileSync(resolved, "utf-8");
|
|
116
|
+
} catch (e) {
|
|
117
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
118
|
+
yield* Console.error(
|
|
119
|
+
`Cannot read file: ${resolved}
|
|
120
|
+
|
|
121
|
+
Error: ${msg}
|
|
122
|
+
|
|
123
|
+
Check file permissions and try again.`
|
|
124
|
+
);
|
|
125
|
+
return yield* Effect2.die("File read error");
|
|
126
|
+
}
|
|
127
|
+
if (fileContent.trim().length === 0) {
|
|
128
|
+
yield* Console.error(
|
|
129
|
+
`File is empty: ${resolved}
|
|
130
|
+
|
|
131
|
+
The report file must contain markdown content. Write your report to the file first, then run this command.
|
|
132
|
+
|
|
133
|
+
Example workflow:
|
|
134
|
+
1. Write your report to report.md
|
|
135
|
+
2. alphamilk report save --name "My Report" --file ./report.md`
|
|
136
|
+
);
|
|
137
|
+
return yield* Effect2.die("Empty file");
|
|
138
|
+
}
|
|
139
|
+
return fileContent;
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// src/cli.ts
|
|
143
|
+
var BASE_URL = "https://alphamilk.ai";
|
|
144
|
+
var exitWithError = (message) => Console2.error(message).pipe(
|
|
145
|
+
Effect3.flatMap(() => Effect3.sync(() => process.exit(1)))
|
|
146
|
+
);
|
|
147
|
+
var requireSession = Effect3.gen(function* () {
|
|
148
|
+
const session = loadSession();
|
|
149
|
+
if (!session) {
|
|
150
|
+
return yield* exitWithError(
|
|
151
|
+
"No active session. Run `alphamilk login --token <tokenId>` first."
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
return session;
|
|
155
|
+
});
|
|
156
|
+
var handleApiError = (context) => Effect3.catchAll((e) => {
|
|
157
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
158
|
+
return exitWithError(`${context}: ${msg}`);
|
|
159
|
+
});
|
|
160
|
+
var loginToken = Options.text("token").pipe(
|
|
161
|
+
Options.withAlias("t"),
|
|
162
|
+
Options.withDescription("Your Alpha Milk access token ID")
|
|
163
|
+
);
|
|
164
|
+
var loginCommand = Command.make(
|
|
165
|
+
"login",
|
|
166
|
+
{ token: loginToken },
|
|
167
|
+
({ token }) => Effect3.gen(function* () {
|
|
168
|
+
yield* Console2.log(`Authenticating with token: ${token.slice(0, 8)}...`);
|
|
169
|
+
const response = yield* apiGet(BASE_URL, `/milk/create-session/${token}`).pipe(
|
|
170
|
+
handleApiError("Login failed")
|
|
171
|
+
);
|
|
172
|
+
const sessionId = parseSessionId(response);
|
|
173
|
+
const expiresAt = parseExpiresAt(response);
|
|
174
|
+
if (!sessionId) {
|
|
175
|
+
return yield* exitWithError("Could not parse session ID from response.");
|
|
176
|
+
}
|
|
177
|
+
saveSession({
|
|
178
|
+
tokenId: token,
|
|
179
|
+
sessionId,
|
|
180
|
+
baseUrl: BASE_URL,
|
|
181
|
+
expiresAt: expiresAt ?? new Date(Date.now() + 24 * 60 * 60 * 1e3).toISOString()
|
|
182
|
+
});
|
|
183
|
+
yield* Console2.log(`Session created: ${sessionId}`);
|
|
184
|
+
yield* Console2.log(`Expires: ${expiresAt ?? "24 hours"}`);
|
|
185
|
+
yield* Console2.log(`
|
|
186
|
+
You're ready to go. Try: alphamilk playbooks`);
|
|
187
|
+
})
|
|
188
|
+
).pipe(Command.withDescription("Authenticate with your access token"));
|
|
189
|
+
var sessionCommand = Command.make(
|
|
190
|
+
"session",
|
|
191
|
+
{},
|
|
192
|
+
() => Effect3.gen(function* () {
|
|
193
|
+
const session = yield* requireSession;
|
|
194
|
+
yield* Console2.log(`Session ID: ${session.sessionId}`);
|
|
195
|
+
yield* Console2.log(`Token: ${session.tokenId.slice(0, 8)}...`);
|
|
196
|
+
yield* Console2.log(`Base URL: ${session.baseUrl}`);
|
|
197
|
+
yield* Console2.log(`Expires: ${session.expiresAt}`);
|
|
198
|
+
})
|
|
199
|
+
).pipe(Command.withDescription("Show current session info"));
|
|
200
|
+
var playbooksCommand = Command.make(
|
|
201
|
+
"playbooks",
|
|
202
|
+
{},
|
|
203
|
+
() => Effect3.gen(function* () {
|
|
204
|
+
const session = yield* requireSession;
|
|
205
|
+
const response = yield* apiGet(
|
|
206
|
+
session.baseUrl,
|
|
207
|
+
`/milk/playbook/${session.sessionId}`
|
|
208
|
+
).pipe(handleApiError("Failed to list playbooks"));
|
|
209
|
+
yield* Console2.log(response);
|
|
210
|
+
})
|
|
211
|
+
).pipe(Command.withDescription("List available research playbooks"));
|
|
212
|
+
var playbookSlug = Args.text({ name: "slug" }).pipe(
|
|
213
|
+
Args.withDescription("Playbook slug (e.g., competitor-discovery)")
|
|
214
|
+
);
|
|
215
|
+
var playbookReport = Options.boolean("report").pipe(
|
|
216
|
+
Options.withAlias("r"),
|
|
217
|
+
Options.withDefault(false),
|
|
218
|
+
Options.withDescription("Get the report template instead of playbook content")
|
|
219
|
+
);
|
|
220
|
+
var playbookCommand = Command.make(
|
|
221
|
+
"playbook",
|
|
222
|
+
{ slug: playbookSlug, report: playbookReport },
|
|
223
|
+
({ slug, report }) => Effect3.gen(function* () {
|
|
224
|
+
const session = yield* requireSession;
|
|
225
|
+
const urlPath = report ? `/milk/playbook/${session.sessionId}/${slug}/report` : `/milk/playbook/${session.sessionId}/${slug}`;
|
|
226
|
+
const response = yield* apiGet(session.baseUrl, urlPath).pipe(
|
|
227
|
+
handleApiError("Failed to get playbook")
|
|
228
|
+
);
|
|
229
|
+
yield* Console2.log(response);
|
|
230
|
+
})
|
|
231
|
+
).pipe(Command.withDescription("Open a playbook or its report template"));
|
|
232
|
+
var skillSlug = Args.text({ name: "slug" }).pipe(
|
|
233
|
+
Args.withDescription("Skill slug (e.g., serp-google, ranked-keywords)")
|
|
234
|
+
);
|
|
235
|
+
var skillParams = Options.keyValueMap("param").pipe(
|
|
236
|
+
Options.withAlias("p"),
|
|
237
|
+
Options.withDescription("Skill parameters as key=value pairs (repeat for multiple)"),
|
|
238
|
+
Options.optional
|
|
239
|
+
);
|
|
240
|
+
var skillCommand = Command.make(
|
|
241
|
+
"skill",
|
|
242
|
+
{ slug: skillSlug, params: skillParams },
|
|
243
|
+
({ slug, params }) => Effect3.gen(function* () {
|
|
244
|
+
const session = yield* requireSession;
|
|
245
|
+
const queryParams = {};
|
|
246
|
+
if (params._tag === "Some") {
|
|
247
|
+
for (const [k, v] of params.value) {
|
|
248
|
+
queryParams[k] = v;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
const response = yield* apiGet(
|
|
252
|
+
session.baseUrl,
|
|
253
|
+
`/milk/skill/${session.sessionId}/${slug}`,
|
|
254
|
+
queryParams
|
|
255
|
+
).pipe(handleApiError("Skill execution failed"));
|
|
256
|
+
yield* Console2.log(response);
|
|
257
|
+
})
|
|
258
|
+
).pipe(Command.withDescription("Execute a research skill"));
|
|
259
|
+
var reportSaveName = Options.text("name").pipe(
|
|
260
|
+
Options.withAlias("n"),
|
|
261
|
+
Options.withDescription("Report title/name")
|
|
262
|
+
);
|
|
263
|
+
var reportSaveContent = Options.text("content").pipe(
|
|
264
|
+
Options.withAlias("c"),
|
|
265
|
+
Options.withDescription("Full markdown report content (inline). Use --file instead for file-based input."),
|
|
266
|
+
Options.optional
|
|
267
|
+
);
|
|
268
|
+
var reportSaveFile = Options.text("file").pipe(
|
|
269
|
+
Options.withAlias("f"),
|
|
270
|
+
Options.withDescription("Path to a markdown file containing the report content"),
|
|
271
|
+
Options.optional
|
|
272
|
+
);
|
|
273
|
+
var reportSaveCommand = Command.make(
|
|
274
|
+
"save",
|
|
275
|
+
{ name: reportSaveName, content: reportSaveContent, file: reportSaveFile },
|
|
276
|
+
({ name, content, file }) => Effect3.gen(function* () {
|
|
277
|
+
const session = yield* requireSession;
|
|
278
|
+
let resolvedContent;
|
|
279
|
+
if (content._tag === "Some" && file._tag === "Some") {
|
|
280
|
+
return yield* exitWithError(
|
|
281
|
+
'Cannot use both --content and --file. Provide one or the other.\n\n --file report.md Read content from a markdown file\n --content "# ..." Pass content inline as a string'
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
if (content._tag === "None" && file._tag === "None") {
|
|
285
|
+
return yield* exitWithError(
|
|
286
|
+
'Missing report content. Provide one of:\n\n --file report.md Read content from a markdown file (recommended)\n --content "# ..." Pass content inline as a string\n\nExample:\n alphamilk report save --name "My Report" --file ./report.md'
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
if (file._tag === "Some") {
|
|
290
|
+
resolvedContent = yield* readReportFile(file.value);
|
|
291
|
+
} else {
|
|
292
|
+
resolvedContent = content.value;
|
|
293
|
+
}
|
|
294
|
+
const response = yield* apiPost(
|
|
295
|
+
session.baseUrl,
|
|
296
|
+
`/milk/report/${session.sessionId}`,
|
|
297
|
+
{ name, content: resolvedContent }
|
|
298
|
+
).pipe(handleApiError("Failed to save report"));
|
|
299
|
+
yield* Console2.log(response);
|
|
300
|
+
})
|
|
301
|
+
).pipe(Command.withDescription("Save a research report"));
|
|
302
|
+
var reportGetId = Args.text({ name: "reportId" }).pipe(
|
|
303
|
+
Args.withDescription("Report ID")
|
|
304
|
+
);
|
|
305
|
+
var reportGetCommand = Command.make(
|
|
306
|
+
"get",
|
|
307
|
+
{ reportId: reportGetId },
|
|
308
|
+
({ reportId }) => Effect3.gen(function* () {
|
|
309
|
+
const session = yield* requireSession;
|
|
310
|
+
const response = yield* apiGet(
|
|
311
|
+
session.baseUrl,
|
|
312
|
+
`/milk/report/${session.sessionId}/${reportId}`
|
|
313
|
+
).pipe(handleApiError("Failed to get report"));
|
|
314
|
+
yield* Console2.log(response);
|
|
315
|
+
})
|
|
316
|
+
).pipe(Command.withDescription("Retrieve a saved report"));
|
|
317
|
+
var reportCommand = Command.make("report").pipe(
|
|
318
|
+
Command.withDescription("Save and retrieve research reports"),
|
|
319
|
+
Command.withSubcommands([reportSaveCommand, reportGetCommand])
|
|
320
|
+
);
|
|
321
|
+
var artifactSaveType = Options.choice("type", ["domains", "keywords"]).pipe(
|
|
322
|
+
Options.withDescription("Artifact type")
|
|
323
|
+
);
|
|
324
|
+
var artifactSaveTags = Options.text("tags").pipe(
|
|
325
|
+
Options.withDescription("Comma-separated tags (e.g., competitors,direct)")
|
|
326
|
+
);
|
|
327
|
+
var artifactSaveData = Options.text("data").pipe(
|
|
328
|
+
Options.withAlias("d"),
|
|
329
|
+
Options.withDescription("Comma-separated data items (e.g., example.com,other.com)")
|
|
330
|
+
);
|
|
331
|
+
var artifactSaveCommand = Command.make(
|
|
332
|
+
"save",
|
|
333
|
+
{ type: artifactSaveType, tags: artifactSaveTags, data: artifactSaveData },
|
|
334
|
+
({ type, tags, data }) => Effect3.gen(function* () {
|
|
335
|
+
const session = yield* requireSession;
|
|
336
|
+
const response = yield* apiPost(
|
|
337
|
+
session.baseUrl,
|
|
338
|
+
`/milk/artifact/${session.sessionId}`,
|
|
339
|
+
{
|
|
340
|
+
type,
|
|
341
|
+
tags: tags.split(",").map((t) => t.trim()),
|
|
342
|
+
data: data.split(",").map((d) => d.trim())
|
|
343
|
+
}
|
|
344
|
+
).pipe(handleApiError("Failed to save artifact"));
|
|
345
|
+
yield* Console2.log(response);
|
|
346
|
+
})
|
|
347
|
+
).pipe(Command.withDescription("Save a research artifact"));
|
|
348
|
+
var artifactGetId = Args.text({ name: "artifactId" }).pipe(
|
|
349
|
+
Args.withDescription("Artifact ID")
|
|
350
|
+
);
|
|
351
|
+
var artifactGetCommand = Command.make(
|
|
352
|
+
"get",
|
|
353
|
+
{ artifactId: artifactGetId },
|
|
354
|
+
({ artifactId }) => Effect3.gen(function* () {
|
|
355
|
+
const session = yield* requireSession;
|
|
356
|
+
const response = yield* apiGet(
|
|
357
|
+
session.baseUrl,
|
|
358
|
+
`/milk/artifact/${session.sessionId}/${artifactId}`
|
|
359
|
+
).pipe(handleApiError("Failed to get artifact"));
|
|
360
|
+
yield* Console2.log(response);
|
|
361
|
+
})
|
|
362
|
+
).pipe(Command.withDescription("Retrieve a saved artifact"));
|
|
363
|
+
var artifactListType = Options.text("type").pipe(
|
|
364
|
+
Options.withDescription("Filter by artifact type"),
|
|
365
|
+
Options.optional
|
|
366
|
+
);
|
|
367
|
+
var artifactListTag = Options.text("tag").pipe(
|
|
368
|
+
Options.withDescription("Filter by tag"),
|
|
369
|
+
Options.optional
|
|
370
|
+
);
|
|
371
|
+
var artifactListCommand = Command.make(
|
|
372
|
+
"list",
|
|
373
|
+
{ type: artifactListType, tag: artifactListTag },
|
|
374
|
+
({ type, tag }) => Effect3.gen(function* () {
|
|
375
|
+
const session = yield* requireSession;
|
|
376
|
+
const params = {};
|
|
377
|
+
if (type._tag === "Some") params.type = type.value;
|
|
378
|
+
if (tag._tag === "Some") params.tag = tag.value;
|
|
379
|
+
const response = yield* apiGet(
|
|
380
|
+
session.baseUrl,
|
|
381
|
+
`/milk/artifact/${session.sessionId}`,
|
|
382
|
+
params
|
|
383
|
+
).pipe(handleApiError("Failed to list artifacts"));
|
|
384
|
+
yield* Console2.log(response);
|
|
385
|
+
})
|
|
386
|
+
).pipe(Command.withDescription("List saved artifacts"));
|
|
387
|
+
var artifactCommand = Command.make("artifact").pipe(
|
|
388
|
+
Command.withDescription("Save and retrieve research artifacts"),
|
|
389
|
+
Command.withSubcommands([artifactSaveCommand, artifactGetCommand, artifactListCommand])
|
|
390
|
+
);
|
|
391
|
+
var logoutCommand = Command.make(
|
|
392
|
+
"logout",
|
|
393
|
+
{},
|
|
394
|
+
() => Effect3.gen(function* () {
|
|
395
|
+
clearSession();
|
|
396
|
+
yield* Console2.log("Session cleared.");
|
|
397
|
+
})
|
|
398
|
+
).pipe(Command.withDescription("Clear your session"));
|
|
399
|
+
var root = Command.make("alphamilk").pipe(
|
|
400
|
+
Command.withDescription("AI-powered SEO research from your terminal"),
|
|
401
|
+
Command.withSubcommands([
|
|
402
|
+
loginCommand,
|
|
403
|
+
logoutCommand,
|
|
404
|
+
sessionCommand,
|
|
405
|
+
playbooksCommand,
|
|
406
|
+
playbookCommand,
|
|
407
|
+
skillCommand,
|
|
408
|
+
reportCommand,
|
|
409
|
+
artifactCommand
|
|
410
|
+
])
|
|
411
|
+
);
|
|
412
|
+
var cli = Command.run(root, {
|
|
413
|
+
name: "alphamilk",
|
|
414
|
+
version: "0.0.1"
|
|
415
|
+
});
|
|
416
|
+
var MainLayer = Layer.mergeAll(
|
|
417
|
+
NodeContext.layer,
|
|
418
|
+
FetchHttpClient.layer
|
|
419
|
+
);
|
|
420
|
+
Effect3.suspend(() => cli(process.argv)).pipe(
|
|
421
|
+
Effect3.provide(MainLayer),
|
|
422
|
+
Logger.withMinimumLogLevel(LogLevel.None),
|
|
423
|
+
NodeRuntime.runMain({ disableErrorReporting: true })
|
|
424
|
+
);
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "alphamilk",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"description": "Alpha Milk CLI - AI-powered SEO research from your terminal",
|
|
7
|
+
"bin": {
|
|
8
|
+
"alphamilk": "dist/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist/"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"dev": "npx tsx src/cli.ts",
|
|
15
|
+
"build": "node build.mjs",
|
|
16
|
+
"prepublishOnly": "rm -rf dist && node build.mjs",
|
|
17
|
+
"check": "tsc --noEmit",
|
|
18
|
+
"test": "vitest"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@effect/cli": "^0.73.2",
|
|
22
|
+
"@effect/platform": "^0.94.4",
|
|
23
|
+
"@effect/platform-node": "^0.104.1",
|
|
24
|
+
"effect": "^3.19.16"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"esbuild": "^0.25.0",
|
|
28
|
+
"vitest": "^3.2.0",
|
|
29
|
+
"@effect/vitest": "latest"
|
|
30
|
+
}
|
|
31
|
+
}
|