coaiajs 0.2.0 → 0.3.0

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.
@@ -10,7 +10,8 @@
10
10
  "Bash(git push *)"
11
11
  ],
12
12
  "additionalDirectories": [
13
- "/src/coaiapy"
13
+ "/src/coaiapy",
14
+ "/workspace/repos/jgwill/miadi-orchestration-kit"
14
15
  ]
15
16
  }
16
17
  }
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "coaiajs-marketplace",
3
+ "owner": {
4
+ "name": "Guillaume Isabelle",
5
+ "email": "jgi@jgwill.com",
6
+ "url": "https://github.com/jgwill"
7
+ },
8
+ "metadata": {
9
+ "description": "CoAIA Claude Code plugins",
10
+ "version": "0.3.0"
11
+ },
12
+ "plugins": [
13
+ {
14
+ "name": "coaiajs-langfuse",
15
+ "source": "./coaiajs-langfuse-claude-plugin",
16
+ "description": "CoAIA unified MCP — 64+ tools (structural tension charts, narrative knowledge graph, PDE, planning, Redis tash/fetch, Langfuse tracing).",
17
+ "version": "0.3.0",
18
+ "author": {
19
+ "name": "Guillaume Isabelle",
20
+ "url": "https://github.com/jgwill"
21
+ },
22
+ "license": "MIT",
23
+ "keywords": ["coaia", "mcp", "structural-tension", "narrative", "pde", "planning", "langfuse"]
24
+ }
25
+ ]
26
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "coaiajs-marketplace",
3
+ "owner": { "name": "Guillaume Isabelle", "email": "jgi@jgwill.com", "url": "https://github.com/jgwill" },
4
+ "metadata": { "description": "CoAIA Claude Code plugins", "version": "0.3.0" },
5
+ "plugins": [
6
+ {
7
+ "name": "coaiajs-langfuse",
8
+ "source": "./",
9
+ "description": "CoAIA unified MCP — 64+ tools (structural tension, narrative graph, PDE, planning, Redis tash/fetch, Langfuse tracing).",
10
+ "version": "0.3.0",
11
+ "author": { "name": "Guillaume Isabelle", "url": "https://github.com/jgwill" },
12
+ "license": "MIT",
13
+ "keywords": ["coaia", "mcp", "structural-tension", "narrative", "pde", "planning", "langfuse"]
14
+ }
15
+ ]
16
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "coaiajs-langfuse",
3
+ "description": "CoAIA unified MCP for Claude Code — 64+ tools across structural-tension charts, narrative knowledge graph, Prompt Decomposition Engine (PDE), action planning, Redis tash/fetch, and Langfuse tracing. Bundles the published `coaiajs` npm package's `coaiajs-mcp` server via npx.",
4
+ "version": "0.3.0",
5
+ "author": { "name": "Guillaume Isabelle", "email": "jgi@jgwill.com", "url": "https://github.com/jgwill" },
6
+ "homepage": "https://github.com/jgwill/coaiajs/tree/master/coaiajs-langfuse-claude-plugin",
7
+ "repository": "https://github.com/jgwill/coaiajs",
8
+ "license": "MIT",
9
+ "keywords": ["coaia", "mcp", "structural-tension", "narrative", "pde", "planning", "langfuse"]
10
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "mcpServers": {
3
+ "coaia": {
4
+ "command": "npx",
5
+ "args": ["-y", "--package=coaiajs", "coaiajs-mcp"]
6
+ }
7
+ }
8
+ }
@@ -0,0 +1,60 @@
1
+ # coaiajs-langfuse — Claude Code Plugin
2
+
3
+ A Claude Code plugin that registers the `coaiajs-mcp` server — 64+ tools, 3 prompts, and 3 resources — as an MCP server inside Claude Code. It does not bundle its own copy of the server; it launches the published `coaiajs` npm package on demand via `npx`:
4
+
5
+ ```
6
+ npx -y --package=coaiajs coaiajs-mcp
7
+ ```
8
+
9
+ That command is declared in `.mcp.json` at the root of this plugin, which Claude Code auto-loads on install.
10
+
11
+ ## Install
12
+
13
+ From a marketplace source (GitHub):
14
+
15
+ ```
16
+ claude plugin marketplace add https://github.com/jgwill/coaiajs
17
+ claude plugin install coaiajs-langfuse@coaiajs-marketplace
18
+ ```
19
+
20
+ Or from a local checkout:
21
+
22
+ ```
23
+ claude plugin marketplace add /a/src/coaiajs/coaiajs-langfuse-claude-plugin
24
+ claude plugin install coaiajs-langfuse@coaiajs-marketplace
25
+ ```
26
+
27
+ Confirm it registered:
28
+
29
+ ```
30
+ claude plugin list
31
+ ```
32
+
33
+ ## Tool families
34
+
35
+ - **Structural-tension charts** — desired outcome / current reality / action steps, the core CoAIA data model.
36
+ - **Narrative knowledge graph** — entities, relations, and graph queries (from coaia-narrative).
37
+ - **PDE (Prompt Decomposition Engine)** — decompose complex prompts into actionable intent maps.
38
+ - **Action planning** — structural tension plans and action-step management (from coaia-planning).
39
+ - **Redis tash/fetch** — SET/GET shorthand against a Redis store.
40
+ - **Langfuse tracing** — trace, span, and observation tools for Langfuse-backed observability.
41
+
42
+ ## Environment variables
43
+
44
+ The server boots with no configuration — it connects to nothing at import time. Individual tool families activate only when their own credentials are present in the environment Claude Code runs in:
45
+
46
+ | Variable | Used by |
47
+ | --- | --- |
48
+ | `REDIS_URL` | tash/fetch |
49
+ | `OPENAI_API_KEY` | LLM-backed tools |
50
+ | `LANGFUSE_PUBLIC_KEY` | Langfuse tracing |
51
+ | `LANGFUSE_SECRET_KEY` | Langfuse tracing |
52
+ | `LANGFUSE_BASE_URL` | Langfuse tracing |
53
+ | `GITHUB_TOKEN` | GitHub-integration tools |
54
+ | AWS credentials (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, etc.) | Polly audio tools |
55
+
56
+ None of these are set by the plugin itself — they are inherited from whatever shell/environment Claude Code is launched in. Set them in your shell profile, `.env`, or however you normally manage secrets for this machine.
57
+
58
+ ## Versioning
59
+
60
+ This plugin tracks the published npm `coaiajs` package rather than vendoring source. `.mcp.json` calls `npx -y --package=coaiajs coaiajs-mcp`, which always resolves the latest published version at launch time. To pin a specific version, edit `.mcp.json` and change `--package=coaiajs` to `--package=coaiajs@<version>` (e.g. `coaiajs@0.3.0`).
@@ -12,6 +12,7 @@ import { tash as redisTash, fetch as redisFetch } from '../src/redis.js';
12
12
  import { ALL_TOOL_DEFINITIONS as NARRATIVE_TOOL_DEFINITIONS, handleToolCall as handleNarrativeTool, KnowledgeGraphManager, } from '../src/narrative/index.js';
