@thispointon/kondi-chat 0.1.2

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 (108) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +556 -0
  3. package/bin/kondi-chat +56 -0
  4. package/bin/kondi-chat.js +72 -0
  5. package/package.json +55 -0
  6. package/scripts/demo.tape +49 -0
  7. package/scripts/postinstall.cjs +103 -0
  8. package/src/audit/analytics.ts +261 -0
  9. package/src/audit/ledger.ts +253 -0
  10. package/src/audit/telemetry.ts +165 -0
  11. package/src/cli/backend.ts +675 -0
  12. package/src/cli/commands.ts +419 -0
  13. package/src/cli/help.ts +182 -0
  14. package/src/cli/submit-helpers.ts +159 -0
  15. package/src/cli/submit.ts +539 -0
  16. package/src/cli/wizard.ts +121 -0
  17. package/src/context/bootstrap.ts +138 -0
  18. package/src/context/budget.ts +100 -0
  19. package/src/context/manager.ts +666 -0
  20. package/src/context/memory.ts +160 -0
  21. package/src/context/preflight.ts +176 -0
  22. package/src/context/project-brain.ts +101 -0
  23. package/src/context/receipts.ts +108 -0
  24. package/src/context/skills.ts +154 -0
  25. package/src/context/symbol-index.ts +240 -0
  26. package/src/council/profiles.ts +137 -0
  27. package/src/council/tool.ts +138 -0
  28. package/src/council-engine/cli/council-artifacts.ts +230 -0
  29. package/src/council-engine/cli/council-config.ts +178 -0
  30. package/src/council-engine/cli/council-session-export.ts +116 -0
  31. package/src/council-engine/cli/kondi.ts +98 -0
  32. package/src/council-engine/cli/llm-caller.ts +229 -0
  33. package/src/council-engine/cli/localStorage-shim.ts +119 -0
  34. package/src/council-engine/cli/node-platform.ts +68 -0
  35. package/src/council-engine/cli/run-council.ts +481 -0
  36. package/src/council-engine/cli/run-pipeline.ts +772 -0
  37. package/src/council-engine/cli/session-export.ts +153 -0
  38. package/src/council-engine/configs/councils/analysis.json +101 -0
  39. package/src/council-engine/configs/councils/code-planning.json +86 -0
  40. package/src/council-engine/configs/councils/coding.json +89 -0
  41. package/src/council-engine/configs/councils/debate.json +97 -0
  42. package/src/council-engine/configs/councils/solo-claude.json +34 -0
  43. package/src/council-engine/configs/councils/solo-gpt.json +34 -0
  44. package/src/council-engine/council/coding-orchestrator.ts +1205 -0
  45. package/src/council-engine/council/context-bootstrap.ts +147 -0
  46. package/src/council-engine/council/context-inspection.ts +42 -0
  47. package/src/council-engine/council/context-store.ts +763 -0
  48. package/src/council-engine/council/deliberation-orchestrator.ts +2762 -0
  49. package/src/council-engine/council/factory.ts +164 -0
  50. package/src/council-engine/council/index.ts +201 -0
  51. package/src/council-engine/council/ledger-store.ts +438 -0
  52. package/src/council-engine/council/prompts.ts +1689 -0
  53. package/src/council-engine/council/storage-cleanup.ts +164 -0
  54. package/src/council-engine/council/store.ts +1110 -0
  55. package/src/council-engine/council/synthesis.ts +291 -0
  56. package/src/council-engine/council/types.ts +845 -0
  57. package/src/council-engine/council/validation.ts +613 -0
  58. package/src/council-engine/pipeline/build-detect.ts +73 -0
  59. package/src/council-engine/pipeline/executor.ts +1048 -0
  60. package/src/council-engine/pipeline/index.ts +9 -0
  61. package/src/council-engine/pipeline/install-detect.ts +84 -0
  62. package/src/council-engine/pipeline/memory-store.ts +182 -0
  63. package/src/council-engine/pipeline/output-parsers.ts +146 -0
  64. package/src/council-engine/pipeline/run-output.ts +149 -0
  65. package/src/council-engine/pipeline/session-import.ts +177 -0
  66. package/src/council-engine/pipeline/store.ts +753 -0
  67. package/src/council-engine/pipeline/test-detect.ts +82 -0
  68. package/src/council-engine/pipeline/types.ts +401 -0
  69. package/src/council-engine/services/deliberationSummary.ts +114 -0
  70. package/src/council-engine/tsconfig.json +16 -0
  71. package/src/council-engine/types/mcp.ts +122 -0
  72. package/src/council-engine/utils/filterTools.ts +73 -0
  73. package/src/engine/apply.ts +238 -0
  74. package/src/engine/checkpoints.ts +237 -0
  75. package/src/engine/consultants.ts +347 -0
  76. package/src/engine/diff.ts +171 -0
  77. package/src/engine/errors.ts +102 -0
  78. package/src/engine/git-tools.ts +246 -0
  79. package/src/engine/hooks.ts +181 -0
  80. package/src/engine/loop-guard.ts +155 -0
  81. package/src/engine/permissions.ts +293 -0
  82. package/src/engine/pipeline.ts +376 -0
  83. package/src/engine/sub-agents.ts +133 -0
  84. package/src/engine/task-card.ts +185 -0
  85. package/src/engine/task-router.ts +256 -0
  86. package/src/engine/task-store.ts +86 -0
  87. package/src/engine/tools.ts +783 -0
  88. package/src/engine/verify.ts +111 -0
  89. package/src/mcp/client.ts +225 -0
  90. package/src/mcp/config.ts +120 -0
  91. package/src/mcp/tool-manager.ts +192 -0
  92. package/src/mcp/types.ts +61 -0
  93. package/src/providers/llm-caller.ts +943 -0
  94. package/src/providers/rate-limiter.ts +238 -0
  95. package/src/router/NOTES.md +28 -0
  96. package/src/router/collector.ts +474 -0
  97. package/src/router/embeddings.ts +286 -0
  98. package/src/router/index.ts +299 -0
  99. package/src/router/intent-router.ts +225 -0
  100. package/src/router/nn-router.ts +205 -0
  101. package/src/router/profiles.ts +309 -0
  102. package/src/router/registry.ts +565 -0
  103. package/src/router/rules.ts +274 -0
  104. package/src/router/train.py +408 -0
  105. package/src/session/store.ts +211 -0
  106. package/src/test-utils/mock-llm.ts +39 -0
  107. package/src/types.ts +322 -0
  108. package/src/web/manager.ts +311 -0
