@lovelybunch/api 1.0.69-alpha.17 → 1.0.69-alpha.18

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.
@@ -2,7 +2,11 @@ import { AuthConfig, LocalAuthUser, AuthSession, ApiKey, UserRole } from '@lovel
2
2
  export declare class AuthManager {
3
3
  private authConfigPath;
4
4
  private authConfig;
5
- constructor(dataPath: string);
5
+ /**
6
+ * Create an AuthManager instance.
7
+ * @param configPath - Optional custom path to auth.json. If not provided, uses OS app data directory.
8
+ */
9
+ constructor(configPath?: string);
6
10
  /**
7
11
  * Check if auth is enabled
8
12
  */
@@ -119,4 +123,8 @@ export declare class AuthManager {
119
123
  */
120
124
  clearCache(): void;
121
125
  }
122
- export declare function getAuthManager(dataPath?: string): AuthManager;
126
+ /**
127
+ * Get the singleton AuthManager instance.
128
+ * @param configPath - Optional custom path to auth.json. If not provided, uses OS app data directory.
129
+ */
130
+ export declare function getAuthManager(configPath?: string): AuthManager;
@@ -3,14 +3,19 @@ import path from 'path';
3
3
  import bcrypt from 'bcrypt';
4
4
  import jwt from 'jsonwebtoken';
5
5
  import crypto from 'crypto';
6
+ import { getAuthConfigPath } from '@lovelybunch/core';
6
7
  const SALT_ROUNDS = 10;
7
8
  const DEFAULT_SESSION_EXPIRY = '7d';
8
9
  const DEFAULT_COOKIE_NAME = 'nut-session';
9
10
  export class AuthManager {
10
11
  authConfigPath;
11
12
  authConfig = null;
12
- constructor(dataPath) {
13
- this.authConfigPath = path.join(dataPath, '.nut', 'auth.json');
13
+ /**
14
+ * Create an AuthManager instance.
15
+ * @param configPath - Optional custom path to auth.json. If not provided, uses OS app data directory.
16
+ */
17
+ constructor(configPath) {
18
+ this.authConfigPath = configPath ?? getAuthConfigPath();
14
19
  }
15
20
  /**
16
21
  * Check if auth is enabled
@@ -45,6 +50,9 @@ export class AuthManager {
45
50
  * Save auth config to file
46
51
  */
47
52
  async saveAuthConfig(config) {
53
+ // Ensure the directory exists
54
+ const dir = path.dirname(this.authConfigPath);
55
+ await fs.mkdir(dir, { recursive: true });
48
56
  await fs.writeFile(this.authConfigPath, JSON.stringify(config, null, 2), 'utf-8');
49
57
  this.authConfig = config;
50
58
  }
@@ -402,10 +410,13 @@ export class AuthManager {
402
410
  }
403
411
  // Singleton instance
404
412
  let authManagerInstance = null;
405
- export function getAuthManager(dataPath) {
413
+ /**
414
+ * Get the singleton AuthManager instance.
415
+ * @param configPath - Optional custom path to auth.json. If not provided, uses OS app data directory.
416
+ */
417
+ export function getAuthManager(configPath) {
406
418
  if (!authManagerInstance) {
407
- const path = dataPath || process.env.GAIT_DATA_PATH || process.cwd();
408
- authManagerInstance = new AuthManager(path);
419
+ authManagerInstance = new AuthManager(configPath);
409
420
  }
410
421
  return authManagerInstance;
411
422
  }
package/dist/lib/git.d.ts CHANGED
@@ -44,6 +44,7 @@ export declare function getCredentialConfig(): Promise<{
44
44
  origin?: string;
45
45
  }>;
46
46
  export declare function setRemoteUrl(remoteUrl: string): Promise<void>;
47
+ export declare function removeRemote(): Promise<void>;
47
48
  export declare function storeCredentials(username: string, password: string, remoteUrl?: string): Promise<void>;
48
49
  export interface WorktreeInfo {
49
50
  name: string;
package/dist/lib/git.js CHANGED
@@ -158,7 +158,8 @@ export async function pushCurrent() {
158
158
  console.error('[git] Error reading GitHub token:', tokenError);
159
159
  }
160
160
  console.log('[git] Executing git push...');
161
- const { stdout } = await runGit(['push'], { timeout: 30000 }); // 30 second timeout for push
161
+ // Use -u to set upstream tracking if not already set (harmless if already configured)
162
+ const { stdout } = await runGit(['push', '-u', 'origin', 'HEAD'], { timeout: 30000 }); // 30 second timeout for push
162
163
  console.log('[git] Push completed successfully');
163
164
  return stdout;
164
165
  }
@@ -257,6 +258,18 @@ export async function setRemoteUrl(remoteUrl) {
257
258
  await runGit(['remote', 'add', 'origin', trimmed]);
258
259
  }
259
260
  }
261
+ export async function removeRemote() {
262
+ // Check if origin exists
263
+ try {
264
+ await runGit(['config', '--get', 'remote.origin.url']);
265
+ // If we get here, origin exists, so remove it
266
+ await runGit(['remote', 'remove', 'origin']);
267
+ }
268
+ catch {
269
+ // Origin doesn't exist, nothing to do
270
+ throw new Error('No remote configured');
271
+ }
272
+ }
260
273
  export async function storeCredentials(username, password, remoteUrl) {
261
274
  // If a remote URL is provided, set it first
262
275
  if (remoteUrl && remoteUrl.trim()) {
@@ -46,11 +46,12 @@ function buildCommand(agent, instruction, config) {
46
46
  break;
47
47
  }
48
48
  case 'droid': {
49
- // For Factory Droid, use the --mcp flag approach if supported
49
+ // For Factory Droid, use exec mode with --auto high for non-interactive scheduled jobs
50
+ // See: https://docs.factory.ai/reference/cli-reference#autonomy-levels
50
51
  const mcpFlags = config.mcpServers && config.mcpServers.length > 0
51
52
  ? config.mcpServers.map(server => `--mcp ${shellQuote(server)}`).join(' ')
52
53
  : '';
53
- mainCommand = `droid ${quotedInstruction} --skip-permissions-unsafe ${mcpFlags}`.trim();
54
+ mainCommand = `droid exec --auto high ${mcpFlags} ${quotedInstruction}`.trim();
54
55
  break;
55
56
  }
56
57
  case 'claude':
@@ -7,8 +7,6 @@ import { Context } from "hono";
7
7
  * Get logging system status and configuration
8
8
  */
9
9
  export declare function GET(c: Context): Promise<(Response & import("hono").TypedResponse<{
10
- error: string;
11
- }, 404, "json">) | (Response & import("hono").TypedResponse<{
12
10
  currentFile: string;
13
11
  sizeBytes: number;
14
12
  lastSeq: number;
@@ -2,28 +2,16 @@
2
2
  * Events status endpoint
3
3
  */
4
4
  import { getLogger } from "@lovelybunch/core/logging";
5
+ import { getLogsDir } from "@lovelybunch/core";
5
6
  import { promises as fs } from "fs";
6
7
  import path from "path";
7
- import { findGaitDirectory } from "../../../../../lib/gait-path.js";
8
- /**
9
- * Get the events directory path
10
- */
11
- async function getEventsDir() {
12
- const gaitDir = await findGaitDirectory();
13
- if (!gaitDir)
14
- return null;
15
- return path.join(gaitDir, "logs");
16
- }
17
8
  /**
18
9
  * GET /api/v1/events/status
19
10
  * Get logging system status and configuration
20
11
  */
21
12
  export async function GET(c) {
22
13
  try {
23
- const eventsDir = await getEventsDir();
24
- if (!eventsDir) {
25
- return c.json({ error: "Events directory not found" }, 404);
26
- }
14
+ const eventsDir = getLogsDir();
27
15
  const logger = getLogger();
28
16
  const currentFile = path.join(eventsDir, "events-current.jsonl");
29
17
  let sizeBytes = 0;
@@ -111,6 +111,17 @@ app.put('/remote', async (c) => {
111
111
  return c.json({ success: false, error: { message: e.message } }, 500);
112
112
  }
113
113
  });
114
+ // Delete Remote URL
115
+ app.delete('/remote', async (c) => {
116
+ try {
117
+ const { removeRemote } = await import('../../../../lib/git.js');
118
+ await removeRemote();
119
+ return c.json({ success: true, data: { message: 'Remote URL removed successfully' } });
120
+ }
121
+ catch (e) {
122
+ return c.json({ success: false, error: { message: e.message } }, 500);
123
+ }
124
+ });
114
125
  // Store Credentials
115
126
  app.post('/credentials', async (c) => {
116
127
  try {
@@ -402,13 +413,19 @@ app.post('/discard', async (c) => {
402
413
  const statusCode = fileChange.status.trim();
403
414
  // Handle different file statuses
404
415
  if (statusCode === '??' || statusCode.includes('U')) {
405
- // Untracked file - remove it
406
- const { unlink } = await import('fs/promises');
416
+ // Untracked file or directory - remove it
417
+ const { rm, stat } = await import('fs/promises');
407
418
  const { getRepoRoot } = await import('../../../../lib/git.js');
408
419
  const repoRoot = await getRepoRoot();
409
420
  const { join } = await import('path');
410
421
  const fullPath = join(repoRoot, filePath);
411
- await unlink(fullPath);
422
+ const stats = await stat(fullPath);
423
+ if (stats.isDirectory()) {
424
+ await rm(fullPath, { recursive: true });
425
+ }
426
+ else {
427
+ await rm(fullPath);
428
+ }
412
429
  }
413
430
  else if (statusCode.includes('D')) {
414
431
  // Deleted file - restore it
@@ -12,8 +12,8 @@ export declare function POST(c: Context): Promise<(Response & import("hono").Typ
12
12
  run: {
13
13
  id: string;
14
14
  jobId: string;
15
- trigger: import("@lovelybunch/types").ScheduledJobTrigger;
16
- status: import("@lovelybunch/types").ScheduledJobRunStatus;
15
+ trigger: import("@lovelybunch/core").ScheduledJobTrigger;
16
+ status: import("@lovelybunch/core").ScheduledJobRunStatus;
17
17
  startedAt: string;
18
18
  finishedAt?: string;
19
19
  outputPath?: string;
@@ -7,7 +7,7 @@ export declare function GET(c: Context): Promise<(Response & import("hono").Type
7
7
  runningCount: number;
8
8
  jobs: {
9
9
  id: string;
10
- status: import("@lovelybunch/types").ScheduledJobStatus;
10
+ status: import("@lovelybunch/core").ScheduledJobStatus;
11
11
  nextRunAt?: string;
12
12
  lastRunAt?: string;
13
13
  timerActive: boolean;
@@ -12,7 +12,7 @@ export declare function GET(c: Context): Promise<(Response & import("hono").Type
12
12
  intent: string;
13
13
  content?: string;
14
14
  author: {
15
- type: import("@lovelybunch/types").AuthorType;
15
+ type: import("@lovelybunch/core").AuthorType;
16
16
  id: string;
17
17
  name: string;
18
18
  email?: string;
@@ -47,7 +47,7 @@ export declare function GET(c: Context): Promise<(Response & import("hono").Type
47
47
  version: string;
48
48
  name: string;
49
49
  description: string;
50
- type: import("@lovelybunch/types").FeatureFlagType;
50
+ type: import("@lovelybunch/core").FeatureFlagType;
51
51
  defaultValue: any;
52
52
  scopes: string[];
53
53
  targets: {
@@ -95,7 +95,7 @@ export declare function GET(c: Context): Promise<(Response & import("hono").Type
95
95
  minimumSampleSize: number;
96
96
  testType: "two-tailed" | "one-tailed";
97
97
  };
98
- status: import("@lovelybunch/types").ExperimentStatus;
98
+ status: import("@lovelybunch/core").ExperimentStatus;
99
99
  startedAt?: string;
100
100
  endedAt?: string;
101
101
  }[];
@@ -133,7 +133,7 @@ export declare function GET(c: Context): Promise<(Response & import("hono").Type
133
133
  schedule?: string;
134
134
  rollbackPlan?: string;
135
135
  };
136
- status: import("@lovelybunch/types").CPStatus;
136
+ status: import("@lovelybunch/core").CPStatus;
137
137
  metadata: {
138
138
  createdAt: string;
139
139
  updatedAt: string;
@@ -169,7 +169,7 @@ export declare function PATCH(c: Context): Promise<(Response & import("hono").Ty
169
169
  intent: string;
170
170
  content?: string;
171
171
  author: {
172
- type: import("@lovelybunch/types").AuthorType;
172
+ type: import("@lovelybunch/core").AuthorType;
173
173
  id: string;
174
174
  name: string;
175
175
  email?: string;
@@ -204,7 +204,7 @@ export declare function PATCH(c: Context): Promise<(Response & import("hono").Ty
204
204
  version: string;
205
205
  name: string;
206
206
  description: string;
207
- type: import("@lovelybunch/types").FeatureFlagType;
207
+ type: import("@lovelybunch/core").FeatureFlagType;
208
208
  defaultValue: any;
209
209
  scopes: string[];
210
210
  targets: {
@@ -252,7 +252,7 @@ export declare function PATCH(c: Context): Promise<(Response & import("hono").Ty
252
252
  minimumSampleSize: number;
253
253
  testType: "two-tailed" | "one-tailed";
254
254
  };
255
- status: import("@lovelybunch/types").ExperimentStatus;
255
+ status: import("@lovelybunch/core").ExperimentStatus;
256
256
  startedAt?: string;
257
257
  endedAt?: string;
258
258
  }[];
@@ -290,7 +290,7 @@ export declare function PATCH(c: Context): Promise<(Response & import("hono").Ty
290
290
  schedule?: string;
291
291
  rollbackPlan?: string;
292
292
  };
293
- status: import("@lovelybunch/types").CPStatus;
293
+ status: import("@lovelybunch/core").CPStatus;
294
294
  metadata: {
295
295
  createdAt: string;
296
296
  updatedAt: string;
@@ -11,6 +11,7 @@ import { getGlobalTerminalManager } from './lib/terminal/global-manager.js';
11
11
  import { getGlobalJobScheduler } from './lib/jobs/global-job-scheduler.js';
12
12
  import { fileURLToPath } from 'url';
13
13
  import { getLogger } from '@lovelybunch/core/logging';
14
+ import { getLogsDir } from '@lovelybunch/core';
14
15
  const __filename = fileURLToPath(import.meta.url);
15
16
  const __dirname = path.dirname(__filename);
16
17
  // Load environment variables from .env file in project root
@@ -30,11 +31,15 @@ function findNutDirectorySync() {
30
31
  }
31
32
  return null;
32
33
  }
33
- // Initialize logger with config from .nut/config.json
34
+ // Initialize logger with config from .nut/config.json or use OS app data directory
34
35
  // This must happen BEFORE importing route handlers (they call getLogger at module level)
35
36
  console.log('🔍 Initializing activity logging...');
36
37
  try {
37
38
  const nutDir = findNutDirectorySync();
39
+ let logsDir = getLogsDir(); // Default to OS app data directory
40
+ let coconutId = 'unknown.coconut';
41
+ let rotateBytes = 128 * 1024 * 1024;
42
+ let loggingEnabled = true; // Default to enabled
38
43
  if (nutDir) {
39
44
  const projectRoot = path.dirname(nutDir);
40
45
  const configPath = path.join(nutDir, 'config.json');
@@ -42,32 +47,42 @@ try {
42
47
  console.log(' Config path:', configPath);
43
48
  const configData = fs.readFileSync(configPath, 'utf-8');
44
49
  const config = JSON.parse(configData);
45
- if (config.logging?.enabled) {
46
- const logsDir = path.resolve(projectRoot, config.logging?.location || '.nut/logs');
47
- const logger = getLogger({
48
- coconutId: config.coconut?.id || 'unknown.coconut',
49
- logsDir: logsDir,
50
- rotateBytes: config.logging?.rotateBytes || 128 * 1024 * 1024
51
- });
52
- console.log('📝 Activity logging ENABLED');
53
- console.log(' Logs directory:', logsDir);
54
- console.log(' Coconut ID:', config.coconut?.id || 'unknown.coconut');
55
- // Test log immediately
56
- logger.log({
57
- kind: 'system.startup',
58
- actor: 'system',
59
- subject: 'server',
60
- tags: ['system', 'startup'],
61
- payload: { message: 'Server starting with logging enabled' }
62
- });
63
- console.log(' ✓ Test event logged');
50
+ // Check if logging is explicitly disabled in config
51
+ if (config.logging?.enabled === false) {
52
+ loggingEnabled = false;
64
53
  }
65
- else {
66
- console.log('📝 Activity logging disabled in config');
54
+ // Allow config to override logs location (relative paths resolve from project root)
55
+ if (config.logging?.location) {
56
+ logsDir = path.resolve(projectRoot, config.logging.location);
67
57
  }
58
+ if (config.coconut?.id) {
59
+ coconutId = config.coconut.id;
60
+ }
61
+ if (config.logging?.rotateBytes) {
62
+ rotateBytes = config.logging.rotateBytes;
63
+ }
64
+ }
65
+ if (loggingEnabled) {
66
+ const logger = getLogger({
67
+ coconutId,
68
+ logsDir,
69
+ rotateBytes
70
+ });
71
+ console.log('📝 Activity logging ENABLED');
72
+ console.log(' Logs directory:', logsDir);
73
+ console.log(' Coconut ID:', coconutId);
74
+ // Test log immediately
75
+ logger.log({
76
+ kind: 'system.startup',
77
+ actor: 'system',
78
+ subject: 'server',
79
+ tags: ['system', 'startup'],
80
+ payload: { message: 'Server starting with logging enabled' }
81
+ });
82
+ console.log(' ✓ Test event logged');
68
83
  }
69
84
  else {
70
- console.log(' .nut directory not found');
85
+ console.log('📝 Activity logging disabled in config');
71
86
  }
72
87
  }
73
88
  catch (error) {
package/dist/server.js CHANGED
@@ -10,6 +10,7 @@ import path from 'path';
10
10
  import { fileURLToPath } from 'url';
11
11
  import fs from 'fs';
12
12
  import { getLogger } from '@lovelybunch/core/logging';
13
+ import { getLogsDir } from '@lovelybunch/core';
13
14
  const __filename = fileURLToPath(import.meta.url);
14
15
  const __dirname = path.dirname(__filename);
15
16
  // Load environment variables from .env file in project root
@@ -29,11 +30,15 @@ function findNutDirectorySync() {
29
30
  }
30
31
  return null;
31
32
  }
32
- // Initialize logger with config from .nut/config.json
33
+ // Initialize logger with config from .nut/config.json or use OS app data directory
33
34
  // This must happen BEFORE importing route handlers (they call getLogger at module level)
34
35
  console.log('🔍 Initializing activity logging...');
35
36
  try {
36
37
  const nutDir = findNutDirectorySync();
38
+ let logsDir = getLogsDir(); // Default to OS app data directory
39
+ let coconutId = 'unknown.coconut';
40
+ let rotateBytes = 128 * 1024 * 1024;
41
+ let loggingEnabled = true; // Default to enabled
37
42
  if (nutDir) {
38
43
  const projectRoot = path.dirname(nutDir);
39
44
  const configPath = path.join(nutDir, 'config.json');
@@ -41,32 +46,42 @@ try {
41
46
  console.log(' Config path:', configPath);
42
47
  const configData = fs.readFileSync(configPath, 'utf-8');
43
48
  const config = JSON.parse(configData);
44
- if (config.logging?.enabled) {
45
- const logsDir = path.resolve(projectRoot, config.logging?.location || '.nut/logs');
46
- const logger = getLogger({
47
- coconutId: config.coconut?.id || 'unknown.coconut',
48
- logsDir: logsDir,
49
- rotateBytes: config.logging?.rotateBytes || 128 * 1024 * 1024
50
- });
51
- console.log('📝 Activity logging ENABLED');
52
- console.log(' Logs directory:', logsDir);
53
- console.log(' Coconut ID:', config.coconut?.id || 'unknown.coconut');
54
- // Test log immediately
55
- logger.log({
56
- kind: 'system.startup',
57
- actor: 'system',
58
- subject: 'server',
59
- tags: ['system', 'startup'],
60
- payload: { message: 'Server starting with logging enabled' }
61
- });
62
- console.log(' ✓ Test event logged');
49
+ // Check if logging is explicitly disabled in config
50
+ if (config.logging?.enabled === false) {
51
+ loggingEnabled = false;
63
52
  }
64
- else {
65
- console.log('📝 Activity logging disabled in config');
53
+ // Allow config to override logs location (relative paths resolve from project root)
54
+ if (config.logging?.location) {
55
+ logsDir = path.resolve(projectRoot, config.logging.location);
66
56
  }
57
+ if (config.coconut?.id) {
58
+ coconutId = config.coconut.id;
59
+ }
60
+ if (config.logging?.rotateBytes) {
61
+ rotateBytes = config.logging.rotateBytes;
62
+ }
63
+ }
64
+ if (loggingEnabled) {
65
+ const logger = getLogger({
66
+ coconutId,
67
+ logsDir,
68
+ rotateBytes
69
+ });
70
+ console.log('📝 Activity logging ENABLED');
71
+ console.log(' Logs directory:', logsDir);
72
+ console.log(' Coconut ID:', coconutId);
73
+ // Test log immediately
74
+ logger.log({
75
+ kind: 'system.startup',
76
+ actor: 'system',
77
+ subject: 'server',
78
+ tags: ['system', 'startup'],
79
+ payload: { message: 'Server starting with logging enabled' }
80
+ });
81
+ console.log(' ✓ Test event logged');
67
82
  }
68
83
  else {
69
- console.log(' .nut directory not found');
84
+ console.log('📝 Activity logging disabled in config');
70
85
  }
71
86
  }
72
87
  catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lovelybunch/api",
3
- "version": "1.0.69-alpha.17",
3
+ "version": "1.0.69-alpha.18",
4
4
  "type": "module",
5
5
  "main": "dist/server-with-static.js",
6
6
  "exports": {
@@ -36,9 +36,9 @@
36
36
  "dependencies": {
37
37
  "@hono/node-server": "^1.13.7",
38
38
  "@hono/node-ws": "^1.0.6",
39
- "@lovelybunch/core": "^1.0.69-alpha.17",
40
- "@lovelybunch/mcp": "^1.0.69-alpha.17",
41
- "@lovelybunch/types": "^1.0.69-alpha.17",
39
+ "@lovelybunch/core": "^1.0.69-alpha.18",
40
+ "@lovelybunch/mcp": "^1.0.69-alpha.18",
41
+ "@lovelybunch/types": "^1.0.69-alpha.18",
42
42
  "arctic": "^1.9.2",
43
43
  "bcrypt": "^5.1.1",
44
44
  "cookie": "^0.6.0",