13
13
  import { PDE_MCP_TOOLS, handlePdeTool } from '../src/pde/index.js';
14
14
  import { PLANNING_MCP_TOOLS, handlePlanningTool } from '../src/planning/index.js';
15
+ import { getPackageVersion } from '../src/version.js';
15
16
  // Langfuse imports
16
17
  import { addTrace, patchTraceOutput, listTraces, getTrace, formatTracesMarkdown, formatTraceTree, } from '../src/langfuse/traces.js';
17
18
  import { addObservation, getObservation, formatObservationDisplay } from '../src/langfuse/observations.js';
@@ -22,7 +23,7 @@ import { listComments, getComment, createComment } from '../src/langfuse/comment
22
23
  import { uploadAndAttachMedia, getMedia, formatMediaDisplay } from '../src/langfuse/media.js';
23
24
  // ─── Server Info ────────────────────────────────────────────────────
24
25
  const SERVER_NAME = 'coaiajs-mcp';
25
- const SERVER_VERSION = '0.1.1';
26
+ const SERVER_VERSION = getPackageVersion();
26
27
  // ─── Parse CLI args ─────────────────────────────────────────────────
27
28
  function parseArgs() {
28
29
  const args = process.argv.slice(2);
package/dist/src/cli.js CHANGED
@@ -8,6 +8,7 @@ import { readConfig } from './config.js';
8
8
  import { llm as llmCall, transcribeAudio, abstractProcess } from './llm.js';
9
9
  import { listIssues, getIssue } from './github.js';
10
10
  import { createEnvironment, findEnvironment } from './environment.js';
11
+ import { getPackageVersion } from './version.js';
11
12
  import { formatTable, formatJson, formatError, formatSuccess, truncate, formatDate, formatProgress, setColorEnabled, } from './cli-helpers.js';
12
13
  // ---------------------------------------------------------------------------
13
14
  // Helpers
@@ -1084,7 +1085,7 @@ async function main() {
1084
1085
  const program = new Command();
1085
1086
  program
1086
1087
  .name('coaia')
1087
- .version('0.1.3', '-V, --version')
1088
+ .version(getPackageVersion(), '-V, --version')
1088
1089
  .description('CoAIA unified CLI — structural tension, narrative, and DevOps tooling')
1089
1090
  .option('--env <path>', 'Load environment file')
1090
1091
  .option('-M, --memory-path <path>', 'JSONL memory file path')
@@ -1,8 +1,8 @@
1
1
  export { LangfuseClient, LangfuseApiError, getClient, resetClient, nowISO } from './client.js';
2
2
  export type { IngestionEvent, LangfuseClientConfig } from './client.js';
3
- export { addTrace, patchTraceOutput, listTraces, getTrace, formatTracesTable, formatTracesMarkdown, formatTraceTree } from './traces.js';
3
+ export { addTrace, createTrace, patchTraceOutput, listTraces, getTrace, traceView, sessionView, formatTracesTable, formatTracesMarkdown, formatTraceTree, } from './traces.js';
4
4
  export type { TraceFilters } from './traces.js';
5
- export { addObservation, getObservation, formatObservationDisplay } from './observations.js';
5
+ export { addObservation, addObservations, getObservation, formatObservationDisplay } from './observations.js';
6
6
  export { listPrompts, getPrompt, createPrompt, formatPromptsTable, formatPromptDisplay } from './prompts.js';
7
7
  export { listDatasets, getDataset, createDataset, listDatasetItems, createDatasetItem, formatDatasetsTable, formatDatasetForFinetuning, } from './datasets.js';
8
8
  export { createScore, applyScoreToTrace, createScoreForTarget, listScores, listScoreConfigs, getScoreConfig, createScoreConfig, exportScoreConfigs, importScoreConfigs, applyScoreConfig, getBuiltInPresets, installPreset, formatScoresTable, formatScoreConfigsTable, } from './scores.js';
@@ -1,7 +1,7 @@
1
1
  // coaiajs/src/langfuse/index.ts — Barrel export
2
2
  export { LangfuseClient, LangfuseApiError, getClient, resetClient, nowISO } from './client.js';
3
- export { addTrace, patchTraceOutput, listTraces, getTrace, formatTracesTable, formatTracesMarkdown, formatTraceTree } from './traces.js';
4
- export { addObservation, getObservation, formatObservationDisplay } from './observations.js';
3
+ export { addTrace, createTrace, patchTraceOutput, listTraces, getTrace, traceView, sessionView, formatTracesTable, formatTracesMarkdown, formatTraceTree, } from './traces.js';
4
+ export { addObservation, addObservations, getObservation, formatObservationDisplay } from './observations.js';
5
5
  export { listPrompts, getPrompt, createPrompt, formatPromptsTable, formatPromptDisplay } from './prompts.js';
6
6
  export { listDatasets, getDataset, createDataset, listDatasetItems, createDatasetItem, formatDatasetsTable, formatDatasetForFinetuning, } from './datasets.js';
7
7
  export { createScore, applyScoreToTrace, createScoreForTarget, listScores, listScoreConfigs, getScoreConfig, createScoreConfig, exportScoreConfigs, importScoreConfigs, applyScoreConfig, getBuiltInPresets, installPreset, formatScoresTable, formatScoreConfigsTable, } from './scores.js';
@@ -4,9 +4,12 @@ export declare function addObservation(params: {
4
4
  type?: string;
5
5
  name: string;
6
6
  parentId?: string;
7
+ parentObservationId?: string;
8
+ input?: string;
7
9
  inputData?: unknown;
10
+ output?: string;
8
11
  outputData?: unknown;
9
- metadata?: unknown;
12
+ metadata?: string | unknown;
10
13
  startTime?: string;
11
14
  endTime?: string;
12
15
  level?: string;
@@ -14,5 +17,9 @@ export declare function addObservation(params: {
14
17
  usage?: unknown;
15
18
  }): Promise<string>;
16
19
  export declare function getObservation(observationId: string): Promise<string>;
20
+ export declare function addObservations(params: {
21
+ traceId?: string;
22
+ observations?: Array<Record<string, unknown>>;
23
+ }): Promise<string>;
17
24
  export declare function formatObservationDisplay(json: unknown): string;
18
25
  //# sourceMappingURL=observations.d.ts.map
@@ -15,14 +15,14 @@ export async function addObservation(params) {
15
15
  };
16
16
  if (params.name)
17
17
  body.name = params.name;
18
- if (params.inputData)
19
- body.input = params.inputData;
20
- if (params.outputData)
21
- body.output = params.outputData;
18
+ if (params.inputData ?? params.input)
19
+ body.input = parseJsonOption(params.inputData ?? params.input);
20
+ if (params.outputData ?? params.output)
21
+ body.output = parseJsonOption(params.outputData ?? params.output);
22
22
  if (params.metadata)
23
- body.metadata = params.metadata;
24
- if (params.parentId)
25
- body.parentObservationId = params.parentId;
23
+ body.metadata = parseJsonOption(params.metadata);
24
+ if (params.parentId ?? params.parentObservationId)
25
+ body.parentObservationId = params.parentId ?? params.parentObservationId;
26
26
  if (params.endTime)
27
27
  body.endTime = params.endTime;
28
28
  if (params.model)
@@ -49,6 +49,28 @@ export async function getObservation(observationId) {
49
49
  const result = await client.request('GET', `/api/public/observations/${observationId}`);
50
50
  return JSON.stringify(result, null, 2);
51
51
  }
52
+ function parseJsonOption(value) {
53
+ if (typeof value !== 'string')
54
+ return value;
55
+ try {
56
+ return JSON.parse(value);
57
+ }
58
+ catch {
59
+ return value;
60
+ }
61
+ }
62
+ export async function addObservations(params) {
63
+ const observations = params.observations ?? [];
64
+ const results = [];
65
+ for (const observation of observations) {
66
+ const traceId = String(observation.traceId ?? params.traceId ?? '');
67
+ const name = String(observation.name ?? 'Observation');
68
+ if (!traceId)
69
+ throw new Error('traceId is required for every observation');
70
+ results.push(JSON.parse(await addObservation({ ...observation, traceId, name })));
71
+ }
72
+ return JSON.stringify({ success: true, count: results.length, observations: results }, null, 2);
73
+ }
52
74
  // ─── Formatters ─────────────────────────────────────────────────────
53
75
  const TYPE_GLYPHS = {
54
76
  SPAN: '🔗',
@@ -21,9 +21,27 @@ export declare function addTrace(params: {
21
21
  outputData?: unknown;
22
22
  metadata?: unknown;
23
23
  }): Promise<string>;
24
- export declare function patchTraceOutput(traceId: string, outputData: unknown): Promise<string>;
24
+ export declare function patchTraceOutput(traceIdOrParams: string | {
25
+ traceId?: string;
26
+ output?: string;
27
+ outputData?: unknown;
28
+ }, outputData?: unknown): Promise<string>;
29
+ export declare function createTrace(params: {
30
+ traceId?: string;
31
+ id?: string;
32
+ userId?: string;
33
+ sessionId?: string;
34
+ name?: string;
35
+ input?: string;
36
+ inputData?: unknown;
37
+ output?: string;
38
+ outputData?: unknown;
39
+ metadata?: string | unknown;
40
+ }): Promise<string>;
25
41
  export declare function listTraces(filters: TraceFilters): Promise<string>;
26
42
  export declare function getTrace(traceId: string): Promise<string>;
43
+ export declare function traceView(traceId: string): Promise<string>;
44
+ export declare function sessionView(sessionId: string): Promise<string>;
27
45
  export declare function formatTracesTable(json: unknown): string;
28
46
  export declare function formatTraceTree(json: unknown): string;
29
47
  export declare function formatTracesMarkdown(json: unknown): string;
@@ -35,13 +35,19 @@ export async function addTrace(params) {
35
35
  message: `Trace ${traceId} created`,
36
36
  }, null, 2);
37
37
  }
38
- export async function patchTraceOutput(traceId, outputData) {
38
+ export async function patchTraceOutput(traceIdOrParams, outputData) {
39
+ const traceId = typeof traceIdOrParams === 'string' ? traceIdOrParams : traceIdOrParams.traceId;
40
+ if (!traceId)
41
+ throw new Error('traceId is required');
42
+ const output = typeof traceIdOrParams === 'string'
43
+ ? outputData
44
+ : parseJsonOption(traceIdOrParams.outputData ?? traceIdOrParams.output);
39
45
  const client = getClient();
40
46
  const now = nowISO();
41
47
  const body = {
42
48
  id: traceId,
43
49
  timestamp: now,
44
- output: outputData,
50
+ output,
45
51
  };
46
52
  const eventId = `${traceId}-patch-${uuidv4().slice(0, 8)}`;
47
53
  const event = {
@@ -57,6 +63,27 @@ export async function patchTraceOutput(traceId, outputData) {
57
63
  message: `Trace output patched for ${traceId}`,
58
64
  }, null, 2);
59
65
  }
66
+ function parseJsonOption(value) {
67
+ if (typeof value !== 'string')
68
+ return value;
69
+ try {
70
+ return JSON.parse(value);
71
+ }
72
+ catch {
73
+ return value;
74
+ }
75
+ }
76
+ export async function createTrace(params) {
77
+ return addTrace({
78
+ traceId: params.traceId ?? params.id,
79
+ userId: params.userId,
80
+ sessionId: params.sessionId,
81
+ name: params.name,
82
+ inputData: parseJsonOption(params.inputData ?? params.input),
83
+ outputData: parseJsonOption(params.outputData ?? params.output),
84
+ metadata: parseJsonOption(params.metadata),
85
+ });
86
+ }
60
87
  export async function listTraces(filters) {
61
88
  const client = getClient();
62
89
  const params = new URLSearchParams();
@@ -111,6 +138,14 @@ export async function getTrace(traceId) {
111
138
  }
112
139
  return JSON.stringify(trace, null, 2);
113
140
  }
141
+ export async function traceView(traceId) {
142
+ const raw = await getTrace(traceId);
143
+ return formatTraceTree(raw);
144
+ }
145
+ export async function sessionView(sessionId) {
146
+ const raw = await listTraces({ sessionId, limit: 20, orderBy: 'timestamp.desc' });
147
+ return formatTracesMarkdown(raw);
148
+ }
114
149
  // ─── Formatters ─────────────────────────────────────────────────────
115
150
  export function formatTracesTable(json) {
116
151
  try {
@@ -0,0 +1,3 @@
1
+ /** Resolved version of the installed `coaiajs` package. */
2
+ export declare function getPackageVersion(): string;
3
+ //# sourceMappingURL=version.d.ts.map
@@ -0,0 +1,33 @@
1
+ // coaiajs/src/version.ts — single source of truth for the runtime version.
2
+ // Reads package.json at runtime so the CLI banner, the MCP handshake, and the
3
+ // published package can never drift apart again.
4
+ import { readFileSync } from 'node:fs';
5
+ import { dirname, join } from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+ const PACKAGE_NAME = 'coaiajs';
8
+ let cached;
9
+ /** Resolved version of the installed `coaiajs` package. */
10
+ export function getPackageVersion() {
11
+ if (cached)
12
+ return cached;
13
+ const here = dirname(fileURLToPath(import.meta.url));
14
+ // dist/src/version.js -> package root is two levels up.
15
+ // src/version.ts (ts-node/tsx) -> package root is one level up.
16
+ const candidates = [join(here, '..', '..', 'package.json'), join(here, '..', 'package.json')];
17
+ for (const candidate of candidates) {
18
+ let raw;
19
+ try {
20
+ raw = readFileSync(candidate, 'utf8');
21
+ }
22
+ catch {
23
+ continue; // not at this depth; try the next candidate
24
+ }
25
+ const pkg = JSON.parse(raw);
26
+ if (pkg.name === PACKAGE_NAME && pkg.version) {
27
+ cached = pkg.version;
28
+ return cached;
29
+ }
30
+ }
31
+ throw new Error(`Unable to resolve ${PACKAGE_NAME} version: no package.json found near ${here}`);
32
+ }
33
+ //# sourceMappingURL=version.js.map
package/mcp/server.ts CHANGED
@@ -28,6 +28,7 @@ import {
28
28
  import { PDE_MCP_TOOLS, handlePdeTool } from '../src/pde/index.js';
29
29
  import { PLANNING_MCP_TOOLS, handlePlanningTool } from '../src/planning/index.js';
30
30
  import type { McpToolResult } from '../src/types.js';
31
+ import { getPackageVersion } from '../src/version.js';
31
32
 
32
33
  // Langfuse imports
33
34
  import {
@@ -46,7 +47,7 @@ import { uploadAndAttachMedia, getMedia, formatMediaDisplay } from '../src/langf
46
47
  // ─── Server Info ────────────────────────────────────────────────────
47
48
 
48
49
  const SERVER_NAME = 'coaiajs-mcp';
49
- const SERVER_VERSION = '0.1.1';
50
+ const SERVER_VERSION = getPackageVersion();
50
51
 
51
52
  // ─── Parse CLI args ─────────────────────────────────────────────────
52
53
 
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "coaiajs",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "description": "CoAIA unified TypeScript monorepo — CLI, MCP server, and library consolidating coaia-narrative, coaia-pde, coaia-planning, and coaiapy patterns into one package.",
6
6
  "bin": {
7
- "coaia": "./dist/src/cli.js",
8
- "coaiajs-mcp": "./dist/mcp/server.js"
7
+ "coaia": "dist/src/cli.js",
8
+ "coaiajs-mcp": "dist/mcp/server.js"
9
9
  },
10
10
  "engines": {
11
11
  "node": ">=20.0.0"
package/src/cli.ts CHANGED
@@ -9,6 +9,7 @@ import { readConfig, getConfig } from './config.js';
9
9
  import { llm as llmCall, transcribeAudio, abstractProcess } from './llm.js';
10
10
  import { listIssues, getIssue } from './github.js';
11
11
  import { createEnvironment, findEnvironment } from './environment.js';
12
+ import { getPackageVersion } from './version.js';
12
13
  import {
13
14
  formatTable, formatJson, formatError, formatSuccess,
14
15
  truncate, formatDate, formatProgress, setColorEnabled,
@@ -1221,7 +1222,7 @@ async function main(): Promise<void> {
1221
1222
 
1222
1223
  program
1223
1224
  .name('coaia')
1224
- .version('0.1.3', '-V, --version')
1225
+ .version(getPackageVersion(), '-V, --version')
1225
1226
  .description('CoAIA unified CLI — structural tension, narrative, and DevOps tooling')
1226
1227
  .option('--env <path>', 'Load environment file')
1227
1228
  .option('-M, --memory-path <path>', 'JSONL memory file path')
@@ -3,10 +3,14 @@
3
3
  export { LangfuseClient, LangfuseApiError, getClient, resetClient, nowISO } from './client.js';
4
4
  export type { IngestionEvent, LangfuseClientConfig } from './client.js';
5
5
 
6
- export { addTrace, patchTraceOutput, listTraces, getTrace, formatTracesTable, formatTracesMarkdown, formatTraceTree } from './traces.js';
6
+ export {
7
+ addTrace, createTrace, patchTraceOutput,
8
+ listTraces, getTrace, traceView, sessionView,
9
+ formatTracesTable, formatTracesMarkdown, formatTraceTree,
10
+ } from './traces.js';
7
11
  export type { TraceFilters } from './traces.js';
8
12
 
9
- export { addObservation, getObservation, formatObservationDisplay } from './observations.js';
13
+ export { addObservation, addObservations, getObservation, formatObservationDisplay } from './observations.js';
10
14
 
11
15
  export { listPrompts, getPrompt, createPrompt, formatPromptsTable, formatPromptDisplay } from './prompts.js';
12
16
 
@@ -11,9 +11,12 @@ export async function addObservation(params: {
11
11
  type?: string;
12
12
  name: string;
13
13
  parentId?: string;
14
+ parentObservationId?: string;
15
+ input?: string;
14
16
  inputData?: unknown;
17
+ output?: string;
15
18
  outputData?: unknown;
16
- metadata?: unknown;
19
+ metadata?: string | unknown;
17
20
  startTime?: string;
18
21
  endTime?: string;
19
22
  level?: string;
@@ -33,10 +36,10 @@ export async function addObservation(params: {
33
36
  };
34
37
 
35
38
  if (params.name) body.name = params.name;
36
- if (params.inputData) body.input = params.inputData;
37
- if (params.outputData) body.output = params.outputData;
38
- if (params.metadata) body.metadata = params.metadata;
39
- if (params.parentId) body.parentObservationId = params.parentId;
39
+ if (params.inputData ?? params.input) body.input = parseJsonOption(params.inputData ?? params.input);
40
+ if (params.outputData ?? params.output) body.output = parseJsonOption(params.outputData ?? params.output);
41
+ if (params.metadata) body.metadata = parseJsonOption(params.metadata);
42
+ if (params.parentId ?? params.parentObservationId) body.parentObservationId = params.parentId ?? params.parentObservationId;
40
43
  if (params.endTime) body.endTime = params.endTime;
41
44
  if (params.model) body.model = params.model;
42
45
  if (params.usage) body.usage = params.usage;
@@ -65,6 +68,27 @@ export async function getObservation(observationId: string): Promise<string> {
65
68
  return JSON.stringify(result, null, 2);
66
69
  }
67
70
 
71
+ function parseJsonOption(value: unknown): unknown {
72
+ if (typeof value !== 'string') return value;
73
+ try {
74
+ return JSON.parse(value) as unknown;
75
+ } catch {
76
+ return value;
77
+ }
78
+ }
79
+
80
+ export async function addObservations(params: { traceId?: string; observations?: Array<Record<string, unknown>> }): Promise<string> {
81
+ const observations = params.observations ?? [];
82
+ const results: unknown[] = [];
83
+ for (const observation of observations) {
84
+ const traceId = String(observation.traceId ?? params.traceId ?? '');
85
+ const name = String(observation.name ?? 'Observation');
86
+ if (!traceId) throw new Error('traceId is required for every observation');
87
+ results.push(JSON.parse(await addObservation({ ...observation, traceId, name })) as unknown);
88
+ }
89
+ return JSON.stringify({ success: true, count: results.length, observations: results }, null, 2);
90
+ }
91
+
68
92
  // ─── Formatters ─────────────────────────────────────────────────────
69
93
 
70
94
  const TYPE_GLYPHS: Record<string, string> = {
@@ -61,14 +61,23 @@ export async function addTrace(params: {
61
61
  }, null, 2);
62
62
  }
63
63
 
64
- export async function patchTraceOutput(traceId: string, outputData: unknown): Promise<string> {
64
+ export async function patchTraceOutput(
65
+ traceIdOrParams: string | { traceId?: string; output?: string; outputData?: unknown },
66
+ outputData?: unknown,
67
+ ): Promise<string> {
68
+ const traceId = typeof traceIdOrParams === 'string' ? traceIdOrParams : traceIdOrParams.traceId;
69
+ if (!traceId) throw new Error('traceId is required');
70
+ const output = typeof traceIdOrParams === 'string'
71
+ ? outputData
72
+ : parseJsonOption(traceIdOrParams.outputData ?? traceIdOrParams.output);
73
+
65
74
  const client = getClient();
66
75
  const now = nowISO();
67
76
 
68
77
  const body: Record<string, unknown> = {
69
78
  id: traceId,
70
79
  timestamp: now,
71
- output: outputData,
80
+ output,
72
81
  };
73
82
 
74
83
  const eventId = `${traceId}-patch-${uuidv4().slice(0, 8)}`;
@@ -88,6 +97,38 @@ export async function patchTraceOutput(traceId: string, outputData: unknown): Pr
88
97
  }, null, 2);
89
98
  }
90
99
 
100
+ function parseJsonOption(value: unknown): unknown {
101
+ if (typeof value !== 'string') return value;
102
+ try {
103
+ return JSON.parse(value) as unknown;
104
+ } catch {
105
+ return value;
106
+ }
107
+ }
108
+
109
+ export async function createTrace(params: {
110
+ traceId?: string;
111
+ id?: string;
112
+ userId?: string;
113
+ sessionId?: string;
114
+ name?: string;
115
+ input?: string;
116
+ inputData?: unknown;
117
+ output?: string;
118
+ outputData?: unknown;
119
+ metadata?: string | unknown;
120
+ }): Promise<string> {
121
+ return addTrace({
122
+ traceId: params.traceId ?? params.id,
123
+ userId: params.userId,
124
+ sessionId: params.sessionId,
125
+ name: params.name,
126
+ inputData: parseJsonOption(params.inputData ?? params.input),
127
+ outputData: parseJsonOption(params.outputData ?? params.output),
128
+ metadata: parseJsonOption(params.metadata),
129
+ });
130
+ }
131
+
91
132
  export async function listTraces(filters: TraceFilters): Promise<string> {
92
133
  const client = getClient();
93
134
  const params = new URLSearchParams();
@@ -141,6 +182,16 @@ export async function getTrace(traceId: string): Promise<string> {
141
182
  return JSON.stringify(trace, null, 2);
142
183
  }
143
184
 
185
+ export async function traceView(traceId: string): Promise<string> {
186
+ const raw = await getTrace(traceId);
187
+ return formatTraceTree(raw);
188
+ }
189
+
190
+ export async function sessionView(sessionId: string): Promise<string> {
191
+ const raw = await listTraces({ sessionId, limit: 20, orderBy: 'timestamp.desc' });
192
+ return formatTracesMarkdown(raw);
193
+ }
194
+
144
195
  // ─── Formatters ─────────────────────────────────────────────────────
145
196
 
146
197
  export function formatTracesTable(json: unknown): string {
package/src/version.ts ADDED
@@ -0,0 +1,39 @@
1
+ // coaiajs/src/version.ts — single source of truth for the runtime version.
2
+ // Reads package.json at runtime so the CLI banner, the MCP handshake, and the
3
+ // published package can never drift apart again.
4
+
5
+ import { readFileSync } from 'node:fs';
6
+ import { dirname, join } from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+
9
+ const PACKAGE_NAME = 'coaiajs';
10
+
11
+ let cached: string | undefined;
12
+
13
+ /** Resolved version of the installed `coaiajs` package. */
14
+ export function getPackageVersion(): string {
15
+ if (cached) return cached;
16
+
17
+ const here = dirname(fileURLToPath(import.meta.url));
18
+ // dist/src/version.js -> package root is two levels up.
19
+ // src/version.ts (ts-node/tsx) -> package root is one level up.
20
+ const candidates = [join(here, '..', '..', 'package.json'), join(here, '..', 'package.json')];
21
+
22
+ for (const candidate of candidates) {
23
+ let raw: string;
24
+ try {
25
+ raw = readFileSync(candidate, 'utf8');
26
+ } catch {
27
+ continue; // not at this depth; try the next candidate
28
+ }
29
+ const pkg = JSON.parse(raw) as { name?: string; version?: string };
30
+ if (pkg.name === PACKAGE_NAME && pkg.version) {
31
+ cached = pkg.version;
32
+ return cached;
33
+ }
34
+ }
35
+
36
+ throw new Error(
37
+ `Unable to resolve ${PACKAGE_NAME} version: no package.json found near ${here}`,
38
+ );
39
+ }