@@ -0,0 +1,230 @@
1
+ /**
2
+ * CLI Council Artifact Writer
3
+ *
4
+ * Writes council deliberation artifacts to disk using node:fs.
5
+ * Supports 5 output formats: full, abbreviated, output-only, json, none.
6
+ */
7
+
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+ import type { Council } from '../council/types';
11
+ import { buildFullDeliberation, buildAbbreviatedSummary } from '../services/deliberationSummary';
12
+ import { getDecision, getLatestOutput } from '../council/context-store';
13
+ import { getAllEntries } from '../council/ledger-store';
14
+ import type { OutputFormat } from './council-config';
15
+
16
+ // ============================================================================
17
+ // Helpers (mirrored from deliberationSaveService.ts, Tauri-free)
18
+ // ============================================================================
19
+
20
+ function sanitizeName(name: string): string {
21
+ return name
22
+ .toLowerCase()
23
+ .replace(/[^a-z0-9_-]/g, '_')
24
+ .replace(/_+/g, '_')
25
+ .replace(/^_|_$/g, '')
26
+ .slice(0, 50);
27
+ }
28
+
29
+ function timestampSlug(): string {
30
+ const d = new Date();
31
+ const pad = (n: number) => String(n).padStart(2, '0');
32
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}_${pad(d.getHours())}-${pad(d.getMinutes())}-${pad(d.getSeconds())}`;
33
+ }
34
+
35
+ function resolveOutputDir(workingDir: string, councilName: string, overrideDir?: string): string {
36
+ if (overrideDir) {
37
+ return path.resolve(overrideDir);
38
+ }
39
+ const safeName = sanitizeName(councilName);
40
+ const ts = timestampSlug();
41
+ const base = workingDir.replace(/\/$/, '');
42
+ return `${base}/.kondi/outputs/${safeName}_${ts}`;
43
+ }
44
+
45
+ function ensureDir(dir: string): void {
46
+ fs.mkdirSync(dir, { recursive: true });
47
+ }
48
+
49
+ function writeFile(filePath: string, content: string): void {
50
+ ensureDir(path.dirname(filePath));
51
+ fs.writeFileSync(filePath, content, 'utf-8');
52
+ }
53
+
54
+ // ============================================================================
55
+ // Format Writers
56
+ // ============================================================================
57
+
58
+ function writeFull(council: Council, outputDir: string): string[] {
59
+ const files: string[] = [];
60
+
61
+ // deliberation.md
62
+ const deliberationMd = buildFullDeliberation(council);
63
+ const deliberationPath = path.join(outputDir, 'deliberation.md');
64
+ writeFile(deliberationPath, deliberationMd);
65
+ files.push(deliberationPath);
66
+
67
+ // decision.md
68
+ const decision = getDecision(council.id);
69
+ let decisionMd = `# Decision\n\n`;
70
+ if (decision) {
71
+ decisionMd += decision.content;
72
+ if (decision.acceptanceCriteria) {
73
+ decisionMd += `\n\n## Acceptance Criteria\n\n${decision.acceptanceCriteria}`;
74
+ }
75
+ } else {
76
+ decisionMd += 'No decision recorded.';
77
+ }
78
+ const decisionPath = path.join(outputDir, 'decision.md');
79
+ writeFile(decisionPath, decisionMd);
80
+ files.push(decisionPath);
81
+
82
+ // output.md
83
+ const output = getLatestOutput(council.id);
84
+ let outputMd = `# Output\n\n`;
85
+ if (output) {
86
+ outputMd += output.content;
87
+ } else {
88
+ outputMd += 'No output recorded.';
89
+ }
90
+ const outputPath = path.join(outputDir, 'output.md');
91
+ writeFile(outputPath, outputMd);
92
+ files.push(outputPath);
93
+
94
+ return files;
95
+ }
96
+
97
+ function writeAbbreviated(council: Council, outputDir: string): string[] {
98
+ const summaryMd = buildAbbreviatedSummary(council);
99
+ const summaryPath = path.join(outputDir, 'summary.md');
100
+ writeFile(summaryPath, summaryMd);
101
+ return [summaryPath];
102
+ }
103
+
104
+ function writeOutputOnly(council: Council, outputDir: string): string[] {
105
+ const output = getLatestOutput(council.id);
106
+ let outputMd = `# Output\n\n`;
107
+ if (output) {
108
+ outputMd += output.content;
109
+ } else {
110
+ outputMd += 'No output recorded.';
111
+ }
112
+ const outputPath = path.join(outputDir, 'output.md');
113
+ writeFile(outputPath, outputMd);
114
+ return [outputPath];
115
+ }
116
+
117
+ function writeJson(council: Council, outputDir: string): string[] {
118
+ const entries = getAllEntries(council.id);
119
+ const decision = getDecision(council.id);
120
+ const output = getLatestOutput(council.id);
121
+
122
+ const result = {
123
+ council: {
124
+ id: council.id,
125
+ name: council.name,
126
+ topic: council.topic,
127
+ createdAt: council.createdAt,
128
+ status: council.status,
129
+ totalTokensUsed: council.totalTokensUsed,
130
+ personas: council.personas.map(p => ({
131
+ id: p.id,
132
+ name: p.name,
133
+ provider: p.provider,
134
+ model: p.model,
135
+ role: p.preferredDeliberationRole,
136
+ })),
137
+ },
138
+ deliberation: {
139
+ rounds: council.deliberationState?.currentRound ?? 0,
140
+ revisions: council.deliberationState?.revisionCount ?? 0,
141
+ phase: council.deliberationState?.currentPhase,
142
+ entryCount: entries.length,
143
+ },
144
+ entries: entries.map(e => ({
145
+ timestamp: e.timestamp,
146
+ author: e.authorPersonaId,
147
+ entryType: e.entryType,
148
+ round: e.roundNumber,
149
+ content: e.content,
150
+ tokensUsed: e.tokensUsed,
151
+ latencyMs: e.latencyMs,
152
+ })),
153
+ decision: decision ? {
154
+ content: decision.content,
155
+ acceptanceCriteria: decision.acceptanceCriteria,
156
+ } : null,
157
+ output: output ? {
158
+ content: output.content,
159
+ version: output.version,
160
+ } : null,
161
+ };
162
+
163
+ const jsonPath = path.join(outputDir, 'council-result.json');
164
+ writeFile(jsonPath, JSON.stringify(result, null, 2));
165
+ return [jsonPath];
166
+ }
167
+
168
+ // ============================================================================
169
+ // Public API
170
+ // ============================================================================
171
+
172
+ export interface WriteArtifactsOpts {
173
+ format: OutputFormat;
174
+ outputDir?: string;
175
+ workingDir: string;
176
+ }
177
+
178
+ /**
179
+ * Write council artifacts to disk.
180
+ * Returns array of written file paths (empty for 'none' format).
181
+ */
182
+ export function writeCouncilArtifacts(council: Council, opts: WriteArtifactsOpts): string[] {
183
+ if (opts.format === 'none') return [];
184
+
185
+ const outputDir = resolveOutputDir(opts.workingDir, council.name, opts.outputDir);
186
+
187
+ switch (opts.format) {
188
+ case 'full':
189
+ return writeFull(council, outputDir);
190
+ case 'abbreviated':
191
+ return writeAbbreviated(council, outputDir);
192
+ case 'output-only':
193
+ return writeOutputOnly(council, outputDir);
194
+ case 'json':
195
+ return writeJson(council, outputDir);
196
+ default:
197
+ return writeFull(council, outputDir);
198
+ }
199
+ }
200
+
201
+ /**
202
+ * Build structured JSON result for --json-stdout output.
203
+ */
204
+ export function buildJsonResult(council: Council, artifactPaths: string[], executionInfo: {
205
+ status: 'completed' | 'failed';
206
+ durationMs: number;
207
+ error?: string;
208
+ }): object {
209
+ const entries = getAllEntries(council.id);
210
+ const decision = getDecision(council.id);
211
+ const output = getLatestOutput(council.id);
212
+
213
+ return {
214
+ status: executionInfo.status,
215
+ durationMs: executionInfo.durationMs,
216
+ error: executionInfo.error,
217
+ council: {
218
+ id: council.id,
219
+ name: council.name,
220
+ topic: council.topic,
221
+ totalTokensUsed: council.totalTokensUsed,
222
+ rounds: council.deliberationState?.currentRound ?? 0,
223
+ revisions: council.deliberationState?.revisionCount ?? 0,
224
+ entryCount: entries.length,
225
+ },
226
+ decision: decision?.content ?? null,
227
+ output: output?.content ?? null,
228
+ artifacts: artifactPaths,
229
+ };
230
+ }
@@ -0,0 +1,178 @@
1
+ /**
2
+ * CLI Council Config Loader
3
+ *
4
+ * Loads council configuration from JSON files with auto-discovery.
5
+ * Search order: explicit --config path, cwd/council.json, ~/.config/kondi/council.json
6
+ */
7
+
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+ import os from 'node:os';
11
+ import type { CouncilStepType } from '../pipeline/types';
12
+
13
+ // ============================================================================
14
+ // Config Schema
15
+ // ============================================================================
16
+
17
+ export type OutputFormat = 'full' | 'abbreviated' | 'output-only' | 'json' | 'none';
18
+
19
+ export interface CouncilPersonaConfig {
20
+ name: string;
21
+ role: 'manager' | 'worker' | 'consultant' | 'reviewer';
22
+ provider?: string;
23
+ model?: string;
24
+ avatar?: string;
25
+ systemPrompt?: string;
26
+ traits?: string[];
27
+ stance?: 'advocate' | 'critic' | 'neutral' | 'wildcard';
28
+ domain?: string;
29
+ temperature?: number;
30
+ suppressPersona?: boolean;
31
+ toolAccess?: 'full' | 'none';
32
+ }
33
+
34
+ export interface CouncilConfigFile {
35
+ name: string;
36
+ task?: string;
37
+ type?: CouncilStepType;
38
+ personas: CouncilPersonaConfig[];
39
+ orchestration?: {
40
+ maxRounds?: number;
41
+ maxRevisions?: number;
42
+ contextTokenBudget?: number;
43
+ summarizeAfterRound?: number;
44
+ summaryMode?: string;
45
+ consultantExecution?: 'sequential' | 'parallel';
46
+ evolveContext?: boolean;
47
+ bootstrapContext?: boolean;
48
+ };
49
+ output?: {
50
+ format?: OutputFormat;
51
+ directory?: string;
52
+ sessionExport?: boolean;
53
+ };
54
+ expectedOutput?: string;
55
+ decisionCriteria?: string[];
56
+ testCommand?: string;
57
+ maxDebugCycles?: number;
58
+ maxReviewCycles?: number;
59
+ }
60
+
61
+ // ============================================================================
62
+ // CLI Args (parsed externally, merged here)
63
+ // ============================================================================
64
+
65
+ export interface CouncilCliArgs {
66
+ configPath?: string;
67
+ councilJsonPath?: string;
68
+ task?: string;
69
+ type?: CouncilStepType;
70
+ workingDir?: string;
71
+ model?: string;
72
+ provider?: string;
73
+ outputFormat?: OutputFormat;
74
+ outputDir?: string;
75
+ noSessionExport?: boolean;
76
+ noCache?: boolean;
77
+ dryRun?: boolean;
78
+ quiet?: boolean;
79
+ jsonStdout?: boolean;
80
+ }
81
+
82
+ // ============================================================================
83
+ // Loader
84
+ // ============================================================================
85
+
86
+ const SEARCH_PATHS = [
87
+ () => path.join(process.cwd(), 'council.json'),
88
+ () => path.join(os.homedir(), '.config', 'kondi', 'council.json'),
89
+ ];
90
+
91
+ export function loadCouncilConfig(configPath?: string): CouncilConfigFile | null {
92
+ // Explicit path
93
+ if (configPath) {
94
+ const resolved = path.resolve(configPath);
95
+ if (!fs.existsSync(resolved)) {
96
+ throw new Error(`Config file not found: ${resolved}`);
97
+ }
98
+ return parseAndValidate(resolved);
99
+ }
100
+
101
+ // Auto-discovery
102
+ for (const getPath of SEARCH_PATHS) {
103
+ const p = getPath();
104
+ if (fs.existsSync(p)) {
105
+ return parseAndValidate(p);
106
+ }
107
+ }
108
+
109
+ return null;
110
+ }
111
+
112
+ function parseAndValidate(filePath: string): CouncilConfigFile {
113
+ const raw = fs.readFileSync(filePath, 'utf-8');
114
+ let parsed: any;
115
+ try {
116
+ parsed = JSON.parse(raw);
117
+ } catch {
118
+ throw new Error(`Invalid JSON in config file: ${filePath}`);
119
+ }
120
+
121
+ // Validate required fields
122
+ if (!parsed.name || typeof parsed.name !== 'string') {
123
+ throw new Error(`Config file missing required "name" field: ${filePath}`);
124
+ }
125
+ if (!Array.isArray(parsed.personas) || parsed.personas.length === 0) {
126
+ throw new Error(`Config file must have at least one persona: ${filePath}`);
127
+ }
128
+ for (const p of parsed.personas) {
129
+ if (!p.name || !p.role) {
130
+ throw new Error(`Each persona must have "name" and "role": ${filePath}`);
131
+ }
132
+ const validRoles = ['manager', 'worker', 'consultant', 'reviewer'];
133
+ if (!validRoles.includes(p.role)) {
134
+ throw new Error(`Invalid persona role "${p.role}" for "${p.name}". Must be: ${validRoles.join(', ')}`);
135
+ }
136
+ }
137
+
138
+ return parsed as CouncilConfigFile;
139
+ }
140
+
141
+ // ============================================================================
142
+ // Merge config with CLI args (CLI wins)
143
+ // ============================================================================
144
+
145
+ export interface ResolvedCouncilConfig {
146
+ config: CouncilConfigFile;
147
+ task: string;
148
+ type: CouncilStepType;
149
+ workingDir?: string;
150
+ model?: string;
151
+ provider?: string;
152
+ outputFormat: OutputFormat;
153
+ outputDir?: string;
154
+ sessionExport: boolean;
155
+ dryRun: boolean;
156
+ quiet: boolean;
157
+ jsonStdout: boolean;
158
+ }
159
+
160
+ export function mergeConfigWithArgs(
161
+ config: CouncilConfigFile,
162
+ args: CouncilCliArgs,
163
+ ): ResolvedCouncilConfig {
164
+ return {
165
+ config,
166
+ task: args.task || config.task || config.name,
167
+ type: args.type || config.type || 'council',
168
+ workingDir: args.workingDir,
169
+ model: args.model,
170
+ provider: args.provider,
171
+ outputFormat: args.outputFormat || config.output?.format || 'full',
172
+ outputDir: args.outputDir || config.output?.directory,
173
+ sessionExport: args.noSessionExport ? false : (config.output?.sessionExport ?? true),
174
+ dryRun: args.dryRun || false,
175
+ quiet: args.quiet || false,
176
+ jsonStdout: args.jsonStdout || false,
177
+ };
178
+ }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * CLI Council Session Export
3
+ *
4
+ * Exports a standalone council session for GUI import.
5
+ * Parallel to session-export.ts but council-focused (no pipeline wrapper).
6
+ *
7
+ * Output: ~/.local/share/kondi/sessions/council-<councilId>-<timestamp>.json
8
+ */
9
+
10
+ import fs from 'node:fs';
11
+ import path from 'node:path';
12
+ import os from 'node:os';
13
+
14
+ const SESSIONS_DIR = path.join(os.homedir(), '.local', 'share', 'kondi', 'sessions');
15
+
16
+ /** localStorage key conventions — must match store modules */
17
+ const KEYS = {
18
+ councils: 'mcp-councils',
19
+ ledgerIndex: (id: string) => `ledger-index-${id}`,
20
+ ledgerChunk: (id: string, n: number) => `ledger-chunk-${id}-${n}`,
21
+ context: (id: string) => `context-${id}`,
22
+ contextHistory: (id: string) => `context-history-${id}`,
23
+ contextPatches: (id: string) => `context-patches-${id}`,
24
+ decision: (id: string) => `decision-${id}`,
25
+ plan: (id: string) => `plan-${id}`,
26
+ directive: (id: string) => `directive-${id}`,
27
+ outputs: (id: string) => `outputs-${id}`,
28
+ };
29
+
30
+ interface CouncilExecutionInfo {
31
+ status: 'completed' | 'failed';
32
+ startedAt: string;
33
+ completedAt: string;
34
+ durationMs: number;
35
+ workingDirectory?: string;
36
+ }
37
+
38
+ /**
39
+ * Export a standalone council session to a JSON file.
40
+ * Reads all council data from the file-backed localStorage shim.
41
+ */
42
+ export function exportCouncilSession(
43
+ councilId: string,
44
+ storage: Storage,
45
+ execution: CouncilExecutionInfo,
46
+ ): string | null {
47
+ try {
48
+ // Load council from store
49
+ const councilsRaw = storage.getItem(KEYS.councils);
50
+ const councilsData = councilsRaw ? JSON.parse(councilsRaw) : { councils: [] };
51
+ const allCouncils = councilsData.councils || [];
52
+ const council = allCouncils.find((c: any) => c.id === councilId);
53
+ if (!council) {
54
+ console.error('[CouncilSessionExport] Council not found:', councilId);
55
+ return null;
56
+ }
57
+
58
+ // Collect ledger data
59
+ const ledgerIndexRaw = storage.getItem(KEYS.ledgerIndex(councilId));
60
+ const ledgerIndex = ledgerIndexRaw ? JSON.parse(ledgerIndexRaw) : null;
61
+
62
+ const ledgerChunks: Record<number, any[]> = {};
63
+ if (ledgerIndex) {
64
+ const chunkCount = ledgerIndex.chunkCount ?? 0;
65
+ for (let n = 0; n <= chunkCount; n++) {
66
+ const chunkRaw = storage.getItem(KEYS.ledgerChunk(councilId, n));
67
+ if (chunkRaw) {
68
+ ledgerChunks[n] = JSON.parse(chunkRaw);
69
+ }
70
+ }
71
+ }
72
+
73
+ // Collect context artifacts
74
+ const contextRaw = storage.getItem(KEYS.context(councilId));
75
+ const contextHistoryRaw = storage.getItem(KEYS.contextHistory(councilId));
76
+ const contextPatchesRaw = storage.getItem(KEYS.contextPatches(councilId));
77
+ const decisionRaw = storage.getItem(KEYS.decision(councilId));
78
+ const planRaw = storage.getItem(KEYS.plan(councilId));
79
+ const directiveRaw = storage.getItem(KEYS.directive(councilId));
80
+ const outputsRaw = storage.getItem(KEYS.outputs(councilId));
81
+
82
+ const councilData = {
83
+ ledgerIndex,
84
+ ledgerChunks,
85
+ context: contextRaw ? JSON.parse(contextRaw) : null,
86
+ contextHistory: contextHistoryRaw ? JSON.parse(contextHistoryRaw) : [],
87
+ contextPatches: contextPatchesRaw ? JSON.parse(contextPatchesRaw) : [],
88
+ decision: decisionRaw ? JSON.parse(decisionRaw) : null,
89
+ plan: planRaw ? JSON.parse(planRaw) : null,
90
+ directive: directiveRaw ? JSON.parse(directiveRaw) : null,
91
+ outputs: outputsRaw ? JSON.parse(outputsRaw) : [],
92
+ };
93
+
94
+ // Build session
95
+ const session = {
96
+ version: 1,
97
+ exportedAt: new Date().toISOString(),
98
+ source: 'cli-council',
99
+ council,
100
+ councilData: { [councilId]: councilData },
101
+ execution,
102
+ };
103
+
104
+ // Write to sessions directory
105
+ fs.mkdirSync(SESSIONS_DIR, { recursive: true });
106
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
107
+ const filename = `council-${councilId}-${timestamp}.json`;
108
+ const filePath = path.join(SESSIONS_DIR, filename);
109
+ fs.writeFileSync(filePath, JSON.stringify(session, null, 2), 'utf-8');
110
+
111
+ return filePath;
112
+ } catch (err) {
113
+ console.error('[CouncilSessionExport] Failed to export session:', err);
114
+ return null;
115
+ }
116
+ }
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env -S npx tsx
2
+ /**
3
+ * Kondi CLI — Multi-LLM Council Platform
4
+ *
5
+ * Usage:
6
+ * kondi council [options] Run a council deliberation
7
+ * kondi pipeline [options] Run a pipeline
8
+ * kondi --help Show help
9
+ * kondi --version Show version
10
+ */
11
+
12
+ // Load .env if present (API keys)
13
+ import { readFileSync, existsSync } from 'node:fs';
14
+ import { resolve, dirname } from 'node:path';
15
+ import { fileURLToPath } from 'node:url';
16
+ const __dirnameLocal = dirname(fileURLToPath(import.meta.url));
17
+ for (const envPath of [resolve(process.cwd(), '.env'), resolve(__dirnameLocal, '..', '..', '.env')]) {
18
+ if (existsSync(envPath)) {
19
+ for (const line of readFileSync(envPath, 'utf-8').split('\n')) {
20
+ const m = line.match(/^([A-Z_]+)=(.+)$/);
21
+ if (m && !process.env[m[1]]) process.env[m[1]] = m[2];
22
+ }
23
+ break;
24
+ }
25
+ }
26
+
27
+ const C = {
28
+ reset: '\x1b[0m',
29
+ bold: '\x1b[1m',
30
+ dim: '\x1b[2m',
31
+ cyan: '\x1b[36m',
32
+ };
33
+
34
+ function printHelp() {
35
+ console.log(`
36
+ ${C.bold}${C.cyan}Kondi${C.reset} — Multi-LLM Council Platform
37
+
38
+ ${C.bold}Commands:${C.reset}
39
+ council Run a council deliberation
40
+ pipeline Run a pipeline from JSON
41
+
42
+ ${C.bold}Usage:${C.reset}
43
+ kondi council --task "Review this code" --working-dir ./myapp
44
+ kondi council --config council.json
45
+ kondi council exported-council.json
46
+ kondi pipeline pipeline.json --working-dir ./project
47
+
48
+ ${C.bold}Options:${C.reset}
49
+ --help, -h Show help (use "kondi council --help" for subcommand help)
50
+ --version, -v Show version
51
+
52
+ Run ${C.dim}kondi <command> --help${C.reset} for subcommand-specific options.
53
+ `);
54
+ }
55
+
56
+ async function main() {
57
+ const subcommand = process.argv[2];
58
+
59
+ if (!subcommand || subcommand === '--help' || subcommand === '-h') {
60
+ printHelp();
61
+ process.exit(0);
62
+ }
63
+
64
+ if (subcommand === '--version' || subcommand === '-v') {
65
+ try {
66
+ const { readFileSync } = await import('node:fs');
67
+ const { join, dirname } = await import('node:path');
68
+ const { fileURLToPath } = await import('node:url');
69
+ const __dirname = dirname(fileURLToPath(import.meta.url));
70
+ const pkg = JSON.parse(readFileSync(join(__dirname, '..', '..', 'package.json'), 'utf-8'));
71
+ console.log(`kondi ${pkg.version || '0.0.0'}`);
72
+ } catch {
73
+ console.log('kondi 0.0.0');
74
+ }
75
+ process.exit(0);
76
+ }
77
+
78
+ // Strip the subcommand from argv so the sub-runner sees the right args
79
+ process.argv = [process.argv[0], process.argv[1], ...process.argv.slice(3)];
80
+
81
+ switch (subcommand) {
82
+ case 'council':
83
+ await import('./run-council');
84
+ break;
85
+ case 'pipeline':
86
+ await import('./run-pipeline');
87
+ break;
88
+ default:
89
+ console.error(`Unknown command: ${subcommand}`);
90
+ console.error(`Run "kondi --help" for available commands.`);
91
+ process.exit(1);
92
+ }
93
+ }
94
+
95
+ main().catch((err) => {
96
+ console.error('Unhandled error:', err);
97
+ process.exit(1);
98
+ });