fathom-cli 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -10
- package/dist/data/remote-manifest.json +78 -0
- package/dist/index.js +665 -26
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-FILMHRDQ.js +0 -2055
- package/dist/chunk-FILMHRDQ.js.map +0 -1
- package/dist/chunk-SEGVTWSK.js +0 -44
- package/dist/chunk-SEGVTWSK.js.map +0 -1
- package/dist/fileFromPath-AY5NHOFK.js +0 -131
- package/dist/fileFromPath-AY5NHOFK.js.map +0 -1
- package/dist/sdk-OEGEIPVM.js +0 -7829
- package/dist/sdk-OEGEIPVM.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,10 +1,39 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
9
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
10
|
+
}) : x)(function(x) {
|
|
11
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
12
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
13
|
+
});
|
|
14
|
+
var __commonJS = (cb, mod) => function __require2() {
|
|
15
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
16
|
+
};
|
|
17
|
+
var __export = (target, all2) => {
|
|
18
|
+
for (var name in all2)
|
|
19
|
+
__defProp(target, name, { get: all2[name], enumerable: true });
|
|
20
|
+
};
|
|
21
|
+
var __copyProps = (to, from, except, desc) => {
|
|
22
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
23
|
+
for (let key of __getOwnPropNames(from))
|
|
24
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
25
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
26
|
+
}
|
|
27
|
+
return to;
|
|
28
|
+
};
|
|
29
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
30
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
31
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
32
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
33
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
34
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
35
|
+
mod
|
|
36
|
+
));
|
|
8
37
|
|
|
9
38
|
// ../../node_modules/.pnpm/extend@3.0.2/node_modules/extend/index.js
|
|
10
39
|
var require_extend = __commonJS({
|
|
@@ -98,7 +127,7 @@ var require_extend = __commonJS({
|
|
|
98
127
|
});
|
|
99
128
|
|
|
100
129
|
// src/index.ts
|
|
101
|
-
import { Command as
|
|
130
|
+
import { Command as Command15 } from "commander";
|
|
102
131
|
|
|
103
132
|
// ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
|
|
104
133
|
var external_exports = {};
|
|
@@ -5141,7 +5170,7 @@ var Processor = class _Processor extends CallableInstance {
|
|
|
5141
5170
|
assertParser("process", this.parser || this.Parser);
|
|
5142
5171
|
assertCompiler("process", this.compiler || this.Compiler);
|
|
5143
5172
|
return done ? executor(void 0, done) : new Promise(executor);
|
|
5144
|
-
function executor(
|
|
5173
|
+
function executor(resolve14, reject) {
|
|
5145
5174
|
const realFile = vfile(file);
|
|
5146
5175
|
const parseTree = (
|
|
5147
5176
|
/** @type {HeadTree extends undefined ? Node : HeadTree} */
|
|
@@ -5172,8 +5201,8 @@ var Processor = class _Processor extends CallableInstance {
|
|
|
5172
5201
|
function realDone(error, file2) {
|
|
5173
5202
|
if (error || !file2) {
|
|
5174
5203
|
reject(error);
|
|
5175
|
-
} else if (
|
|
5176
|
-
|
|
5204
|
+
} else if (resolve14) {
|
|
5205
|
+
resolve14(file2);
|
|
5177
5206
|
} else {
|
|
5178
5207
|
ok(done, "`done` is defined if `resolve` is not");
|
|
5179
5208
|
done(void 0, file2);
|
|
@@ -5275,7 +5304,7 @@ var Processor = class _Processor extends CallableInstance {
|
|
|
5275
5304
|
file = void 0;
|
|
5276
5305
|
}
|
|
5277
5306
|
return done ? executor(void 0, done) : new Promise(executor);
|
|
5278
|
-
function executor(
|
|
5307
|
+
function executor(resolve14, reject) {
|
|
5279
5308
|
ok(
|
|
5280
5309
|
typeof file !== "function",
|
|
5281
5310
|
"`file` can\u2019t be a `done` anymore, we checked"
|
|
@@ -5289,8 +5318,8 @@ var Processor = class _Processor extends CallableInstance {
|
|
|
5289
5318
|
);
|
|
5290
5319
|
if (error) {
|
|
5291
5320
|
reject(error);
|
|
5292
|
-
} else if (
|
|
5293
|
-
|
|
5321
|
+
} else if (resolve14) {
|
|
5322
|
+
resolve14(resultingTree);
|
|
5294
5323
|
} else {
|
|
5295
5324
|
ok(done, "`done` is defined if `resolve` is not");
|
|
5296
5325
|
done(void 0, resultingTree, file2);
|
|
@@ -8118,10 +8147,10 @@ function resolveAll(constructs2, events, context) {
|
|
|
8118
8147
|
const called = [];
|
|
8119
8148
|
let index2 = -1;
|
|
8120
8149
|
while (++index2 < constructs2.length) {
|
|
8121
|
-
const
|
|
8122
|
-
if (
|
|
8123
|
-
events =
|
|
8124
|
-
called.push(
|
|
8150
|
+
const resolve14 = constructs2[index2].resolveAll;
|
|
8151
|
+
if (resolve14 && !called.includes(resolve14)) {
|
|
8152
|
+
events = resolve14(events, context);
|
|
8153
|
+
called.push(resolve14);
|
|
8125
8154
|
}
|
|
8126
8155
|
}
|
|
8127
8156
|
return events;
|
|
@@ -15638,6 +15667,62 @@ function generateRegistry(project, features, estimates) {
|
|
|
15638
15667
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
15639
15668
|
};
|
|
15640
15669
|
}
|
|
15670
|
+
async function getAnthropicClient(apiKey) {
|
|
15671
|
+
const { default: AnthropicSDK } = await import("@anthropic-ai/sdk");
|
|
15672
|
+
return new AnthropicSDK({ apiKey: apiKey ?? process.env.ANTHROPIC_API_KEY });
|
|
15673
|
+
}
|
|
15674
|
+
var AIComplexityResult = external_exports.object({
|
|
15675
|
+
complexity: Complexity,
|
|
15676
|
+
taskTypes: external_exports.array(TaskType),
|
|
15677
|
+
dependencies: external_exports.array(external_exports.string()),
|
|
15678
|
+
aiTasks: external_exports.array(external_exports.string()),
|
|
15679
|
+
rationale: external_exports.string()
|
|
15680
|
+
});
|
|
15681
|
+
var TASK_TYPE_VALUES = TaskType.options.join(", ");
|
|
15682
|
+
var SYSTEM_PROMPT = `You are a technical complexity scorer for software features. Given a feature description, analyze it and return a JSON object with:
|
|
15683
|
+
|
|
15684
|
+
- complexity: One of "S", "M", "L", "XL"
|
|
15685
|
+
- S: Single file, simple change, <30 min
|
|
15686
|
+
- M: 2-5 files, typical feature, 1-3 hours
|
|
15687
|
+
- L: 5-15 files, multiple integrations, 3-8 hours
|
|
15688
|
+
- XL: 15+ files, system-wide changes, 8+ hours
|
|
15689
|
+
- taskTypes: Array of applicable task types from: ${TASK_TYPE_VALUES}
|
|
15690
|
+
- dependencies: Array of likely external dependencies or integrations (e.g., "OAuth provider", "database migration")
|
|
15691
|
+
- aiTasks: Array of 2-5 discrete AI coding tasks (e.g., "scaffold-auth-flow", "write-api-tests")
|
|
15692
|
+
- rationale: One sentence explaining the complexity assessment
|
|
15693
|
+
|
|
15694
|
+
Respond with ONLY a JSON object. No markdown, no explanation.`;
|
|
15695
|
+
async function scoreComplexityAI(description, options = {}) {
|
|
15696
|
+
try {
|
|
15697
|
+
const client = options.client ?? await getAnthropicClient(options.apiKey);
|
|
15698
|
+
const model = options.model ?? "claude-haiku-4-5-20251001";
|
|
15699
|
+
const response = await client.messages.create({
|
|
15700
|
+
model,
|
|
15701
|
+
max_tokens: 1024,
|
|
15702
|
+
system: SYSTEM_PROMPT,
|
|
15703
|
+
messages: [
|
|
15704
|
+
{
|
|
15705
|
+
role: "user",
|
|
15706
|
+
content: `Analyze the complexity of this feature:
|
|
15707
|
+
|
|
15708
|
+
${description}`
|
|
15709
|
+
}
|
|
15710
|
+
]
|
|
15711
|
+
});
|
|
15712
|
+
const textBlock = response.content.find((block) => block.type === "text");
|
|
15713
|
+
if (!textBlock || textBlock.type !== "text") {
|
|
15714
|
+
return null;
|
|
15715
|
+
}
|
|
15716
|
+
let jsonText = textBlock.text.trim();
|
|
15717
|
+
if (jsonText.startsWith("```")) {
|
|
15718
|
+
jsonText = jsonText.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "");
|
|
15719
|
+
}
|
|
15720
|
+
const parsed = JSON.parse(jsonText);
|
|
15721
|
+
return AIComplexityResult.parse(parsed);
|
|
15722
|
+
} catch {
|
|
15723
|
+
return null;
|
|
15724
|
+
}
|
|
15725
|
+
}
|
|
15641
15726
|
var VERSION = "0.1.0";
|
|
15642
15727
|
|
|
15643
15728
|
// src/commands/analyze.ts
|
|
@@ -16957,8 +17042,8 @@ Calibration summary (${drifts.length} features with data):`));
|
|
|
16957
17042
|
// src/commands/estimate.ts
|
|
16958
17043
|
import { Command as Command8 } from "commander";
|
|
16959
17044
|
import chalk9 from "chalk";
|
|
16960
|
-
var estimateCommand = new Command8("estimate").description("Quick single-feature estimate from a description").argument("<description>", "Feature description").option("-c, --complexity <size>", "Override complexity (S/M/L/XL)").option("--category <cat>", "Feature category", "general").action(
|
|
16961
|
-
(description, options) => {
|
|
17045
|
+
var estimateCommand = new Command8("estimate").description("Quick single-feature estimate from a description").argument("<description>", "Feature description").option("-c, --complexity <size>", "Override complexity (S/M/L/XL)").option("--category <cat>", "Feature category", "general").option("--no-ai", "Skip AI complexity scoring, use heuristic only").action(
|
|
17046
|
+
async (description, options) => {
|
|
16962
17047
|
const data = loadData();
|
|
16963
17048
|
const feature = {
|
|
16964
17049
|
id: "quick-estimate",
|
|
@@ -16969,7 +17054,27 @@ var estimateCommand = new Command8("estimate").description("Quick single-feature
|
|
|
16969
17054
|
dependencies: [],
|
|
16970
17055
|
aiTasks: []
|
|
16971
17056
|
};
|
|
16972
|
-
|
|
17057
|
+
let complexitySource = "heuristic";
|
|
17058
|
+
let rationale;
|
|
17059
|
+
if (options.complexity) {
|
|
17060
|
+
complexitySource = "manual";
|
|
17061
|
+
} else if (options.ai !== false) {
|
|
17062
|
+
const aiResult = await scoreComplexityAI(description);
|
|
17063
|
+
if (aiResult) {
|
|
17064
|
+
feature.complexity = aiResult.complexity;
|
|
17065
|
+
feature.dependencies = aiResult.dependencies;
|
|
17066
|
+
feature.aiTasks = aiResult.aiTasks;
|
|
17067
|
+
complexitySource = "AI";
|
|
17068
|
+
rationale = aiResult.rationale;
|
|
17069
|
+
} else {
|
|
17070
|
+
feature.complexity = scoreComplexity(feature);
|
|
17071
|
+
console.log(
|
|
17072
|
+
chalk9.dim(
|
|
17073
|
+
" (AI scoring unavailable \u2014 using heuristic. Set ANTHROPIC_API_KEY for richer estimates)\n"
|
|
17074
|
+
)
|
|
17075
|
+
);
|
|
17076
|
+
}
|
|
17077
|
+
} else {
|
|
16973
17078
|
feature.complexity = scoreComplexity(feature);
|
|
16974
17079
|
}
|
|
16975
17080
|
const taskTypes = mapTaskTypes(feature);
|
|
@@ -16978,7 +17083,12 @@ var estimateCommand = new Command8("estimate").description("Quick single-feature
|
|
|
16978
17083
|
console.log(chalk9.bold("\nFathom \u2014 Quick Estimate"));
|
|
16979
17084
|
console.log(chalk9.dim("\u2500".repeat(50)));
|
|
16980
17085
|
console.log(` Feature: ${feature.name}`);
|
|
16981
|
-
console.log(
|
|
17086
|
+
console.log(
|
|
17087
|
+
` Complexity: ${feature.complexity} ${chalk9.dim(`(${complexitySource})`)}`
|
|
17088
|
+
);
|
|
17089
|
+
if (rationale) {
|
|
17090
|
+
console.log(` Rationale: ${chalk9.dim(rationale)}`);
|
|
17091
|
+
}
|
|
16982
17092
|
console.log(` Tasks: ${taskTypes.join(", ")}`);
|
|
16983
17093
|
console.log(` Model: ${rec.model} (${rec.reason})`);
|
|
16984
17094
|
console.log(chalk9.dim("\u2500".repeat(50)));
|
|
@@ -17603,8 +17713,8 @@ var IntakeResult = external_exports.object({
|
|
|
17603
17713
|
totalEstimatedCost: external_exports.number(),
|
|
17604
17714
|
createdAt: external_exports.string()
|
|
17605
17715
|
});
|
|
17606
|
-
async function
|
|
17607
|
-
const { default: AnthropicSDK } = await import("
|
|
17716
|
+
async function getAnthropicClient2(apiKey) {
|
|
17717
|
+
const { default: AnthropicSDK } = await import("@anthropic-ai/sdk");
|
|
17608
17718
|
return new AnthropicSDK({ apiKey: apiKey ?? process.env.ANTHROPIC_API_KEY });
|
|
17609
17719
|
}
|
|
17610
17720
|
var EXTRACTION_SYSTEM_PROMPT = `You are a technical project analyst. Your job is to extract discrete, actionable work items from raw input (user feedback, bug reports, meeting notes, design handoffs, etc.).
|
|
@@ -17624,8 +17734,8 @@ Split compound requests into separate items. Identify implicit requirements. Be
|
|
|
17624
17734
|
Respond with ONLY a JSON array of items. No markdown, no explanation.`;
|
|
17625
17735
|
var ExtractionResponse = external_exports.array(ExtractedItem);
|
|
17626
17736
|
async function extractWorkItems(rawInput, options = {}) {
|
|
17627
|
-
const client = options.client ?? await
|
|
17628
|
-
const model = options.model ?? "claude-sonnet-4-
|
|
17737
|
+
const client = options.client ?? await getAnthropicClient2(options.apiKey);
|
|
17738
|
+
const model = options.model ?? "claude-sonnet-4-20250514";
|
|
17629
17739
|
const response = await client.messages.create({
|
|
17630
17740
|
model,
|
|
17631
17741
|
max_tokens: 4096,
|
|
@@ -17941,8 +18051,535 @@ var initCommand = new Command12("init").description("Initialize Fathom global co
|
|
|
17941
18051
|
);
|
|
17942
18052
|
});
|
|
17943
18053
|
|
|
18054
|
+
// src/commands/rename.ts
|
|
18055
|
+
import { Command as Command13 } from "commander";
|
|
18056
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync5, existsSync as existsSync12 } from "fs";
|
|
18057
|
+
import { resolve as resolve12 } from "path";
|
|
18058
|
+
import chalk14 from "chalk";
|
|
18059
|
+
var renameCommand = new Command13("rename").description("Rename the project in all .claude/te/ config files").argument("<name>", "New project name").action((name) => {
|
|
18060
|
+
const baseDir = process.cwd();
|
|
18061
|
+
const teDir = resolve12(baseDir, ".claude", "te");
|
|
18062
|
+
if (!existsSync12(teDir)) {
|
|
18063
|
+
console.error(
|
|
18064
|
+
chalk14.red("\n Error: .claude/te/ not found. Run `fathom setup` first.\n")
|
|
18065
|
+
);
|
|
18066
|
+
process.exit(1);
|
|
18067
|
+
}
|
|
18068
|
+
const jsonFiles = [
|
|
18069
|
+
resolve12(teDir, "config.json"),
|
|
18070
|
+
resolve12(teDir, "registry.json"),
|
|
18071
|
+
resolve12(teDir, "tracking", "summary.json")
|
|
18072
|
+
];
|
|
18073
|
+
let oldName = "unnamed";
|
|
18074
|
+
let updated = 0;
|
|
18075
|
+
for (const filePath of jsonFiles) {
|
|
18076
|
+
if (!existsSync12(filePath)) continue;
|
|
18077
|
+
const data = JSON.parse(readFileSync12(filePath, "utf-8"));
|
|
18078
|
+
if (data.project) {
|
|
18079
|
+
oldName = data.project;
|
|
18080
|
+
data.project = name;
|
|
18081
|
+
writeFileSync5(filePath, JSON.stringify(data, null, 2) + "\n");
|
|
18082
|
+
updated++;
|
|
18083
|
+
}
|
|
18084
|
+
}
|
|
18085
|
+
const skillPath = resolve12(baseDir, ".claude", "skills", "fathom", "SKILL.md");
|
|
18086
|
+
if (existsSync12(skillPath)) {
|
|
18087
|
+
const content3 = readFileSync12(skillPath, "utf-8");
|
|
18088
|
+
const newContent = content3.replaceAll(oldName, name);
|
|
18089
|
+
if (newContent !== content3) {
|
|
18090
|
+
writeFileSync5(skillPath, newContent);
|
|
18091
|
+
updated++;
|
|
18092
|
+
}
|
|
18093
|
+
}
|
|
18094
|
+
const hookPath = resolve12(baseDir, ".claude", "hooks", "te-session-sync.sh");
|
|
18095
|
+
if (existsSync12(hookPath)) {
|
|
18096
|
+
const content3 = readFileSync12(hookPath, "utf-8");
|
|
18097
|
+
const newContent = content3.replaceAll(oldName, name);
|
|
18098
|
+
if (newContent !== content3) {
|
|
18099
|
+
writeFileSync5(hookPath, newContent);
|
|
18100
|
+
updated++;
|
|
18101
|
+
}
|
|
18102
|
+
}
|
|
18103
|
+
console.log(
|
|
18104
|
+
chalk14.bold(`
|
|
18105
|
+
Renamed "${oldName}" \u2192 "${name}" (${updated} files updated)
|
|
18106
|
+
`)
|
|
18107
|
+
);
|
|
18108
|
+
});
|
|
18109
|
+
|
|
18110
|
+
// src/commands/research.ts
|
|
18111
|
+
import { Command as Command14 } from "commander";
|
|
18112
|
+
import { writeFileSync as writeFileSync6, existsSync as existsSync13 } from "fs";
|
|
18113
|
+
import { resolve as resolve13 } from "path";
|
|
18114
|
+
import chalk15 from "chalk";
|
|
18115
|
+
|
|
18116
|
+
// src/fetchers/types.ts
|
|
18117
|
+
var RemoteManifest = external_exports.object({
|
|
18118
|
+
version: external_exports.number(),
|
|
18119
|
+
updated: external_exports.string(),
|
|
18120
|
+
models: external_exports.array(ModelPricing),
|
|
18121
|
+
recommendations: external_exports.record(external_exports.string(), AgentRecommendation)
|
|
18122
|
+
});
|
|
18123
|
+
var IntelligenceSuggestion = external_exports.object({
|
|
18124
|
+
type: external_exports.enum(["new-model", "price-change", "recommendation-change", "deprecation", "general"]),
|
|
18125
|
+
model: external_exports.string().optional(),
|
|
18126
|
+
description: external_exports.string(),
|
|
18127
|
+
confidence: external_exports.enum(["high", "medium", "low"])
|
|
18128
|
+
});
|
|
18129
|
+
var IntelligenceResponse = external_exports.object({
|
|
18130
|
+
suggestions: external_exports.array(IntelligenceSuggestion),
|
|
18131
|
+
summary: external_exports.string()
|
|
18132
|
+
});
|
|
18133
|
+
|
|
18134
|
+
// src/fetchers/pricing-fetcher.ts
|
|
18135
|
+
var MANIFEST_URL = "https://raw.githubusercontent.com/anthropics/fathom/main/data/remote-manifest.json";
|
|
18136
|
+
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
18137
|
+
async function fetchPricing(options = {}) {
|
|
18138
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
18139
|
+
if (options.offline) {
|
|
18140
|
+
return loadLocalPricing(now);
|
|
18141
|
+
}
|
|
18142
|
+
try {
|
|
18143
|
+
const controller = new AbortController();
|
|
18144
|
+
const timeoutId = setTimeout(
|
|
18145
|
+
() => controller.abort(),
|
|
18146
|
+
options.timeoutMs ?? DEFAULT_TIMEOUT_MS
|
|
18147
|
+
);
|
|
18148
|
+
if (options.verbose) {
|
|
18149
|
+
console.log(` Fetching: ${MANIFEST_URL}`);
|
|
18150
|
+
}
|
|
18151
|
+
const response = await fetch(MANIFEST_URL, { signal: controller.signal });
|
|
18152
|
+
clearTimeout(timeoutId);
|
|
18153
|
+
if (!response.ok) {
|
|
18154
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
18155
|
+
}
|
|
18156
|
+
const json2 = await response.json();
|
|
18157
|
+
const manifest = RemoteManifest.parse(json2);
|
|
18158
|
+
return {
|
|
18159
|
+
data: {
|
|
18160
|
+
models: manifest.models,
|
|
18161
|
+
recommendations: manifest.recommendations,
|
|
18162
|
+
manifestVersion: manifest.version,
|
|
18163
|
+
manifestDate: manifest.updated
|
|
18164
|
+
},
|
|
18165
|
+
source: "remote",
|
|
18166
|
+
stale: false,
|
|
18167
|
+
fetchedAt: now
|
|
18168
|
+
};
|
|
18169
|
+
} catch (err) {
|
|
18170
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
18171
|
+
if (options.verbose) {
|
|
18172
|
+
console.log(` Remote fetch failed: ${errorMsg}`);
|
|
18173
|
+
}
|
|
18174
|
+
return loadLocalPricing(now, errorMsg);
|
|
18175
|
+
}
|
|
18176
|
+
}
|
|
18177
|
+
function loadLocalPricing(fetchedAt, error) {
|
|
18178
|
+
const data = loadData();
|
|
18179
|
+
return {
|
|
18180
|
+
data: {
|
|
18181
|
+
models: data.models,
|
|
18182
|
+
recommendations: data.recommendations,
|
|
18183
|
+
manifestVersion: 0,
|
|
18184
|
+
manifestDate: "local"
|
|
18185
|
+
},
|
|
18186
|
+
source: "local",
|
|
18187
|
+
stale: true,
|
|
18188
|
+
fetchedAt,
|
|
18189
|
+
error: error ?? "offline mode"
|
|
18190
|
+
};
|
|
18191
|
+
}
|
|
18192
|
+
|
|
18193
|
+
// src/fetchers/intelligence-fetcher.ts
|
|
18194
|
+
async function getAnthropicClient3(apiKey) {
|
|
18195
|
+
const { default: AnthropicSDK } = await import("@anthropic-ai/sdk");
|
|
18196
|
+
return new AnthropicSDK({ apiKey: apiKey ?? process.env.ANTHROPIC_API_KEY });
|
|
18197
|
+
}
|
|
18198
|
+
var SYSTEM_PROMPT2 = `You are an AI model pricing analyst. Given the current model pricing and agent recommendations for an AI development workflow tool, review them and suggest updates.
|
|
18199
|
+
|
|
18200
|
+
You should:
|
|
18201
|
+
1. Flag any models with outdated pricing based on your knowledge
|
|
18202
|
+
2. Suggest new models that should be added (only well-established models from major providers)
|
|
18203
|
+
3. Flag any recommendation changes (e.g., if a newer model is better suited for a task category)
|
|
18204
|
+
4. Note any models that may be deprecated or renamed
|
|
18205
|
+
|
|
18206
|
+
Respond with ONLY valid JSON matching this schema:
|
|
18207
|
+
{
|
|
18208
|
+
"suggestions": [
|
|
18209
|
+
{
|
|
18210
|
+
"type": "new-model" | "price-change" | "recommendation-change" | "deprecation" | "general",
|
|
18211
|
+
"model": "model-id (optional)",
|
|
18212
|
+
"description": "what changed or should change",
|
|
18213
|
+
"confidence": "high" | "medium" | "low"
|
|
18214
|
+
}
|
|
18215
|
+
],
|
|
18216
|
+
"summary": "one sentence overall assessment"
|
|
18217
|
+
}
|
|
18218
|
+
|
|
18219
|
+
If everything looks current, return an empty suggestions array with a summary saying so.`;
|
|
18220
|
+
async function fetchIntelligence(models, recommendations, options = {}) {
|
|
18221
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
18222
|
+
const apiKey = options.apiKey ?? process.env.ANTHROPIC_API_KEY;
|
|
18223
|
+
if (options.offline || !apiKey) {
|
|
18224
|
+
if (options.verbose) {
|
|
18225
|
+
console.log(
|
|
18226
|
+
apiKey ? " Intelligence: skipped (offline mode)" : " Intelligence: skipped (no ANTHROPIC_API_KEY)"
|
|
18227
|
+
);
|
|
18228
|
+
}
|
|
18229
|
+
return null;
|
|
18230
|
+
}
|
|
18231
|
+
try {
|
|
18232
|
+
if (options.verbose) {
|
|
18233
|
+
console.log(" Intelligence: querying Claude Haiku...");
|
|
18234
|
+
}
|
|
18235
|
+
const client = await getAnthropicClient3(apiKey);
|
|
18236
|
+
const modelsDesc = models.map((m) => `${m.id} (${m.name}): $${m.input_per_mtok}/$${m.output_per_mtok} per MTok, tier=${m.tier}`).join("\n");
|
|
18237
|
+
const recsDesc = Object.entries(recommendations).map(([cat, rec]) => `${cat}: ${rec.model} \u2014 ${rec.reason}`).join("\n");
|
|
18238
|
+
const userPrompt = `Current model pricing:
|
|
18239
|
+
${modelsDesc}
|
|
18240
|
+
|
|
18241
|
+
Current agent recommendations:
|
|
18242
|
+
${recsDesc}`;
|
|
18243
|
+
const response = await client.messages.create({
|
|
18244
|
+
model: "claude-haiku-4-5-20251001",
|
|
18245
|
+
max_tokens: 1024,
|
|
18246
|
+
system: SYSTEM_PROMPT2,
|
|
18247
|
+
messages: [{ role: "user", content: userPrompt }]
|
|
18248
|
+
});
|
|
18249
|
+
const textBlock = response.content.find((block) => block.type === "text");
|
|
18250
|
+
if (!textBlock || textBlock.type !== "text") {
|
|
18251
|
+
throw new Error("No text content in response");
|
|
18252
|
+
}
|
|
18253
|
+
let jsonText = textBlock.text.trim();
|
|
18254
|
+
if (jsonText.startsWith("```")) {
|
|
18255
|
+
jsonText = jsonText.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "");
|
|
18256
|
+
}
|
|
18257
|
+
const parsed = JSON.parse(jsonText);
|
|
18258
|
+
const intelligence = IntelligenceResponse.parse(parsed);
|
|
18259
|
+
return {
|
|
18260
|
+
data: intelligence,
|
|
18261
|
+
source: "api",
|
|
18262
|
+
stale: false,
|
|
18263
|
+
fetchedAt: now
|
|
18264
|
+
};
|
|
18265
|
+
} catch (err) {
|
|
18266
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
18267
|
+
if (options.verbose) {
|
|
18268
|
+
console.log(` Intelligence fetch failed: ${errorMsg}`);
|
|
18269
|
+
}
|
|
18270
|
+
return null;
|
|
18271
|
+
}
|
|
18272
|
+
}
|
|
18273
|
+
|
|
18274
|
+
// src/fetchers/merge.ts
|
|
18275
|
+
function mergePricingData(localModels, remoteModels, localRecs, remoteRecs, suggestions = []) {
|
|
18276
|
+
const mergedModels = [];
|
|
18277
|
+
const modelChanges = [];
|
|
18278
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
18279
|
+
for (const remote of remoteModels) {
|
|
18280
|
+
seenIds.add(remote.id);
|
|
18281
|
+
const local = localModels.find((m) => m.id === remote.id);
|
|
18282
|
+
if (!local) {
|
|
18283
|
+
mergedModels.push(remote);
|
|
18284
|
+
modelChanges.push({ id: remote.id, name: remote.name, status: "added" });
|
|
18285
|
+
continue;
|
|
18286
|
+
}
|
|
18287
|
+
const fields = [];
|
|
18288
|
+
const pricingKeys = [
|
|
18289
|
+
"input_per_mtok",
|
|
18290
|
+
"output_per_mtok",
|
|
18291
|
+
"cache_read_per_mtok",
|
|
18292
|
+
"batch_discount"
|
|
18293
|
+
];
|
|
18294
|
+
for (const key of pricingKeys) {
|
|
18295
|
+
if (local[key] !== remote[key]) {
|
|
18296
|
+
fields.push({ field: key, old: local[key], new: remote[key] });
|
|
18297
|
+
}
|
|
18298
|
+
}
|
|
18299
|
+
if (local.tier !== remote.tier) {
|
|
18300
|
+
fields.push({ field: "tier", old: local.tier, new: remote.tier });
|
|
18301
|
+
}
|
|
18302
|
+
mergedModels.push(remote);
|
|
18303
|
+
modelChanges.push({
|
|
18304
|
+
id: remote.id,
|
|
18305
|
+
name: remote.name,
|
|
18306
|
+
status: fields.length > 0 ? "updated" : "unchanged",
|
|
18307
|
+
fields: fields.length > 0 ? fields : void 0
|
|
18308
|
+
});
|
|
18309
|
+
}
|
|
18310
|
+
for (const local of localModels) {
|
|
18311
|
+
if (!seenIds.has(local.id)) {
|
|
18312
|
+
mergedModels.push(local);
|
|
18313
|
+
modelChanges.push({ id: local.id, name: local.name, status: "unchanged" });
|
|
18314
|
+
}
|
|
18315
|
+
}
|
|
18316
|
+
const mergedRecs = { ...localRecs };
|
|
18317
|
+
const recChanges = [];
|
|
18318
|
+
for (const [cat, remoteRec] of Object.entries(remoteRecs)) {
|
|
18319
|
+
const localRec = localRecs[cat];
|
|
18320
|
+
if (!localRec) {
|
|
18321
|
+
mergedRecs[cat] = remoteRec;
|
|
18322
|
+
recChanges.push({
|
|
18323
|
+
category: cat,
|
|
18324
|
+
status: "added",
|
|
18325
|
+
newModel: remoteRec.model,
|
|
18326
|
+
reason: remoteRec.reason
|
|
18327
|
+
});
|
|
18328
|
+
} else if (localRec.model !== remoteRec.model || localRec.reason !== remoteRec.reason) {
|
|
18329
|
+
mergedRecs[cat] = remoteRec;
|
|
18330
|
+
recChanges.push({
|
|
18331
|
+
category: cat,
|
|
18332
|
+
status: "updated",
|
|
18333
|
+
oldModel: localRec.model,
|
|
18334
|
+
newModel: remoteRec.model,
|
|
18335
|
+
reason: remoteRec.reason
|
|
18336
|
+
});
|
|
18337
|
+
} else {
|
|
18338
|
+
recChanges.push({ category: cat, status: "unchanged" });
|
|
18339
|
+
}
|
|
18340
|
+
}
|
|
18341
|
+
return {
|
|
18342
|
+
models: {
|
|
18343
|
+
merged: mergedModels,
|
|
18344
|
+
changes: modelChanges,
|
|
18345
|
+
added: modelChanges.filter((c) => c.status === "added").length,
|
|
18346
|
+
updated: modelChanges.filter((c) => c.status === "updated").length,
|
|
18347
|
+
unchanged: modelChanges.filter((c) => c.status === "unchanged").length
|
|
18348
|
+
},
|
|
18349
|
+
recommendations: {
|
|
18350
|
+
merged: mergedRecs,
|
|
18351
|
+
changes: recChanges,
|
|
18352
|
+
added: recChanges.filter((c) => c.status === "added").length,
|
|
18353
|
+
updated: recChanges.filter((c) => c.status === "updated").length,
|
|
18354
|
+
unchanged: recChanges.filter((c) => c.status === "unchanged").length
|
|
18355
|
+
},
|
|
18356
|
+
suggestions
|
|
18357
|
+
};
|
|
18358
|
+
}
|
|
18359
|
+
function generateModelsYaml(models) {
|
|
18360
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
18361
|
+
let yaml = `# Model pricing registry \u2014 updated via \`fathom research\`
|
|
18362
|
+
`;
|
|
18363
|
+
yaml += `# Last verified: ${date}
|
|
18364
|
+
|
|
18365
|
+
`;
|
|
18366
|
+
yaml += `models:
|
|
18367
|
+
`;
|
|
18368
|
+
for (const m of models) {
|
|
18369
|
+
yaml += ` - id: ${m.id}
|
|
18370
|
+
`;
|
|
18371
|
+
yaml += ` provider: ${m.provider}
|
|
18372
|
+
`;
|
|
18373
|
+
yaml += ` name: ${m.name}
|
|
18374
|
+
`;
|
|
18375
|
+
yaml += ` input_per_mtok: ${m.input_per_mtok.toFixed(2)}
|
|
18376
|
+
`;
|
|
18377
|
+
yaml += ` output_per_mtok: ${m.output_per_mtok.toFixed(2)}
|
|
18378
|
+
`;
|
|
18379
|
+
yaml += ` cache_read_per_mtok: ${m.cache_read_per_mtok.toFixed(2)}
|
|
18380
|
+
`;
|
|
18381
|
+
yaml += ` batch_discount: ${m.batch_discount.toFixed(2)}
|
|
18382
|
+
`;
|
|
18383
|
+
yaml += ` tier: ${m.tier}
|
|
18384
|
+
|
|
18385
|
+
`;
|
|
18386
|
+
}
|
|
18387
|
+
return yaml;
|
|
18388
|
+
}
|
|
18389
|
+
function generateAgentsYaml(recommendations) {
|
|
18390
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
18391
|
+
let yaml = `# Agent/model recommendation mappings
|
|
18392
|
+
`;
|
|
18393
|
+
yaml += `# Maps task categories to recommended models
|
|
18394
|
+
`;
|
|
18395
|
+
yaml += `# Last updated: ${date}
|
|
18396
|
+
|
|
18397
|
+
`;
|
|
18398
|
+
yaml += `recommendations:
|
|
18399
|
+
`;
|
|
18400
|
+
for (const [category, rec] of Object.entries(recommendations)) {
|
|
18401
|
+
yaml += ` ${category}:
|
|
18402
|
+
`;
|
|
18403
|
+
yaml += ` model: ${rec.model}
|
|
18404
|
+
`;
|
|
18405
|
+
yaml += ` reason: ${rec.reason}
|
|
18406
|
+
|
|
18407
|
+
`;
|
|
18408
|
+
}
|
|
18409
|
+
return yaml;
|
|
18410
|
+
}
|
|
18411
|
+
|
|
18412
|
+
// src/commands/research.ts
|
|
18413
|
+
function findDataDir2() {
|
|
18414
|
+
const bundled = resolve13(import.meta.dirname ?? __dirname, "data");
|
|
18415
|
+
if (existsSync13(resolve13(bundled, "models.yaml"))) return bundled;
|
|
18416
|
+
return resolve13(import.meta.dirname ?? __dirname, "../../../../data");
|
|
18417
|
+
}
|
|
18418
|
+
var researchCommand = new Command14("research").description("Check and update model pricing data (fetches live data)").option("--update", "Update local data files with latest pricing").option("--sync", "Also sync updated pricing to Convex").option("--check", "Check for differences without updating (default)").option("--offline", "Skip remote fetches, use local data only").option("--verbose", "Show fetch sources and timing details").action(async (options) => {
|
|
18419
|
+
console.log(chalk15.bold("\nFathom \u2014 Research: Model Pricing"));
|
|
18420
|
+
console.log(chalk15.dim("\u2500".repeat(55)));
|
|
18421
|
+
const startTime = Date.now();
|
|
18422
|
+
let localData;
|
|
18423
|
+
try {
|
|
18424
|
+
localData = loadData();
|
|
18425
|
+
} catch {
|
|
18426
|
+
console.log(chalk15.yellow(" Warning: could not load local data files"));
|
|
18427
|
+
localData = { models: [], recommendations: {} };
|
|
18428
|
+
}
|
|
18429
|
+
const [pricingResult, intelligenceResult] = await Promise.all([
|
|
18430
|
+
fetchPricing({ offline: options.offline, verbose: options.verbose }),
|
|
18431
|
+
fetchIntelligence(localData.models, localData.recommendations, {
|
|
18432
|
+
offline: options.offline,
|
|
18433
|
+
verbose: options.verbose
|
|
18434
|
+
})
|
|
18435
|
+
]);
|
|
18436
|
+
if (options.verbose) {
|
|
18437
|
+
const elapsed = Date.now() - startTime;
|
|
18438
|
+
console.log(chalk15.dim(`
|
|
18439
|
+
Fetch time: ${elapsed}ms`));
|
|
18440
|
+
console.log(chalk15.dim(` Pricing source: ${pricingResult.source}`));
|
|
18441
|
+
if (intelligenceResult) {
|
|
18442
|
+
console.log(chalk15.dim(` Intelligence source: ${intelligenceResult.source}`));
|
|
18443
|
+
}
|
|
18444
|
+
}
|
|
18445
|
+
if (pricingResult.stale) {
|
|
18446
|
+
console.log(chalk15.yellow(`
|
|
18447
|
+
\u26A0 Using local data (${pricingResult.error ?? "stale"})`));
|
|
18448
|
+
} else {
|
|
18449
|
+
console.log(chalk15.green(`
|
|
18450
|
+
Pricing: live data (manifest v${pricingResult.data.manifestVersion}, ${pricingResult.data.manifestDate})`));
|
|
18451
|
+
}
|
|
18452
|
+
const mergeResult = mergePricingData(
|
|
18453
|
+
localData.models,
|
|
18454
|
+
pricingResult.data.models,
|
|
18455
|
+
localData.recommendations,
|
|
18456
|
+
pricingResult.data.recommendations,
|
|
18457
|
+
intelligenceResult?.data.suggestions ?? []
|
|
18458
|
+
);
|
|
18459
|
+
displayPricingReport(mergeResult);
|
|
18460
|
+
if (intelligenceResult?.data) {
|
|
18461
|
+
displayIntelligence(intelligenceResult.data);
|
|
18462
|
+
}
|
|
18463
|
+
displayRecommendations(mergeResult);
|
|
18464
|
+
if (options.update) {
|
|
18465
|
+
const dataDir = findDataDir2();
|
|
18466
|
+
const modelsPath = resolve13(dataDir, "models.yaml");
|
|
18467
|
+
const agentsPath = resolve13(dataDir, "agents.yaml");
|
|
18468
|
+
writeFileSync6(modelsPath, generateModelsYaml(mergeResult.models.merged));
|
|
18469
|
+
console.log(chalk15.green(`
|
|
18470
|
+
Updated: ${modelsPath}`));
|
|
18471
|
+
writeFileSync6(agentsPath, generateAgentsYaml(mergeResult.recommendations.merged));
|
|
18472
|
+
console.log(chalk15.green(` Updated: ${agentsPath}`));
|
|
18473
|
+
}
|
|
18474
|
+
if (options.sync) {
|
|
18475
|
+
await syncToConvex(mergeResult);
|
|
18476
|
+
}
|
|
18477
|
+
if (options.verbose) {
|
|
18478
|
+
const totalElapsed = Date.now() - startTime;
|
|
18479
|
+
console.log(chalk15.dim(`
|
|
18480
|
+
Total time: ${totalElapsed}ms`));
|
|
18481
|
+
}
|
|
18482
|
+
console.log();
|
|
18483
|
+
});
|
|
18484
|
+
function displayPricingReport(result) {
|
|
18485
|
+
const { models } = result;
|
|
18486
|
+
if (models.added === 0 && models.updated === 0) {
|
|
18487
|
+
console.log(chalk15.green(` All model pricing is up to date.`));
|
|
18488
|
+
console.log(chalk15.dim(` ${models.merged.length} models checked.`));
|
|
18489
|
+
} else {
|
|
18490
|
+
if (models.updated > 0) {
|
|
18491
|
+
console.log(chalk15.yellow(`
|
|
18492
|
+
${models.updated} pricing change(s) found:`));
|
|
18493
|
+
for (const change of models.changes) {
|
|
18494
|
+
if (change.status === "updated" && change.fields) {
|
|
18495
|
+
for (const f of change.fields) {
|
|
18496
|
+
console.log(chalk15.dim(` ${change.name}: ${f.field} ${f.old} \u2192 ${f.new}`));
|
|
18497
|
+
}
|
|
18498
|
+
}
|
|
18499
|
+
}
|
|
18500
|
+
}
|
|
18501
|
+
if (models.added > 0) {
|
|
18502
|
+
console.log(chalk15.yellow(`
|
|
18503
|
+
${models.added} new model(s) found:`));
|
|
18504
|
+
for (const change of models.changes) {
|
|
18505
|
+
if (change.status === "added") {
|
|
18506
|
+
const m = models.merged.find((x) => x.id === change.id);
|
|
18507
|
+
if (m) {
|
|
18508
|
+
console.log(
|
|
18509
|
+
chalk15.dim(` ${m.name} (${m.tier}): $${m.input_per_mtok}/$${m.output_per_mtok} per MTok`)
|
|
18510
|
+
);
|
|
18511
|
+
}
|
|
18512
|
+
}
|
|
18513
|
+
}
|
|
18514
|
+
}
|
|
18515
|
+
}
|
|
18516
|
+
}
|
|
18517
|
+
function displayIntelligence(intelligence) {
|
|
18518
|
+
if (intelligence.suggestions.length === 0) {
|
|
18519
|
+
console.log(chalk15.dim(`
|
|
18520
|
+
Intelligence: ${intelligence.summary}`));
|
|
18521
|
+
return;
|
|
18522
|
+
}
|
|
18523
|
+
console.log(chalk15.cyan(`
|
|
18524
|
+
Intelligence suggestions (${intelligence.suggestions.length}):`));
|
|
18525
|
+
for (const s of intelligence.suggestions) {
|
|
18526
|
+
const badge = s.confidence === "high" ? chalk15.red("HIGH") : s.confidence === "medium" ? chalk15.yellow("MED") : chalk15.dim("LOW");
|
|
18527
|
+
const model = s.model ? chalk15.dim(` [${s.model}]`) : "";
|
|
18528
|
+
console.log(` ${badge} ${s.description}${model}`);
|
|
18529
|
+
}
|
|
18530
|
+
console.log(chalk15.dim(` Summary: ${intelligence.summary}`));
|
|
18531
|
+
console.log(chalk15.dim(` Note: Task profiles are team-calibrated and shown as suggestions only.`));
|
|
18532
|
+
}
|
|
18533
|
+
function displayRecommendations(result) {
|
|
18534
|
+
const { recommendations } = result;
|
|
18535
|
+
if (recommendations.updated > 0 || recommendations.added > 0) {
|
|
18536
|
+
console.log(chalk15.yellow(`
|
|
18537
|
+
Recommendation changes:`));
|
|
18538
|
+
for (const change of recommendations.changes) {
|
|
18539
|
+
if (change.status === "updated") {
|
|
18540
|
+
console.log(chalk15.dim(` ${change.category}: ${change.oldModel} \u2192 ${change.newModel}`));
|
|
18541
|
+
} else if (change.status === "added") {
|
|
18542
|
+
console.log(chalk15.dim(` ${change.category}: ${change.newModel} (new)`));
|
|
18543
|
+
}
|
|
18544
|
+
}
|
|
18545
|
+
}
|
|
18546
|
+
console.log(chalk15.dim("\n Current model recommendations:"));
|
|
18547
|
+
console.log(chalk15.dim(" S/M complexity \u2192 Haiku 4.5 ($1/$5 per MTok) \u2014 fast, cheap"));
|
|
18548
|
+
console.log(chalk15.dim(" L complexity \u2192 Sonnet 4.6 ($3/$15 per MTok) \u2014 balanced"));
|
|
18549
|
+
console.log(chalk15.dim(" XL complexity \u2192 Opus 4.6 ($15/$75 per MTok) \u2014 highest quality"));
|
|
18550
|
+
console.log(
|
|
18551
|
+
chalk15.dim("\n Tip: Enable prompt caching to save 90% on input tokens for repeated context.")
|
|
18552
|
+
);
|
|
18553
|
+
}
|
|
18554
|
+
async function syncToConvex(result) {
|
|
18555
|
+
const client = getConvexClient();
|
|
18556
|
+
if (!client) {
|
|
18557
|
+
logConvexStatus(false);
|
|
18558
|
+
return;
|
|
18559
|
+
}
|
|
18560
|
+
let synced = 0;
|
|
18561
|
+
for (const m of result.models.merged) {
|
|
18562
|
+
try {
|
|
18563
|
+
await client.mutation(api.modelPricing.upsert, {
|
|
18564
|
+
model: m.name,
|
|
18565
|
+
provider: m.provider,
|
|
18566
|
+
inputPerMTok: m.input_per_mtok,
|
|
18567
|
+
outputPerMTok: m.output_per_mtok,
|
|
18568
|
+
cacheReadPerMTok: m.cache_read_per_mtok,
|
|
18569
|
+
batchDiscount: m.batch_discount
|
|
18570
|
+
});
|
|
18571
|
+
synced++;
|
|
18572
|
+
} catch {
|
|
18573
|
+
}
|
|
18574
|
+
}
|
|
18575
|
+
logConvexStatus(synced > 0);
|
|
18576
|
+
if (synced > 0) {
|
|
18577
|
+
console.log(chalk15.dim(` ${synced} models synced to Convex.`));
|
|
18578
|
+
}
|
|
18579
|
+
}
|
|
18580
|
+
|
|
17944
18581
|
// src/index.ts
|
|
17945
|
-
var program = new
|
|
18582
|
+
var program = new Command15();
|
|
17946
18583
|
program.name("fathom").description("Workflow intelligence platform for AI-augmented development").version(VERSION);
|
|
17947
18584
|
program.addCommand(analyzeCommand);
|
|
17948
18585
|
program.addCommand(pricingCommand);
|
|
@@ -17956,6 +18593,8 @@ program.addCommand(validateCommand);
|
|
|
17956
18593
|
program.addCommand(velocityCommand);
|
|
17957
18594
|
program.addCommand(intakeCommand);
|
|
17958
18595
|
program.addCommand(initCommand);
|
|
18596
|
+
program.addCommand(renameCommand);
|
|
18597
|
+
program.addCommand(researchCommand);
|
|
17959
18598
|
program.parse();
|
|
17960
18599
|
/*! Bundled license information:
|
|
17961
18600
|
|