alys-akusa 0.1.2 → 0.1.4

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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/dist/index.cjs +252 -53
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Alys CLI
2
2
 
3
- Alys is the authenticated CLI for Akusa-managed dataset generation.
3
+ Alys is the authenticated CLI for terminal-native dataset generation.
4
4
 
5
5
  ```bash
6
6
  npm install -g alys-akusa
package/dist/index.cjs CHANGED
@@ -4908,6 +4908,8 @@ var CONFIG_DIR = import_node_path.default.join(import_node_os.default.homedir(),
4908
4908
  var CONFIG_PATH = import_node_path.default.join(CONFIG_DIR, "config.json");
4909
4909
  var ALYS_APP_URL = process.env.ALYS_APP_URL || process.env.NEXT_PUBLIC_SITE_URL || "https://alys.akusa.dev";
4910
4910
  var MAX_DATASETS_PER_RUN = 5;
4911
+ var MAX_SOURCES_PER_RUN = 8;
4912
+ var MAX_ROWS_PER_DATASET = 125;
4911
4913
  var FIGLET_LOGO = [
4912
4914
  " o ooooo ooooo oooo oooooooo8 ",
4913
4915
  " 888 888 888 88 888 ",
@@ -4928,6 +4930,8 @@ function printBanner() {
4928
4930
  function printHelp() {
4929
4931
  printBanner();
4930
4932
  console.log(`
4933
+ Alys runs in your terminal. The dashboard tracks what it generates.
4934
+
4931
4935
  Usage:
4932
4936
  npx alys-akusa
4933
4937
  npx alys-akusa login
@@ -4940,8 +4944,8 @@ Flags:
4940
4944
  --type instruction|rag|qa
4941
4945
  --datasets 1
4942
4946
  --depth shallow|medium|deep
4943
- --sources 12
4944
- --rows 1000
4947
+ --sources 8
4948
+ --rows 125
4945
4949
  --workspace ~/Desktop/alys-output
4946
4950
  --verify
4947
4951
  --no-verify
