@locusai/cli 0.14.2 → 0.14.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.
Files changed (2) hide show
  1. package/bin/locus.js +2096 -1835
  2. package/package.json +3 -3
package/bin/locus.js CHANGED
@@ -41904,133 +41904,446 @@ var init_index_node = __esm(() => {
41904
41904
  init_planning();
41905
41905
  });
41906
41906
 
41907
- // src/display/tool-display.ts
41908
- import * as path2 from "node:path";
41909
-
41910
- class ToolDisplay {
41911
- formatToolStart(tool) {
41912
- const lines = [];
41913
- switch (tool.name) {
41914
- case "Read":
41915
- lines.push(...this.formatReadTool(tool));
41916
- break;
41917
- case "Write":
41918
- lines.push(...this.formatWriteTool(tool));
41919
- break;
41920
- case "Edit":
41921
- lines.push(...this.formatEditTool(tool));
41922
- break;
41923
- case "Bash":
41924
- lines.push(...this.formatBashTool(tool));
41925
- break;
41926
- case "Grep":
41927
- lines.push(...this.formatGrepTool(tool));
41928
- break;
41929
- case "Glob":
41930
- lines.push(...this.formatGlobTool(tool));
41931
- break;
41932
- case "WebFetch":
41933
- lines.push(...this.formatWebFetchTool(tool));
41934
- break;
41935
- case "Task":
41936
- lines.push(...this.formatTaskTool(tool));
41937
- break;
41938
- default:
41939
- lines.push(...this.formatGenericTool(tool));
41907
+ // src/utils/version.ts
41908
+ import { existsSync as existsSync12, readFileSync as readFileSync11 } from "node:fs";
41909
+ import { dirname as dirname3, join as join12 } from "node:path";
41910
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
41911
+ function getVersion() {
41912
+ try {
41913
+ const __filename2 = fileURLToPath3(import.meta.url);
41914
+ const __dirname2 = dirname3(__filename2);
41915
+ const bundledPath = join12(__dirname2, "..", "package.json");
41916
+ const sourcePath = join12(__dirname2, "..", "..", "package.json");
41917
+ if (existsSync12(bundledPath)) {
41918
+ const pkg = JSON.parse(readFileSync11(bundledPath, "utf-8"));
41919
+ if (pkg.name === "@locusai/cli") {
41920
+ return pkg.version || "0.0.0";
41921
+ }
41940
41922
  }
41941
- return lines;
41942
- }
41943
- formatToolResult(tool, result) {
41944
- const lines = [];
41945
- if (result.success) {
41946
- const durationStr = result.duration ? ` (${result.duration}ms)` : "";
41947
- lines.push(c.green(` ✓ Completed${durationStr}`));
41948
- const resultDetails = this.formatResultDetails(tool.name, result.data);
41949
- if (resultDetails) {
41950
- lines.push(c.dim(` ${resultDetails}`));
41923
+ if (existsSync12(sourcePath)) {
41924
+ const pkg = JSON.parse(readFileSync11(sourcePath, "utf-8"));
41925
+ if (pkg.name === "@locusai/cli") {
41926
+ return pkg.version || "0.0.0";
41951
41927
  }
41952
- } else {
41953
- const errorMsg = result.error ? this.truncate(result.error, 60) : "Unknown error";
41954
- lines.push(c.red(` ✗ Failed: ${errorMsg}`));
41955
41928
  }
41956
- return lines;
41929
+ } catch {}
41930
+ return "0.0.0";
41931
+ }
41932
+ var VERSION2;
41933
+ var init_version = __esm(() => {
41934
+ VERSION2 = getVersion();
41935
+ });
41936
+
41937
+ // src/utils/banner.ts
41938
+ function printBanner() {
41939
+ console.log(c.primary(`
41940
+ _ ____ ____ _ _ ____
41941
+ | | / __ \\ / ___| | | |/ ___|
41942
+ | | | | | | | | | | |\\___ \\
41943
+ | |___| |__| | |___| |_| |___) |
41944
+ |_____|\\____/ \\____|\\___/|____/ ${c.dim(`v${VERSION2}`)}
41945
+ `));
41946
+ }
41947
+ var init_banner = __esm(() => {
41948
+ init_index_node();
41949
+ init_version();
41950
+ });
41951
+
41952
+ // src/utils/helpers.ts
41953
+ import { existsSync as existsSync13 } from "node:fs";
41954
+ import { join as join13 } from "node:path";
41955
+ function isProjectInitialized(projectPath) {
41956
+ const locusDir = join13(projectPath, LOCUS_CONFIG.dir);
41957
+ const configPath = join13(locusDir, LOCUS_CONFIG.configFile);
41958
+ return existsSync13(locusDir) && existsSync13(configPath);
41959
+ }
41960
+ function requireInitialization(projectPath, command) {
41961
+ if (!isProjectInitialized(projectPath)) {
41962
+ console.error(`
41963
+ ${c.error("✖ Error")} ${c.red(`Locus is not initialized in this directory.`)}
41964
+
41965
+ The '${c.bold(command)}' command requires a Locus project to be initialized.
41966
+
41967
+ To initialize Locus in this directory, run:
41968
+ ${c.primary("locus init")}
41969
+
41970
+ This will create a ${c.dim(".locus")} directory with the necessary configuration.
41971
+ `);
41972
+ process.exit(1);
41957
41973
  }
41958
- formatReadTool(tool) {
41959
- const params = tool.parameters;
41960
- const lines = [];
41961
- if (params?.file_path) {
41962
- const fileName = path2.basename(params.file_path);
41963
- const dirPath = this.formatPath(params.file_path);
41964
- lines.push(c.cyan(`\uD83D\uDCD6 Reading ${c.bold(fileName)}`));
41965
- lines.push(c.dim(` ${dirPath}`));
41966
- if (params.offset !== undefined || params.limit !== undefined) {
41967
- const rangeInfo = [];
41968
- if (params.offset !== undefined)
41969
- rangeInfo.push(`offset: ${params.offset}`);
41970
- if (params.limit !== undefined)
41971
- rangeInfo.push(`limit: ${params.limit}`);
41972
- lines.push(c.dim(` (${rangeInfo.join(", ")})`));
41974
+ }
41975
+ function resolveProvider3(input) {
41976
+ if (!input)
41977
+ return PROVIDER.CLAUDE;
41978
+ if (input === PROVIDER.CLAUDE || input === PROVIDER.CODEX)
41979
+ return input;
41980
+ console.error(c.error(`Error: invalid provider '${input}'. Use 'claude' or 'codex'.`));
41981
+ process.exit(1);
41982
+ }
41983
+ var init_helpers2 = __esm(() => {
41984
+ init_index_node();
41985
+ });
41986
+
41987
+ // src/utils/index.ts
41988
+ var init_utils3 = __esm(() => {
41989
+ init_banner();
41990
+ init_helpers2();
41991
+ init_version();
41992
+ });
41993
+
41994
+ // src/display/execution-stats.ts
41995
+ class ExecutionStatsTracker {
41996
+ startTime;
41997
+ endTime = null;
41998
+ toolTimings = new Map;
41999
+ toolOrder = [];
42000
+ tokensUsed = null;
42001
+ error = null;
42002
+ constructor() {
42003
+ this.startTime = Date.now();
42004
+ }
42005
+ toolStarted(toolName, toolId) {
42006
+ const key = toolId ?? `${toolName}-${Date.now()}`;
42007
+ this.toolTimings.set(key, {
42008
+ name: toolName,
42009
+ id: toolId,
42010
+ startTime: Date.now()
42011
+ });
42012
+ this.toolOrder.push(key);
42013
+ }
42014
+ toolCompleted(toolName, toolId) {
42015
+ const key = this.findToolKey(toolName, toolId);
42016
+ if (key) {
42017
+ const timing = this.toolTimings.get(key);
42018
+ if (timing) {
42019
+ timing.endTime = Date.now();
42020
+ timing.duration = timing.endTime - timing.startTime;
42021
+ timing.success = true;
41973
42022
  }
41974
- } else {
41975
- lines.push(c.cyan("\uD83D\uDCD6 Reading file"));
41976
42023
  }
41977
- return lines;
41978
42024
  }
41979
- formatWriteTool(tool) {
41980
- const params = tool.parameters;
41981
- const lines = [];
41982
- if (params?.file_path) {
41983
- const fileName = path2.basename(params.file_path);
41984
- const dirPath = this.formatPath(params.file_path);
41985
- const size = params.content?.length ?? 0;
41986
- lines.push(c.cyan(`✍️ Writing ${c.bold(fileName)}`));
41987
- lines.push(c.dim(` ${dirPath} (${this.formatBytes(size)})`));
41988
- } else {
41989
- lines.push(c.cyan("✍️ Writing file"));
42025
+ toolFailed(toolName, error48, toolId) {
42026
+ const key = this.findToolKey(toolName, toolId);
42027
+ if (key) {
42028
+ const timing = this.toolTimings.get(key);
42029
+ if (timing) {
42030
+ timing.endTime = Date.now();
42031
+ timing.duration = timing.endTime - timing.startTime;
42032
+ timing.success = false;
42033
+ timing.error = error48;
42034
+ }
41990
42035
  }
41991
- return lines;
41992
42036
  }
41993
- formatEditTool(tool) {
41994
- const params = tool.parameters;
41995
- const lines = [];
41996
- if (params?.file_path) {
41997
- const fileName = path2.basename(params.file_path);
41998
- const dirPath = this.formatPath(params.file_path);
41999
- lines.push(c.cyan(`✏️ Editing ${c.bold(fileName)}`));
42000
- lines.push(c.dim(` ${dirPath}`));
42001
- if (params.replace_all) {
42002
- lines.push(c.dim(" (replace all occurrences)"));
42037
+ setTokensUsed(tokens) {
42038
+ this.tokensUsed = tokens;
42039
+ }
42040
+ setError(error48) {
42041
+ this.error = error48;
42042
+ }
42043
+ finalize() {
42044
+ this.endTime = Date.now();
42045
+ const toolsUsed = [];
42046
+ const seenTools = new Set;
42047
+ for (const key of this.toolOrder) {
42048
+ const timing = this.toolTimings.get(key);
42049
+ if (timing && !seenTools.has(timing.name)) {
42050
+ seenTools.add(timing.name);
42051
+ toolsUsed.push(timing.name);
42003
42052
  }
42004
- } else {
42005
- lines.push(c.cyan("✏️ Editing file"));
42006
42053
  }
42007
- return lines;
42054
+ const toolTimings = this.toolOrder.map((key) => this.toolTimings.get(key)).filter((t) => t !== undefined);
42055
+ const stats = {
42056
+ duration: this.endTime - this.startTime,
42057
+ toolsUsed,
42058
+ toolTimings,
42059
+ success: this.error === null
42060
+ };
42061
+ if (this.tokensUsed !== null) {
42062
+ stats.tokensUsed = this.tokensUsed;
42063
+ }
42064
+ if (this.error !== null) {
42065
+ stats.error = this.error;
42066
+ }
42067
+ return stats;
42008
42068
  }
42009
- formatBashTool(tool) {
42010
- const params = tool.parameters;
42011
- const lines = [];
42012
- if (params) {
42013
- const description = params.description || "Running command";
42014
- lines.push(c.cyan(`⚡ ${description}`));
42015
- if (params.command) {
42016
- const truncatedCmd = this.truncate(params.command, 80);
42017
- lines.push(c.dim(` $ ${truncatedCmd}`));
42069
+ getCurrentDuration() {
42070
+ return Date.now() - this.startTime;
42071
+ }
42072
+ findToolKey(toolName, toolId) {
42073
+ if (toolId && this.toolTimings.has(toolId)) {
42074
+ return toolId;
42075
+ }
42076
+ for (let i = this.toolOrder.length - 1;i >= 0; i--) {
42077
+ const key = this.toolOrder[i];
42078
+ const timing = this.toolTimings.get(key);
42079
+ if (timing && timing.name === toolName && timing.endTime === undefined) {
42080
+ return key;
42018
42081
  }
42019
- if (params.timeout) {
42020
- lines.push(c.dim(` (timeout: ${params.timeout}ms)`));
42082
+ }
42083
+ for (let i = this.toolOrder.length - 1;i >= 0; i--) {
42084
+ const key = this.toolOrder[i];
42085
+ const timing = this.toolTimings.get(key);
42086
+ if (timing && timing.name === toolName) {
42087
+ return key;
42021
42088
  }
42022
- } else {
42023
- lines.push(c.cyan("⚡ Running command"));
42024
42089
  }
42025
- return lines;
42090
+ return null;
42026
42091
  }
42027
- formatGrepTool(tool) {
42028
- const params = tool.parameters;
42029
- const lines = [];
42030
- if (params?.pattern) {
42031
- const truncatedPattern = this.truncate(params.pattern, 40);
42032
- lines.push(c.cyan(`\uD83D\uDD0D Searching for "${truncatedPattern}"`));
42033
- const searchScope = [];
42092
+ }
42093
+
42094
+ // src/display/json-stream-renderer.ts
42095
+ class JsonStreamRenderer {
42096
+ sessionId;
42097
+ command;
42098
+ model;
42099
+ provider;
42100
+ cwd;
42101
+ statsTracker;
42102
+ started = false;
42103
+ done = false;
42104
+ constructor(options) {
42105
+ this.sessionId = options.sessionId;
42106
+ this.command = options.command;
42107
+ this.model = options.model;
42108
+ this.provider = options.provider;
42109
+ this.cwd = options.cwd;
42110
+ this.statsTracker = new ExecutionStatsTracker;
42111
+ }
42112
+ emitStart() {
42113
+ if (this.started)
42114
+ return;
42115
+ this.started = true;
42116
+ this.emit(createCliStreamEvent(CliStreamEventType.START, this.sessionId, {
42117
+ command: this.command,
42118
+ model: this.model,
42119
+ provider: this.provider,
42120
+ cwd: this.cwd
42121
+ }));
42122
+ }
42123
+ handleChunk(chunk) {
42124
+ this.ensureStarted();
42125
+ switch (chunk.type) {
42126
+ case "text_delta":
42127
+ this.emit(createCliStreamEvent(CliStreamEventType.TEXT_DELTA, this.sessionId, {
42128
+ content: chunk.content
42129
+ }));
42130
+ break;
42131
+ case "thinking":
42132
+ this.emit(createCliStreamEvent(CliStreamEventType.THINKING, this.sessionId, {
42133
+ content: chunk.content
42134
+ }));
42135
+ break;
42136
+ case "tool_use":
42137
+ this.statsTracker.toolStarted(chunk.tool, chunk.id);
42138
+ this.emit(createCliStreamEvent(CliStreamEventType.TOOL_STARTED, this.sessionId, {
42139
+ tool: chunk.tool,
42140
+ toolId: chunk.id,
42141
+ parameters: chunk.parameters
42142
+ }));
42143
+ break;
42144
+ case "tool_result":
42145
+ if (chunk.success) {
42146
+ this.statsTracker.toolCompleted(chunk.tool, chunk.id);
42147
+ } else {
42148
+ this.statsTracker.toolFailed(chunk.tool, chunk.error ?? "Unknown error", chunk.id);
42149
+ }
42150
+ this.emit(createCliStreamEvent(CliStreamEventType.TOOL_COMPLETED, this.sessionId, {
42151
+ tool: chunk.tool,
42152
+ toolId: chunk.id,
42153
+ success: chunk.success,
42154
+ duration: chunk.duration,
42155
+ error: chunk.error
42156
+ }));
42157
+ break;
42158
+ case "tool_parameters":
42159
+ break;
42160
+ case "result":
42161
+ break;
42162
+ case "error":
42163
+ this.statsTracker.setError(chunk.error);
42164
+ this.emitError("UNKNOWN", chunk.error);
42165
+ break;
42166
+ }
42167
+ }
42168
+ emitStatus(status, message) {
42169
+ this.ensureStarted();
42170
+ this.emit(createCliStreamEvent(CliStreamEventType.STATUS, this.sessionId, {
42171
+ status,
42172
+ message
42173
+ }));
42174
+ }
42175
+ emitError(code, message, options) {
42176
+ this.ensureStarted();
42177
+ this.emit(createCliStreamEvent(CliStreamEventType.ERROR, this.sessionId, {
42178
+ error: createProtocolError(code, message, options)
42179
+ }));
42180
+ }
42181
+ emitDone(exitCode) {
42182
+ if (this.done)
42183
+ return;
42184
+ this.done = true;
42185
+ this.ensureStarted();
42186
+ const stats = this.statsTracker.finalize();
42187
+ this.emit(createCliStreamEvent(CliStreamEventType.DONE, this.sessionId, {
42188
+ exitCode,
42189
+ duration: stats.duration,
42190
+ toolsUsed: stats.toolsUsed.length > 0 ? stats.toolsUsed : undefined,
42191
+ tokensUsed: stats.tokensUsed,
42192
+ success: exitCode === 0
42193
+ }));
42194
+ }
42195
+ emitFatalError(code, message, options) {
42196
+ this.statsTracker.setError(message);
42197
+ this.emitError(code, message, {
42198
+ ...options,
42199
+ recoverable: false
42200
+ });
42201
+ this.emitDone(1);
42202
+ }
42203
+ isDone() {
42204
+ return this.done;
42205
+ }
42206
+ ensureStarted() {
42207
+ if (!this.started) {
42208
+ this.emitStart();
42209
+ }
42210
+ }
42211
+ emit(event) {
42212
+ process.stdout.write(`${JSON.stringify(event)}
42213
+ `);
42214
+ }
42215
+ }
42216
+ var init_json_stream_renderer = __esm(() => {
42217
+ init_src();
42218
+ });
42219
+
42220
+ // src/display/tool-display.ts
42221
+ import * as path2 from "node:path";
42222
+
42223
+ class ToolDisplay {
42224
+ formatToolStart(tool) {
42225
+ const lines = [];
42226
+ switch (tool.name) {
42227
+ case "Read":
42228
+ lines.push(...this.formatReadTool(tool));
42229
+ break;
42230
+ case "Write":
42231
+ lines.push(...this.formatWriteTool(tool));
42232
+ break;
42233
+ case "Edit":
42234
+ lines.push(...this.formatEditTool(tool));
42235
+ break;
42236
+ case "Bash":
42237
+ lines.push(...this.formatBashTool(tool));
42238
+ break;
42239
+ case "Grep":
42240
+ lines.push(...this.formatGrepTool(tool));
42241
+ break;
42242
+ case "Glob":
42243
+ lines.push(...this.formatGlobTool(tool));
42244
+ break;
42245
+ case "WebFetch":
42246
+ lines.push(...this.formatWebFetchTool(tool));
42247
+ break;
42248
+ case "Task":
42249
+ lines.push(...this.formatTaskTool(tool));
42250
+ break;
42251
+ default:
42252
+ lines.push(...this.formatGenericTool(tool));
42253
+ }
42254
+ return lines;
42255
+ }
42256
+ formatToolResult(tool, result) {
42257
+ const lines = [];
42258
+ if (result.success) {
42259
+ const durationStr = result.duration ? ` (${result.duration}ms)` : "";
42260
+ lines.push(c.green(` ✓ Completed${durationStr}`));
42261
+ const resultDetails = this.formatResultDetails(tool.name, result.data);
42262
+ if (resultDetails) {
42263
+ lines.push(c.dim(` ${resultDetails}`));
42264
+ }
42265
+ } else {
42266
+ const errorMsg = result.error ? this.truncate(result.error, 60) : "Unknown error";
42267
+ lines.push(c.red(` ✗ Failed: ${errorMsg}`));
42268
+ }
42269
+ return lines;
42270
+ }
42271
+ formatReadTool(tool) {
42272
+ const params = tool.parameters;
42273
+ const lines = [];
42274
+ if (params?.file_path) {
42275
+ const fileName = path2.basename(params.file_path);
42276
+ const dirPath = this.formatPath(params.file_path);
42277
+ lines.push(c.cyan(`\uD83D\uDCD6 Reading ${c.bold(fileName)}`));
42278
+ lines.push(c.dim(` ${dirPath}`));
42279
+ if (params.offset !== undefined || params.limit !== undefined) {
42280
+ const rangeInfo = [];
42281
+ if (params.offset !== undefined)
42282
+ rangeInfo.push(`offset: ${params.offset}`);
42283
+ if (params.limit !== undefined)
42284
+ rangeInfo.push(`limit: ${params.limit}`);
42285
+ lines.push(c.dim(` (${rangeInfo.join(", ")})`));
42286
+ }
42287
+ } else {
42288
+ lines.push(c.cyan("\uD83D\uDCD6 Reading file"));
42289
+ }
42290
+ return lines;
42291
+ }
42292
+ formatWriteTool(tool) {
42293
+ const params = tool.parameters;
42294
+ const lines = [];
42295
+ if (params?.file_path) {
42296
+ const fileName = path2.basename(params.file_path);
42297
+ const dirPath = this.formatPath(params.file_path);
42298
+ const size = params.content?.length ?? 0;
42299
+ lines.push(c.cyan(`✍️ Writing ${c.bold(fileName)}`));
42300
+ lines.push(c.dim(` ${dirPath} (${this.formatBytes(size)})`));
42301
+ } else {
42302
+ lines.push(c.cyan("✍️ Writing file"));
42303
+ }
42304
+ return lines;
42305
+ }
42306
+ formatEditTool(tool) {
42307
+ const params = tool.parameters;
42308
+ const lines = [];
42309
+ if (params?.file_path) {
42310
+ const fileName = path2.basename(params.file_path);
42311
+ const dirPath = this.formatPath(params.file_path);
42312
+ lines.push(c.cyan(`✏️ Editing ${c.bold(fileName)}`));
42313
+ lines.push(c.dim(` ${dirPath}`));
42314
+ if (params.replace_all) {
42315
+ lines.push(c.dim(" (replace all occurrences)"));
42316
+ }
42317
+ } else {
42318
+ lines.push(c.cyan("✏️ Editing file"));
42319
+ }
42320
+ return lines;
42321
+ }
42322
+ formatBashTool(tool) {
42323
+ const params = tool.parameters;
42324
+ const lines = [];
42325
+ if (params) {
42326
+ const description = params.description || "Running command";
42327
+ lines.push(c.cyan(`⚡ ${description}`));
42328
+ if (params.command) {
42329
+ const truncatedCmd = this.truncate(params.command, 80);
42330
+ lines.push(c.dim(` $ ${truncatedCmd}`));
42331
+ }
42332
+ if (params.timeout) {
42333
+ lines.push(c.dim(` (timeout: ${params.timeout}ms)`));
42334
+ }
42335
+ } else {
42336
+ lines.push(c.cyan("⚡ Running command"));
42337
+ }
42338
+ return lines;
42339
+ }
42340
+ formatGrepTool(tool) {
42341
+ const params = tool.parameters;
42342
+ const lines = [];
42343
+ if (params?.pattern) {
42344
+ const truncatedPattern = this.truncate(params.pattern, 40);
42345
+ lines.push(c.cyan(`\uD83D\uDD0D Searching for "${truncatedPattern}"`));
42346
+ const searchScope = [];
42034
42347
  if (params.path)
42035
42348
  searchScope.push(`in ${this.formatPath(params.path)}`);
42036
42349
  if (params.glob)
@@ -42494,208 +42807,318 @@ var init_progress_renderer = __esm(() => {
42494
42807
  SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
42495
42808
  });
42496
42809
 
42497
- // src/display/execution-stats.ts
42498
- class ExecutionStatsTracker {
42499
- startTime;
42500
- endTime = null;
42501
- toolTimings = new Map;
42502
- toolOrder = [];
42503
- tokensUsed = null;
42504
- error = null;
42505
- constructor() {
42506
- this.startTime = Date.now();
42507
- }
42508
- toolStarted(toolName, toolId) {
42509
- const key = toolId ?? `${toolName}-${Date.now()}`;
42510
- this.toolTimings.set(key, {
42511
- name: toolName,
42512
- id: toolId,
42513
- startTime: Date.now()
42514
- });
42515
- this.toolOrder.push(key);
42810
+ // src/settings-manager.ts
42811
+ import { existsSync as existsSync14, readFileSync as readFileSync12, unlinkSync as unlinkSync4, writeFileSync as writeFileSync6 } from "node:fs";
42812
+ import { join as join14 } from "node:path";
42813
+ function getSettingsPath(projectPath) {
42814
+ return join14(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.settingsFile);
42815
+ }
42816
+
42817
+ class SettingsManager {
42818
+ projectPath;
42819
+ constructor(projectPath) {
42820
+ this.projectPath = projectPath;
42516
42821
  }
42517
- toolCompleted(toolName, toolId) {
42518
- const key = this.findToolKey(toolName, toolId);
42519
- if (key) {
42520
- const timing = this.toolTimings.get(key);
42521
- if (timing) {
42522
- timing.endTime = Date.now();
42523
- timing.duration = timing.endTime - timing.startTime;
42524
- timing.success = true;
42525
- }
42822
+ load() {
42823
+ const settingsPath = getSettingsPath(this.projectPath);
42824
+ if (!existsSync14(settingsPath)) {
42825
+ return {};
42526
42826
  }
42827
+ return JSON.parse(readFileSync12(settingsPath, "utf-8"));
42527
42828
  }
42528
- toolFailed(toolName, error48, toolId) {
42529
- const key = this.findToolKey(toolName, toolId);
42530
- if (key) {
42531
- const timing = this.toolTimings.get(key);
42532
- if (timing) {
42533
- timing.endTime = Date.now();
42534
- timing.duration = timing.endTime - timing.startTime;
42535
- timing.success = false;
42536
- timing.error = error48;
42537
- }
42538
- }
42829
+ save(settings) {
42830
+ const { $schema: _2, ...rest } = settings;
42831
+ const ordered = { $schema: LOCUS_SCHEMAS.settings, ...rest };
42832
+ const settingsPath = getSettingsPath(this.projectPath);
42833
+ writeFileSync6(settingsPath, JSON.stringify(ordered, null, 2), "utf-8");
42539
42834
  }
42540
- setTokensUsed(tokens) {
42541
- this.tokensUsed = tokens;
42835
+ get(key) {
42836
+ return this.load()[key];
42542
42837
  }
42543
- setError(error48) {
42544
- this.error = error48;
42838
+ set(key, value) {
42839
+ const settings = this.load();
42840
+ settings[key] = value;
42841
+ this.save(settings);
42545
42842
  }
42546
- finalize() {
42547
- this.endTime = Date.now();
42548
- const toolsUsed = [];
42549
- const seenTools = new Set;
42550
- for (const key of this.toolOrder) {
42551
- const timing = this.toolTimings.get(key);
42552
- if (timing && !seenTools.has(timing.name)) {
42553
- seenTools.add(timing.name);
42554
- toolsUsed.push(timing.name);
42555
- }
42556
- }
42557
- const toolTimings = this.toolOrder.map((key) => this.toolTimings.get(key)).filter((t) => t !== undefined);
42558
- const stats = {
42559
- duration: this.endTime - this.startTime,
42560
- toolsUsed,
42561
- toolTimings,
42562
- success: this.error === null
42563
- };
42564
- if (this.tokensUsed !== null) {
42565
- stats.tokensUsed = this.tokensUsed;
42566
- }
42567
- if (this.error !== null) {
42568
- stats.error = this.error;
42843
+ remove() {
42844
+ const settingsPath = getSettingsPath(this.projectPath);
42845
+ if (existsSync14(settingsPath)) {
42846
+ unlinkSync4(settingsPath);
42569
42847
  }
42570
- return stats;
42571
- }
42572
- getCurrentDuration() {
42573
- return Date.now() - this.startTime;
42574
42848
  }
42575
- findToolKey(toolName, toolId) {
42576
- if (toolId && this.toolTimings.has(toolId)) {
42577
- return toolId;
42578
- }
42579
- for (let i = this.toolOrder.length - 1;i >= 0; i--) {
42580
- const key = this.toolOrder[i];
42581
- const timing = this.toolTimings.get(key);
42582
- if (timing && timing.name === toolName && timing.endTime === undefined) {
42583
- return key;
42584
- }
42585
- }
42586
- for (let i = this.toolOrder.length - 1;i >= 0; i--) {
42587
- const key = this.toolOrder[i];
42588
- const timing = this.toolTimings.get(key);
42589
- if (timing && timing.name === toolName) {
42590
- return key;
42591
- }
42592
- }
42593
- return null;
42849
+ exists() {
42850
+ return existsSync14(getSettingsPath(this.projectPath));
42594
42851
  }
42595
42852
  }
42853
+ var init_settings_manager = __esm(() => {
42854
+ init_index_node();
42855
+ });
42596
42856
 
42597
- // src/repl/commands.ts
42598
- function parseCommand(input) {
42599
- const lowerInput = input.toLowerCase();
42600
- for (const cmd of REPL_COMMANDS) {
42601
- if (lowerInput === cmd.name || cmd.aliases.includes(lowerInput)) {
42602
- return cmd;
42603
- }
42857
+ // src/commands/exec-sessions.ts
42858
+ function formatRelativeTime(timestamp) {
42859
+ const now = Date.now();
42860
+ const diff = now - timestamp;
42861
+ const seconds = Math.floor(diff / 1000);
42862
+ const minutes = Math.floor(seconds / 60);
42863
+ const hours = Math.floor(minutes / 60);
42864
+ const days = Math.floor(hours / 24);
42865
+ if (days > 0) {
42866
+ return days === 1 ? "yesterday" : `${days} days ago`;
42604
42867
  }
42605
- return null;
42868
+ if (hours > 0) {
42869
+ return hours === 1 ? "1 hour ago" : `${hours} hours ago`;
42870
+ }
42871
+ if (minutes > 0) {
42872
+ return minutes === 1 ? "1 minute ago" : `${minutes} minutes ago`;
42873
+ }
42874
+ return "just now";
42606
42875
  }
42607
- function showHelp() {
42608
- console.log(`
42609
- ${c.primary("Available Commands")}
42610
42876
 
42611
- ${c.success("exit")} / ${c.dim("quit, q")} Exit interactive mode
42612
- ${c.success("clear")} / ${c.dim("cls")} Clear the screen
42613
- ${c.success("help")} / ${c.dim("?, h")} Show this help message
42614
- ${c.success("reset")} / ${c.dim("r")} Reset conversation context
42615
- ${c.success("history")} / ${c.dim("hist")} List recent sessions
42616
- ${c.success("session")} / ${c.dim("sid")} Show current session ID
42617
-
42618
- ${c.dim("Any other input will be sent as a prompt to the AI.")}
42877
+ class SessionCommands {
42878
+ historyManager;
42879
+ constructor(projectPath) {
42880
+ this.historyManager = new HistoryManager(projectPath);
42881
+ }
42882
+ async list() {
42883
+ const sessions = this.historyManager.listSessions();
42884
+ if (sessions.length === 0) {
42885
+ console.log(`
42886
+ ${c.dim("No exec sessions found.")}
42619
42887
  `);
42620
- }
42621
- function showHistory(session2, args) {
42622
- const historyManager = session2.getHistoryManager();
42623
- const limit = args ? parseInt(args, 10) : 10;
42624
- const sessions = historyManager.listSessions({
42625
- limit: Number.isNaN(limit) ? 10 : limit
42626
- });
42627
- if (sessions.length === 0) {
42888
+ return;
42889
+ }
42628
42890
  console.log(`
42629
- ${c.dim("No sessions found.")}
42630
- `);
42631
- return;
42632
- }
42633
- console.log(`
42634
- ${c.primary("Recent Sessions")}
42891
+ ${c.primary("Recent Exec Sessions:")}
42635
42892
  `);
42636
- for (const sess of sessions) {
42637
- const date5 = new Date(sess.updatedAt);
42638
- const dateStr = date5.toLocaleDateString();
42639
- const timeStr = date5.toLocaleTimeString();
42640
- const msgCount = sess.messages.length;
42641
- const isCurrent = sess.id === session2.getSessionId();
42642
- const marker = isCurrent ? c.success("*") : " ";
42643
- console.log(` ${marker} ${c.cyan(sess.id)} ${c.dim(`- ${dateStr} ${timeStr} (${msgCount} messages)`)}`);
42644
- if (sess.messages.length > 0) {
42645
- const lastMsg = sess.messages[sess.messages.length - 1];
42646
- const preview = lastMsg.content.slice(0, 60).replace(/\n/g, " ");
42647
- console.log(` ${c.dim(preview + (lastMsg.content.length > 60 ? "..." : ""))}`);
42893
+ for (const session2 of sessions.slice(0, 10)) {
42894
+ const shortId = this.getShortId(session2.id);
42895
+ const age = formatRelativeTime(session2.updatedAt);
42896
+ const msgCount = session2.messages.length;
42897
+ const firstUserMsg = session2.messages.find((m) => m.role === "user");
42898
+ const preview = firstUserMsg ? firstUserMsg.content.slice(0, 50).replace(/\n/g, " ") : "(empty session)";
42899
+ console.log(` ${c.cyan(shortId)} ${c.gray("-")} ${preview}${firstUserMsg && firstUserMsg.content.length > 50 ? "..." : ""}`);
42900
+ console.log(` ${c.dim(`${msgCount} messages ${age}`)}`);
42901
+ console.log();
42902
+ }
42903
+ if (sessions.length > 10) {
42904
+ console.log(c.dim(` ... and ${sessions.length - 10} more sessions
42905
+ `));
42648
42906
  }
42649
42907
  }
42650
- console.log(`
42651
- ${c.dim("Use --session <id> to resume a session")}
42908
+ async show(sessionId) {
42909
+ if (!sessionId) {
42910
+ console.error(`
42911
+ ${c.error("Error:")} Session ID is required
42652
42912
  `);
42653
- }
42654
- var REPL_COMMANDS;
42655
- var init_commands = __esm(() => {
42656
- init_index_node();
42657
- REPL_COMMANDS = [
42658
- {
42659
- name: "exit",
42660
- aliases: ["quit", "q"],
42661
- description: "Exit interactive mode",
42662
- execute: (session2) => session2.shutdown()
42663
- },
42664
- {
42665
- name: "clear",
42666
- aliases: ["cls"],
42667
- description: "Clear the screen",
42668
- execute: () => console.clear()
42669
- },
42670
- {
42671
- name: "help",
42672
- aliases: ["?", "h"],
42673
- description: "Show available commands",
42674
- execute: () => showHelp()
42675
- },
42676
- {
42677
- name: "reset",
42678
- aliases: ["r"],
42679
- description: "Reset conversation context",
42680
- execute: (session2) => session2.resetContext()
42681
- },
42682
- {
42683
- name: "history",
42684
- aliases: ["hist"],
42685
- description: "List recent sessions",
42686
- execute: (session2, args) => showHistory(session2, args)
42687
- },
42688
- {
42689
- name: "session",
42690
- aliases: ["sid"],
42691
- description: "Show current session ID",
42692
- execute: (session2) => {
42693
- console.log(`
42694
- ${c.dim("Session ID:")} ${c.cyan(session2.getSessionId())}
42913
+ console.log(` ${c.dim("Usage: locus exec sessions show <session-id>")}
42695
42914
  `);
42696
- }
42915
+ return;
42697
42916
  }
42698
- ];
42917
+ const session2 = this.historyManager.findSessionByPartialId(sessionId);
42918
+ if (!session2) {
42919
+ console.error(`
42920
+ ${c.error("Error:")} Session ${c.cyan(sessionId)} not found
42921
+ `);
42922
+ console.log(` ${c.dim("Use 'locus exec sessions list' to see available sessions")}
42923
+ `);
42924
+ return;
42925
+ }
42926
+ console.log(`
42927
+ ${c.primary("Session:")} ${c.cyan(session2.id)}`);
42928
+ console.log(` ${c.dim(`Created: ${new Date(session2.createdAt).toLocaleString()}`)}`);
42929
+ console.log(` ${c.dim(`Model: ${session2.metadata.model} (${session2.metadata.provider})`)}
42930
+ `);
42931
+ if (session2.messages.length === 0) {
42932
+ console.log(` ${c.dim("(No messages in this session)")}
42933
+ `);
42934
+ return;
42935
+ }
42936
+ console.log(c.dim(" ─".repeat(30)));
42937
+ console.log();
42938
+ for (const message of session2.messages) {
42939
+ const role = message.role === "user" ? c.cyan("You") : c.green("AI");
42940
+ const content = message.content;
42941
+ console.log(` ${role}:`);
42942
+ const lines = content.split(`
42943
+ `);
42944
+ for (const line of lines) {
42945
+ console.log(` ${line}`);
42946
+ }
42947
+ console.log();
42948
+ }
42949
+ }
42950
+ async delete(sessionId) {
42951
+ if (!sessionId) {
42952
+ console.error(`
42953
+ ${c.error("Error:")} Session ID is required
42954
+ `);
42955
+ console.log(` ${c.dim("Usage: locus exec sessions delete <session-id>")}
42956
+ `);
42957
+ return;
42958
+ }
42959
+ const session2 = this.historyManager.findSessionByPartialId(sessionId);
42960
+ if (!session2) {
42961
+ console.error(`
42962
+ ${c.error("Error:")} Session ${c.cyan(sessionId)} not found
42963
+ `);
42964
+ return;
42965
+ }
42966
+ const deleted = this.historyManager.deleteSession(session2.id);
42967
+ if (deleted) {
42968
+ console.log(`
42969
+ ${c.success("✔")} Deleted session ${c.cyan(this.getShortId(session2.id))}
42970
+ `);
42971
+ } else {
42972
+ console.error(`
42973
+ ${c.error("Error:")} Failed to delete session
42974
+ `);
42975
+ }
42976
+ }
42977
+ async clear() {
42978
+ const count = this.historyManager.getSessionCount();
42979
+ if (count === 0) {
42980
+ console.log(`
42981
+ ${c.dim("No sessions to clear.")}
42982
+ `);
42983
+ return;
42984
+ }
42985
+ const deleted = this.historyManager.clearAllSessions();
42986
+ console.log(`
42987
+ ${c.success("✔")} Cleared ${deleted} exec session${deleted === 1 ? "" : "s"}
42988
+ `);
42989
+ }
42990
+ getShortId(sessionId) {
42991
+ const parts = sessionId.split("-");
42992
+ if (parts.length >= 3) {
42993
+ return parts.slice(-1)[0].slice(0, 8);
42994
+ }
42995
+ return sessionId.slice(0, 8);
42996
+ }
42997
+ }
42998
+ function showSessionsHelp() {
42999
+ console.log(`
43000
+ ${c.primary("Session Commands")}
43001
+
43002
+ ${c.success("list")} List recent exec sessions
43003
+ ${c.success("show")} ${c.dim("<id>")} Show all messages in a session
43004
+ ${c.success("delete")} ${c.dim("<id>")} Delete a specific session
43005
+ ${c.success("clear")} Clear all exec sessions
43006
+
43007
+ ${c.header(" EXAMPLES ")}
43008
+ ${c.dim("$")} locus exec sessions list
43009
+ ${c.dim("$")} locus exec sessions show e7f3a2b1
43010
+ ${c.dim("$")} locus exec sessions delete e7f3a2b1
43011
+ ${c.dim("$")} locus exec sessions clear
43012
+
43013
+ ${c.dim("Session IDs can be partial (first 8 characters).")}
43014
+ `);
43015
+ }
43016
+ var init_exec_sessions = __esm(() => {
43017
+ init_index_node();
43018
+ });
43019
+
43020
+ // src/repl/commands.ts
43021
+ function parseCommand(input) {
43022
+ const lowerInput = input.toLowerCase();
43023
+ for (const cmd of REPL_COMMANDS) {
43024
+ if (lowerInput === cmd.name || cmd.aliases.includes(lowerInput)) {
43025
+ return cmd;
43026
+ }
43027
+ }
43028
+ return null;
43029
+ }
43030
+ function showHelp() {
43031
+ console.log(`
43032
+ ${c.primary("Available Commands")}
43033
+
43034
+ ${c.success("exit")} / ${c.dim("quit, q")} Exit interactive mode
43035
+ ${c.success("clear")} / ${c.dim("cls")} Clear the screen
43036
+ ${c.success("help")} / ${c.dim("?, h")} Show this help message
43037
+ ${c.success("reset")} / ${c.dim("r")} Reset conversation context
43038
+ ${c.success("history")} / ${c.dim("hist")} List recent sessions
43039
+ ${c.success("session")} / ${c.dim("sid")} Show current session ID
43040
+
43041
+ ${c.dim("Any other input will be sent as a prompt to the AI.")}
43042
+ `);
43043
+ }
43044
+ function showHistory(session2, args) {
43045
+ const historyManager = session2.getHistoryManager();
43046
+ const limit = args ? parseInt(args, 10) : 10;
43047
+ const sessions = historyManager.listSessions({
43048
+ limit: Number.isNaN(limit) ? 10 : limit
43049
+ });
43050
+ if (sessions.length === 0) {
43051
+ console.log(`
43052
+ ${c.dim("No sessions found.")}
43053
+ `);
43054
+ return;
43055
+ }
43056
+ console.log(`
43057
+ ${c.primary("Recent Sessions")}
43058
+ `);
43059
+ for (const sess of sessions) {
43060
+ const date5 = new Date(sess.updatedAt);
43061
+ const dateStr = date5.toLocaleDateString();
43062
+ const timeStr = date5.toLocaleTimeString();
43063
+ const msgCount = sess.messages.length;
43064
+ const isCurrent = sess.id === session2.getSessionId();
43065
+ const marker = isCurrent ? c.success("*") : " ";
43066
+ console.log(` ${marker} ${c.cyan(sess.id)} ${c.dim(`- ${dateStr} ${timeStr} (${msgCount} messages)`)}`);
43067
+ if (sess.messages.length > 0) {
43068
+ const lastMsg = sess.messages[sess.messages.length - 1];
43069
+ const preview = lastMsg.content.slice(0, 60).replace(/\n/g, " ");
43070
+ console.log(` ${c.dim(preview + (lastMsg.content.length > 60 ? "..." : ""))}`);
43071
+ }
43072
+ }
43073
+ console.log(`
43074
+ ${c.dim("Use --session <id> to resume a session")}
43075
+ `);
43076
+ }
43077
+ var REPL_COMMANDS;
43078
+ var init_commands = __esm(() => {
43079
+ init_index_node();
43080
+ REPL_COMMANDS = [
43081
+ {
43082
+ name: "exit",
43083
+ aliases: ["quit", "q"],
43084
+ description: "Exit interactive mode",
43085
+ execute: (session2) => session2.shutdown()
43086
+ },
43087
+ {
43088
+ name: "clear",
43089
+ aliases: ["cls"],
43090
+ description: "Clear the screen",
43091
+ execute: () => console.clear()
43092
+ },
43093
+ {
43094
+ name: "help",
43095
+ aliases: ["?", "h"],
43096
+ description: "Show available commands",
43097
+ execute: () => showHelp()
43098
+ },
43099
+ {
43100
+ name: "reset",
43101
+ aliases: ["r"],
43102
+ description: "Reset conversation context",
43103
+ execute: (session2) => session2.resetContext()
43104
+ },
43105
+ {
43106
+ name: "history",
43107
+ aliases: ["hist"],
43108
+ description: "List recent sessions",
43109
+ execute: (session2, args) => showHistory(session2, args)
43110
+ },
43111
+ {
43112
+ name: "session",
43113
+ aliases: ["sid"],
43114
+ description: "Show current session ID",
43115
+ execute: (session2) => {
43116
+ console.log(`
43117
+ ${c.dim("Session ID:")} ${c.cyan(session2.getSessionId())}
43118
+ `);
43119
+ }
43120
+ }
43121
+ ];
42699
43122
  });
42700
43123
 
42701
43124
  // src/repl/interactive-session.ts
@@ -42703,7 +43126,7 @@ var exports_interactive_session = {};
42703
43126
  __export(exports_interactive_session, {
42704
43127
  InteractiveSession: () => InteractiveSession
42705
43128
  });
42706
- import * as readline2 from "node:readline";
43129
+ import * as readline from "node:readline";
42707
43130
 
42708
43131
  class InteractiveSession {
42709
43132
  readline = null;
@@ -42749,7 +43172,7 @@ class InteractiveSession {
42749
43172
  }
42750
43173
  async start() {
42751
43174
  this.printWelcome();
42752
- this.readline = readline2.createInterface({
43175
+ this.readline = readline.createInterface({
42753
43176
  input: process.stdin,
42754
43177
  output: process.stdout,
42755
43178
  terminal: true
@@ -42937,1700 +43360,1519 @@ var init_interactive_session = __esm(() => {
42937
43360
  init_commands();
42938
43361
  });
42939
43362
 
42940
- // src/cli.ts
42941
- init_index_node();
42942
-
42943
- // src/commands/config.ts
42944
- init_index_node();
42945
- import { createInterface } from "node:readline";
42946
-
42947
- // src/settings-manager.ts
42948
- init_index_node();
42949
- import { existsSync as existsSync12, readFileSync as readFileSync11, unlinkSync as unlinkSync4, writeFileSync as writeFileSync6 } from "node:fs";
42950
- import { join as join12 } from "node:path";
42951
- function getSettingsPath(projectPath) {
42952
- return join12(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.settingsFile);
42953
- }
42954
-
42955
- class SettingsManager {
42956
- projectPath;
42957
- constructor(projectPath) {
42958
- this.projectPath = projectPath;
42959
- }
42960
- load() {
42961
- const settingsPath = getSettingsPath(this.projectPath);
42962
- if (!existsSync12(settingsPath)) {
42963
- return {};
42964
- }
42965
- return JSON.parse(readFileSync11(settingsPath, "utf-8"));
43363
+ // src/commands/exec.ts
43364
+ var exports_exec = {};
43365
+ __export(exports_exec, {
43366
+ execCommand: () => execCommand
43367
+ });
43368
+ import { randomUUID as randomUUID2 } from "node:crypto";
43369
+ import { parseArgs } from "node:util";
43370
+ async function execCommand(args) {
43371
+ const { values, positionals } = parseArgs({
43372
+ args,
43373
+ options: {
43374
+ model: { type: "string" },
43375
+ provider: { type: "string" },
43376
+ "reasoning-effort": { type: "string" },
43377
+ dir: { type: "string" },
43378
+ "no-stream": { type: "boolean" },
43379
+ "no-status": { type: "boolean" },
43380
+ interactive: { type: "boolean", short: "i" },
43381
+ session: { type: "string", short: "s" },
43382
+ "session-id": { type: "string" },
43383
+ "json-stream": { type: "boolean" }
43384
+ },
43385
+ strict: false
43386
+ });
43387
+ const jsonStream = values["json-stream"];
43388
+ const projectPath = values.dir || process.cwd();
43389
+ if (jsonStream) {
43390
+ await execJsonStream(values, positionals, projectPath);
43391
+ return;
42966
43392
  }
42967
- save(settings) {
42968
- const { $schema: _2, ...rest } = settings;
42969
- const ordered = { $schema: LOCUS_SCHEMAS.settings, ...rest };
42970
- const settingsPath = getSettingsPath(this.projectPath);
42971
- writeFileSync6(settingsPath, JSON.stringify(ordered, null, 2), "utf-8");
42972
- }
42973
- get(key) {
42974
- return this.load()[key];
42975
- }
42976
- set(key, value) {
42977
- const settings = this.load();
42978
- settings[key] = value;
42979
- this.save(settings);
42980
- }
42981
- remove() {
42982
- const settingsPath = getSettingsPath(this.projectPath);
42983
- if (existsSync12(settingsPath)) {
42984
- unlinkSync4(settingsPath);
42985
- }
42986
- }
42987
- exists() {
42988
- return existsSync12(getSettingsPath(this.projectPath));
42989
- }
42990
- }
42991
-
42992
- // src/commands/config.ts
42993
- function ask(question) {
42994
- const rl = createInterface({
42995
- input: process.stdin,
42996
- output: process.stdout
42997
- });
42998
- return new Promise((resolve2) => {
42999
- rl.question(question, (answer) => {
43000
- rl.close();
43001
- resolve2(answer.trim());
43002
- });
43003
- });
43004
- }
43005
- var TOP_LEVEL_KEYS = [
43006
- "apiKey",
43007
- "apiUrl",
43008
- "provider",
43009
- "model",
43010
- "workspaceId"
43011
- ];
43012
- var TELEGRAM_KEYS = [
43013
- "telegram.botToken",
43014
- "telegram.chatId",
43015
- "telegram.testMode"
43016
- ];
43017
- var ALL_KEYS = [...TOP_LEVEL_KEYS, ...TELEGRAM_KEYS];
43018
- function maskSecret(value) {
43019
- if (value.length <= 8)
43020
- return "****";
43021
- return `${value.slice(0, 4)}...${value.slice(-4)}`;
43022
- }
43023
- function showConfigHelp() {
43024
- console.log(`
43025
- ${c.header(" CONFIG ")}
43026
- ${c.primary("locus config")} ${c.dim("<subcommand> [options]")}
43027
-
43028
- ${c.header(" SUBCOMMANDS ")}
43029
- ${c.success("setup")} Interactive configuration (or pass flags below)
43030
- ${c.dim("--api-key <KEY> Locus API key (required)")}
43031
- ${c.dim("--api-url <URL> API base URL (optional)")}
43032
- ${c.dim("--provider <P> AI provider (optional)")}
43033
- ${c.dim("--model <M> AI model (optional)")}
43034
- ${c.success("show")} Show current settings
43035
- ${c.success("set")} Set a config value
43036
- ${c.dim("locus config set <key> <value>")}
43037
- ${c.dim(`Keys: ${ALL_KEYS.join(", ")}`)}
43038
- ${c.success("remove")} Remove all settings
43039
-
43040
- ${c.header(" EXAMPLES ")}
43041
- ${c.dim("$")} ${c.primary("locus config setup")}
43042
- ${c.dim("$")} ${c.primary("locus config setup --api-key sk-xxx")}
43043
- ${c.dim("$")} ${c.primary("locus config show")}
43044
- ${c.dim("$")} ${c.primary("locus config set apiKey sk-new-key")}
43045
- ${c.dim("$")} ${c.primary("locus config set provider codex")}
43046
- ${c.dim("$")} ${c.primary("locus config set telegram.botToken 123:ABC")}
43047
- ${c.dim("$")} ${c.primary("locus config remove")}
43048
- `);
43049
- }
43050
- async function setupCommand(args, projectPath) {
43051
- let apiKey;
43052
- let apiUrl;
43053
- let provider;
43054
- let model;
43055
- for (let i = 0;i < args.length; i++) {
43056
- if (args[i] === "--api-key" && args[i + 1]) {
43057
- apiKey = args[++i]?.trim();
43058
- } else if (args[i] === "--api-url" && args[i + 1]) {
43059
- apiUrl = args[++i]?.trim();
43060
- } else if (args[i] === "--provider" && args[i + 1]) {
43061
- provider = args[++i]?.trim();
43062
- } else if (args[i] === "--model" && args[i + 1]) {
43063
- model = args[++i]?.trim();
43064
- }
43065
- }
43066
- if (!apiKey && !apiUrl && !provider && !model) {
43067
- console.log(`
43068
- ${c.header(" LOCUS SETUP ")}
43069
- `);
43070
- console.log(` ${c.dim("Configure your Locus settings. Press Enter to skip optional fields.")}
43071
- `);
43072
- while (!apiKey) {
43073
- apiKey = await ask(` ${c.primary("API Key")} ${c.dim("(required)")}: `);
43074
- if (!apiKey) {
43075
- console.log(` ${c.error("✖")} API key is required. Get one from ${c.underline("Workspace Settings > API Keys")}`);
43076
- }
43077
- }
43078
- provider = await ask(` ${c.primary("Provider")} ${c.dim("(optional, e.g. claude, codex)")}: `);
43079
- model = await ask(` ${c.primary("Model")} ${c.dim("(optional, e.g. opus, sonnet)")}: `);
43080
- if (!provider)
43081
- provider = undefined;
43082
- if (!model)
43083
- model = undefined;
43084
- }
43085
- if (!apiKey) {
43086
- console.error(`
43087
- ${c.error("✖")} ${c.bold("Missing --api-key flag.")}
43088
- ` + ` Get an API key from ${c.underline("Workspace Settings > API Keys")}
43089
- `);
43090
- process.exit(1);
43091
- }
43092
- const manager = new SettingsManager(projectPath);
43093
- const existing = manager.load();
43094
- const settings = {
43095
- ...existing,
43096
- apiKey
43097
- };
43098
- if (apiUrl)
43099
- settings.apiUrl = apiUrl;
43100
- if (provider)
43101
- settings.provider = provider;
43102
- if (model)
43103
- settings.model = model;
43104
- manager.save(settings);
43105
- console.log(`
43106
- ${c.success("✔")} ${c.bold("Settings configured successfully!")}
43107
-
43108
- ${c.bold("Saved to:")} ${c.dim(".locus/settings.json")}
43109
-
43110
- ${c.bold("Configuration:")}
43111
- ${c.primary("API Key:")} ${maskSecret(apiKey)}${apiUrl ? `
43112
- ${c.primary("API URL:")} ${apiUrl}` : ""}${provider ? `
43113
- ${c.primary("Provider:")} ${provider}` : ""}${model ? `
43114
- ${c.primary("Model:")} ${model}` : ""}
43115
-
43116
- ${c.bold("Next steps:")}
43117
- Run agents: ${c.primary("locus run")}
43118
- Setup Telegram: ${c.primary("locus telegram setup")}
43119
- `);
43120
- }
43121
- function showCommand(projectPath) {
43122
- const manager = new SettingsManager(projectPath);
43123
- const settings = manager.load();
43124
- if (Object.keys(settings).length === 0) {
43125
- console.log(`
43126
- ${c.dim("No settings found.")}
43127
- ` + ` Run ${c.primary("locus config setup")} to configure.
43128
- `);
43129
- return;
43130
- }
43131
- console.log(`
43132
- ${c.header(" SETTINGS ")}`);
43133
- console.log(` ${c.dim("File: .locus/settings.json")}
43134
- `);
43135
- if (settings.apiKey) {
43136
- console.log(` ${c.primary("apiKey:")} ${maskSecret(settings.apiKey)}`);
43137
- }
43138
- if (settings.apiUrl) {
43139
- console.log(` ${c.primary("apiUrl:")} ${settings.apiUrl}`);
43140
- }
43141
- if (settings.provider) {
43142
- console.log(` ${c.primary("provider:")} ${settings.provider}`);
43143
- }
43144
- if (settings.model) {
43145
- console.log(` ${c.primary("model:")} ${settings.model}`);
43146
- }
43147
- if (settings.workspaceId) {
43148
- console.log(` ${c.primary("workspaceId:")} ${settings.workspaceId}`);
43149
- }
43150
- if (settings.telegram) {
43151
- const tg = settings.telegram;
43152
- console.log(`
43153
- ${c.header(" TELEGRAM ")}`);
43154
- if (tg.botToken) {
43155
- console.log(` ${c.primary("botToken:")} ${maskSecret(tg.botToken)}`);
43156
- }
43157
- if (tg.chatId) {
43158
- console.log(` ${c.primary("chatId:")} ${tg.chatId}`);
43159
- }
43160
- if (tg.testMode !== undefined) {
43161
- console.log(` ${c.primary("testMode:")} ${tg.testMode}`);
43162
- }
43163
- }
43164
- console.log("");
43165
- }
43166
- function setCommand(args, projectPath) {
43167
- const key = args[0]?.trim();
43168
- const value = args.slice(1).join(" ").trim();
43169
- if (!key || !value) {
43170
- console.error(`
43171
- ${c.error("✖")} ${c.bold("Usage:")} locus config set <key> <value>
43172
- ` + ` ${c.dim(`Available keys: ${ALL_KEYS.join(", ")}`)}
43173
- `);
43174
- process.exit(1);
43175
- }
43176
- if (!ALL_KEYS.includes(key)) {
43177
- console.error(`
43178
- ${c.error("✖")} ${c.bold(`Unknown key: ${key}`)}
43179
- ` + ` ${c.dim(`Available keys: ${ALL_KEYS.join(", ")}`)}
43180
- `);
43181
- process.exit(1);
43182
- }
43183
- const manager = new SettingsManager(projectPath);
43184
- const settings = manager.load();
43185
- if (key.startsWith("telegram.")) {
43186
- const telegramKey = key.replace("telegram.", "");
43187
- if (!settings.telegram) {
43188
- settings.telegram = {};
43189
- }
43190
- if (telegramKey === "chatId") {
43191
- const num = Number(value);
43192
- if (Number.isNaN(num)) {
43193
- console.error(`
43194
- ${c.error("✖")} ${c.bold(`${key} must be a number.`)}
43195
- `);
43196
- process.exit(1);
43197
- }
43198
- settings.telegram[telegramKey] = num;
43199
- } else if (telegramKey === "testMode") {
43200
- settings.telegram[telegramKey] = value === "true" || value === "1";
43201
- } else {
43202
- settings.telegram[telegramKey] = value;
43203
- }
43204
- } else {
43205
- settings[key] = value;
43206
- }
43207
- manager.save(settings);
43208
- const displayValue = key === "apiKey" || key === "telegram.botToken" ? maskSecret(value) : value;
43209
- console.log(`
43210
- ${c.success("✔")} Set ${c.primary(key)} = ${displayValue}
43211
- `);
43212
- }
43213
- function removeCommand(projectPath) {
43214
- const manager = new SettingsManager(projectPath);
43215
- if (!manager.exists()) {
43216
- console.log(`
43217
- ${c.dim("No settings found. Nothing to remove.")}
43218
- `);
43219
- return;
43220
- }
43221
- manager.remove();
43222
- console.log(`
43223
- ${c.success("✔")} ${c.bold("Settings removed.")}
43224
- `);
43225
- }
43226
- async function configCommand(args) {
43227
- const projectPath = process.cwd();
43228
- const subcommand = args[0];
43229
- const subArgs = args.slice(1);
43230
- switch (subcommand) {
43231
- case "setup":
43232
- await setupCommand(subArgs, projectPath);
43233
- break;
43234
- case "show":
43235
- showCommand(projectPath);
43236
- break;
43237
- case "set":
43238
- setCommand(subArgs, projectPath);
43239
- break;
43240
- case "remove":
43241
- removeCommand(projectPath);
43242
- break;
43243
- default:
43244
- showConfigHelp();
43245
- }
43246
- }
43247
- // src/commands/discuss.ts
43248
- init_index_node();
43249
- init_progress_renderer();
43250
- import * as readline from "node:readline";
43251
- import { parseArgs } from "node:util";
43252
-
43253
- // src/utils/banner.ts
43254
- init_index_node();
43255
-
43256
- // src/utils/version.ts
43257
- import { existsSync as existsSync13, readFileSync as readFileSync12 } from "node:fs";
43258
- import { dirname as dirname3, join as join13 } from "node:path";
43259
- import { fileURLToPath as fileURLToPath3 } from "node:url";
43260
- function getVersion() {
43261
- try {
43262
- const __filename2 = fileURLToPath3(import.meta.url);
43263
- const __dirname2 = dirname3(__filename2);
43264
- const bundledPath = join13(__dirname2, "..", "package.json");
43265
- const sourcePath = join13(__dirname2, "..", "..", "package.json");
43266
- if (existsSync13(bundledPath)) {
43267
- const pkg = JSON.parse(readFileSync12(bundledPath, "utf-8"));
43268
- if (pkg.name === "@locusai/cli") {
43269
- return pkg.version || "0.0.0";
43270
- }
43271
- }
43272
- if (existsSync13(sourcePath)) {
43273
- const pkg = JSON.parse(readFileSync12(sourcePath, "utf-8"));
43274
- if (pkg.name === "@locusai/cli") {
43275
- return pkg.version || "0.0.0";
43276
- }
43393
+ requireInitialization(projectPath, "exec");
43394
+ if (positionals[0] === "sessions") {
43395
+ const sessionAction = positionals[1];
43396
+ const sessionArg = positionals[2];
43397
+ const cmds = new SessionCommands(projectPath);
43398
+ switch (sessionAction) {
43399
+ case "list":
43400
+ await cmds.list();
43401
+ return;
43402
+ case "show":
43403
+ await cmds.show(sessionArg);
43404
+ return;
43405
+ case "delete":
43406
+ await cmds.delete(sessionArg);
43407
+ return;
43408
+ case "clear":
43409
+ await cmds.clear();
43410
+ return;
43411
+ default:
43412
+ showSessionsHelp();
43413
+ return;
43277
43414
  }
43278
- } catch {}
43279
- return "0.0.0";
43280
- }
43281
- var VERSION2 = getVersion();
43282
-
43283
- // src/utils/banner.ts
43284
- function printBanner() {
43285
- console.log(c.primary(`
43286
- _ ____ ____ _ _ ____
43287
- | | / __ \\ / ___| | | |/ ___|
43288
- | | | | | | | | | | |\\___ \\
43289
- | |___| |__| | |___| |_| |___) |
43290
- |_____|\\____/ \\____|\\___/|____/ ${c.dim(`v${VERSION2}`)}
43291
- `));
43292
- }
43293
- // src/utils/helpers.ts
43294
- init_index_node();
43295
- import { existsSync as existsSync14 } from "node:fs";
43296
- import { join as join14 } from "node:path";
43297
- function isProjectInitialized(projectPath) {
43298
- const locusDir = join14(projectPath, LOCUS_CONFIG.dir);
43299
- const configPath = join14(locusDir, LOCUS_CONFIG.configFile);
43300
- return existsSync14(locusDir) && existsSync14(configPath);
43301
- }
43302
- function requireInitialization(projectPath, command) {
43303
- if (!isProjectInitialized(projectPath)) {
43304
- console.error(`
43305
- ${c.error("✖ Error")} ${c.red(`Locus is not initialized in this directory.`)}
43306
-
43307
- The '${c.bold(command)}' command requires a Locus project to be initialized.
43308
-
43309
- To initialize Locus in this directory, run:
43310
- ${c.primary("locus init")}
43311
-
43312
- This will create a ${c.dim(".locus")} directory with the necessary configuration.
43313
- `);
43314
- process.exit(1);
43315
- }
43316
- }
43317
- function resolveProvider3(input) {
43318
- if (!input)
43319
- return PROVIDER.CLAUDE;
43320
- if (input === PROVIDER.CLAUDE || input === PROVIDER.CODEX)
43321
- return input;
43322
- console.error(c.error(`Error: invalid provider '${input}'. Use 'claude' or 'codex'.`));
43323
- process.exit(1);
43324
- }
43325
- // src/commands/discuss.ts
43326
- async function discussCommand(args) {
43327
- const { values, positionals } = parseArgs({
43328
- args,
43329
- options: {
43330
- list: { type: "boolean" },
43331
- show: { type: "string" },
43332
- archive: { type: "string" },
43333
- delete: { type: "string" },
43334
- model: { type: "string" },
43335
- provider: { type: "string" },
43336
- "reasoning-effort": { type: "string" },
43337
- dir: { type: "string" }
43338
- },
43339
- strict: false,
43340
- allowPositionals: true
43341
- });
43342
- const projectPath = values.dir || process.cwd();
43343
- requireInitialization(projectPath, "discuss");
43344
- const discussionManager = new DiscussionManager(projectPath);
43345
- if (values.list) {
43346
- return listDiscussions(discussionManager);
43347
- }
43348
- if (values.show) {
43349
- return showDiscussion(discussionManager, values.show);
43350
- }
43351
- if (values.archive) {
43352
- return archiveDiscussion(discussionManager, values.archive);
43353
- }
43354
- if (values.delete) {
43355
- return deleteDiscussion(discussionManager, values.delete);
43356
43415
  }
43357
- const topic = positionals.join(" ").trim();
43358
- if (!topic) {
43359
- showDiscussHelp();
43416
+ const execSettings = new SettingsManager(projectPath).load();
43417
+ const provider = resolveProvider3(values.provider || execSettings.provider);
43418
+ const model = values.model || execSettings.model || DEFAULT_MODEL[provider];
43419
+ const isInteractive = values.interactive;
43420
+ const sessionId = values.session;
43421
+ if (isInteractive) {
43422
+ const { InteractiveSession: InteractiveSession2 } = await Promise.resolve().then(() => (init_interactive_session(), exports_interactive_session));
43423
+ const session2 = new InteractiveSession2({
43424
+ projectPath,
43425
+ provider,
43426
+ model,
43427
+ sessionId
43428
+ });
43429
+ await session2.start();
43360
43430
  return;
43361
43431
  }
43362
- const settings = new SettingsManager(projectPath).load();
43363
- const provider = resolveProvider3(values.provider || settings.provider);
43364
- const model = values.model || settings.model || DEFAULT_MODEL[provider];
43432
+ const promptInput = positionals.join(" ");
43433
+ if (!promptInput) {
43434
+ console.error(c.error('Error: Prompt is required. Usage: locus exec "your prompt" or locus exec --interactive'));
43435
+ process.exit(1);
43436
+ }
43437
+ const useStreaming = !values["no-stream"];
43365
43438
  const reasoningEffort = values["reasoning-effort"];
43366
43439
  const aiRunner = createAiRunner(provider, {
43367
43440
  projectPath,
43368
43441
  model,
43369
43442
  reasoningEffort
43370
43443
  });
43371
- const log = (message, level) => {
43372
- const icon = level === "success" ? c.success("✔") : level === "error" ? c.error("✖") : level === "warn" ? c.warning("!") : c.info("●");
43373
- console.log(` ${icon} ${message}`);
43374
- };
43375
- const facilitator = new DiscussionFacilitator({
43376
- projectPath,
43377
- aiRunner,
43378
- discussionManager,
43379
- log,
43380
- provider,
43381
- model
43382
- });
43383
- console.log(`
43384
- ${c.header(" DISCUSSION ")} ${c.bold("Starting interactive discussion...")}
43385
- `);
43386
- console.log(` ${c.dim("Topic:")} ${c.bold(topic)}`);
43387
- console.log(` ${c.dim("Model:")} ${c.dim(`${model} (${provider})`)}
43388
- `);
43389
- const renderer = new ProgressRenderer({ animated: true });
43390
- let discussionId;
43444
+ const builder = new PromptBuilder(projectPath);
43445
+ const fullPrompt = await builder.buildGenericPrompt(promptInput);
43446
+ console.log("");
43447
+ console.log(`${c.primary("\uD83D\uDE80")} ${c.bold("Executing prompt with repository context...")}`);
43448
+ console.log("");
43449
+ let responseContent = "";
43391
43450
  try {
43392
- renderer.showThinkingStarted();
43393
- const result = await facilitator.startDiscussion(topic);
43394
- renderer.showThinkingStopped();
43395
- discussionId = result.discussion.id;
43396
- process.stdout.write(`
43451
+ if (useStreaming) {
43452
+ const renderer = new ProgressRenderer;
43453
+ const statsTracker = new ExecutionStatsTracker;
43454
+ const stream4 = aiRunner.runStream(fullPrompt);
43455
+ renderer.showThinkingStarted();
43456
+ for await (const chunk of stream4) {
43457
+ switch (chunk.type) {
43458
+ case "text_delta":
43459
+ renderer.renderTextDelta(chunk.content);
43460
+ responseContent += chunk.content;
43461
+ break;
43462
+ case "tool_use":
43463
+ statsTracker.toolStarted(chunk.tool, chunk.id);
43464
+ renderer.showToolStarted(chunk.tool, chunk.id);
43465
+ break;
43466
+ case "thinking":
43467
+ renderer.showThinkingStarted();
43468
+ break;
43469
+ case "tool_result":
43470
+ if (chunk.success) {
43471
+ statsTracker.toolCompleted(chunk.tool, chunk.id);
43472
+ renderer.showToolCompleted(chunk.tool, undefined, chunk.id);
43473
+ } else {
43474
+ statsTracker.toolFailed(chunk.tool, chunk.error ?? "Unknown error", chunk.id);
43475
+ renderer.showToolFailed(chunk.tool, chunk.error ?? "Unknown error", chunk.id);
43476
+ }
43477
+ break;
43478
+ case "result":
43479
+ break;
43480
+ case "error": {
43481
+ statsTracker.setError(chunk.error);
43482
+ renderer.renderError(chunk.error);
43483
+ renderer.finalize();
43484
+ const errorStats = statsTracker.finalize();
43485
+ renderer.showSummary(errorStats);
43486
+ console.error(`
43487
+ ${c.error("✖")} ${c.error("Execution failed!")}
43397
43488
  `);
43398
- process.stdout.write(result.message);
43399
- process.stdout.write(`
43400
-
43489
+ process.exit(1);
43490
+ }
43491
+ }
43492
+ }
43493
+ renderer.finalize();
43494
+ const stats = statsTracker.finalize();
43495
+ renderer.showSummary(stats);
43496
+ } else {
43497
+ responseContent = await aiRunner.run(fullPrompt);
43498
+ console.log(responseContent);
43499
+ }
43500
+ console.log(`
43501
+ ${c.success("✔")} ${c.success("Execution finished!")}
43401
43502
  `);
43402
- renderer.finalize();
43403
43503
  } catch (error48) {
43404
- renderer.finalize();
43405
43504
  console.error(`
43406
- ${c.error("✖")} ${c.red("Failed to start discussion:")} ${error48 instanceof Error ? error48.message : String(error48)}
43505
+ ${c.error("✖")} ${c.error("Execution failed:")} ${c.red(error48 instanceof Error ? error48.message : String(error48))}
43407
43506
  `);
43408
43507
  process.exit(1);
43409
43508
  }
43410
- console.log(` ${c.dim("Type your response, or 'help' for commands. Use 'exit' or Ctrl+C to quit.")}
43411
- `);
43412
- const rl = readline.createInterface({
43413
- input: process.stdin,
43414
- output: process.stdout,
43415
- terminal: true
43509
+ }
43510
+ async function execJsonStream(values, positionals, projectPath) {
43511
+ const sessionId = values["session-id"] ?? values.session ?? randomUUID2();
43512
+ const execSettings = new SettingsManager(projectPath).load();
43513
+ const provider = resolveProvider3(values.provider || execSettings.provider);
43514
+ const model = values.model || execSettings.model || DEFAULT_MODEL[provider];
43515
+ const renderer = new JsonStreamRenderer({
43516
+ sessionId,
43517
+ command: "exec",
43518
+ model,
43519
+ provider,
43520
+ cwd: projectPath
43416
43521
  });
43417
- rl.setPrompt(c.cyan("> "));
43418
- rl.prompt();
43419
- let isProcessing = false;
43420
- const shutdown = () => {
43421
- if (isProcessing) {
43422
- aiRunner.abort();
43522
+ const handleSignal = () => {
43523
+ if (renderer.isDone()) {
43524
+ return;
43423
43525
  }
43424
- console.log(`
43425
- ${c.dim("Discussion saved.")} ${c.dim("ID:")} ${c.cyan(discussionId)}`);
43426
- console.log(c.dim(`
43427
- Goodbye!
43428
- `));
43429
- rl.close();
43430
- process.exit(0);
43431
- };
43432
- process.on("SIGINT", () => {
43433
- if (isProcessing) {
43434
- aiRunner.abort();
43435
- isProcessing = false;
43436
- console.log(c.dim(`
43437
- [Interrupted]`));
43438
- rl.prompt();
43526
+ renderer.emitFatalError("PROCESS_CRASHED", "Process terminated by signal");
43527
+ if (process.stdout.writableNeedDrain) {
43528
+ process.stdout.once("drain", () => process.exit(1));
43439
43529
  } else {
43440
- shutdown();
43530
+ process.exit(1);
43441
43531
  }
43442
- });
43443
- rl.on("close", () => {
43444
- shutdown();
43445
- });
43446
- rl.on("line", async (input) => {
43447
- const trimmed = input.trim();
43448
- if (trimmed === "" || isProcessing) {
43449
- rl.prompt();
43450
- return;
43532
+ };
43533
+ process.on("SIGINT", handleSignal);
43534
+ process.on("SIGTERM", handleSignal);
43535
+ try {
43536
+ try {
43537
+ requireInitialization(projectPath, "exec");
43538
+ } catch (initError) {
43539
+ renderer.emitFatalError("CLI_NOT_FOUND", initError instanceof Error ? initError.message : String(initError));
43540
+ process.exit(1);
43451
43541
  }
43452
- const lowerInput = trimmed.toLowerCase();
43453
- if (lowerInput === "help") {
43454
- showReplHelp();
43455
- rl.prompt();
43456
- return;
43542
+ const promptInput = positionals.join(" ");
43543
+ if (!promptInput) {
43544
+ renderer.emitFatalError("UNKNOWN", 'Prompt is required. Usage: locus exec --json-stream "your prompt"');
43545
+ process.exit(1);
43457
43546
  }
43458
- if (lowerInput === "exit" || lowerInput === "quit") {
43459
- shutdown();
43460
- return;
43547
+ renderer.emitStart();
43548
+ renderer.emitStatus("running", "Building prompt context");
43549
+ const aiRunner = createAiRunner(provider, {
43550
+ projectPath,
43551
+ model
43552
+ });
43553
+ const builder = new PromptBuilder(projectPath);
43554
+ const fullPrompt = await builder.buildGenericPrompt(promptInput);
43555
+ renderer.emitStatus("streaming", "Streaming AI response");
43556
+ const stream4 = aiRunner.runStream(fullPrompt);
43557
+ for await (const chunk of stream4) {
43558
+ renderer.handleChunk(chunk);
43461
43559
  }
43462
- if (lowerInput === "insights") {
43463
- showCurrentInsights(discussionManager, discussionId);
43464
- rl.prompt();
43465
- return;
43560
+ renderer.emitDone(0);
43561
+ process.removeListener("SIGINT", handleSignal);
43562
+ process.removeListener("SIGTERM", handleSignal);
43563
+ } catch (error48) {
43564
+ const message = error48 instanceof Error ? error48.message : String(error48);
43565
+ if (!renderer.isDone()) {
43566
+ renderer.emitFatalError("UNKNOWN", message);
43466
43567
  }
43467
- if (lowerInput === "summary") {
43468
- isProcessing = true;
43469
- const summaryRenderer = new ProgressRenderer({ animated: true });
43470
- try {
43471
- summaryRenderer.showThinkingStarted();
43472
- const summary = await facilitator.summarizeDiscussion(discussionId);
43473
- summaryRenderer.showThinkingStopped();
43474
- process.stdout.write(`
43475
- `);
43476
- process.stdout.write(summary);
43477
- process.stdout.write(`
43478
- `);
43479
- summaryRenderer.finalize();
43480
- const discussion2 = discussionManager.load(discussionId);
43481
- if (discussion2) {
43482
- console.log(`
43483
- ${c.success("✔")} ${c.success("Discussion completed!")}
43484
- `);
43485
- console.log(` ${c.dim("Messages:")} ${discussion2.messages.length} ${c.dim("Insights:")} ${discussion2.insights.length}
43568
+ process.exit(1);
43569
+ }
43570
+ }
43571
+ var init_exec2 = __esm(() => {
43572
+ init_index_node();
43573
+ init_json_stream_renderer();
43574
+ init_progress_renderer();
43575
+ init_settings_manager();
43576
+ init_utils3();
43577
+ init_exec_sessions();
43578
+ });
43579
+
43580
+ // src/cli.ts
43581
+ init_index_node();
43582
+
43583
+ // src/commands/artifacts.ts
43584
+ init_index_node();
43585
+ init_utils3();
43586
+ import { existsSync as existsSync15, readdirSync as readdirSync5, readFileSync as readFileSync13, statSync } from "node:fs";
43587
+ import { join as join15 } from "node:path";
43588
+ import { parseArgs as parseArgs2 } from "node:util";
43589
+ function listArtifacts(projectPath) {
43590
+ const artifactsDir = getLocusPath(projectPath, "artifactsDir");
43591
+ if (!existsSync15(artifactsDir)) {
43592
+ return [];
43593
+ }
43594
+ const files = readdirSync5(artifactsDir).filter((f) => f.endsWith(".md"));
43595
+ return files.map((fileName) => {
43596
+ const filePath = join15(artifactsDir, fileName);
43597
+ const stat = statSync(filePath);
43598
+ const name = fileName.replace(/\.md$/, "");
43599
+ return {
43600
+ name,
43601
+ fileName,
43602
+ createdAt: stat.birthtime,
43603
+ size: stat.size
43604
+ };
43605
+ }).sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
43606
+ }
43607
+ function readArtifact(projectPath, name) {
43608
+ const artifactsDir = getLocusPath(projectPath, "artifactsDir");
43609
+ const fileName = name.endsWith(".md") ? name : `${name}.md`;
43610
+ const filePath = join15(artifactsDir, fileName);
43611
+ if (!existsSync15(filePath)) {
43612
+ return null;
43613
+ }
43614
+ const stat = statSync(filePath);
43615
+ const content = readFileSync13(filePath, "utf-8");
43616
+ return {
43617
+ content,
43618
+ info: {
43619
+ name: fileName.replace(/\.md$/, ""),
43620
+ fileName,
43621
+ createdAt: stat.birthtime,
43622
+ size: stat.size
43623
+ }
43624
+ };
43625
+ }
43626
+ function formatSize(bytes) {
43627
+ if (bytes < 1024)
43628
+ return `${bytes}B`;
43629
+ const kb = bytes / 1024;
43630
+ if (kb < 1024)
43631
+ return `${kb.toFixed(1)}KB`;
43632
+ const mb = kb / 1024;
43633
+ return `${mb.toFixed(1)}MB`;
43634
+ }
43635
+ function formatDate(date5) {
43636
+ return date5.toLocaleDateString("en-US", {
43637
+ year: "numeric",
43638
+ month: "short",
43639
+ day: "numeric",
43640
+ hour: "2-digit",
43641
+ minute: "2-digit"
43642
+ });
43643
+ }
43644
+ async function artifactsCommand(args) {
43645
+ const { positionals } = parseArgs2({
43646
+ args,
43647
+ options: {
43648
+ dir: { type: "string" }
43649
+ },
43650
+ strict: false,
43651
+ allowPositionals: true
43652
+ });
43653
+ const projectPath = process.cwd();
43654
+ requireInitialization(projectPath, "artifacts");
43655
+ const subcommand = positionals[0];
43656
+ switch (subcommand) {
43657
+ case "show":
43658
+ case "view": {
43659
+ const name = positionals.slice(1).join(" ");
43660
+ if (!name) {
43661
+ console.error(`
43662
+ ${c.error("Error:")} Artifact name is required
43486
43663
  `);
43487
- }
43488
- console.log(` ${c.dim("To review:")} ${c.cyan(`locus discuss --show ${discussionId}`)}`);
43489
- console.log(` ${c.dim("To list all:")} ${c.cyan("locus discuss --list")}
43664
+ console.log(` ${c.dim("Usage: locus artifacts show <name>")}
43490
43665
  `);
43491
- } catch (error48) {
43492
- summaryRenderer.finalize();
43666
+ return;
43667
+ }
43668
+ await showArtifact(projectPath, name);
43669
+ break;
43670
+ }
43671
+ case "plan": {
43672
+ const name = positionals.slice(1).join(" ");
43673
+ if (!name) {
43493
43674
  console.error(`
43494
- ${c.error("✖")} ${c.red("Failed to summarize:")} ${error48 instanceof Error ? error48.message : String(error48)}
43675
+ ${c.error("Error:")} Artifact name is required
43676
+ `);
43677
+ console.log(` ${c.dim("Usage: locus artifacts plan <name>")}
43495
43678
  `);
43679
+ return;
43496
43680
  }
43497
- rl.close();
43498
- process.exit(0);
43499
- return;
43681
+ await convertToPlan(projectPath, name);
43682
+ break;
43500
43683
  }
43501
- isProcessing = true;
43502
- const chunkRenderer = new ProgressRenderer({ animated: true });
43503
- try {
43504
- chunkRenderer.showThinkingStarted();
43505
- const stream4 = facilitator.continueDiscussionStream(discussionId, trimmed);
43506
- let result = {
43507
- response: "",
43508
- insights: []
43509
- };
43510
- let iterResult = await stream4.next();
43511
- while (!iterResult.done) {
43512
- chunkRenderer.renderChunk(iterResult.value);
43513
- iterResult = await stream4.next();
43514
- }
43515
- result = iterResult.value;
43516
- chunkRenderer.finalize();
43517
- if (result.insights.length > 0) {
43518
- console.log("");
43519
- for (const insight of result.insights) {
43520
- const tag = formatInsightTag(insight.type);
43521
- console.log(` ${tag} ${c.bold(insight.title)}`);
43522
- console.log(` ${c.dim(insight.content)}
43684
+ default:
43685
+ await listArtifactsCommand(projectPath);
43686
+ break;
43687
+ }
43688
+ }
43689
+ async function listArtifactsCommand(projectPath) {
43690
+ const artifacts = listArtifacts(projectPath);
43691
+ if (artifacts.length === 0) {
43692
+ console.log(`
43693
+ ${c.dim("No artifacts found.")}
43523
43694
  `);
43524
- }
43695
+ return;
43696
+ }
43697
+ console.log(`
43698
+ ${c.primary("Artifacts")} ${c.dim(`(${artifacts.length} total)`)}
43699
+ `);
43700
+ for (let i = 0;i < artifacts.length; i++) {
43701
+ const artifact = artifacts[i];
43702
+ const index = c.dim(`${String(i + 1).padStart(2)}.`);
43703
+ const date5 = formatDate(artifact.createdAt);
43704
+ const size = formatSize(artifact.size);
43705
+ console.log(` ${index} ${c.cyan(artifact.name)}`);
43706
+ console.log(` ${c.dim(`${date5} • ${size}`)}`);
43707
+ }
43708
+ console.log(`
43709
+ ${c.dim("Use")} ${c.primary("locus artifacts show <name>")} ${c.dim("to view content")}`);
43710
+ console.log(` ${c.dim("Use")} ${c.primary("locus artifacts plan <name>")} ${c.dim("to convert to a plan")}
43711
+ `);
43712
+ }
43713
+ async function showArtifact(projectPath, name) {
43714
+ const result = readArtifact(projectPath, name);
43715
+ if (!result) {
43716
+ const artifacts = listArtifacts(projectPath);
43717
+ const matches = artifacts.filter((a) => a.name.toLowerCase().includes(name.toLowerCase()));
43718
+ if (matches.length === 1) {
43719
+ const match = readArtifact(projectPath, matches[0].name);
43720
+ if (match) {
43721
+ printArtifact(match.info, match.content);
43722
+ return;
43525
43723
  }
43526
- } catch (error48) {
43527
- chunkRenderer.finalize();
43724
+ }
43725
+ if (matches.length > 1) {
43528
43726
  console.error(`
43529
- ${c.error("")} ${c.red(error48 instanceof Error ? error48.message : String(error48))}
43727
+ ${c.error("Error:")} Multiple artifacts match "${name}":
43530
43728
  `);
43729
+ for (const m of matches) {
43730
+ console.log(` ${c.cyan(m.name)}`);
43731
+ }
43732
+ console.log();
43733
+ return;
43531
43734
  }
43532
- isProcessing = false;
43533
- rl.prompt();
43534
- });
43535
- }
43536
- function listDiscussions(discussionManager) {
43537
- const discussions = discussionManager.list();
43538
- if (discussions.length === 0) {
43539
- console.log(`
43540
- ${c.dim("No discussions found.")}
43735
+ console.error(`
43736
+ ${c.error("Error:")} Artifact "${name}" not found
43541
43737
  `);
43542
- console.log(` ${c.dim("Start one with:")} ${c.cyan('locus discuss "your topic"')}
43738
+ console.log(` ${c.dim("Use 'locus artifacts' to see available artifacts")}
43543
43739
  `);
43544
43740
  return;
43545
43741
  }
43742
+ printArtifact(result.info, result.content);
43743
+ }
43744
+ function printArtifact(info, content) {
43745
+ const date5 = formatDate(info.createdAt);
43746
+ const size = formatSize(info.size);
43546
43747
  console.log(`
43547
- ${c.header(" DISCUSSIONS ")} ${c.dim(`(${discussions.length})`)}
43748
+ ${c.primary(info.name)}`);
43749
+ console.log(` ${c.dim(`${date5} • ${size}`)}`);
43750
+ console.log(c.dim(" ─".repeat(30)));
43751
+ console.log();
43752
+ console.log(content);
43753
+ }
43754
+ async function convertToPlan(projectPath, name) {
43755
+ const result = readArtifact(projectPath, name);
43756
+ if (!result) {
43757
+ const artifacts = listArtifacts(projectPath);
43758
+ const matches = artifacts.filter((a) => a.name.toLowerCase().includes(name.toLowerCase()));
43759
+ if (matches.length === 1) {
43760
+ const match = readArtifact(projectPath, matches[0].name);
43761
+ if (match) {
43762
+ await runPlanConversion(match.info.name);
43763
+ return;
43764
+ }
43765
+ }
43766
+ console.error(`
43767
+ ${c.error("Error:")} Artifact "${name}" not found
43548
43768
  `);
43549
- for (const disc of discussions) {
43550
- const statusIcon = disc.status === "active" ? c.warning("◯") : disc.status === "completed" ? c.success("✔") : c.dim("⊘");
43551
- console.log(` ${statusIcon} ${c.bold(disc.title)} ${c.dim(`[${disc.status}]`)} ${c.dim(`— ${disc.messages.length} messages, ${disc.insights.length} insights`)}`);
43552
- console.log(` ${c.dim("ID:")} ${disc.id}`);
43553
- console.log(` ${c.dim("Created:")} ${disc.createdAt}`);
43554
- console.log("");
43769
+ console.log(` ${c.dim("Use 'locus artifacts' to see available artifacts")}
43770
+ `);
43771
+ return;
43555
43772
  }
43773
+ await runPlanConversion(result.info.name);
43556
43774
  }
43557
- function showDiscussion(discussionManager, id) {
43558
- const md = discussionManager.getMarkdown(id);
43559
- if (!md) {
43560
- console.error(`
43561
- ${c.error("✖")} ${c.red(`Discussion not found: ${id}`)}
43775
+ async function runPlanConversion(artifactName) {
43776
+ const { execCommand: execCommand2 } = await Promise.resolve().then(() => (init_exec2(), exports_exec));
43777
+ console.log(`
43778
+ ${c.primary("Converting artifact to plan:")} ${c.cyan(artifactName)}
43562
43779
  `);
43563
- process.exit(1);
43780
+ const prompt = `Create a plan according to ${artifactName}`;
43781
+ await execCommand2([prompt]);
43782
+ }
43783
+ // src/commands/config.ts
43784
+ init_index_node();
43785
+ init_settings_manager();
43786
+ import { createInterface as createInterface2 } from "node:readline";
43787
+ function ask(question) {
43788
+ const rl = createInterface2({
43789
+ input: process.stdin,
43790
+ output: process.stdout
43791
+ });
43792
+ return new Promise((resolve2) => {
43793
+ rl.question(question, (answer) => {
43794
+ rl.close();
43795
+ resolve2(answer.trim());
43796
+ });
43797
+ });
43798
+ }
43799
+ var TOP_LEVEL_KEYS = [
43800
+ "apiKey",
43801
+ "apiUrl",
43802
+ "provider",
43803
+ "model",
43804
+ "workspaceId"
43805
+ ];
43806
+ var TELEGRAM_KEYS = [
43807
+ "telegram.botToken",
43808
+ "telegram.chatId",
43809
+ "telegram.testMode"
43810
+ ];
43811
+ var ALL_KEYS = [...TOP_LEVEL_KEYS, ...TELEGRAM_KEYS];
43812
+ function maskSecret(value) {
43813
+ if (value.length <= 8)
43814
+ return "****";
43815
+ return `${value.slice(0, 4)}...${value.slice(-4)}`;
43816
+ }
43817
+ function showConfigHelp() {
43818
+ console.log(`
43819
+ ${c.header(" CONFIG ")}
43820
+ ${c.primary("locus config")} ${c.dim("<subcommand> [options]")}
43821
+
43822
+ ${c.header(" SUBCOMMANDS ")}
43823
+ ${c.success("setup")} Interactive configuration (or pass flags below)
43824
+ ${c.dim("--api-key <KEY> Locus API key (required)")}
43825
+ ${c.dim("--api-url <URL> API base URL (optional)")}
43826
+ ${c.dim("--provider <P> AI provider (optional)")}
43827
+ ${c.dim("--model <M> AI model (optional)")}
43828
+ ${c.success("show")} Show current settings
43829
+ ${c.success("set")} Set a config value
43830
+ ${c.dim("locus config set <key> <value>")}
43831
+ ${c.dim(`Keys: ${ALL_KEYS.join(", ")}`)}
43832
+ ${c.success("remove")} Remove all settings
43833
+
43834
+ ${c.header(" EXAMPLES ")}
43835
+ ${c.dim("$")} ${c.primary("locus config setup")}
43836
+ ${c.dim("$")} ${c.primary("locus config setup --api-key sk-xxx")}
43837
+ ${c.dim("$")} ${c.primary("locus config show")}
43838
+ ${c.dim("$")} ${c.primary("locus config set apiKey sk-new-key")}
43839
+ ${c.dim("$")} ${c.primary("locus config set provider codex")}
43840
+ ${c.dim("$")} ${c.primary("locus config set telegram.botToken 123:ABC")}
43841
+ ${c.dim("$")} ${c.primary("locus config remove")}
43842
+ `);
43843
+ }
43844
+ async function setupCommand(args, projectPath) {
43845
+ let apiKey;
43846
+ let apiUrl;
43847
+ let provider;
43848
+ let model;
43849
+ for (let i = 0;i < args.length; i++) {
43850
+ if (args[i] === "--api-key" && args[i + 1]) {
43851
+ apiKey = args[++i]?.trim();
43852
+ } else if (args[i] === "--api-url" && args[i + 1]) {
43853
+ apiUrl = args[++i]?.trim();
43854
+ } else if (args[i] === "--provider" && args[i + 1]) {
43855
+ provider = args[++i]?.trim();
43856
+ } else if (args[i] === "--model" && args[i + 1]) {
43857
+ model = args[++i]?.trim();
43858
+ }
43564
43859
  }
43565
- console.log(`
43566
- ${md}
43567
- `);
43568
- }
43569
- function archiveDiscussion(discussionManager, id) {
43570
- try {
43571
- discussionManager.archive(id);
43860
+ if (!apiKey && !apiUrl && !provider && !model) {
43572
43861
  console.log(`
43573
- ${c.success("✔")} ${c.dim("Discussion archived.")}
43862
+ ${c.header(" LOCUS SETUP ")}
43574
43863
  `);
43575
- } catch (error48) {
43576
- console.error(`
43577
- ${c.error("✖")} ${c.red(error48 instanceof Error ? error48.message : String(error48))}
43864
+ console.log(` ${c.dim("Configure your Locus settings. Press Enter to skip optional fields.")}
43578
43865
  `);
43579
- process.exit(1);
43866
+ while (!apiKey) {
43867
+ apiKey = await ask(` ${c.primary("API Key")} ${c.dim("(required)")}: `);
43868
+ if (!apiKey) {
43869
+ console.log(` ${c.error("✖")} API key is required. Get one from ${c.underline("Workspace Settings > API Keys")}`);
43870
+ }
43871
+ }
43872
+ provider = await ask(` ${c.primary("Provider")} ${c.dim("(optional, e.g. claude, codex)")}: `);
43873
+ model = await ask(` ${c.primary("Model")} ${c.dim("(optional, e.g. opus, sonnet)")}: `);
43874
+ if (!provider)
43875
+ provider = undefined;
43876
+ if (!model)
43877
+ model = undefined;
43580
43878
  }
43581
- }
43582
- function deleteDiscussion(discussionManager, id) {
43583
- try {
43584
- discussionManager.delete(id);
43585
- console.log(`
43586
- ${c.success("✔")} ${c.dim("Discussion deleted.")}
43587
- `);
43588
- } catch (error48) {
43879
+ if (!apiKey) {
43589
43880
  console.error(`
43590
- ${c.error("✖")} ${c.red(error48 instanceof Error ? error48.message : String(error48))}
43881
+ ${c.error("✖")} ${c.bold("Missing --api-key flag.")}
43882
+ ` + ` Get an API key from ${c.underline("Workspace Settings > API Keys")}
43591
43883
  `);
43592
43884
  process.exit(1);
43593
43885
  }
43594
- }
43595
- function showCurrentInsights(discussionManager, discussionId) {
43596
- const discussion2 = discussionManager.load(discussionId);
43597
- if (!discussion2 || discussion2.insights.length === 0) {
43598
- console.log(`
43599
- ${c.dim("No insights extracted yet.")}
43600
- `);
43601
- return;
43602
- }
43603
- console.log(`
43604
- ${c.header(" INSIGHTS ")} ${c.dim(`(${discussion2.insights.length})`)}
43605
- `);
43606
- for (const insight of discussion2.insights) {
43607
- const tag = formatInsightTag(insight.type);
43608
- console.log(` ${tag} ${c.bold(insight.title)}`);
43609
- console.log(` ${c.dim(insight.content)}`);
43610
- if (insight.tags.length > 0) {
43611
- console.log(` ${c.dim(`Tags: ${insight.tags.join(", ")}`)}`);
43612
- }
43613
- console.log("");
43614
- }
43615
- }
43616
- function formatInsightTag(type) {
43617
- switch (type) {
43618
- case "decision":
43619
- return c.green("[DECISION]");
43620
- case "requirement":
43621
- return c.blue("[REQUIREMENT]");
43622
- case "idea":
43623
- return c.yellow("[IDEA]");
43624
- case "concern":
43625
- return c.red("[CONCERN]");
43626
- case "learning":
43627
- return c.cyan("[LEARNING]");
43628
- }
43629
- }
43630
- function showReplHelp() {
43631
- console.log(`
43632
- ${c.header(" DISCUSSION COMMANDS ")}
43633
-
43634
- ${c.cyan("summary")} Generate a final summary and end the discussion
43635
- ${c.cyan("insights")} Show all insights extracted so far
43636
- ${c.cyan("exit")} Save and exit without generating a summary
43637
- ${c.cyan("help")} Show this help message
43638
-
43639
- ${c.dim("Type anything else to continue the discussion.")}
43640
- `);
43641
- }
43642
- function showDiscussHelp() {
43886
+ const manager = new SettingsManager(projectPath);
43887
+ const existing = manager.load();
43888
+ const settings = {
43889
+ ...existing,
43890
+ apiKey
43891
+ };
43892
+ if (apiUrl)
43893
+ settings.apiUrl = apiUrl;
43894
+ if (provider)
43895
+ settings.provider = provider;
43896
+ if (model)
43897
+ settings.model = model;
43898
+ manager.save(settings);
43643
43899
  console.log(`
43644
- ${c.header(" LOCUS DISCUSS ")} ${c.dim(" Interactive AI Discussion")}
43645
-
43646
- ${c.bold("Usage:")}
43647
- ${c.cyan('locus discuss "topic"')} Start a discussion on a topic
43648
- ${c.cyan("locus discuss --list")} List all discussions
43649
- ${c.cyan("locus discuss --show <id>")} Show discussion details
43650
- ${c.cyan("locus discuss --archive <id>")} Archive a discussion
43651
- ${c.cyan("locus discuss --delete <id>")} Delete a discussion
43652
-
43653
- ${c.bold("Options:")}
43654
- ${c.dim("--model <model>")} AI model (claude: opus, sonnet, haiku | codex: gpt-5.3-codex, gpt-5-codex-mini)
43655
- ${c.dim("--provider <p>")} AI provider (claude, codex)
43656
- ${c.dim("--reasoning-effort <level>")} Reasoning effort (low, medium, high)
43657
- ${c.dim("--dir <path>")} Project directory
43658
-
43659
- ${c.bold("REPL Commands:")}
43660
- ${c.dim("summary")} Generate final summary and end the discussion
43661
- ${c.dim("insights")} Show all insights extracted so far
43662
- ${c.dim("exit")} Save and exit without generating a summary
43663
- ${c.dim("help")} Show available commands
43664
-
43665
- ${c.bold("Examples:")}
43666
- ${c.dim("# Start a discussion about architecture")}
43667
- ${c.cyan('locus discuss "how should we structure the auth system?"')}
43668
-
43669
- ${c.dim("# Review a past discussion")}
43670
- ${c.cyan("locus discuss --show disc-1234567890")}
43671
-
43672
- ${c.dim("# List all discussions")}
43673
- ${c.cyan("locus discuss --list")}
43674
- `);
43675
- }
43676
- // src/commands/docs.ts
43677
- init_index_node();
43678
- import { parseArgs as parseArgs2 } from "node:util";
43679
-
43680
- // src/config-manager.ts
43681
- init_index_node();
43682
- import { execSync as execSync2 } from "node:child_process";
43683
- import { existsSync as existsSync15, mkdirSync as mkdirSync7, readFileSync as readFileSync13, writeFileSync as writeFileSync7 } from "node:fs";
43684
- import { join as join15 } from "node:path";
43685
- var LOCUS_GITIGNORE_MARKER = "# Locus AI";
43686
- var LOCUS_MD_TEMPLATE = `## Planning First
43687
-
43688
- Complex tasks must be planned before writing code. Create ".locus/plans/<task-name>.md" with:
43689
- - **Goal**: What we're trying to achieve and why
43690
- - **Approach**: Step-by-step strategy with technical decisions
43691
- - **Affected files**: List of files to create/modify/delete
43692
- - **Acceptance criteria**: Specific, testable conditions for completion
43693
- - **Dependencies**: Required packages, APIs, or external services
43694
-
43695
- Delete the planning .md files after successful execution.
43696
-
43697
- ## Code Quality
43698
-
43699
- - **Follow existing patterns**: Run formatters and linters before finishing (check "package.json" scripts or project config)
43700
- - **Minimize changes**: Keep modifications atomic. Separate refactors from behavioral changes into different tasks
43701
- - **Never commit secrets**: No API keys, passwords, or credentials in code. Use environment variables or secret management
43702
- - **Test as you go**: If tests exist, run relevant ones. If breaking changes occur, update tests accordingly
43703
- - **Comment complex logic**: Explain *why*, not *what*. Focus on business logic and non-obvious decisions
43704
-
43705
- ## Artifacts
43706
-
43707
- When a task produces knowledge, analysis, or research output rather than (or in addition to) code changes, you **must** save results as Markdown in ".locus/artifacts/<descriptive-name>.md":
43708
-
43709
- **Always create artifacts for:**
43710
- - Code quality audits, security reviews, vulnerability assessments
43711
- - Architecture analyses, system design proposals, or recommendations
43712
- - Dependency reports, performance profiling, benchmarking results
43713
- - Research summaries, technology comparisons, or feasibility studies
43714
- - Migration plans, deployment strategies, or rollback procedures
43715
- - Post-mortems, incident analysis, or debugging investigations
43716
-
43717
- **Artifact structure:**
43718
- - Clear title and date
43719
- - Executive summary (2-3 sentences)
43720
- - Detailed findings/analysis
43721
- - Actionable recommendations (if applicable)
43722
-
43723
- ## Git Operations
43724
-
43725
- - **Do NOT run**: git add, git commit, git push, git checkout, git branch, or any git commands
43726
- - **Why**: The Locus orchestrator handles all version control automatically after execution
43727
- - **Your role**: Focus solely on making file changes. The system commits, pushes, and creates PRs
43728
-
43729
- ## Continuous Learning
43730
-
43731
- Read ".locus/LEARNINGS.md" **before starting any task** to avoid repeating mistakes.
43732
-
43733
- **When to update:**
43734
- - User corrects your approach or provides guidance
43735
- - You discover a better pattern while working
43736
- - A decision prevents future confusion (e.g., "use X not Y because Z")
43737
- - You encounter and solve a tricky problem
43738
-
43739
- **What to record:**
43740
- - Architectural decisions and their rationale
43741
- - Preferred libraries, tools, or patterns for this project
43742
- - Common pitfalls and how to avoid them
43743
- - Project-specific conventions or user preferences
43744
- - Solutions to non-obvious problems
43745
-
43746
- **Format (append-only, never delete):**
43747
-
43748
- """
43749
- - **[Category]**: Concise description (1-2 lines max). *Context if needed.*
43750
- """
43751
-
43752
- **Categories:** Architecture, Dependencies, Patterns, Debugging, Performance, Security, DevOps, User Preferences
43753
-
43754
- ## Error Handling
43755
-
43756
- - **Read error messages carefully**: Don't guess. Parse the actual error before proposing fixes
43757
- - **Check dependencies first**: Missing packages, wrong versions, and environment issues are common
43758
- - **Verify assumptions**: If something "should work," confirm it actually does in this environment
43759
- - **Ask for context**: If you need environment details, configuration, or logs, request them explicitly
43760
-
43761
- ## Communication
43900
+ ${c.success("")} ${c.bold("Settings configured successfully!")}
43762
43901
 
43763
- - **Be precise**: When uncertain, state what you know and what you're assuming
43764
- - **Show your work**: For complex changes, briefly explain the approach before executing
43765
- - **Highlight trade-offs**: If multiple approaches exist, note why you chose one over others
43766
- - **Request feedback**: For ambiguous requirements, propose an approach and ask for confirmation
43767
- `;
43768
- var DEFAULT_LEARNINGS_MD = `# Learnings
43902
+ ${c.bold("Saved to:")} ${c.dim(".locus/settings.json")}
43769
43903
 
43770
- This file captures important lessons, decisions, and corrections made during development.
43771
- It is read by AI agents before every task to avoid repeating mistakes and to follow established patterns.
43904
+ ${c.bold("Configuration:")}
43905
+ ${c.primary("API Key:")} ${maskSecret(apiKey)}${apiUrl ? `
43906
+ ${c.primary("API URL:")} ${apiUrl}` : ""}${provider ? `
43907
+ ${c.primary("Provider:")} ${provider}` : ""}${model ? `
43908
+ ${c.primary("Model:")} ${model}` : ""}
43772
43909
 
43773
- <!-- Add learnings below this line. Format: - **[Category]**: Description -->
43774
- `;
43775
- function updateGitignore(projectPath) {
43776
- const gitignorePath = join15(projectPath, ".gitignore");
43777
- let content = "";
43778
- const locusBlock = LOCUS_GITIGNORE_PATTERNS.join(`
43779
- `);
43780
- if (existsSync15(gitignorePath)) {
43781
- content = readFileSync13(gitignorePath, "utf-8");
43782
- if (content.includes(LOCUS_GITIGNORE_MARKER)) {
43783
- const lines = content.split(`
43784
- `);
43785
- const startIdx = lines.findIndex((l) => l.includes(LOCUS_GITIGNORE_MARKER));
43786
- let endIdx = startIdx;
43787
- for (let i = startIdx;i < lines.length; i++) {
43788
- if (lines[i].startsWith(LOCUS_GITIGNORE_MARKER) || lines[i].startsWith(".locus") || lines[i].trim() === "") {
43789
- endIdx = i;
43790
- } else {
43791
- break;
43792
- }
43793
- }
43794
- const before = lines.slice(0, startIdx);
43795
- const after = lines.slice(endIdx + 1);
43796
- content = [...before, locusBlock, ...after].join(`
43910
+ ${c.bold("Next steps:")}
43911
+ Run agents: ${c.primary("locus run")}
43912
+ Setup Telegram: ${c.primary("locus telegram setup")}
43797
43913
  `);
43798
- writeFileSync7(gitignorePath, content);
43799
- return;
43800
- }
43801
- if (content.length > 0 && !content.endsWith(`
43802
- `)) {
43803
- content += `
43804
- `;
43805
- }
43806
- if (content.trim().length > 0) {
43807
- content += `
43808
- `;
43809
- }
43810
- }
43811
- content += `${locusBlock}
43812
- `;
43813
- writeFileSync7(gitignorePath, content);
43814
43914
  }
43815
- function ensureGitIdentity(projectPath) {
43816
- const hasName = (() => {
43817
- try {
43818
- return execSync2("git config --get user.name", {
43819
- cwd: projectPath,
43820
- stdio: ["pipe", "pipe", "pipe"]
43821
- }).toString().trim();
43822
- } catch {
43823
- return "";
43824
- }
43825
- })();
43826
- const hasEmail = (() => {
43827
- try {
43828
- return execSync2("git config --get user.email", {
43829
- cwd: projectPath,
43830
- stdio: ["pipe", "pipe", "pipe"]
43831
- }).toString().trim();
43832
- } catch {
43833
- return "";
43834
- }
43835
- })();
43836
- if (!hasName) {
43837
- execSync2('git config user.name "LocusAgent"', {
43838
- cwd: projectPath,
43839
- stdio: "ignore"
43840
- });
43915
+ function showCommand(projectPath) {
43916
+ const manager = new SettingsManager(projectPath);
43917
+ const settings = manager.load();
43918
+ if (Object.keys(settings).length === 0) {
43919
+ console.log(`
43920
+ ${c.dim("No settings found.")}
43921
+ ` + ` Run ${c.primary("locus config setup")} to configure.
43922
+ `);
43923
+ return;
43841
43924
  }
43842
- if (!hasEmail) {
43843
- execSync2('git config user.email "agent@locusai.team"', {
43844
- cwd: projectPath,
43845
- stdio: "ignore"
43846
- });
43925
+ console.log(`
43926
+ ${c.header(" SETTINGS ")}`);
43927
+ console.log(` ${c.dim("File: .locus/settings.json")}
43928
+ `);
43929
+ if (settings.apiKey) {
43930
+ console.log(` ${c.primary("apiKey:")} ${maskSecret(settings.apiKey)}`);
43847
43931
  }
43848
- execSync2("git config --global pull.rebase true", {
43849
- cwd: projectPath,
43850
- stdio: "ignore"
43851
- });
43852
- }
43853
-
43854
- class ConfigManager {
43855
- projectPath;
43856
- constructor(projectPath) {
43857
- this.projectPath = projectPath;
43932
+ if (settings.apiUrl) {
43933
+ console.log(` ${c.primary("apiUrl:")} ${settings.apiUrl}`);
43858
43934
  }
43859
- async init(version2) {
43860
- const locusConfigDir = join15(this.projectPath, LOCUS_CONFIG.dir);
43861
- const locusConfigPath = getLocusPath(this.projectPath, "configFile");
43862
- if (!existsSync15(locusConfigDir)) {
43863
- mkdirSync7(locusConfigDir, { recursive: true });
43864
- }
43865
- const locusSubdirs = [
43866
- LOCUS_CONFIG.artifactsDir,
43867
- LOCUS_CONFIG.documentsDir,
43868
- LOCUS_CONFIG.sessionsDir,
43869
- LOCUS_CONFIG.reviewsDir,
43870
- LOCUS_CONFIG.plansDir,
43871
- LOCUS_CONFIG.discussionsDir
43872
- ];
43873
- for (const subdir of locusSubdirs) {
43874
- const subdirPath = join15(locusConfigDir, subdir);
43875
- if (!existsSync15(subdirPath)) {
43876
- mkdirSync7(subdirPath, { recursive: true });
43877
- }
43878
- }
43879
- const locusMdPath = getLocusPath(this.projectPath, "contextFile");
43880
- if (!existsSync15(locusMdPath)) {
43881
- writeFileSync7(locusMdPath, LOCUS_MD_TEMPLATE);
43882
- }
43883
- const learningsMdPath = getLocusPath(this.projectPath, "learningsFile");
43884
- if (!existsSync15(learningsMdPath)) {
43885
- writeFileSync7(learningsMdPath, DEFAULT_LEARNINGS_MD);
43886
- }
43887
- if (!existsSync15(locusConfigPath)) {
43888
- const config2 = {
43889
- $schema: LOCUS_SCHEMAS.config,
43890
- version: version2,
43891
- createdAt: new Date().toISOString(),
43892
- projectPath: "."
43893
- };
43894
- writeFileSync7(locusConfigPath, JSON.stringify(config2, null, 2));
43895
- }
43896
- updateGitignore(this.projectPath);
43897
- ensureGitIdentity(this.projectPath);
43935
+ if (settings.provider) {
43936
+ console.log(` ${c.primary("provider:")} ${settings.provider}`);
43898
43937
  }
43899
- loadConfig() {
43900
- const path3 = getLocusPath(this.projectPath, "configFile");
43901
- if (existsSync15(path3)) {
43902
- return JSON.parse(readFileSync13(path3, "utf-8"));
43903
- }
43904
- return null;
43938
+ if (settings.model) {
43939
+ console.log(` ${c.primary("model:")} ${settings.model}`);
43905
43940
  }
43906
- updateVersion(version2) {
43907
- const config2 = this.loadConfig();
43908
- if (config2 && config2.version !== version2) {
43909
- config2.version = version2;
43910
- this.saveConfig(config2);
43911
- }
43941
+ if (settings.workspaceId) {
43942
+ console.log(` ${c.primary("workspaceId:")} ${settings.workspaceId}`);
43912
43943
  }
43913
- async reinit(version2) {
43914
- const result = {
43915
- versionUpdated: false,
43916
- previousVersion: null,
43917
- directoriesCreated: [],
43918
- gitignoreUpdated: false
43919
- };
43920
- const config2 = this.loadConfig();
43921
- if (config2) {
43922
- result.previousVersion = config2.version;
43923
- const needsSchemaUpdate = config2.$schema !== LOCUS_SCHEMAS.config;
43924
- if (config2.version !== version2) {
43925
- config2.version = version2;
43926
- result.versionUpdated = true;
43927
- }
43928
- if (result.versionUpdated || needsSchemaUpdate) {
43929
- this.saveConfig(config2);
43930
- }
43931
- }
43932
- const settingsPath = join15(this.projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.settingsFile);
43933
- if (existsSync15(settingsPath)) {
43934
- const raw = readFileSync13(settingsPath, "utf-8");
43935
- const settings = JSON.parse(raw);
43936
- if (settings.$schema !== LOCUS_SCHEMAS.settings) {
43937
- const { $schema: _2, ...rest } = settings;
43938
- const ordered = { $schema: LOCUS_SCHEMAS.settings, ...rest };
43939
- writeFileSync7(settingsPath, JSON.stringify(ordered, null, 2), "utf-8");
43940
- }
43941
- }
43942
- const locusMdPath = getLocusPath(this.projectPath, "contextFile");
43943
- const locusMdExisted = existsSync15(locusMdPath);
43944
- writeFileSync7(locusMdPath, LOCUS_MD_TEMPLATE);
43945
- if (!locusMdExisted) {
43946
- result.directoriesCreated.push(".locus/LOCUS.md");
43947
- }
43948
- const locusSubdirs = [
43949
- LOCUS_CONFIG.artifactsDir,
43950
- LOCUS_CONFIG.documentsDir,
43951
- LOCUS_CONFIG.sessionsDir,
43952
- LOCUS_CONFIG.reviewsDir,
43953
- LOCUS_CONFIG.plansDir,
43954
- LOCUS_CONFIG.discussionsDir
43955
- ];
43956
- const locusConfigDir = join15(this.projectPath, LOCUS_CONFIG.dir);
43957
- for (const subdir of locusSubdirs) {
43958
- const subdirPath = join15(locusConfigDir, subdir);
43959
- if (!existsSync15(subdirPath)) {
43960
- mkdirSync7(subdirPath, { recursive: true });
43961
- result.directoriesCreated.push(`.locus/${subdir}`);
43962
- }
43944
+ if (settings.telegram) {
43945
+ const tg = settings.telegram;
43946
+ console.log(`
43947
+ ${c.header(" TELEGRAM ")}`);
43948
+ if (tg.botToken) {
43949
+ console.log(` ${c.primary("botToken:")} ${maskSecret(tg.botToken)}`);
43963
43950
  }
43964
- const learningsMdPath = getLocusPath(this.projectPath, "learningsFile");
43965
- if (!existsSync15(learningsMdPath)) {
43966
- writeFileSync7(learningsMdPath, DEFAULT_LEARNINGS_MD);
43967
- result.directoriesCreated.push(".locus/LEARNINGS.md");
43951
+ if (tg.chatId) {
43952
+ console.log(` ${c.primary("chatId:")} ${tg.chatId}`);
43968
43953
  }
43969
- const gitignorePath = join15(this.projectPath, ".gitignore");
43970
- const gitignoreBefore = existsSync15(gitignorePath) ? readFileSync13(gitignorePath, "utf-8") : "";
43971
- updateGitignore(this.projectPath);
43972
- const gitignoreAfter = readFileSync13(gitignorePath, "utf-8");
43973
- if (gitignoreBefore !== gitignoreAfter) {
43974
- result.gitignoreUpdated = true;
43954
+ if (tg.testMode !== undefined) {
43955
+ console.log(` ${c.primary("testMode:")} ${tg.testMode}`);
43975
43956
  }
43976
- ensureGitIdentity(this.projectPath);
43977
- return result;
43978
- }
43979
- saveConfig(config2) {
43980
- const { $schema: _2, ...rest } = config2;
43981
- const ordered = { $schema: LOCUS_SCHEMAS.config, ...rest };
43982
- const path3 = getLocusPath(this.projectPath, "configFile");
43983
- writeFileSync7(path3, JSON.stringify(ordered, null, 2));
43984
43957
  }
43958
+ console.log("");
43985
43959
  }
43986
-
43987
- // src/workspace-resolver.ts
43988
- init_index_node();
43989
-
43990
- class WorkspaceResolver {
43991
- options;
43992
- constructor(options) {
43993
- this.options = options;
43960
+ function setCommand(args, projectPath) {
43961
+ const key = args[0]?.trim();
43962
+ const value = args.slice(1).join(" ").trim();
43963
+ if (!key || !value) {
43964
+ console.error(`
43965
+ ${c.error("✖")} ${c.bold("Usage:")} locus config set <key> <value>
43966
+ ` + ` ${c.dim(`Available keys: ${ALL_KEYS.join(", ")}`)}
43967
+ `);
43968
+ process.exit(1);
43994
43969
  }
43995
- async resolve() {
43996
- if (this.options.workspaceId) {
43997
- return this.options.workspaceId;
43970
+ if (!ALL_KEYS.includes(key)) {
43971
+ console.error(`
43972
+ ${c.error("✖")} ${c.bold(`Unknown key: ${key}`)}
43973
+ ` + ` ${c.dim(`Available keys: ${ALL_KEYS.join(", ")}`)}
43974
+ `);
43975
+ process.exit(1);
43976
+ }
43977
+ const manager = new SettingsManager(projectPath);
43978
+ const settings = manager.load();
43979
+ if (key.startsWith("telegram.")) {
43980
+ const telegramKey = key.replace("telegram.", "");
43981
+ if (!settings.telegram) {
43982
+ settings.telegram = {};
43998
43983
  }
43999
- try {
44000
- console.log(c.dim("ℹ Resolving workspace from API key..."));
44001
- const client = new LocusClient({
44002
- baseUrl: this.options.apiBase,
44003
- token: this.options.apiKey
44004
- });
44005
- const info = await client.auth.getApiKeyInfo();
44006
- if (info.workspaceId) {
44007
- console.log(c.success(`✓ Resolved workspace: ${info.workspaceId}`));
44008
- return info.workspaceId;
43984
+ if (telegramKey === "chatId") {
43985
+ const num = Number(value);
43986
+ if (Number.isNaN(num)) {
43987
+ console.error(`
43988
+ ${c.error("✖")} ${c.bold(`${key} must be a number.`)}
43989
+ `);
43990
+ process.exit(1);
44009
43991
  }
44010
- throw new Error("API key is not associated with a workspace. Please specify --workspace.");
44011
- } catch (error48) {
44012
- throw new Error(`Error resolving workspace: ${error48 instanceof Error ? error48.message : String(error48)}`);
43992
+ settings.telegram[telegramKey] = num;
43993
+ } else if (telegramKey === "testMode") {
43994
+ settings.telegram[telegramKey] = value === "true" || value === "1";
43995
+ } else {
43996
+ settings.telegram[telegramKey] = value;
44013
43997
  }
43998
+ } else {
43999
+ settings[key] = value;
44000
+ }
44001
+ manager.save(settings);
44002
+ const displayValue = key === "apiKey" || key === "telegram.botToken" ? maskSecret(value) : value;
44003
+ console.log(`
44004
+ ${c.success("✔")} Set ${c.primary(key)} = ${displayValue}
44005
+ `);
44006
+ }
44007
+ function removeCommand(projectPath) {
44008
+ const manager = new SettingsManager(projectPath);
44009
+ if (!manager.exists()) {
44010
+ console.log(`
44011
+ ${c.dim("No settings found. Nothing to remove.")}
44012
+ `);
44013
+ return;
44014
44014
  }
44015
+ manager.remove();
44016
+ console.log(`
44017
+ ${c.success("✔")} ${c.bold("Settings removed.")}
44018
+ `);
44015
44019
  }
44016
-
44017
- // src/commands/docs.ts
44018
- async function docsCommand(args) {
44020
+ async function configCommand(args) {
44021
+ const projectPath = process.cwd();
44019
44022
  const subcommand = args[0];
44020
44023
  const subArgs = args.slice(1);
44021
44024
  switch (subcommand) {
44022
- case "sync":
44023
- await docsSyncCommand(subArgs);
44025
+ case "setup":
44026
+ await setupCommand(subArgs, projectPath);
44024
44027
  break;
44025
- default:
44026
- showDocsHelp();
44028
+ case "show":
44029
+ showCommand(projectPath);
44030
+ break;
44031
+ case "set":
44032
+ setCommand(subArgs, projectPath);
44033
+ break;
44034
+ case "remove":
44035
+ removeCommand(projectPath);
44027
44036
  break;
44037
+ default:
44038
+ showConfigHelp();
44028
44039
  }
44029
44040
  }
44030
- async function docsSyncCommand(args) {
44031
- const { values } = parseArgs2({
44041
+ // src/commands/discuss.ts
44042
+ init_index_node();
44043
+ init_progress_renderer();
44044
+ init_settings_manager();
44045
+ init_utils3();
44046
+ import * as readline2 from "node:readline";
44047
+ import { parseArgs as parseArgs3 } from "node:util";
44048
+ async function discussCommand(args) {
44049
+ const { values, positionals } = parseArgs3({
44032
44050
  args,
44033
44051
  options: {
44034
- "api-key": { type: "string" },
44035
- "api-url": { type: "string" },
44036
- workspace: { type: "string" },
44037
- dir: { type: "string" },
44038
- help: { type: "boolean" }
44052
+ list: { type: "boolean" },
44053
+ show: { type: "string" },
44054
+ archive: { type: "string" },
44055
+ delete: { type: "string" },
44056
+ model: { type: "string" },
44057
+ provider: { type: "string" },
44058
+ "reasoning-effort": { type: "string" },
44059
+ dir: { type: "string" }
44039
44060
  },
44040
- strict: false
44061
+ strict: false,
44062
+ allowPositionals: true
44041
44063
  });
44042
- if (values.help) {
44043
- showDocsSyncHelp();
44064
+ const projectPath = values.dir || process.cwd();
44065
+ requireInitialization(projectPath, "discuss");
44066
+ const discussionManager = new DiscussionManager(projectPath);
44067
+ if (values.list) {
44068
+ return listDiscussions(discussionManager);
44069
+ }
44070
+ if (values.show) {
44071
+ return showDiscussion(discussionManager, values.show);
44072
+ }
44073
+ if (values.archive) {
44074
+ return archiveDiscussion(discussionManager, values.archive);
44075
+ }
44076
+ if (values.delete) {
44077
+ return deleteDiscussion(discussionManager, values.delete);
44078
+ }
44079
+ const topic = positionals.join(" ").trim();
44080
+ if (!topic) {
44081
+ showDiscussHelp();
44082
+ return;
44083
+ }
44084
+ const settings = new SettingsManager(projectPath).load();
44085
+ const provider = resolveProvider3(values.provider || settings.provider);
44086
+ const model = values.model || settings.model || DEFAULT_MODEL[provider];
44087
+ const reasoningEffort = values["reasoning-effort"];
44088
+ const aiRunner = createAiRunner(provider, {
44089
+ projectPath,
44090
+ model,
44091
+ reasoningEffort
44092
+ });
44093
+ const log = (message, level) => {
44094
+ const icon = level === "success" ? c.success("✔") : level === "error" ? c.error("✖") : level === "warn" ? c.warning("!") : c.info("●");
44095
+ console.log(` ${icon} ${message}`);
44096
+ };
44097
+ const facilitator = new DiscussionFacilitator({
44098
+ projectPath,
44099
+ aiRunner,
44100
+ discussionManager,
44101
+ log,
44102
+ provider,
44103
+ model
44104
+ });
44105
+ console.log(`
44106
+ ${c.header(" DISCUSSION ")} ${c.bold("Starting interactive discussion...")}
44107
+ `);
44108
+ console.log(` ${c.dim("Topic:")} ${c.bold(topic)}`);
44109
+ console.log(` ${c.dim("Model:")} ${c.dim(`${model} (${provider})`)}
44110
+ `);
44111
+ const renderer = new ProgressRenderer({ animated: true });
44112
+ let discussionId;
44113
+ try {
44114
+ renderer.showThinkingStarted();
44115
+ const result = await facilitator.startDiscussion(topic);
44116
+ renderer.showThinkingStopped();
44117
+ discussionId = result.discussion.id;
44118
+ process.stdout.write(`
44119
+ `);
44120
+ process.stdout.write(result.message);
44121
+ process.stdout.write(`
44122
+
44123
+ `);
44124
+ renderer.finalize();
44125
+ } catch (error48) {
44126
+ renderer.finalize();
44127
+ console.error(`
44128
+ ${c.error("✖")} ${c.red("Failed to start discussion:")} ${error48 instanceof Error ? error48.message : String(error48)}
44129
+ `);
44130
+ process.exit(1);
44131
+ }
44132
+ console.log(` ${c.dim("Type your response, or 'help' for commands. Use 'exit' or Ctrl+C to quit.")}
44133
+ `);
44134
+ const rl = readline2.createInterface({
44135
+ input: process.stdin,
44136
+ output: process.stdout,
44137
+ terminal: true
44138
+ });
44139
+ rl.setPrompt(c.cyan("> "));
44140
+ rl.prompt();
44141
+ let isProcessing = false;
44142
+ const shutdown = () => {
44143
+ if (isProcessing) {
44144
+ aiRunner.abort();
44145
+ }
44146
+ console.log(`
44147
+ ${c.dim("Discussion saved.")} ${c.dim("ID:")} ${c.cyan(discussionId)}`);
44148
+ console.log(c.dim(`
44149
+ Goodbye!
44150
+ `));
44151
+ rl.close();
44152
+ process.exit(0);
44153
+ };
44154
+ process.on("SIGINT", () => {
44155
+ if (isProcessing) {
44156
+ aiRunner.abort();
44157
+ isProcessing = false;
44158
+ console.log(c.dim(`
44159
+ [Interrupted]`));
44160
+ rl.prompt();
44161
+ } else {
44162
+ shutdown();
44163
+ }
44164
+ });
44165
+ rl.on("close", () => {
44166
+ shutdown();
44167
+ });
44168
+ rl.on("line", async (input) => {
44169
+ const trimmed = input.trim();
44170
+ if (trimmed === "" || isProcessing) {
44171
+ rl.prompt();
44172
+ return;
44173
+ }
44174
+ const lowerInput = trimmed.toLowerCase();
44175
+ if (lowerInput === "help") {
44176
+ showReplHelp();
44177
+ rl.prompt();
44178
+ return;
44179
+ }
44180
+ if (lowerInput === "exit" || lowerInput === "quit") {
44181
+ shutdown();
44182
+ return;
44183
+ }
44184
+ if (lowerInput === "insights") {
44185
+ showCurrentInsights(discussionManager, discussionId);
44186
+ rl.prompt();
44187
+ return;
44188
+ }
44189
+ if (lowerInput === "summary") {
44190
+ isProcessing = true;
44191
+ const summaryRenderer = new ProgressRenderer({ animated: true });
44192
+ try {
44193
+ summaryRenderer.showThinkingStarted();
44194
+ const summary = await facilitator.summarizeDiscussion(discussionId);
44195
+ summaryRenderer.showThinkingStopped();
44196
+ process.stdout.write(`
44197
+ `);
44198
+ process.stdout.write(summary);
44199
+ process.stdout.write(`
44200
+ `);
44201
+ summaryRenderer.finalize();
44202
+ const discussion2 = discussionManager.load(discussionId);
44203
+ if (discussion2) {
44204
+ console.log(`
44205
+ ${c.success("✔")} ${c.success("Discussion completed!")}
44206
+ `);
44207
+ console.log(` ${c.dim("Messages:")} ${discussion2.messages.length} ${c.dim("Insights:")} ${discussion2.insights.length}
44208
+ `);
44209
+ }
44210
+ console.log(` ${c.dim("To review:")} ${c.cyan(`locus discuss --show ${discussionId}`)}`);
44211
+ console.log(` ${c.dim("To list all:")} ${c.cyan("locus discuss --list")}
44212
+ `);
44213
+ } catch (error48) {
44214
+ summaryRenderer.finalize();
44215
+ console.error(`
44216
+ ${c.error("✖")} ${c.red("Failed to summarize:")} ${error48 instanceof Error ? error48.message : String(error48)}
44217
+ `);
44218
+ }
44219
+ rl.close();
44220
+ process.exit(0);
44221
+ return;
44222
+ }
44223
+ isProcessing = true;
44224
+ const chunkRenderer = new ProgressRenderer({ animated: true });
44225
+ try {
44226
+ chunkRenderer.showThinkingStarted();
44227
+ const stream4 = facilitator.continueDiscussionStream(discussionId, trimmed);
44228
+ let result = {
44229
+ response: "",
44230
+ insights: []
44231
+ };
44232
+ let iterResult = await stream4.next();
44233
+ while (!iterResult.done) {
44234
+ chunkRenderer.renderChunk(iterResult.value);
44235
+ iterResult = await stream4.next();
44236
+ }
44237
+ result = iterResult.value;
44238
+ chunkRenderer.finalize();
44239
+ if (result.insights.length > 0) {
44240
+ console.log("");
44241
+ for (const insight of result.insights) {
44242
+ const tag = formatInsightTag(insight.type);
44243
+ console.log(` ${tag} ${c.bold(insight.title)}`);
44244
+ console.log(` ${c.dim(insight.content)}
44245
+ `);
44246
+ }
44247
+ }
44248
+ } catch (error48) {
44249
+ chunkRenderer.finalize();
44250
+ console.error(`
44251
+ ${c.error("✖")} ${c.red(error48 instanceof Error ? error48.message : String(error48))}
44252
+ `);
44253
+ }
44254
+ isProcessing = false;
44255
+ rl.prompt();
44256
+ });
44257
+ }
44258
+ function listDiscussions(discussionManager) {
44259
+ const discussions = discussionManager.list();
44260
+ if (discussions.length === 0) {
44261
+ console.log(`
44262
+ ${c.dim("No discussions found.")}
44263
+ `);
44264
+ console.log(` ${c.dim("Start one with:")} ${c.cyan('locus discuss "your topic"')}
44265
+ `);
44044
44266
  return;
44045
44267
  }
44046
- const projectPath = values.dir || process.cwd();
44047
- requireInitialization(projectPath, "docs sync");
44048
- const configManager = new ConfigManager(projectPath);
44049
- configManager.updateVersion(VERSION2);
44050
- const settingsManager = new SettingsManager(projectPath);
44051
- const settings = settingsManager.load();
44052
- const apiKey = values["api-key"] || settings.apiKey;
44053
- if (!apiKey) {
44268
+ console.log(`
44269
+ ${c.header(" DISCUSSIONS ")} ${c.dim(`(${discussions.length})`)}
44270
+ `);
44271
+ for (const disc of discussions) {
44272
+ const statusIcon = disc.status === "active" ? c.warning("◯") : disc.status === "completed" ? c.success("✔") : c.dim("⊘");
44273
+ console.log(` ${statusIcon} ${c.bold(disc.title)} ${c.dim(`[${disc.status}]`)} ${c.dim(`— ${disc.messages.length} messages, ${disc.insights.length} insights`)}`);
44274
+ console.log(` ${c.dim("ID:")} ${disc.id}`);
44275
+ console.log(` ${c.dim("Created:")} ${disc.createdAt}`);
44276
+ console.log("");
44277
+ }
44278
+ }
44279
+ function showDiscussion(discussionManager, id) {
44280
+ const md = discussionManager.getMarkdown(id);
44281
+ if (!md) {
44054
44282
  console.error(`
44055
- ${c.error("✖")} ${c.red("API key is required")}
44056
- ` + ` ${c.dim(`Configure with: locus config setup --api-key <key>
44057
- Or pass --api-key flag`)}
44283
+ ${c.error("✖")} ${c.red(`Discussion not found: ${id}`)}
44058
44284
  `);
44059
44285
  process.exit(1);
44060
44286
  }
44061
- const apiBase = values["api-url"] || settings.apiUrl || "https://api.locusai.dev/api";
44062
- const resolver = new WorkspaceResolver({
44063
- apiKey,
44064
- apiBase,
44065
- workspaceId: values.workspace
44066
- });
44067
- let workspaceId;
44287
+ console.log(`
44288
+ ${md}
44289
+ `);
44290
+ }
44291
+ function archiveDiscussion(discussionManager, id) {
44068
44292
  try {
44069
- workspaceId = await resolver.resolve();
44293
+ discussionManager.archive(id);
44294
+ console.log(`
44295
+ ${c.success("✔")} ${c.dim("Discussion archived.")}
44296
+ `);
44070
44297
  } catch (error48) {
44071
44298
  console.error(`
44072
44299
  ${c.error("✖")} ${c.red(error48 instanceof Error ? error48.message : String(error48))}
44073
44300
  `);
44074
44301
  process.exit(1);
44075
44302
  }
44076
- const client = new LocusClient({
44077
- baseUrl: apiBase,
44078
- token: apiKey
44079
- });
44080
- const fetcher = new DocumentFetcher({
44081
- client,
44082
- workspaceId,
44083
- projectPath,
44084
- log: (message, level) => {
44085
- if (level === "error") {
44086
- console.log(` ${c.error("✖")} ${message}`);
44087
- return;
44088
- }
44089
- if (level === "warn") {
44090
- console.log(` ${c.warning("!")} ${message}`);
44091
- return;
44092
- }
44093
- if (level === "success") {
44094
- console.log(` ${c.success("✔")} ${message}`);
44095
- return;
44096
- }
44097
- console.log(` ${c.info("●")} ${message}`);
44098
- }
44099
- });
44100
- console.log(`
44101
- ${c.info("●")} ${c.bold("Syncing docs from API...")}
44102
- `);
44303
+ }
44304
+ function deleteDiscussion(discussionManager, id) {
44103
44305
  try {
44104
- await fetcher.fetch();
44306
+ discussionManager.delete(id);
44105
44307
  console.log(`
44106
- ${c.success("✔")} ${c.success("Docs sync complete.")} ${c.dim("Local docs: .locus/documents")}
44308
+ ${c.success("✔")} ${c.dim("Discussion deleted.")}
44107
44309
  `);
44108
44310
  } catch (error48) {
44109
44311
  console.error(`
44110
- ${c.error("✖")} ${c.red(`Docs sync failed: ${error48 instanceof Error ? error48.message : String(error48)}`)}
44312
+ ${c.error("✖")} ${c.red(error48 instanceof Error ? error48.message : String(error48))}
44111
44313
  `);
44112
44314
  process.exit(1);
44113
44315
  }
44114
44316
  }
44115
- function showDocsHelp() {
44317
+ function showCurrentInsights(discussionManager, discussionId) {
44318
+ const discussion2 = discussionManager.load(discussionId);
44319
+ if (!discussion2 || discussion2.insights.length === 0) {
44320
+ console.log(`
44321
+ ${c.dim("No insights extracted yet.")}
44322
+ `);
44323
+ return;
44324
+ }
44116
44325
  console.log(`
44117
- ${c.header(" DOCS ")}
44118
- ${c.primary("locus docs")} ${c.dim("<command> [options]")}
44326
+ ${c.header(" INSIGHTS ")} ${c.dim(`(${discussion2.insights.length})`)}
44327
+ `);
44328
+ for (const insight of discussion2.insights) {
44329
+ const tag = formatInsightTag(insight.type);
44330
+ console.log(` ${tag} ${c.bold(insight.title)}`);
44331
+ console.log(` ${c.dim(insight.content)}`);
44332
+ if (insight.tags.length > 0) {
44333
+ console.log(` ${c.dim(`Tags: ${insight.tags.join(", ")}`)}`);
44334
+ }
44335
+ console.log("");
44336
+ }
44337
+ }
44338
+ function formatInsightTag(type) {
44339
+ switch (type) {
44340
+ case "decision":
44341
+ return c.green("[DECISION]");
44342
+ case "requirement":
44343
+ return c.blue("[REQUIREMENT]");
44344
+ case "idea":
44345
+ return c.yellow("[IDEA]");
44346
+ case "concern":
44347
+ return c.red("[CONCERN]");
44348
+ case "learning":
44349
+ return c.cyan("[LEARNING]");
44350
+ }
44351
+ }
44352
+ function showReplHelp() {
44353
+ console.log(`
44354
+ ${c.header(" DISCUSSION COMMANDS ")}
44119
44355
 
44120
- ${c.header(" COMMANDS ")}
44121
- ${c.success("sync")} Sync workspace docs from API to .locus/documents
44356
+ ${c.cyan("summary")} Generate a final summary and end the discussion
44357
+ ${c.cyan("insights")} Show all insights extracted so far
44358
+ ${c.cyan("exit")} Save and exit without generating a summary
44359
+ ${c.cyan("help")} Show this help message
44122
44360
 
44123
- ${c.header(" EXAMPLES ")}
44124
- ${c.dim("$")} ${c.primary("locus docs sync")}
44125
- ${c.dim("$")} ${c.primary("locus docs sync --workspace ws_123")}
44361
+ ${c.dim("Type anything else to continue the discussion.")}
44126
44362
  `);
44127
44363
  }
44128
- function showDocsSyncHelp() {
44364
+ function showDiscussHelp() {
44129
44365
  console.log(`
44130
- ${c.header(" DOCS SYNC ")}
44131
- ${c.primary("locus docs sync")} ${c.dim("[options]")}
44366
+ ${c.header(" LOCUS DISCUSS ")} ${c.dim("— Interactive AI Discussion")}
44132
44367
 
44133
- ${c.header(" OPTIONS ")}
44134
- ${c.secondary("--api-key")} <key> API key override (reads from settings.json)
44135
- ${c.secondary("--api-url")} <url> API base URL (default: https://api.locusai.dev/api)
44136
- ${c.secondary("--workspace")} <id> Workspace ID (optional if persisted or resolvable)
44137
- ${c.secondary("--dir")} <path> Project directory (default: current)
44138
- ${c.secondary("--help")} Show docs sync help
44368
+ ${c.bold("Usage:")}
44369
+ ${c.cyan('locus discuss "topic"')} Start a discussion on a topic
44370
+ ${c.cyan("locus discuss --list")} List all discussions
44371
+ ${c.cyan("locus discuss --show <id>")} Show discussion details
44372
+ ${c.cyan("locus discuss --archive <id>")} Archive a discussion
44373
+ ${c.cyan("locus discuss --delete <id>")} Delete a discussion
44374
+
44375
+ ${c.bold("Options:")}
44376
+ ${c.dim("--model <model>")} AI model (claude: opus, sonnet, haiku | codex: gpt-5.3-codex, gpt-5-codex-mini)
44377
+ ${c.dim("--provider <p>")} AI provider (claude, codex)
44378
+ ${c.dim("--reasoning-effort <level>")} Reasoning effort (low, medium, high)
44379
+ ${c.dim("--dir <path>")} Project directory
44380
+
44381
+ ${c.bold("REPL Commands:")}
44382
+ ${c.dim("summary")} Generate final summary and end the discussion
44383
+ ${c.dim("insights")} Show all insights extracted so far
44384
+ ${c.dim("exit")} Save and exit without generating a summary
44385
+ ${c.dim("help")} Show available commands
44386
+
44387
+ ${c.bold("Examples:")}
44388
+ ${c.dim("# Start a discussion about architecture")}
44389
+ ${c.cyan('locus discuss "how should we structure the auth system?"')}
44390
+
44391
+ ${c.dim("# Review a past discussion")}
44392
+ ${c.cyan("locus discuss --show disc-1234567890")}
44393
+
44394
+ ${c.dim("# List all discussions")}
44395
+ ${c.cyan("locus discuss --list")}
44396
+ `);
44397
+ }
44398
+ // src/commands/docs.ts
44399
+ init_index_node();
44400
+ import { parseArgs as parseArgs4 } from "node:util";
44401
+
44402
+ // src/config-manager.ts
44403
+ init_index_node();
44404
+ import { execSync as execSync2 } from "node:child_process";
44405
+ import { existsSync as existsSync16, mkdirSync as mkdirSync7, readFileSync as readFileSync14, writeFileSync as writeFileSync7 } from "node:fs";
44406
+ import { join as join16 } from "node:path";
44407
+ var LOCUS_GITIGNORE_MARKER = "# Locus AI";
44408
+ var LOCUS_MD_TEMPLATE = `## Planning First
44409
+
44410
+ Complex tasks must be planned before writing code. Create ".locus/plans/<task-name>.md" with:
44411
+ - **Goal**: What we're trying to achieve and why
44412
+ - **Approach**: Step-by-step strategy with technical decisions
44413
+ - **Affected files**: List of files to create/modify/delete
44414
+ - **Acceptance criteria**: Specific, testable conditions for completion
44415
+ - **Dependencies**: Required packages, APIs, or external services
44416
+
44417
+ Delete the planning .md files after successful execution.
44418
+
44419
+ ## Code Quality
44420
+
44421
+ - **Follow existing patterns**: Run formatters and linters before finishing (check "package.json" scripts or project config)
44422
+ - **Minimize changes**: Keep modifications atomic. Separate refactors from behavioral changes into different tasks
44423
+ - **Never commit secrets**: No API keys, passwords, or credentials in code. Use environment variables or secret management
44424
+ - **Test as you go**: If tests exist, run relevant ones. If breaking changes occur, update tests accordingly
44425
+ - **Comment complex logic**: Explain *why*, not *what*. Focus on business logic and non-obvious decisions
44426
+
44427
+ ## Artifacts
44428
+
44429
+ When a task produces knowledge, analysis, or research output rather than (or in addition to) code changes, you **must** save results as Markdown in ".locus/artifacts/<descriptive-name>.md":
44430
+
44431
+ **Always create artifacts for:**
44432
+ - Code quality audits, security reviews, vulnerability assessments
44433
+ - Architecture analyses, system design proposals, or recommendations
44434
+ - Dependency reports, performance profiling, benchmarking results
44435
+ - Research summaries, technology comparisons, or feasibility studies
44436
+ - Migration plans, deployment strategies, or rollback procedures
44437
+ - Post-mortems, incident analysis, or debugging investigations
44438
+
44439
+ **Artifact structure:**
44440
+ - Clear title and date
44441
+ - Executive summary (2-3 sentences)
44442
+ - Detailed findings/analysis
44443
+ - Actionable recommendations (if applicable)
44444
+
44445
+ ## Git Operations
44446
+
44447
+ - **Do NOT run**: git add, git commit, git push, git checkout, git branch, or any git commands
44448
+ - **Why**: The Locus orchestrator handles all version control automatically after execution
44449
+ - **Your role**: Focus solely on making file changes. The system commits, pushes, and creates PRs
44450
+
44451
+ ## Continuous Learning
44452
+
44453
+ Read ".locus/LEARNINGS.md" **before starting any task** to avoid repeating mistakes.
44454
+
44455
+ **When to update:**
44456
+ - User corrects your approach or provides guidance
44457
+ - You discover a better pattern while working
44458
+ - A decision prevents future confusion (e.g., "use X not Y because Z")
44459
+ - You encounter and solve a tricky problem
44460
+
44461
+ **What to record:**
44462
+ - Architectural decisions and their rationale
44463
+ - Preferred libraries, tools, or patterns for this project
44464
+ - Common pitfalls and how to avoid them
44465
+ - Project-specific conventions or user preferences
44466
+ - Solutions to non-obvious problems
44467
+
44468
+ **Format (append-only, never delete):**
44469
+
44470
+ """
44471
+ - **[Category]**: Concise description (1-2 lines max). *Context if needed.*
44472
+ """
44473
+
44474
+ **Categories:** Architecture, Dependencies, Patterns, Debugging, Performance, Security, DevOps, User Preferences
44475
+
44476
+ ## Error Handling
44477
+
44478
+ - **Read error messages carefully**: Don't guess. Parse the actual error before proposing fixes
44479
+ - **Check dependencies first**: Missing packages, wrong versions, and environment issues are common
44480
+ - **Verify assumptions**: If something "should work," confirm it actually does in this environment
44481
+ - **Ask for context**: If you need environment details, configuration, or logs, request them explicitly
44139
44482
 
44140
- ${c.header(" EXAMPLES ")}
44141
- ${c.dim("$")} ${c.primary("locus docs sync")}
44142
- ${c.dim("$")} ${c.primary("locus docs sync --workspace ws_123")}
44143
- `);
44144
- }
44145
- // src/commands/exec.ts
44146
- init_index_node();
44147
- import { randomUUID as randomUUID2 } from "node:crypto";
44148
- import { parseArgs as parseArgs3 } from "node:util";
44483
+ ## Communication
44149
44484
 
44150
- // src/display/json-stream-renderer.ts
44151
- init_src();
44485
+ - **Be precise**: When uncertain, state what you know and what you're assuming
44486
+ - **Show your work**: For complex changes, briefly explain the approach before executing
44487
+ - **Highlight trade-offs**: If multiple approaches exist, note why you chose one over others
44488
+ - **Request feedback**: For ambiguous requirements, propose an approach and ask for confirmation
44489
+ `;
44490
+ var DEFAULT_LEARNINGS_MD = `# Learnings
44152
44491
 
44153
- class JsonStreamRenderer {
44154
- sessionId;
44155
- command;
44156
- model;
44157
- provider;
44158
- cwd;
44159
- statsTracker;
44160
- started = false;
44161
- done = false;
44162
- constructor(options) {
44163
- this.sessionId = options.sessionId;
44164
- this.command = options.command;
44165
- this.model = options.model;
44166
- this.provider = options.provider;
44167
- this.cwd = options.cwd;
44168
- this.statsTracker = new ExecutionStatsTracker;
44169
- }
44170
- emitStart() {
44171
- if (this.started)
44172
- return;
44173
- this.started = true;
44174
- this.emit(createCliStreamEvent(CliStreamEventType.START, this.sessionId, {
44175
- command: this.command,
44176
- model: this.model,
44177
- provider: this.provider,
44178
- cwd: this.cwd
44179
- }));
44180
- }
44181
- handleChunk(chunk) {
44182
- this.ensureStarted();
44183
- switch (chunk.type) {
44184
- case "text_delta":
44185
- this.emit(createCliStreamEvent(CliStreamEventType.TEXT_DELTA, this.sessionId, {
44186
- content: chunk.content
44187
- }));
44188
- break;
44189
- case "thinking":
44190
- this.emit(createCliStreamEvent(CliStreamEventType.THINKING, this.sessionId, {
44191
- content: chunk.content
44192
- }));
44193
- break;
44194
- case "tool_use":
44195
- this.statsTracker.toolStarted(chunk.tool, chunk.id);
44196
- this.emit(createCliStreamEvent(CliStreamEventType.TOOL_STARTED, this.sessionId, {
44197
- tool: chunk.tool,
44198
- toolId: chunk.id,
44199
- parameters: chunk.parameters
44200
- }));
44201
- break;
44202
- case "tool_result":
44203
- if (chunk.success) {
44204
- this.statsTracker.toolCompleted(chunk.tool, chunk.id);
44492
+ This file captures important lessons, decisions, and corrections made during development.
44493
+ It is read by AI agents before every task to avoid repeating mistakes and to follow established patterns.
44494
+
44495
+ <!-- Add learnings below this line. Format: - **[Category]**: Description -->
44496
+ `;
44497
+ function updateGitignore(projectPath) {
44498
+ const gitignorePath = join16(projectPath, ".gitignore");
44499
+ let content = "";
44500
+ const locusBlock = LOCUS_GITIGNORE_PATTERNS.join(`
44501
+ `);
44502
+ if (existsSync16(gitignorePath)) {
44503
+ content = readFileSync14(gitignorePath, "utf-8");
44504
+ if (content.includes(LOCUS_GITIGNORE_MARKER)) {
44505
+ const lines = content.split(`
44506
+ `);
44507
+ const startIdx = lines.findIndex((l) => l.includes(LOCUS_GITIGNORE_MARKER));
44508
+ let endIdx = startIdx;
44509
+ for (let i = startIdx;i < lines.length; i++) {
44510
+ if (lines[i].startsWith(LOCUS_GITIGNORE_MARKER) || lines[i].startsWith(".locus") || lines[i].trim() === "") {
44511
+ endIdx = i;
44205
44512
  } else {
44206
- this.statsTracker.toolFailed(chunk.tool, chunk.error ?? "Unknown error", chunk.id);
44513
+ break;
44207
44514
  }
44208
- this.emit(createCliStreamEvent(CliStreamEventType.TOOL_COMPLETED, this.sessionId, {
44209
- tool: chunk.tool,
44210
- toolId: chunk.id,
44211
- success: chunk.success,
44212
- duration: chunk.duration,
44213
- error: chunk.error
44214
- }));
44215
- break;
44216
- case "tool_parameters":
44217
- break;
44218
- case "result":
44219
- break;
44220
- case "error":
44221
- this.statsTracker.setError(chunk.error);
44222
- this.emitError("UNKNOWN", chunk.error);
44223
- break;
44224
- }
44225
- }
44226
- emitStatus(status, message) {
44227
- this.ensureStarted();
44228
- this.emit(createCliStreamEvent(CliStreamEventType.STATUS, this.sessionId, {
44229
- status,
44230
- message
44231
- }));
44232
- }
44233
- emitError(code, message, options) {
44234
- this.ensureStarted();
44235
- this.emit(createCliStreamEvent(CliStreamEventType.ERROR, this.sessionId, {
44236
- error: createProtocolError(code, message, options)
44237
- }));
44238
- }
44239
- emitDone(exitCode) {
44240
- if (this.done)
44515
+ }
44516
+ const before = lines.slice(0, startIdx);
44517
+ const after = lines.slice(endIdx + 1);
44518
+ content = [...before, locusBlock, ...after].join(`
44519
+ `);
44520
+ writeFileSync7(gitignorePath, content);
44241
44521
  return;
44242
- this.done = true;
44243
- this.ensureStarted();
44244
- const stats = this.statsTracker.finalize();
44245
- this.emit(createCliStreamEvent(CliStreamEventType.DONE, this.sessionId, {
44246
- exitCode,
44247
- duration: stats.duration,
44248
- toolsUsed: stats.toolsUsed.length > 0 ? stats.toolsUsed : undefined,
44249
- tokensUsed: stats.tokensUsed,
44250
- success: exitCode === 0
44251
- }));
44252
- }
44253
- emitFatalError(code, message, options) {
44254
- this.statsTracker.setError(message);
44255
- this.emitError(code, message, {
44256
- ...options,
44257
- recoverable: false
44258
- });
44259
- this.emitDone(1);
44260
- }
44261
- isDone() {
44262
- return this.done;
44263
- }
44264
- ensureStarted() {
44265
- if (!this.started) {
44266
- this.emitStart();
44522
+ }
44523
+ if (content.length > 0 && !content.endsWith(`
44524
+ `)) {
44525
+ content += `
44526
+ `;
44527
+ }
44528
+ if (content.trim().length > 0) {
44529
+ content += `
44530
+ `;
44267
44531
  }
44268
44532
  }
44269
- emit(event) {
44270
- process.stdout.write(`${JSON.stringify(event)}
44271
- `);
44272
- }
44533
+ content += `${locusBlock}
44534
+ `;
44535
+ writeFileSync7(gitignorePath, content);
44273
44536
  }
44274
-
44275
- // src/commands/exec.ts
44276
- init_progress_renderer();
44277
-
44278
- // src/commands/exec-sessions.ts
44279
- init_index_node();
44280
- function formatRelativeTime(timestamp) {
44281
- const now = Date.now();
44282
- const diff = now - timestamp;
44283
- const seconds = Math.floor(diff / 1000);
44284
- const minutes = Math.floor(seconds / 60);
44285
- const hours = Math.floor(minutes / 60);
44286
- const days = Math.floor(hours / 24);
44287
- if (days > 0) {
44288
- return days === 1 ? "yesterday" : `${days} days ago`;
44289
- }
44290
- if (hours > 0) {
44291
- return hours === 1 ? "1 hour ago" : `${hours} hours ago`;
44537
+ function ensureGitIdentity(projectPath) {
44538
+ const hasName = (() => {
44539
+ try {
44540
+ return execSync2("git config --get user.name", {
44541
+ cwd: projectPath,
44542
+ stdio: ["pipe", "pipe", "pipe"]
44543
+ }).toString().trim();
44544
+ } catch {
44545
+ return "";
44546
+ }
44547
+ })();
44548
+ const hasEmail = (() => {
44549
+ try {
44550
+ return execSync2("git config --get user.email", {
44551
+ cwd: projectPath,
44552
+ stdio: ["pipe", "pipe", "pipe"]
44553
+ }).toString().trim();
44554
+ } catch {
44555
+ return "";
44556
+ }
44557
+ })();
44558
+ if (!hasName) {
44559
+ execSync2('git config user.name "LocusAgent"', {
44560
+ cwd: projectPath,
44561
+ stdio: "ignore"
44562
+ });
44292
44563
  }
44293
- if (minutes > 0) {
44294
- return minutes === 1 ? "1 minute ago" : `${minutes} minutes ago`;
44564
+ if (!hasEmail) {
44565
+ execSync2('git config user.email "agent@locusai.team"', {
44566
+ cwd: projectPath,
44567
+ stdio: "ignore"
44568
+ });
44295
44569
  }
44296
- return "just now";
44570
+ execSync2("git config --global pull.rebase true", {
44571
+ cwd: projectPath,
44572
+ stdio: "ignore"
44573
+ });
44297
44574
  }
44298
44575
 
44299
- class SessionCommands {
44300
- historyManager;
44576
+ class ConfigManager {
44577
+ projectPath;
44301
44578
  constructor(projectPath) {
44302
- this.historyManager = new HistoryManager(projectPath);
44579
+ this.projectPath = projectPath;
44303
44580
  }
44304
- async list() {
44305
- const sessions = this.historyManager.listSessions();
44306
- if (sessions.length === 0) {
44307
- console.log(`
44308
- ${c.dim("No exec sessions found.")}
44309
- `);
44310
- return;
44581
+ async init(version2) {
44582
+ const locusConfigDir = join16(this.projectPath, LOCUS_CONFIG.dir);
44583
+ const locusConfigPath = getLocusPath(this.projectPath, "configFile");
44584
+ if (!existsSync16(locusConfigDir)) {
44585
+ mkdirSync7(locusConfigDir, { recursive: true });
44586
+ }
44587
+ const locusSubdirs = [
44588
+ LOCUS_CONFIG.artifactsDir,
44589
+ LOCUS_CONFIG.documentsDir,
44590
+ LOCUS_CONFIG.sessionsDir,
44591
+ LOCUS_CONFIG.reviewsDir,
44592
+ LOCUS_CONFIG.plansDir,
44593
+ LOCUS_CONFIG.discussionsDir
44594
+ ];
44595
+ for (const subdir of locusSubdirs) {
44596
+ const subdirPath = join16(locusConfigDir, subdir);
44597
+ if (!existsSync16(subdirPath)) {
44598
+ mkdirSync7(subdirPath, { recursive: true });
44599
+ }
44600
+ }
44601
+ const locusMdPath = getLocusPath(this.projectPath, "contextFile");
44602
+ if (!existsSync16(locusMdPath)) {
44603
+ writeFileSync7(locusMdPath, LOCUS_MD_TEMPLATE);
44311
44604
  }
44312
- console.log(`
44313
- ${c.primary("Recent Exec Sessions:")}
44314
- `);
44315
- for (const session2 of sessions.slice(0, 10)) {
44316
- const shortId = this.getShortId(session2.id);
44317
- const age = formatRelativeTime(session2.updatedAt);
44318
- const msgCount = session2.messages.length;
44319
- const firstUserMsg = session2.messages.find((m) => m.role === "user");
44320
- const preview = firstUserMsg ? firstUserMsg.content.slice(0, 50).replace(/\n/g, " ") : "(empty session)";
44321
- console.log(` ${c.cyan(shortId)} ${c.gray("-")} ${preview}${firstUserMsg && firstUserMsg.content.length > 50 ? "..." : ""}`);
44322
- console.log(` ${c.dim(`${msgCount} messages • ${age}`)}`);
44323
- console.log();
44605
+ const learningsMdPath = getLocusPath(this.projectPath, "learningsFile");
44606
+ if (!existsSync16(learningsMdPath)) {
44607
+ writeFileSync7(learningsMdPath, DEFAULT_LEARNINGS_MD);
44324
44608
  }
44325
- if (sessions.length > 10) {
44326
- console.log(c.dim(` ... and ${sessions.length - 10} more sessions
44327
- `));
44609
+ if (!existsSync16(locusConfigPath)) {
44610
+ const config2 = {
44611
+ $schema: LOCUS_SCHEMAS.config,
44612
+ version: version2,
44613
+ createdAt: new Date().toISOString(),
44614
+ projectPath: "."
44615
+ };
44616
+ writeFileSync7(locusConfigPath, JSON.stringify(config2, null, 2));
44328
44617
  }
44618
+ updateGitignore(this.projectPath);
44619
+ ensureGitIdentity(this.projectPath);
44329
44620
  }
44330
- async show(sessionId) {
44331
- if (!sessionId) {
44332
- console.error(`
44333
- ${c.error("Error:")} Session ID is required
44334
- `);
44335
- console.log(` ${c.dim("Usage: locus exec sessions show <session-id>")}
44336
- `);
44337
- return;
44621
+ loadConfig() {
44622
+ const path3 = getLocusPath(this.projectPath, "configFile");
44623
+ if (existsSync16(path3)) {
44624
+ return JSON.parse(readFileSync14(path3, "utf-8"));
44338
44625
  }
44339
- const session2 = this.historyManager.findSessionByPartialId(sessionId);
44340
- if (!session2) {
44341
- console.error(`
44342
- ${c.error("Error:")} Session ${c.cyan(sessionId)} not found
44343
- `);
44344
- console.log(` ${c.dim("Use 'locus exec sessions list' to see available sessions")}
44345
- `);
44346
- return;
44626
+ return null;
44627
+ }
44628
+ updateVersion(version2) {
44629
+ const config2 = this.loadConfig();
44630
+ if (config2 && config2.version !== version2) {
44631
+ config2.version = version2;
44632
+ this.saveConfig(config2);
44347
44633
  }
44348
- console.log(`
44349
- ${c.primary("Session:")} ${c.cyan(session2.id)}`);
44350
- console.log(` ${c.dim(`Created: ${new Date(session2.createdAt).toLocaleString()}`)}`);
44351
- console.log(` ${c.dim(`Model: ${session2.metadata.model} (${session2.metadata.provider})`)}
44352
- `);
44353
- if (session2.messages.length === 0) {
44354
- console.log(` ${c.dim("(No messages in this session)")}
44355
- `);
44356
- return;
44634
+ }
44635
+ async reinit(version2) {
44636
+ const result = {
44637
+ versionUpdated: false,
44638
+ previousVersion: null,
44639
+ directoriesCreated: [],
44640
+ gitignoreUpdated: false
44641
+ };
44642
+ const config2 = this.loadConfig();
44643
+ if (config2) {
44644
+ result.previousVersion = config2.version;
44645
+ const needsSchemaUpdate = config2.$schema !== LOCUS_SCHEMAS.config;
44646
+ if (config2.version !== version2) {
44647
+ config2.version = version2;
44648
+ result.versionUpdated = true;
44649
+ }
44650
+ if (result.versionUpdated || needsSchemaUpdate) {
44651
+ this.saveConfig(config2);
44652
+ }
44357
44653
  }
44358
- console.log(c.dim(" ─".repeat(30)));
44359
- console.log();
44360
- for (const message of session2.messages) {
44361
- const role = message.role === "user" ? c.cyan("You") : c.green("AI");
44362
- const content = message.content;
44363
- console.log(` ${role}:`);
44364
- const lines = content.split(`
44365
- `);
44366
- for (const line of lines) {
44367
- console.log(` ${line}`);
44654
+ const settingsPath = join16(this.projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.settingsFile);
44655
+ if (existsSync16(settingsPath)) {
44656
+ const raw = readFileSync14(settingsPath, "utf-8");
44657
+ const settings = JSON.parse(raw);
44658
+ if (settings.$schema !== LOCUS_SCHEMAS.settings) {
44659
+ const { $schema: _2, ...rest } = settings;
44660
+ const ordered = { $schema: LOCUS_SCHEMAS.settings, ...rest };
44661
+ writeFileSync7(settingsPath, JSON.stringify(ordered, null, 2), "utf-8");
44368
44662
  }
44369
- console.log();
44370
44663
  }
44371
- }
44372
- async delete(sessionId) {
44373
- if (!sessionId) {
44374
- console.error(`
44375
- ${c.error("Error:")} Session ID is required
44376
- `);
44377
- console.log(` ${c.dim("Usage: locus exec sessions delete <session-id>")}
44378
- `);
44379
- return;
44664
+ const locusMdPath = getLocusPath(this.projectPath, "contextFile");
44665
+ const locusMdExisted = existsSync16(locusMdPath);
44666
+ writeFileSync7(locusMdPath, LOCUS_MD_TEMPLATE);
44667
+ if (!locusMdExisted) {
44668
+ result.directoriesCreated.push(".locus/LOCUS.md");
44380
44669
  }
44381
- const session2 = this.historyManager.findSessionByPartialId(sessionId);
44382
- if (!session2) {
44383
- console.error(`
44384
- ${c.error("Error:")} Session ${c.cyan(sessionId)} not found
44385
- `);
44386
- return;
44670
+ const locusSubdirs = [
44671
+ LOCUS_CONFIG.artifactsDir,
44672
+ LOCUS_CONFIG.documentsDir,
44673
+ LOCUS_CONFIG.sessionsDir,
44674
+ LOCUS_CONFIG.reviewsDir,
44675
+ LOCUS_CONFIG.plansDir,
44676
+ LOCUS_CONFIG.discussionsDir
44677
+ ];
44678
+ const locusConfigDir = join16(this.projectPath, LOCUS_CONFIG.dir);
44679
+ for (const subdir of locusSubdirs) {
44680
+ const subdirPath = join16(locusConfigDir, subdir);
44681
+ if (!existsSync16(subdirPath)) {
44682
+ mkdirSync7(subdirPath, { recursive: true });
44683
+ result.directoriesCreated.push(`.locus/${subdir}`);
44684
+ }
44387
44685
  }
44388
- const deleted = this.historyManager.deleteSession(session2.id);
44389
- if (deleted) {
44390
- console.log(`
44391
- ${c.success("✔")} Deleted session ${c.cyan(this.getShortId(session2.id))}
44392
- `);
44393
- } else {
44394
- console.error(`
44395
- ${c.error("Error:")} Failed to delete session
44396
- `);
44686
+ const learningsMdPath = getLocusPath(this.projectPath, "learningsFile");
44687
+ if (!existsSync16(learningsMdPath)) {
44688
+ writeFileSync7(learningsMdPath, DEFAULT_LEARNINGS_MD);
44689
+ result.directoriesCreated.push(".locus/LEARNINGS.md");
44397
44690
  }
44398
- }
44399
- async clear() {
44400
- const count = this.historyManager.getSessionCount();
44401
- if (count === 0) {
44402
- console.log(`
44403
- ${c.dim("No sessions to clear.")}
44404
- `);
44405
- return;
44691
+ const gitignorePath = join16(this.projectPath, ".gitignore");
44692
+ const gitignoreBefore = existsSync16(gitignorePath) ? readFileSync14(gitignorePath, "utf-8") : "";
44693
+ updateGitignore(this.projectPath);
44694
+ const gitignoreAfter = readFileSync14(gitignorePath, "utf-8");
44695
+ if (gitignoreBefore !== gitignoreAfter) {
44696
+ result.gitignoreUpdated = true;
44406
44697
  }
44407
- const deleted = this.historyManager.clearAllSessions();
44408
- console.log(`
44409
- ${c.success("✔")} Cleared ${deleted} exec session${deleted === 1 ? "" : "s"}
44410
- `);
44698
+ ensureGitIdentity(this.projectPath);
44699
+ return result;
44411
44700
  }
44412
- getShortId(sessionId) {
44413
- const parts = sessionId.split("-");
44414
- if (parts.length >= 3) {
44415
- return parts.slice(-1)[0].slice(0, 8);
44416
- }
44417
- return sessionId.slice(0, 8);
44701
+ saveConfig(config2) {
44702
+ const { $schema: _2, ...rest } = config2;
44703
+ const ordered = { $schema: LOCUS_SCHEMAS.config, ...rest };
44704
+ const path3 = getLocusPath(this.projectPath, "configFile");
44705
+ writeFileSync7(path3, JSON.stringify(ordered, null, 2));
44418
44706
  }
44419
44707
  }
44420
- function showSessionsHelp() {
44421
- console.log(`
44422
- ${c.primary("Session Commands")}
44423
-
44424
- ${c.success("list")} List recent exec sessions
44425
- ${c.success("show")} ${c.dim("<id>")} Show all messages in a session
44426
- ${c.success("delete")} ${c.dim("<id>")} Delete a specific session
44427
- ${c.success("clear")} Clear all exec sessions
44428
44708
 
44429
- ${c.header(" EXAMPLES ")}
44430
- ${c.dim("$")} locus exec sessions list
44431
- ${c.dim("$")} locus exec sessions show e7f3a2b1
44432
- ${c.dim("$")} locus exec sessions delete e7f3a2b1
44433
- ${c.dim("$")} locus exec sessions clear
44709
+ // src/commands/docs.ts
44710
+ init_settings_manager();
44711
+ init_utils3();
44434
44712
 
44435
- ${c.dim("Session IDs can be partial (first 8 characters).")}
44436
- `);
44437
- }
44713
+ // src/workspace-resolver.ts
44714
+ init_index_node();
44438
44715
 
44439
- // src/commands/exec.ts
44440
- async function execCommand(args) {
44441
- const { values, positionals } = parseArgs3({
44442
- args,
44443
- options: {
44444
- model: { type: "string" },
44445
- provider: { type: "string" },
44446
- "reasoning-effort": { type: "string" },
44447
- dir: { type: "string" },
44448
- "no-stream": { type: "boolean" },
44449
- "no-status": { type: "boolean" },
44450
- interactive: { type: "boolean", short: "i" },
44451
- session: { type: "string", short: "s" },
44452
- "session-id": { type: "string" },
44453
- "json-stream": { type: "boolean" }
44454
- },
44455
- strict: false
44456
- });
44457
- const jsonStream = values["json-stream"];
44458
- const projectPath = values.dir || process.cwd();
44459
- if (jsonStream) {
44460
- await execJsonStream(values, positionals, projectPath);
44461
- return;
44716
+ class WorkspaceResolver {
44717
+ options;
44718
+ constructor(options) {
44719
+ this.options = options;
44462
44720
  }
44463
- requireInitialization(projectPath, "exec");
44464
- if (positionals[0] === "sessions") {
44465
- const sessionAction = positionals[1];
44466
- const sessionArg = positionals[2];
44467
- const cmds = new SessionCommands(projectPath);
44468
- switch (sessionAction) {
44469
- case "list":
44470
- await cmds.list();
44471
- return;
44472
- case "show":
44473
- await cmds.show(sessionArg);
44474
- return;
44475
- case "delete":
44476
- await cmds.delete(sessionArg);
44477
- return;
44478
- case "clear":
44479
- await cmds.clear();
44480
- return;
44481
- default:
44482
- showSessionsHelp();
44483
- return;
44721
+ async resolve() {
44722
+ if (this.options.workspaceId) {
44723
+ return this.options.workspaceId;
44724
+ }
44725
+ try {
44726
+ console.log(c.dim("ℹ Resolving workspace from API key..."));
44727
+ const client = new LocusClient({
44728
+ baseUrl: this.options.apiBase,
44729
+ token: this.options.apiKey
44730
+ });
44731
+ const info = await client.auth.getApiKeyInfo();
44732
+ if (info.workspaceId) {
44733
+ console.log(c.success(`✓ Resolved workspace: ${info.workspaceId}`));
44734
+ return info.workspaceId;
44735
+ }
44736
+ throw new Error("API key is not associated with a workspace. Please specify --workspace.");
44737
+ } catch (error48) {
44738
+ throw new Error(`Error resolving workspace: ${error48 instanceof Error ? error48.message : String(error48)}`);
44484
44739
  }
44485
44740
  }
44486
- const execSettings = new SettingsManager(projectPath).load();
44487
- const provider = resolveProvider3(values.provider || execSettings.provider);
44488
- const model = values.model || execSettings.model || DEFAULT_MODEL[provider];
44489
- const isInteractive = values.interactive;
44490
- const sessionId = values.session;
44491
- if (isInteractive) {
44492
- const { InteractiveSession: InteractiveSession2 } = await Promise.resolve().then(() => (init_interactive_session(), exports_interactive_session));
44493
- const session2 = new InteractiveSession2({
44494
- projectPath,
44495
- provider,
44496
- model,
44497
- sessionId
44498
- });
44499
- await session2.start();
44741
+ }
44742
+
44743
+ // src/commands/docs.ts
44744
+ async function docsCommand(args) {
44745
+ const subcommand = args[0];
44746
+ const subArgs = args.slice(1);
44747
+ switch (subcommand) {
44748
+ case "sync":
44749
+ await docsSyncCommand(subArgs);
44750
+ break;
44751
+ default:
44752
+ showDocsHelp();
44753
+ break;
44754
+ }
44755
+ }
44756
+ async function docsSyncCommand(args) {
44757
+ const { values } = parseArgs4({
44758
+ args,
44759
+ options: {
44760
+ "api-key": { type: "string" },
44761
+ "api-url": { type: "string" },
44762
+ workspace: { type: "string" },
44763
+ dir: { type: "string" },
44764
+ help: { type: "boolean" }
44765
+ },
44766
+ strict: false
44767
+ });
44768
+ if (values.help) {
44769
+ showDocsSyncHelp();
44500
44770
  return;
44501
44771
  }
44502
- const promptInput = positionals.join(" ");
44503
- if (!promptInput) {
44504
- console.error(c.error('Error: Prompt is required. Usage: locus exec "your prompt" or locus exec --interactive'));
44772
+ const projectPath = values.dir || process.cwd();
44773
+ requireInitialization(projectPath, "docs sync");
44774
+ const configManager = new ConfigManager(projectPath);
44775
+ configManager.updateVersion(VERSION2);
44776
+ const settingsManager = new SettingsManager(projectPath);
44777
+ const settings = settingsManager.load();
44778
+ const apiKey = values["api-key"] || settings.apiKey;
44779
+ if (!apiKey) {
44780
+ console.error(`
44781
+ ${c.error("✖")} ${c.red("API key is required")}
44782
+ ` + ` ${c.dim(`Configure with: locus config setup --api-key <key>
44783
+ Or pass --api-key flag`)}
44784
+ `);
44505
44785
  process.exit(1);
44506
44786
  }
44507
- const useStreaming = !values["no-stream"];
44508
- const reasoningEffort = values["reasoning-effort"];
44509
- const aiRunner = createAiRunner(provider, {
44510
- projectPath,
44511
- model,
44512
- reasoningEffort
44787
+ const apiBase = values["api-url"] || settings.apiUrl || "https://api.locusai.dev/api";
44788
+ const resolver = new WorkspaceResolver({
44789
+ apiKey,
44790
+ apiBase,
44791
+ workspaceId: values.workspace
44513
44792
  });
44514
- const builder = new PromptBuilder(projectPath);
44515
- const fullPrompt = await builder.buildGenericPrompt(promptInput);
44516
- console.log("");
44517
- console.log(`${c.primary("\uD83D\uDE80")} ${c.bold("Executing prompt with repository context...")}`);
44518
- console.log("");
44519
- let responseContent = "";
44793
+ let workspaceId;
44520
44794
  try {
44521
- if (useStreaming) {
44522
- const renderer = new ProgressRenderer;
44523
- const statsTracker = new ExecutionStatsTracker;
44524
- const stream4 = aiRunner.runStream(fullPrompt);
44525
- renderer.showThinkingStarted();
44526
- for await (const chunk of stream4) {
44527
- switch (chunk.type) {
44528
- case "text_delta":
44529
- renderer.renderTextDelta(chunk.content);
44530
- responseContent += chunk.content;
44531
- break;
44532
- case "tool_use":
44533
- statsTracker.toolStarted(chunk.tool, chunk.id);
44534
- renderer.showToolStarted(chunk.tool, chunk.id);
44535
- break;
44536
- case "thinking":
44537
- renderer.showThinkingStarted();
44538
- break;
44539
- case "tool_result":
44540
- if (chunk.success) {
44541
- statsTracker.toolCompleted(chunk.tool, chunk.id);
44542
- renderer.showToolCompleted(chunk.tool, undefined, chunk.id);
44543
- } else {
44544
- statsTracker.toolFailed(chunk.tool, chunk.error ?? "Unknown error", chunk.id);
44545
- renderer.showToolFailed(chunk.tool, chunk.error ?? "Unknown error", chunk.id);
44546
- }
44547
- break;
44548
- case "result":
44549
- break;
44550
- case "error": {
44551
- statsTracker.setError(chunk.error);
44552
- renderer.renderError(chunk.error);
44553
- renderer.finalize();
44554
- const errorStats = statsTracker.finalize();
44555
- renderer.showSummary(errorStats);
44556
- console.error(`
44557
- ${c.error("✖")} ${c.error("Execution failed!")}
44558
- `);
44559
- process.exit(1);
44560
- }
44561
- }
44562
- }
44563
- renderer.finalize();
44564
- const stats = statsTracker.finalize();
44565
- renderer.showSummary(stats);
44566
- } else {
44567
- responseContent = await aiRunner.run(fullPrompt);
44568
- console.log(responseContent);
44569
- }
44570
- console.log(`
44571
- ${c.success("✔")} ${c.success("Execution finished!")}
44572
- `);
44795
+ workspaceId = await resolver.resolve();
44573
44796
  } catch (error48) {
44574
44797
  console.error(`
44575
- ${c.error("✖")} ${c.error("Execution failed:")} ${c.red(error48 instanceof Error ? error48.message : String(error48))}
44798
+ ${c.error("✖")} ${c.red(error48 instanceof Error ? error48.message : String(error48))}
44576
44799
  `);
44577
44800
  process.exit(1);
44578
44801
  }
44579
- }
44580
- async function execJsonStream(values, positionals, projectPath) {
44581
- const sessionId = values["session-id"] ?? values.session ?? randomUUID2();
44582
- const execSettings = new SettingsManager(projectPath).load();
44583
- const provider = resolveProvider3(values.provider || execSettings.provider);
44584
- const model = values.model || execSettings.model || DEFAULT_MODEL[provider];
44585
- const renderer = new JsonStreamRenderer({
44586
- sessionId,
44587
- command: "exec",
44588
- model,
44589
- provider,
44590
- cwd: projectPath
44802
+ const client = new LocusClient({
44803
+ baseUrl: apiBase,
44804
+ token: apiKey
44591
44805
  });
44592
- const handleSignal = () => {
44593
- if (!renderer.isDone()) {
44594
- renderer.emitFatalError("PROCESS_CRASHED", "Process terminated by signal");
44806
+ const fetcher = new DocumentFetcher({
44807
+ client,
44808
+ workspaceId,
44809
+ projectPath,
44810
+ log: (message, level) => {
44811
+ if (level === "error") {
44812
+ console.log(` ${c.error("✖")} ${message}`);
44813
+ return;
44814
+ }
44815
+ if (level === "warn") {
44816
+ console.log(` ${c.warning("!")} ${message}`);
44817
+ return;
44818
+ }
44819
+ if (level === "success") {
44820
+ console.log(` ${c.success("✔")} ${message}`);
44821
+ return;
44822
+ }
44823
+ console.log(` ${c.info("●")} ${message}`);
44595
44824
  }
44596
- process.exit(1);
44597
- };
44598
- process.on("SIGINT", handleSignal);
44599
- process.on("SIGTERM", handleSignal);
44825
+ });
44826
+ console.log(`
44827
+ ${c.info("")} ${c.bold("Syncing docs from API...")}
44828
+ `);
44600
44829
  try {
44601
- try {
44602
- requireInitialization(projectPath, "exec");
44603
- } catch (initError) {
44604
- renderer.emitFatalError("CLI_NOT_FOUND", initError instanceof Error ? initError.message : String(initError));
44605
- process.exit(1);
44606
- }
44607
- const promptInput = positionals.join(" ");
44608
- if (!promptInput) {
44609
- renderer.emitFatalError("UNKNOWN", 'Prompt is required. Usage: locus exec --json-stream "your prompt"');
44610
- process.exit(1);
44611
- }
44612
- renderer.emitStart();
44613
- renderer.emitStatus("running", "Building prompt context");
44614
- const aiRunner = createAiRunner(provider, {
44615
- projectPath,
44616
- model
44617
- });
44618
- const builder = new PromptBuilder(projectPath);
44619
- const fullPrompt = await builder.buildGenericPrompt(promptInput);
44620
- renderer.emitStatus("streaming", "Streaming AI response");
44621
- const stream4 = aiRunner.runStream(fullPrompt);
44622
- for await (const chunk of stream4) {
44623
- renderer.handleChunk(chunk);
44624
- }
44625
- renderer.emitDone(0);
44830
+ await fetcher.fetch();
44831
+ console.log(`
44832
+ ${c.success("✔")} ${c.success("Docs sync complete.")} ${c.dim("Local docs: .locus/documents")}
44833
+ `);
44626
44834
  } catch (error48) {
44627
- const message = error48 instanceof Error ? error48.message : String(error48);
44628
- if (!renderer.isDone()) {
44629
- renderer.emitFatalError("UNKNOWN", message);
44630
- }
44835
+ console.error(`
44836
+ ${c.error("✖")} ${c.red(`Docs sync failed: ${error48 instanceof Error ? error48.message : String(error48)}`)}
44837
+ `);
44631
44838
  process.exit(1);
44632
44839
  }
44633
44840
  }
44841
+ function showDocsHelp() {
44842
+ console.log(`
44843
+ ${c.header(" DOCS ")}
44844
+ ${c.primary("locus docs")} ${c.dim("<command> [options]")}
44845
+
44846
+ ${c.header(" COMMANDS ")}
44847
+ ${c.success("sync")} Sync workspace docs from API to .locus/documents
44848
+
44849
+ ${c.header(" EXAMPLES ")}
44850
+ ${c.dim("$")} ${c.primary("locus docs sync")}
44851
+ ${c.dim("$")} ${c.primary("locus docs sync --workspace ws_123")}
44852
+ `);
44853
+ }
44854
+ function showDocsSyncHelp() {
44855
+ console.log(`
44856
+ ${c.header(" DOCS SYNC ")}
44857
+ ${c.primary("locus docs sync")} ${c.dim("[options]")}
44858
+
44859
+ ${c.header(" OPTIONS ")}
44860
+ ${c.secondary("--api-key")} <key> API key override (reads from settings.json)
44861
+ ${c.secondary("--api-url")} <url> API base URL (default: https://api.locusai.dev/api)
44862
+ ${c.secondary("--workspace")} <id> Workspace ID (optional if persisted or resolvable)
44863
+ ${c.secondary("--dir")} <path> Project directory (default: current)
44864
+ ${c.secondary("--help")} Show docs sync help
44865
+
44866
+ ${c.header(" EXAMPLES ")}
44867
+ ${c.dim("$")} ${c.primary("locus docs sync")}
44868
+ ${c.dim("$")} ${c.primary("locus docs sync --workspace ws_123")}
44869
+ `);
44870
+ }
44871
+
44872
+ // src/commands/index.ts
44873
+ init_exec2();
44874
+ init_exec_sessions();
44875
+
44634
44876
  // src/commands/help.ts
44635
44877
  init_index_node();
44636
44878
  function showHelp2() {
@@ -44669,6 +44911,9 @@ function showHelp2() {
44669
44911
  ${c.dim("sessions show <id> Show session messages")}
44670
44912
  ${c.dim("sessions delete <id> Delete a session")}
44671
44913
  ${c.dim("sessions clear Clear all sessions")}
44914
+ ${c.success("artifacts")} List and manage knowledge artifacts
44915
+ ${c.dim("show <name> Show artifact content")}
44916
+ ${c.dim("plan <name> Convert artifact to a plan")}
44672
44917
  ${c.success("version")} Show installed package versions
44673
44918
  ${c.success("upgrade")} Update CLI and Telegram to the latest version
44674
44919
 
@@ -44692,13 +44937,15 @@ function showHelp2() {
44692
44937
  ${c.dim("$")} ${c.primary("locus telegram setup")}
44693
44938
  ${c.dim("$")} ${c.primary('locus discuss "how should we design the auth system?"')}
44694
44939
  ${c.dim("$")} ${c.primary("locus exec sessions list")}
44940
+ ${c.dim("$")} ${c.primary("locus artifacts")}
44941
+ ${c.dim("$")} ${c.primary("locus artifacts show reduce-cli-terminal-output")}
44695
44942
 
44696
44943
  For more information, visit: ${c.underline("https://docs.locusai.dev")}
44697
44944
  `);
44698
44945
  }
44699
44946
  // src/commands/index-codebase.ts
44700
44947
  init_index_node();
44701
- import { parseArgs as parseArgs4 } from "node:util";
44948
+ import { parseArgs as parseArgs5 } from "node:util";
44702
44949
 
44703
44950
  // src/tree-summarizer.ts
44704
44951
  init_index_node();
@@ -44725,8 +44972,9 @@ ${tree}`;
44725
44972
  }
44726
44973
 
44727
44974
  // src/commands/index-codebase.ts
44975
+ init_utils3();
44728
44976
  async function indexCommand(args) {
44729
- const { values } = parseArgs4({
44977
+ const { values } = parseArgs5({
44730
44978
  args,
44731
44979
  options: {
44732
44980
  dir: { type: "string" },
@@ -44765,6 +45013,7 @@ async function indexCommand(args) {
44765
45013
  }
44766
45014
  // src/commands/init.ts
44767
45015
  init_index_node();
45016
+ init_utils3();
44768
45017
  async function initCommand() {
44769
45018
  const projectPath = process.cwd();
44770
45019
  const configManager = new ConfigManager(projectPath);
@@ -44824,9 +45073,11 @@ async function initCommand() {
44824
45073
  }
44825
45074
  // src/commands/plan.ts
44826
45075
  init_index_node();
44827
- import { existsSync as existsSync16, unlinkSync as unlinkSync5 } from "node:fs";
44828
- import { join as join16 } from "node:path";
44829
- import { parseArgs as parseArgs5 } from "node:util";
45076
+ import { existsSync as existsSync17, unlinkSync as unlinkSync5 } from "node:fs";
45077
+ import { join as join17 } from "node:path";
45078
+ import { parseArgs as parseArgs6 } from "node:util";
45079
+ init_settings_manager();
45080
+ init_utils3();
44830
45081
  function normalizePlanIdArgs(args) {
44831
45082
  const planIdFlags = new Set(["--approve", "--reject", "--cancel", "--show"]);
44832
45083
  const result = [];
@@ -44845,7 +45096,7 @@ function normalizePlanIdArgs(args) {
44845
45096
  }
44846
45097
  async function planCommand(args) {
44847
45098
  const normalizedArgs = normalizePlanIdArgs(args);
44848
- const { values, positionals } = parseArgs5({
45099
+ const { values, positionals } = parseArgs6({
44849
45100
  args: normalizedArgs,
44850
45101
  options: {
44851
45102
  approve: { type: "string" },
@@ -44932,8 +45183,8 @@ async function planCommand(args) {
44932
45183
  try {
44933
45184
  const result = await meeting.run(directive, feedback);
44934
45185
  planManager.save(result.plan);
44935
- const tempFile = join16(getLocusPath(projectPath, "plansDir"), `${result.plan.id}.json`);
44936
- if (existsSync16(tempFile)) {
45186
+ const tempFile = join17(getLocusPath(projectPath, "plansDir"), `${result.plan.id}.json`);
45187
+ if (existsSync17(tempFile)) {
44937
45188
  unlinkSync5(tempFile);
44938
45189
  }
44939
45190
  console.log(`
@@ -45130,9 +45381,11 @@ function showPlanHelp() {
45130
45381
  }
45131
45382
  // src/commands/review.ts
45132
45383
  init_index_node();
45133
- import { existsSync as existsSync17, mkdirSync as mkdirSync8, writeFileSync as writeFileSync8 } from "node:fs";
45134
- import { join as join17 } from "node:path";
45135
- import { parseArgs as parseArgs6 } from "node:util";
45384
+ import { existsSync as existsSync18, mkdirSync as mkdirSync8, writeFileSync as writeFileSync8 } from "node:fs";
45385
+ import { join as join18 } from "node:path";
45386
+ import { parseArgs as parseArgs7 } from "node:util";
45387
+ init_settings_manager();
45388
+ init_utils3();
45136
45389
  async function reviewCommand(args) {
45137
45390
  const subcommand = args[0];
45138
45391
  if (subcommand === "local") {
@@ -45141,7 +45394,7 @@ async function reviewCommand(args) {
45141
45394
  return reviewPrsCommand(args);
45142
45395
  }
45143
45396
  async function reviewPrsCommand(args) {
45144
- const { values } = parseArgs6({
45397
+ const { values } = parseArgs7({
45145
45398
  args,
45146
45399
  options: {
45147
45400
  "api-key": { type: "string" },
@@ -45227,7 +45480,7 @@ ${c.info("Received shutdown signal. Stopping reviewer...")}`);
45227
45480
  await reviewer.run();
45228
45481
  }
45229
45482
  async function reviewLocalCommand(args) {
45230
- const { values } = parseArgs6({
45483
+ const { values } = parseArgs7({
45231
45484
  args,
45232
45485
  options: {
45233
45486
  model: { type: "string" },
@@ -45270,12 +45523,12 @@ async function reviewLocalCommand(args) {
45270
45523
  `);
45271
45524
  return;
45272
45525
  }
45273
- const reviewsDir = join17(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.reviewsDir);
45274
- if (!existsSync17(reviewsDir)) {
45526
+ const reviewsDir = join18(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.reviewsDir);
45527
+ if (!existsSync18(reviewsDir)) {
45275
45528
  mkdirSync8(reviewsDir, { recursive: true });
45276
45529
  }
45277
45530
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
45278
- const reportPath = join17(reviewsDir, `review-${timestamp}.md`);
45531
+ const reportPath = join18(reviewsDir, `review-${timestamp}.md`);
45279
45532
  writeFileSync8(reportPath, report, "utf-8");
45280
45533
  console.log(`
45281
45534
  ${c.success("✔")} ${c.success("Review complete!")}`);
@@ -45284,9 +45537,11 @@ async function reviewLocalCommand(args) {
45284
45537
  }
45285
45538
  // src/commands/run.ts
45286
45539
  init_index_node();
45287
- import { parseArgs as parseArgs7 } from "node:util";
45540
+ import { parseArgs as parseArgs8 } from "node:util";
45541
+ init_settings_manager();
45542
+ init_utils3();
45288
45543
  async function runCommand(args) {
45289
- const { values } = parseArgs7({
45544
+ const { values } = parseArgs8({
45290
45545
  args,
45291
45546
  options: {
45292
45547
  "api-key": { type: "string" },
@@ -45367,9 +45622,10 @@ ${c.info(`Received ${signal}. Stopping agent and cleaning up...`)}`);
45367
45622
  }
45368
45623
  // src/commands/telegram.ts
45369
45624
  init_index_node();
45625
+ init_settings_manager();
45370
45626
  import { spawn as spawn4 } from "node:child_process";
45371
- import { existsSync as existsSync18 } from "node:fs";
45372
- import { join as join18 } from "node:path";
45627
+ import { existsSync as existsSync19 } from "node:fs";
45628
+ import { join as join19 } from "node:path";
45373
45629
  import { createInterface as createInterface4 } from "node:readline";
45374
45630
  function ask2(question) {
45375
45631
  const rl = createInterface4({
@@ -45603,8 +45859,8 @@ function runBotCommand(projectPath) {
45603
45859
  `);
45604
45860
  process.exit(1);
45605
45861
  }
45606
- const monorepoTelegramEntry = join18(projectPath, "packages/telegram/src/index.ts");
45607
- const isMonorepo = existsSync18(monorepoTelegramEntry);
45862
+ const monorepoTelegramEntry = join19(projectPath, "packages/telegram/src/index.ts");
45863
+ const isMonorepo = existsSync19(monorepoTelegramEntry);
45608
45864
  let cmd;
45609
45865
  let args;
45610
45866
  if (isMonorepo) {
@@ -45733,6 +45989,7 @@ async function upgradeCommand() {
45733
45989
  }
45734
45990
  // src/commands/version.ts
45735
45991
  init_index_node();
45992
+ init_utils3();
45736
45993
  import { execSync as execSync4 } from "node:child_process";
45737
45994
  function getTelegramVersion() {
45738
45995
  try {
@@ -45759,6 +46016,7 @@ function versionCommand() {
45759
46016
  console.log("");
45760
46017
  }
45761
46018
  // src/cli.ts
46019
+ init_utils3();
45762
46020
  var isJsonStream = process.argv.includes("--json-stream");
45763
46021
  async function main() {
45764
46022
  const command = process.argv[2];
@@ -45783,6 +46041,9 @@ async function main() {
45783
46041
  case "exec":
45784
46042
  await execCommand(args);
45785
46043
  break;
46044
+ case "artifacts":
46045
+ await artifactsCommand(args);
46046
+ break;
45786
46047
  case "discuss":
45787
46048
  await discussCommand(args);
45788
46049
  break;