@@ -5014,7 +5018,7 @@ async function requestJson(pathname, init = {}, token) {
5014
5018
  const response = await fetch(apiUrl(pathname), { ...init, headers });
5015
5019
  const payload = await response.json().catch(() => ({}));
5016
5020
  if (!response.ok) {
5017
- const message = typeof payload?.error === "string" ? payload.error : `Alys API request failed (${response.status}).`;
5021
+ const message = typeof payload?.error === "string" ? payload.error : `Alys request failed (${response.status}).`;
5018
5022
  throw new CliApiError(message, response.status, payload);
5019
5023
  }
5020
5024
  return payload;
@@ -5078,6 +5082,19 @@ function parseDepth(value) {
5078
5082
  if (value === "shallow" || value === "medium" || value === "deep") return value;
5079
5083
  return void 0;
5080
5084
  }
5085
+ function normalizeTopicInput(value) {
5086
+ if (typeof value !== "string") return "";
5087
+ let topic = value.replace(/\[[^\]]+\][^\n\r]*/g, "").replace(/\s+/g, " ").trim();
5088
+ const commandMatch = topic.match(/^(?:npx\s+)?(?:alys-akusa(?:@latest)?|alys)\s+generate\s+(.+)$/i);
5089
+ if (commandMatch) {
5090
+ topic = commandMatch[1].trim();
5091
+ }
5092
+ const quoted = topic.match(/^"([^"]+)"|^'([^']+)'/);
5093
+ if (quoted) {
5094
+ return (quoted[1] || quoted[2] || "").trim();
5095
+ }
5096
+ return topic.replace(/\s+--(?:format|type|datasets|depth|sources|rows|workspace)\s+\S.*$/i, "").replace(/\s+--(?:verify|no-verify|yes)\b.*$/i, "").trim();
5097
+ }
5081
5098
  function resolveHome(p) {
5082
5099
  if (p.startsWith("~/")) return import_node_path.default.join(import_node_os.default.homedir(), p.slice(2));
5083
5100
  return p;
@@ -5085,14 +5102,94 @@ function resolveHome(p) {
5085
5102
  function formatInt(value) {
5086
5103
  return new Intl.NumberFormat("en-US").format(value);
5087
5104
  }
5105
+ function formatPercent(value) {
5106
+ if (!Number.isFinite(value) || value <= 0) return "pending";
5107
+ return `${Math.round(value * 100)}%`;
5108
+ }
5109
+ function formatScore(value) {
5110
+ if (!Number.isFinite(value) || value <= 0) return "pending";
5111
+ return `${Math.round(value)}/100`;
5112
+ }
5113
+ function average(values) {
5114
+ const filtered = values.filter((value) => Number.isFinite(value) && value > 0);
5115
+ return filtered.length ? filtered.reduce((sum, value) => sum + value, 0) / filtered.length : 0;
5116
+ }
5117
+ function truncate(value, max = 88) {
5118
+ const normalized = value.replace(/\s+/g, " ").trim();
5119
+ return normalized.length > max ? `${normalized.slice(0, max - 1)}\u2026` : normalized;
5120
+ }
5121
+ function getMetrics(dataset) {
5122
+ const metrics = dataset.manifest.metrics;
5123
+ return metrics && typeof metrics === "object" ? metrics : {};
5124
+ }
5125
+ function getSummary(dataset) {
5126
+ const summary = dataset.manifest.generationSummary;
5127
+ return summary && typeof summary === "object" ? summary : {};
5128
+ }
5129
+ function getQualityMetrics(dataset) {
5130
+ const metrics = dataset.manifest.qualityMetrics;
5131
+ return metrics && typeof metrics === "object" ? metrics : {};
5132
+ }
5133
+ function getEvaluation(dataset) {
5134
+ const evaluation = dataset.manifest.evaluation;
5135
+ return evaluation && typeof evaluation === "object" ? evaluation : {};
5136
+ }
5137
+ function printStage(code, status, label, metric) {
5138
+ const tint = status === "DONE" || status === "OK" ? "green" : status === "WARN" ? "yellow" : "cyan";
5139
+ const prefix = `${paint(`[${code.padEnd(4).slice(0, 4)}]`, "gray")} ${paint(status.padEnd(4), tint)}`;
5140
+ console.log(`${prefix} ${label}${metric ? ` ${paint(metric, "gray")}` : ""}`);
5141
+ }
5142
+ async function withSpinner(label, task) {
5143
+ if (!process.stdout.isTTY) {
5144
+ printStage("RUN", "RUN", label);
5145
+ return task;
5146
+ }
5147
+ const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
5148
+ let index = 0;
5149
+ process.stdout.write(`${paint(frames[index], "yellow")} ${label}`);
5150
+ const interval = setInterval(() => {
5151
+ index = (index + 1) % frames.length;
5152
+ process.stdout.write(`\r${paint(frames[index], "yellow")} ${label}`);
5153
+ }, 90);
5154
+ try {
5155
+ const result = await task;
5156
+ clearInterval(interval);
5157
+ process.stdout.write(`\r${paint("\u2713", "green")} ${label}
5158
+ `);
5159
+ return result;
5160
+ } catch (error) {
5161
+ clearInterval(interval);
5162
+ process.stdout.write(`\r${paint("\xD7", "red")} ${label}
5163
+ `);
5164
+ throw error;
5165
+ }
5166
+ }
5167
+ function previewRecord(dataset) {
5168
+ const file = dataset.files.find((item) => item.format === "jsonl" || item.format === "instruction" || item.format === "rag");
5169
+ const firstLine = file?.content.split(/\r?\n/).find((line) => line.trim().length > 0);
5170
+ if (!firstLine) return null;
5171
+ try {
5172
+ const parsed = JSON.parse(firstLine);
5173
+ const input = typeof parsed.input === "string" ? parsed.input : typeof parsed.instruction === "string" ? parsed.instruction : typeof parsed.question === "string" ? parsed.question : "";
5174
+ const output = typeof parsed.output === "string" ? parsed.output : typeof parsed.answer === "string" ? parsed.answer : typeof parsed.completion === "string" ? parsed.completion : typeof parsed.text === "string" ? parsed.text : "";
5175
+ if (!input && !output) return null;
5176
+ const metadata = parsed.metadata && typeof parsed.metadata === "object" ? parsed.metadata : {};
5177
+ const explanation = Array.isArray(metadata.acceptance_explanation) ? metadata.acceptance_explanation.filter((item) => typeof item === "string") : Array.isArray(metadata.acceptance_reasons) ? metadata.acceptance_reasons.filter((item) => typeof item === "string") : [];
5178
+ return { input: truncate(input, 92), output: truncate(output, 120), why: explanation.slice(0, 2).map((item) => truncate(item, 112)) };
5179
+ } catch {
5180
+ return null;
5181
+ }
5182
+ }
5088
5183
  function depthMultiplier(depth) {
5089
5184
  if (depth === "deep") return 1.6;
5090
5185
  if (depth === "shallow") return 0.75;
5091
5186
  return 1;
5092
5187
  }
5093
5188
  function printUsage(profile) {
5094
- const name = profile.user.name || profile.user.email || "Alys user";
5095
- console.log(paint(`Signed in as ${name}`, "white"));
5189
+ const name = profile.user.name || "Alys user";
5190
+ const email = profile.user.email;
5191
+ const account = email && email !== name ? `${name} <${email}>` : name;
5192
+ console.log(paint(`Account: ${account}`, "white"));
5096
5193
  console.log(
5097
5194
  profile.usage.isUnlimited ? paint("Generations: unlimited", "green") : paint(`Generations: ${profile.usage.used}/${profile.usage.limit} used, ${profile.usage.remaining} remaining`, "yellow")
5098
5195
  );
@@ -5102,10 +5199,14 @@ function printRunPlan(args) {
5102
5199
  const effectiveSources = Math.max(1, Math.floor(args.sourceLimit * multiplier));
5103
5200
  const effectiveRows = Math.max(1, Math.floor(args.targetRows * multiplier));
5104
5201
  const totalRows = effectiveRows * args.datasetCount;
5105
- console.log(paint("\n\u250C\u2500 Alys API run plan \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510", "gray"));
5202
+ const name = args.profile.user.name || "Alys user";
5203
+ const email = args.profile.user.email;
5204
+ const account = email && email !== name ? `${name} <${email}>` : name;
5205
+ console.log(paint("\n\u250C\u2500 Alys run plan \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510", "gray"));
5106
5206
  console.log(`${paint("\u2502", "gray")} Topic ${paint(args.topic.slice(0, 58), "white")}`);
5107
- console.log(`${paint("\u2502", "gray")} Type ${paint(args.datasetType, "cyan")} Depth ${paint(args.depth, "cyan")} Mode ${paint("Akusa API", "yellow")}`);
5108
- console.log(`${paint("\u2502", "gray")} Datasets ${formatInt(args.datasetCount)} / ${MAX_DATASETS_PER_RUN} max Rows/dataset ${formatInt(effectiveRows)}`);
5207
+ console.log(`${paint("\u2502", "gray")} Account ${paint(account.slice(0, 58), "white")}`);
5208
+ console.log(`${paint("\u2502", "gray")} Type ${paint(args.datasetType, "cyan")} Depth ${paint(args.depth, "cyan")} Mode ${paint("Alys runtime", "yellow")}`);
5209
+ console.log(`${paint("\u2502", "gray")} Datasets ${formatInt(args.datasetCount)} / ${MAX_DATASETS_PER_RUN} max Rows/dataset ${formatInt(effectiveRows)} max`);
5109
5210
  console.log(`${paint("\u2502", "gray")} Total rows ${paint(formatInt(totalRows), "green")} Formats ${args.exportFormats.join(", ")}`);
5110
5211
  console.log(`${paint("\u2502", "gray")} Credits ${args.profile.usage.isUnlimited ? "Unlimited" : `${args.profile.usage.remaining ?? 0} remaining before run`}`);
5111
5212
  console.log(`${paint("\u2502", "gray")} Output ${resolveHome(args.workspaceRoot)}`);
@@ -5121,7 +5222,7 @@ async function confirmRun(args) {
5121
5222
  const response = await (0, import_prompts.default)({
5122
5223
  type: "toggle",
5123
5224
  name: "continueRun",
5124
- message: "Generate through the Alys API?",
5225
+ message: "Start this Alys run?",
5125
5226
  initial: true,
5126
5227
  active: "Yes",
5127
5228
  inactive: "No"
@@ -5144,6 +5245,108 @@ async function writeGeneratedDatasets(workspaceRoot, datasets) {
5144
5245
  );
5145
5246
  }
5146
5247
  }
5248
+ function printGenerationSummary(response, workspaceRoot) {
5249
+ const root = resolveHome(workspaceRoot);
5250
+ const totals = response.datasets.reduce(
5251
+ (acc, dataset) => {
5252
+ const metrics = getMetrics(dataset);
5253
+ const summary = getSummary(dataset);
5254
+ acc.records += Number(metrics.recordsGenerated ?? summary.recordsAccepted ?? 0);
5255
+ acc.sources += Number(metrics.sourcesDiscovered ?? 0);
5256
+ acc.documents += Number(metrics.documentsExtracted ?? 0);
5257
+ acc.findings += Number(metrics.findingsVerified ?? 0);
5258
+ acc.duplicates += Number(metrics.duplicatesRemoved ?? summary.duplicatesRemoved ?? 0);
5259
+ const quality = getQualityMetrics(dataset);
5260
+ acc.contradictions += Number(quality.contradictionResolutionCount ?? 0);
5261
+ acc.lowTrustFiltered += Number(quality.lowTrustSourceFilterRate ?? 0);
5262
+ acc.citationCoverage.push(Number(quality.citationCoverage ?? 0));
5263
+ acc.uniqueness.push(Number(quality.recordUniqueness ?? 0));
5264
+ acc.relevance.push(Number(quality.relevanceScore ?? 0));
5265
+ const suitability = getEvaluation(dataset).suitability ?? getEvaluation(dataset).summary?.scores ?? {};
5266
+ acc.ragSuitability.push(Number(suitability.ragSuitability ?? 0));
5267
+ acc.instructionTuning.push(Number(suitability.instructionTuning ?? 0));
5268
+ acc.factualGrounding.push(Number(suitability.factualGrounding ?? 0));
5269
+ acc.humanUsefulness.push(Number(suitability.humanUsefulness ?? 0));
5270
+ const confidence2 = Number(metrics.averageConfidence ?? summary.averageConfidence ?? 0);
5271
+ if (confidence2 > 0) acc.confidences.push(confidence2);
5272
+ return acc;
5273
+ },
5274
+ {
5275
+ records: 0,
5276
+ sources: 0,
5277
+ documents: 0,
5278
+ findings: 0,
5279
+ duplicates: 0,
5280
+ contradictions: 0,
5281
+ lowTrustFiltered: 0,
5282
+ confidences: [],
5283
+ citationCoverage: [],
5284
+ uniqueness: [],
5285
+ relevance: [],
5286
+ ragSuitability: [],
5287
+ instructionTuning: [],
5288
+ factualGrounding: [],
5289
+ humanUsefulness: []
5290
+ }
5291
+ );
5292
+ const confidence = totals.confidences.length ? totals.confidences.reduce((sum, value) => sum + value, 0) / totals.confidences.length : 0;
5293
+ const citationCoverage = average(totals.citationCoverage);
5294
+ const uniqueness = average(totals.uniqueness);
5295
+ const relevance = average(totals.relevance);
5296
+ const ragSuitability = average(totals.ragSuitability);
5297
+ const instructionTuning = average(totals.instructionTuning);
5298
+ const factualGrounding = average(totals.factualGrounding);
5299
+ const humanUsefulness = average(totals.humanUsefulness);
5300
+ console.log("");
5301
+ console.log(paint("Alys run complete", "green"));
5302
+ printStage("SRC", "DONE", "Authoritative sources ranked", formatInt(totals.sources));
5303
+ printStage("SRC", "DONE", "Low-trust source filter applied", `${Math.round(totals.lowTrustFiltered / Math.max(1, response.datasets.length) * 100)}% avg filtered`);
5304
+ printStage("EXT", "DONE", "Source documents normalized", formatInt(totals.documents));
5305
+ printStage("CHK", "DONE", "Findings verified", formatInt(totals.findings));
5306
+ printStage("CHK", "DONE", "Contradictory claims resolved", formatInt(totals.contradictions));
5307
+ printStage("DED", "DONE", "Duplicate candidates removed", formatInt(totals.duplicates));
5308
+ printStage("GEN", "DONE", "Canonical records accepted", formatInt(totals.records));
5309
+ printStage("EVAL", "DONE", "Average confidence", formatPercent(confidence));
5310
+ printStage("EVAL", "DONE", "Citation coverage", formatPercent(citationCoverage));
5311
+ printStage("EVAL", "DONE", "Record uniqueness", formatPercent(uniqueness));
5312
+ printStage("EVAL", "DONE", "Topic relevance", formatPercent(relevance));
5313
+ printStage("EVAL", "DONE", "RAG suitability", formatScore(ragSuitability));
5314
+ printStage("EVAL", "DONE", "Instruction tuning suitability", formatScore(instructionTuning));
5315
+ printStage("EVAL", "DONE", "Factual grounding", formatScore(factualGrounding));
5316
+ printStage("EVAL", "DONE", "Human usefulness proxy", formatScore(humanUsefulness));
5317
+ printStage("OUT", "DONE", "Exports written", root);
5318
+ console.log("");
5319
+ console.log(paint("Datasets", "white"));
5320
+ for (const dataset of response.datasets) {
5321
+ const metrics = getMetrics(dataset);
5322
+ const summary = getSummary(dataset);
5323
+ const quality = getQualityMetrics(dataset);
5324
+ const suitability = getEvaluation(dataset).suitability ?? getEvaluation(dataset).summary?.scores ?? {};
5325
+ const records = Number(metrics.recordsGenerated ?? summary.recordsAccepted ?? 0);
5326
+ const sources = Number(metrics.sourcesDiscovered ?? 0);
5327
+ const confidenceValue = Number(metrics.averageConfidence ?? summary.averageConfidence ?? 0);
5328
+ const outputDir = import_node_path.default.join(root, "datasets", dataset.id);
5329
+ console.log(`${paint("\u2022", "yellow")} ${paint(dataset.id, "white")} ${formatInt(records)} records ${formatInt(sources)} sources ${formatPercent(confidenceValue)} confidence`);
5330
+ console.log(` ${truncate(dataset.topic, 110)}`);
5331
+ console.log(` ${paint(outputDir, "cyan")}`);
5332
+ console.log(` quality ${formatPercent(Number(quality.citationCoverage ?? 0))} citations \xB7 ${formatPercent(Number(quality.recordUniqueness ?? 0))} unique \xB7 ${formatPercent(Number(quality.sourceDiversity ?? 0))} source diversity`);
5333
+ console.log(` suitability RAG ${formatScore(Number(suitability.ragSuitability ?? 0))} \xB7 tuning ${formatScore(Number(suitability.instructionTuning ?? 0))} \xB7 usefulness ${formatScore(Number(suitability.humanUsefulness ?? 0))}`);
5334
+ const preview = previewRecord(dataset);
5335
+ if (preview) {
5336
+ console.log(paint(" preview", "gray"));
5337
+ if (preview.input) console.log(` in ${paint(preview.input, "gray")}`);
5338
+ if (preview.output) console.log(` out ${preview.output}`);
5339
+ for (const reason of preview.why) {
5340
+ console.log(` why ${paint(reason, "gray")}`);
5341
+ }
5342
+ }
5343
+ }
5344
+ console.log("");
5345
+ console.log(
5346
+ response.usage.isUnlimited ? paint("Generations remaining: unlimited", "green") : paint(`Generations remaining: ${response.usage.remaining ?? 0}`, "yellow")
5347
+ );
5348
+ console.log(paint(`Dashboard: ${appUrl()}/dashboard`, "cyan"));
5349
+ }
5147
5350
  async function handleStatus() {
5148
5351
  printBanner();
5149
5352
  const { profile } = await ensureAuthenticated();
@@ -5171,6 +5374,9 @@ async function handleLogout() {
5171
5374
  }
5172
5375
  async function handleGenerate(args, command) {
5173
5376
  const { config, profile } = await ensureAuthenticated();
5377
+ printBanner();
5378
+ printUsage(profile);
5379
+ console.log("");
5174
5380
  const { values, positionals } = (0, import_node_util.parseArgs)({
5175
5381
  args: command === "generate" ? args.slice(1) : args,
5176
5382
  allowPositionals: true,
@@ -5187,13 +5393,13 @@ async function handleGenerate(args, command) {
5187
5393
  yes: { type: "boolean" }
5188
5394
  }
5189
5395
  });
5190
- const topicFromArgs = positionals.join(" ").trim();
5191
- const topic = topicFromArgs ? topicFromArgs : (await (0, import_prompts.default)({
5396
+ const topicFromArgs = normalizeTopicInput(positionals.join(" "));
5397
+ const topic = topicFromArgs ? topicFromArgs : normalizeTopicInput((await (0, import_prompts.default)({
5192
5398
  type: "text",
5193
5399
  name: "topic",
5194
5400
  message: "What do you want to generate?",
5195
5401
  validate: (v) => v.trim().length ? true : "Please enter a topic."
5196
- })).topic;
5402
+ })).topic);
5197
5403
  if (!topic) throw new Error("Missing topic.");
5198
5404
  const datasetType = parseDatasetType(values.type) ?? (await (0, import_prompts.default)({
5199
5405
  type: "select",
@@ -5241,19 +5447,21 @@ async function handleGenerate(args, command) {
5241
5447
  { title: "Deep", value: "deep" }
5242
5448
  ]
5243
5449
  })).depth;
5244
- const sourceLimit = values.sources ? Math.max(1, Number(values.sources)) : (await (0, import_prompts.default)({
5450
+ const sourceLimit = values.sources ? Math.min(MAX_SOURCES_PER_RUN, Math.max(1, Number(values.sources))) : (await (0, import_prompts.default)({
5245
5451
  type: "number",
5246
5452
  name: "sourceLimit",
5247
5453
  message: "How many sources?",
5248
- initial: 12,
5249
- min: 1
5454
+ initial: MAX_SOURCES_PER_RUN,
5455
+ min: 1,
5456
+ max: MAX_SOURCES_PER_RUN
5250
5457
  })).sourceLimit;
5251
- const targetRows = values.rows ? Math.max(1, Number(values.rows)) : (await (0, import_prompts.default)({
5458
+ const targetRows = values.rows ? Math.min(MAX_ROWS_PER_DATASET, Math.max(1, Number(values.rows))) : (await (0, import_prompts.default)({
5252
5459
  type: "number",
5253
5460
  name: "targetRows",
5254
5461
  message: "Rows per dataset?",
5255
- initial: 1e3,
5256
- min: 1
5462
+ initial: MAX_ROWS_PER_DATASET,
5463
+ min: 1,
5464
+ max: MAX_ROWS_PER_DATASET
5257
5465
  })).targetRows;
5258
5466
  const workspaceRoot = (values.workspace ? String(values.workspace) : "").trim() || (await (0, import_prompts.default)({
5259
5467
  type: "text",
@@ -5265,13 +5473,11 @@ async function handleGenerate(args, command) {
5265
5473
  const verificationEnabled = values.verify === true ? true : values["no-verify"] === true ? false : (await (0, import_prompts.default)({
5266
5474
  type: "toggle",
5267
5475
  name: "verificationEnabled",
5268
- message: "Enable verification + debate swarm?",
5476
+ message: "Enable verification checks?",
5269
5477
  initial: true,
5270
5478
  active: "Yes",
5271
5479
  inactive: "No"
5272
5480
  })).verificationEnabled;
5273
- printBanner();
5274
- printUsage(profile);
5275
5481
  printRunPlan({
5276
5482
  topic,
5277
5483
  datasetType,
@@ -5292,39 +5498,32 @@ async function handleGenerate(args, command) {
5292
5498
  console.log(paint("Run cancelled. No generations spent.", "green"));
5293
5499
  return;
5294
5500
  }
5295
- console.log(`${paint("[AUTH]", "gray")} ${paint("OK ", "green")} Usage linked to ${appUrl()}`);
5296
- console.log(`${paint("[API ]", "gray")} ${paint("RUN ", "yellow")} Generating datasets through Akusa API...`);
5297
- const response = await requestJson(
5298
- "/api/cli/generate",
5299
- {
5300
- method: "POST",
5301
- body: JSON.stringify({
5302
- topic,
5303
- datasetType,
5304
- datasetCount,
5305
- formats: exportFormats,
5306
- sourceLimit,
5307
- targetRows,
5308
- depth,
5309
- verificationEnabled
5310
- })
5311
- },
5312
- config.token
5313
- );
5314
- await writeGeneratedDatasets(workspaceRoot, response.datasets);
5315
- console.log(`${paint("[API ]", "gray")} ${paint("DONE", "green")} Generation complete`);
5316
- console.log(`${paint("[OUT ]", "gray")} ${paint("DONE", "green")} Files written to ${resolveHome(workspaceRoot)}`);
5317
- console.log(paint("\nDatasets generated", "green"));
5318
- for (const dataset of response.datasets) {
5319
- const records = Number(dataset.manifest?.metrics && typeof dataset.manifest.metrics === "object" ? dataset.manifest.metrics.recordsGenerated ?? 0 : 0);
5320
- console.log(`${paint("\u2022", "yellow")} ${dataset.id} records: ${formatInt(records)}`);
5321
- console.log(` ${import_node_path.default.join(resolveHome(workspaceRoot), "datasets", dataset.id)}`);
5322
- }
5323
- console.log("");
5324
- console.log(
5325
- response.usage.isUnlimited ? paint("Generations remaining: unlimited", "green") : paint(`Generations remaining: ${response.usage.remaining ?? 0}`, "yellow")
5501
+ console.log(paint("Runtime", "white"));
5502
+ printStage("AUTH", "OK", "Usage linked", appUrl());
5503
+ printStage("PLAN", "OK", "Generations charged only after successful completion", `${datasetCount} requested`);
5504
+ printStage("SRC", "RUN", "Research pipeline starting", `${sourceLimit} source target`);
5505
+ const response = await withSpinner(
5506
+ "Alys research runtime executing",
5507
+ requestJson(
5508
+ "/api/cli/generate",
5509
+ {
5510
+ method: "POST",
5511
+ body: JSON.stringify({
5512
+ topic,
5513
+ datasetType,
5514
+ datasetCount,
5515
+ formats: exportFormats,
5516
+ sourceLimit,
5517
+ targetRows,
5518
+ depth,
5519
+ verificationEnabled
5520
+ })
5521
+ },
5522
+ config.token
5523
+ )
5326
5524
  );
5327
- console.log(paint(`Dashboard: ${appUrl()}/dashboard`, "cyan"));
5525
+ await withSpinner("Writing local exports", writeGeneratedDatasets(workspaceRoot, response.datasets));
5526
+ printGenerationSummary(response, workspaceRoot);
5328
5527
  }
5329
5528
  function printFailure(error) {
5330
5529
  const message = error instanceof Error ? error.message : "Alys failed.";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alys-akusa",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "private": false,
5
5
  "description": "Alys local CLI runtime for autonomous dataset generation.",
6
6
  "license": "UNLICENSED",