@northflare/runner 0.0.24 → 0.0.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/northflare-runner +225 -163
- package/dist/components/claude-sdk-manager.d.ts +6 -0
- package/dist/components/claude-sdk-manager.d.ts.map +1 -1
- package/dist/components/claude-sdk-manager.js +46 -5
- package/dist/components/claude-sdk-manager.js.map +1 -1
- package/dist/components/message-handler-sse.d.ts.map +1 -1
- package/dist/components/message-handler-sse.js +48 -56
- package/dist/components/message-handler-sse.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +45 -6
- package/dist/index.js.map +1 -1
- package/dist/runner-sse.d.ts +9 -6
- package/dist/runner-sse.d.ts.map +1 -1
- package/dist/runner-sse.js +124 -112
- package/dist/runner-sse.js.map +1 -1
- package/dist/services/RunnerAPIClient.d.ts +19 -0
- package/dist/services/RunnerAPIClient.d.ts.map +1 -1
- package/dist/services/RunnerAPIClient.js +44 -0
- package/dist/services/RunnerAPIClient.js.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/runner-interface.d.ts +3 -1
- package/dist/types/runner-interface.d.ts.map +1 -1
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +42 -19
- package/dist/utils/config.js.map +1 -1
- package/package.json +1 -1
package/bin/northflare-runner
CHANGED
|
@@ -9,6 +9,7 @@ import path from "path";
|
|
|
9
9
|
import { fileURLToPath } from "url";
|
|
10
10
|
import { createRequire } from "module";
|
|
11
11
|
import fs from "fs/promises";
|
|
12
|
+
import { existsSync } from "fs";
|
|
12
13
|
import crypto from "crypto";
|
|
13
14
|
import envPaths from "env-paths";
|
|
14
15
|
|
|
@@ -16,8 +17,26 @@ import envPaths from "env-paths";
|
|
|
16
17
|
const require = createRequire(import.meta.url);
|
|
17
18
|
const pkg = require("../package.json");
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
function resolveEnvPathsAppName() {
|
|
21
|
+
if (process.env["NODE_ENV"] === "development") return "northflare-runner-dev";
|
|
22
|
+
|
|
23
|
+
// Heuristic: treat as local workspace when pnpm-workspace.yaml sits two levels up (repo root)
|
|
24
|
+
try {
|
|
25
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
const repoRoot = path.resolve(__dirname, "../..");
|
|
27
|
+
if (existsSync(path.join(repoRoot, "pnpm-workspace.yaml"))) {
|
|
28
|
+
return "northflare-runner-dev";
|
|
29
|
+
}
|
|
30
|
+
} catch {
|
|
31
|
+
// ignore and fall back
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return "northflare-runner";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const ENV_PATHS_APP_NAME = resolveEnvPathsAppName();
|
|
20
38
|
const ENV_PATHS_OPTIONS = { suffix: "" };
|
|
39
|
+
const IS_DEV_APP = ENV_PATHS_APP_NAME.endsWith("-dev");
|
|
21
40
|
|
|
22
41
|
let DEFAULT_DATA_DIR;
|
|
23
42
|
try {
|
|
@@ -28,7 +47,7 @@ try {
|
|
|
28
47
|
error
|
|
29
48
|
);
|
|
30
49
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
31
|
-
DEFAULT_DATA_DIR = path.resolve(__dirname, "../data");
|
|
50
|
+
DEFAULT_DATA_DIR = path.resolve(__dirname, IS_DEV_APP ? "../data/dev" : "../data");
|
|
32
51
|
}
|
|
33
52
|
|
|
34
53
|
/**
|
|
@@ -113,6 +132,31 @@ const startCommand = new Command("start")
|
|
|
113
132
|
process.env.NORTHFLARE_RUNNER_INACTIVITY_TIMEOUT = inactivityTimeout;
|
|
114
133
|
}
|
|
115
134
|
|
|
135
|
+
const debugEnabled =
|
|
136
|
+
!!process.env.DEBUG || !!process.env.NORTHFLARE_RUNNER_DEBUG;
|
|
137
|
+
// Determine config path for logging purposes (actual load happens in main)
|
|
138
|
+
let resolvedConfigPath =
|
|
139
|
+
options.config || command.parent?.opts().config || null;
|
|
140
|
+
if (!resolvedConfigPath) {
|
|
141
|
+
try {
|
|
142
|
+
const paths = envPaths(ENV_PATHS_APP_NAME, ENV_PATHS_OPTIONS);
|
|
143
|
+
resolvedConfigPath = path.join(paths.config, "config.json");
|
|
144
|
+
} catch {
|
|
145
|
+
const localDir = IS_DEV_APP ? "./config/dev" : "./config";
|
|
146
|
+
resolvedConfigPath = path.resolve(localDir, "config.json");
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (debugEnabled) {
|
|
150
|
+
console.log(
|
|
151
|
+
"[start] Config file location:",
|
|
152
|
+
path.resolve(resolvedConfigPath)
|
|
153
|
+
);
|
|
154
|
+
console.log(
|
|
155
|
+
"[start] Orchestrator URL (env):",
|
|
156
|
+
process.env.NORTHFLARE_ORCHESTRATOR_URL || "(not set)"
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
116
160
|
// Load the main module with config path if provided
|
|
117
161
|
// Clear argv[2] since it contains 'start' command
|
|
118
162
|
process.argv.splice(2);
|
|
@@ -190,70 +234,54 @@ const listReposCommand = new Command("list-repos")
|
|
|
190
234
|
process.exit(1);
|
|
191
235
|
}
|
|
192
236
|
|
|
193
|
-
//
|
|
194
|
-
const paths = envPaths("northflare-runner", { suffix: "" });
|
|
195
|
-
|
|
196
|
-
// Determine config path from command options, parent options, or default
|
|
197
|
-
const configOption = options.config || command.parent?.opts().config;
|
|
198
|
-
let configPath;
|
|
199
|
-
if (configOption) {
|
|
200
|
-
configPath = path.resolve(configOption);
|
|
201
|
-
} else {
|
|
202
|
-
configPath = path.join(paths.config, "config.json");
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Load config file
|
|
237
|
+
// Optional config load for orchestrator URL
|
|
206
238
|
let config = {};
|
|
239
|
+
const paths = envPaths(ENV_PATHS_APP_NAME, ENV_PATHS_OPTIONS);
|
|
240
|
+
const configOption = options.config || command.parent?.opts().config;
|
|
241
|
+
const configPath = configOption
|
|
242
|
+
? path.resolve(configOption)
|
|
243
|
+
: path.join(paths.config, "config.json");
|
|
207
244
|
try {
|
|
208
245
|
const content = await fs.readFile(configPath, "utf-8");
|
|
209
246
|
config = JSON.parse(content);
|
|
210
247
|
} catch (error) {
|
|
211
|
-
if (error.code
|
|
212
|
-
|
|
213
|
-
console.error(`Configuration file not found: ${configPath}`);
|
|
214
|
-
process.exit(1);
|
|
215
|
-
} else {
|
|
216
|
-
console.log(
|
|
217
|
-
`No configuration file found at default location: ${configPath}`
|
|
218
|
-
);
|
|
219
|
-
console.log(
|
|
220
|
-
"Use -c/--config to specify a config file or add-repo to create one."
|
|
221
|
-
);
|
|
222
|
-
process.exit(0);
|
|
223
|
-
}
|
|
248
|
+
if (error.code !== "ENOENT") {
|
|
249
|
+
throw error;
|
|
224
250
|
}
|
|
225
|
-
throw error;
|
|
226
251
|
}
|
|
227
252
|
|
|
228
|
-
// Log config file location in debug mode
|
|
229
|
-
if (process.env.DEBUG) {
|
|
230
|
-
console.log(`Config file location: ${configPath}`);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Get orchestrator URL from config or environment
|
|
234
253
|
const orchestratorUrl =
|
|
235
|
-
config.orchestratorUrl ||
|
|
236
254
|
process.env.NORTHFLARE_ORCHESTRATOR_URL ||
|
|
255
|
+
config.orchestratorUrl ||
|
|
237
256
|
"https://api.northflare.app";
|
|
238
257
|
|
|
239
258
|
// Fetch runner ID
|
|
240
259
|
const runnerId = await fetchRunnerId(orchestratorUrl, token);
|
|
241
260
|
console.log(`Runner ID: ${runnerId}\n`);
|
|
242
261
|
|
|
243
|
-
//
|
|
244
|
-
|
|
245
|
-
|
|
262
|
+
// Fetch snapshot from server
|
|
263
|
+
const resp = await fetch(`${orchestratorUrl}/api/runner/repos`, {
|
|
264
|
+
method: "GET",
|
|
265
|
+
headers: {
|
|
266
|
+
Authorization: `Bearer ${token}`,
|
|
267
|
+
"X-Runner-Id": runnerId,
|
|
268
|
+
"Content-Type": "application/json",
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
if (!resp.ok) {
|
|
273
|
+
const error = await resp.text();
|
|
274
|
+
throw new Error(`Failed to fetch runner repos: ${resp.status} - ${error}`);
|
|
246
275
|
}
|
|
247
276
|
|
|
248
|
-
|
|
249
|
-
const repos =
|
|
277
|
+
const data = await resp.json();
|
|
278
|
+
const repos = Array.isArray(data.repos) ? data.repos : [];
|
|
250
279
|
|
|
251
|
-
// List runner repos
|
|
252
280
|
if (repos.length > 0) {
|
|
253
281
|
console.log(`Found ${repos.length} runner repositories:`);
|
|
254
282
|
repos.forEach((repo, index) => {
|
|
255
283
|
console.log(
|
|
256
|
-
`${index + 1}. ${repo.name} (${repo.path})${
|
|
284
|
+
`${index + 1}. ${repo.name} (${repo.runnerPath || repo.path})${
|
|
257
285
|
repo.uuid ? ` [UUID: ${repo.uuid}]` : ""
|
|
258
286
|
}`
|
|
259
287
|
);
|
|
@@ -279,9 +307,17 @@ const addRepoCommand = new Command("add-repo")
|
|
|
279
307
|
)
|
|
280
308
|
.option("-n, --name <name>", "repository name (defaults to folder name)")
|
|
281
309
|
.option("--token <token>", "authentication token")
|
|
310
|
+
.option(
|
|
311
|
+
"-d, --debug",
|
|
312
|
+
"enable verbose logging for add-repo (prints config paths, URLs, etc.)"
|
|
313
|
+
)
|
|
282
314
|
.action(async (repoPath, options, command) => {
|
|
315
|
+
const debugEnabled = Boolean(options.debug || process.env.DEBUG);
|
|
316
|
+
const debug = (...args) => {
|
|
317
|
+
if (debugEnabled) console.log(...args);
|
|
318
|
+
};
|
|
319
|
+
|
|
283
320
|
try {
|
|
284
|
-
// Get token from command options, parent options, or environment
|
|
285
321
|
const token =
|
|
286
322
|
options.token ||
|
|
287
323
|
command.parent?.opts().token ||
|
|
@@ -293,72 +329,33 @@ const addRepoCommand = new Command("add-repo")
|
|
|
293
329
|
process.exit(1);
|
|
294
330
|
}
|
|
295
331
|
|
|
296
|
-
//
|
|
297
|
-
const paths = envPaths(
|
|
298
|
-
|
|
299
|
-
// Determine config path from command options, parent options, or default
|
|
332
|
+
// Optional config for orchestrator/workspace hints
|
|
333
|
+
const paths = envPaths(ENV_PATHS_APP_NAME, ENV_PATHS_OPTIONS);
|
|
300
334
|
const configOption = options.config || command.parent?.opts().config;
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
} else {
|
|
305
|
-
configPath = path.join(paths.config, "config.json");
|
|
306
|
-
// Ensure config directory exists
|
|
307
|
-
await fs.mkdir(paths.config, { recursive: true });
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// Load existing config
|
|
335
|
+
const configPath = configOption
|
|
336
|
+
? path.resolve(configOption)
|
|
337
|
+
: path.join(paths.config, "config.json");
|
|
311
338
|
let config = {};
|
|
312
339
|
try {
|
|
313
340
|
const content = await fs.readFile(configPath, "utf-8");
|
|
314
341
|
config = JSON.parse(content);
|
|
315
342
|
} catch (error) {
|
|
316
|
-
if (error.code
|
|
317
|
-
console.log(
|
|
318
|
-
`Configuration file not found: ${configPath}. Creating new config file.`
|
|
319
|
-
);
|
|
320
|
-
config = {};
|
|
321
|
-
} else {
|
|
322
|
-
throw error;
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
// Log config file location in debug mode
|
|
327
|
-
if (process.env.DEBUG) {
|
|
328
|
-
console.log(`Config file location: ${configPath}`);
|
|
343
|
+
if (error.code !== "ENOENT") throw error;
|
|
329
344
|
}
|
|
330
345
|
|
|
331
|
-
// Get orchestrator URL from config or environment
|
|
332
346
|
const orchestratorUrl =
|
|
333
|
-
config.orchestratorUrl ||
|
|
334
347
|
process.env.NORTHFLARE_ORCHESTRATOR_URL ||
|
|
348
|
+
config.orchestratorUrl ||
|
|
335
349
|
"https://api.northflare.app";
|
|
350
|
+
debug("Orchestrator URL:", orchestratorUrl);
|
|
336
351
|
|
|
337
|
-
// Fetch runner ID
|
|
338
352
|
const runnerId = await fetchRunnerId(orchestratorUrl, token);
|
|
339
353
|
console.log(`Runner ID: ${runnerId}`);
|
|
340
354
|
|
|
341
|
-
// Ensure runnerRepos is properly structured
|
|
342
|
-
if (
|
|
343
|
-
!config.runnerRepos ||
|
|
344
|
-
typeof config.runnerRepos !== "object" ||
|
|
345
|
-
Array.isArray(config.runnerRepos)
|
|
346
|
-
) {
|
|
347
|
-
config.runnerRepos = {};
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
// Initialize repos array for this runner if not present
|
|
351
|
-
if (
|
|
352
|
-
!config.runnerRepos[runnerId] ||
|
|
353
|
-
!Array.isArray(config.runnerRepos[runnerId])
|
|
354
|
-
) {
|
|
355
|
-
config.runnerRepos[runnerId] = [];
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
// Resolve the repository path
|
|
359
355
|
const absoluteRepoPath = path.resolve(repoPath);
|
|
356
|
+
debug("Repository path (absolute):", absoluteRepoPath);
|
|
360
357
|
|
|
361
|
-
//
|
|
358
|
+
// Validate directory
|
|
362
359
|
try {
|
|
363
360
|
const stats = await fs.stat(absoluteRepoPath);
|
|
364
361
|
if (!stats.isDirectory()) {
|
|
@@ -372,82 +369,78 @@ const addRepoCommand = new Command("add-repo")
|
|
|
372
369
|
process.exit(1);
|
|
373
370
|
}
|
|
374
371
|
|
|
375
|
-
// Check for duplicates
|
|
376
|
-
const duplicate = config.runnerRepos[runnerId].find(
|
|
377
|
-
(repo) => repo.path === absoluteRepoPath
|
|
378
|
-
);
|
|
379
|
-
if (duplicate) {
|
|
380
|
-
console.error(
|
|
381
|
-
`Error: Repository already exists in configuration: ${duplicate.name} (${duplicate.path})`
|
|
382
|
-
);
|
|
383
|
-
process.exit(1);
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
// Determine repository name
|
|
387
372
|
const repoName = options.name || path.basename(absoluteRepoPath);
|
|
388
|
-
|
|
389
|
-
// Generate UUID for the repository
|
|
390
373
|
const repoUuid = crypto.randomUUID();
|
|
391
374
|
|
|
392
|
-
//
|
|
393
|
-
const
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
await
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
"Content-Type": "application/json",
|
|
421
|
-
},
|
|
422
|
-
body: JSON.stringify({
|
|
423
|
-
repos: config.runnerRepos[runnerId].map((r) => ({
|
|
424
|
-
uuid: r.uuid,
|
|
425
|
-
name: r.name,
|
|
426
|
-
path: r.path,
|
|
427
|
-
})),
|
|
428
|
-
}),
|
|
429
|
-
});
|
|
430
|
-
|
|
431
|
-
if (!response.ok) {
|
|
432
|
-
const error = await response.text();
|
|
433
|
-
console.warn(
|
|
434
|
-
`Warning: Failed to sync repos with orchestrator: ${error}`
|
|
435
|
-
);
|
|
375
|
+
// Derive workspacePath for external flag
|
|
376
|
+
const workspacePath =
|
|
377
|
+
process.env.NORTHFLARE_WORKSPACE_PATH || config.workspacePath;
|
|
378
|
+
const insideWorkspace =
|
|
379
|
+
workspacePath &&
|
|
380
|
+
path
|
|
381
|
+
.resolve(absoluteRepoPath)
|
|
382
|
+
.startsWith(path.resolve(workspacePath) + path.sep);
|
|
383
|
+
const external = !insideWorkspace;
|
|
384
|
+
|
|
385
|
+
// Preflight snapshot to warn on duplicates
|
|
386
|
+
const snapshotResp = await fetch(`${orchestratorUrl}/api/runner/repos`, {
|
|
387
|
+
method: "GET",
|
|
388
|
+
headers: {
|
|
389
|
+
Authorization: `Bearer ${token}`,
|
|
390
|
+
"X-Runner-Id": runnerId,
|
|
391
|
+
"Content-Type": "application/json",
|
|
392
|
+
},
|
|
393
|
+
});
|
|
394
|
+
let existingRepos = [];
|
|
395
|
+
if (snapshotResp.ok) {
|
|
396
|
+
const data = await snapshotResp.json();
|
|
397
|
+
existingRepos = Array.isArray(data.repos) ? data.repos : [];
|
|
398
|
+
const dup = existingRepos.find(
|
|
399
|
+
(r) =>
|
|
400
|
+
path.resolve(r.runnerPath || r.path || "") === absoluteRepoPath
|
|
401
|
+
);
|
|
402
|
+
if (dup) {
|
|
436
403
|
console.warn(
|
|
437
|
-
|
|
438
|
-
);
|
|
439
|
-
} else {
|
|
440
|
-
const data = await response.json();
|
|
441
|
-
console.log(
|
|
442
|
-
`Successfully synced ${data.syncedCount} repositories with orchestrator`
|
|
404
|
+
`Warning: Repository already exists on server: ${dup.name} (${dup.runnerPath || dup.path})`
|
|
443
405
|
);
|
|
444
406
|
}
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
|
|
407
|
+
} else {
|
|
408
|
+
debug(
|
|
409
|
+
"Snapshot fetch failed (continuing)",
|
|
410
|
+
snapshotResp.status,
|
|
411
|
+
await snapshotResp.text().catch(() => "")
|
|
448
412
|
);
|
|
449
|
-
|
|
450
|
-
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Upsert server-side
|
|
416
|
+
const resp = await fetch(`${orchestratorUrl}/api/runner/repos`, {
|
|
417
|
+
method: "POST",
|
|
418
|
+
headers: {
|
|
419
|
+
Authorization: `Bearer ${token}`,
|
|
420
|
+
"X-Runner-Id": runnerId,
|
|
421
|
+
"Content-Type": "application/json",
|
|
422
|
+
},
|
|
423
|
+
body: JSON.stringify({
|
|
424
|
+
uuid: repoUuid,
|
|
425
|
+
name: repoName,
|
|
426
|
+
path: absoluteRepoPath,
|
|
427
|
+
external,
|
|
428
|
+
}),
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
if (!resp.ok) {
|
|
432
|
+
const error = await resp.text();
|
|
433
|
+
throw new Error(`Failed to upsert repo: ${resp.status} - ${error}`);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const data = await resp.json();
|
|
437
|
+
console.log(
|
|
438
|
+
`Successfully registered repository: ${data.repo?.name || repoName} (${data.repo?.runnerPath || absoluteRepoPath})`
|
|
439
|
+
);
|
|
440
|
+
console.log(`UUID: ${data.repo?.uuid || repoUuid}`);
|
|
441
|
+
if (external) {
|
|
442
|
+
console.log(
|
|
443
|
+
"Marked as external (outside workspacePath) - only added via CLI."
|
|
451
444
|
);
|
|
452
445
|
}
|
|
453
446
|
|
|
@@ -480,6 +473,75 @@ program
|
|
|
480
473
|
.addCommand(startCommand, { isDefault: true })
|
|
481
474
|
.addCommand(validateCommand)
|
|
482
475
|
.addCommand(listReposCommand)
|
|
483
|
-
.addCommand(addRepoCommand)
|
|
476
|
+
.addCommand(addRepoCommand)
|
|
477
|
+
.addCommand(
|
|
478
|
+
new Command("sync-repos")
|
|
479
|
+
.description("pull a fresh runner repo snapshot from server (no config writes)")
|
|
480
|
+
.option("--token <token>", "authentication token")
|
|
481
|
+
.option("-c, --config <path>", "path to configuration file")
|
|
482
|
+
.action(async (options, command) => {
|
|
483
|
+
try {
|
|
484
|
+
const token =
|
|
485
|
+
options.token ||
|
|
486
|
+
command.parent?.opts().token ||
|
|
487
|
+
process.env.NORTHFLARE_RUNNER_TOKEN;
|
|
488
|
+
if (!token) {
|
|
489
|
+
console.error(
|
|
490
|
+
"Error: NORTHFLARE_RUNNER_TOKEN is required. Provide it via --token or environment variable."
|
|
491
|
+
);
|
|
492
|
+
process.exit(1);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
let config = {};
|
|
496
|
+
const paths = envPaths(ENV_PATHS_APP_NAME, ENV_PATHS_OPTIONS);
|
|
497
|
+
const configOption = options.config || command.parent?.opts().config;
|
|
498
|
+
const configPath = configOption
|
|
499
|
+
? path.resolve(configOption)
|
|
500
|
+
: path.join(paths.config, "config.json");
|
|
501
|
+
try {
|
|
502
|
+
const content = await fs.readFile(configPath, "utf-8");
|
|
503
|
+
config = JSON.parse(content);
|
|
504
|
+
} catch (err) {
|
|
505
|
+
if (err.code !== "ENOENT") throw err;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
const orchestratorUrl =
|
|
509
|
+
process.env.NORTHFLARE_ORCHESTRATOR_URL ||
|
|
510
|
+
config.orchestratorUrl ||
|
|
511
|
+
"https://api.northflare.app";
|
|
512
|
+
|
|
513
|
+
const runnerId = await fetchRunnerId(orchestratorUrl, token);
|
|
514
|
+
console.log(`Runner ID: ${runnerId}`);
|
|
515
|
+
|
|
516
|
+
const resp = await fetch(`${orchestratorUrl}/api/runner/repos`, {
|
|
517
|
+
method: "GET",
|
|
518
|
+
headers: {
|
|
519
|
+
Authorization: `Bearer ${token}`,
|
|
520
|
+
"X-Runner-Id": runnerId,
|
|
521
|
+
"Content-Type": "application/json",
|
|
522
|
+
},
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
if (!resp.ok) {
|
|
526
|
+
const error = await resp.text();
|
|
527
|
+
throw new Error(`Failed to fetch runner repos: ${resp.status} - ${error}`);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
const data = await resp.json();
|
|
531
|
+
const repos = Array.isArray(data.repos) ? data.repos : [];
|
|
532
|
+
console.log(`Synced ${repos.length} repos from server snapshot.`);
|
|
533
|
+
repos.forEach((r, idx) => {
|
|
534
|
+
console.log(
|
|
535
|
+
`${idx + 1}. ${r.name} (${r.runnerPath || r.path})${r.external ? " [external]" : ""}`
|
|
536
|
+
);
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
process.exit(0);
|
|
540
|
+
} catch (error) {
|
|
541
|
+
console.error("Error syncing repositories:", error.message);
|
|
542
|
+
process.exit(1);
|
|
543
|
+
}
|
|
544
|
+
})
|
|
545
|
+
);
|
|
484
546
|
|
|
485
547
|
program.parse(process.argv);
|
|
@@ -45,6 +45,12 @@ export declare class ClaudeManager {
|
|
|
45
45
|
* Normalize arbitrary content shapes into a plain string for the CLI
|
|
46
46
|
*/
|
|
47
47
|
private normalizeToText;
|
|
48
|
+
/**
|
|
49
|
+
* Normalize arbitrary content shapes into content blocks array for multimodal support.
|
|
50
|
+
* Returns the array of content blocks if the content contains image blocks,
|
|
51
|
+
* otherwise returns null to indicate text-only content.
|
|
52
|
+
*/
|
|
53
|
+
private normalizeToContentBlocks;
|
|
48
54
|
private handleStreamedMessage;
|
|
49
55
|
}
|
|
50
56
|
//# sourceMappingURL=claude-sdk-manager.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claude-sdk-manager.d.ts","sourceRoot":"","sources":["../../src/components/claude-sdk-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAMH,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC1D,OAAO,EAAE,yBAAyB,EAAE,MAAM,kCAAkC,CAAC;AAC7E,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAuBrF,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,iBAAiB,CAA4B;gBAGnD,MAAM,EAAE,UAAU,EAClB,iBAAiB,EAAE,yBAAyB;IAiB9C,OAAO,CAAC,sBAAsB;IAKxB,iBAAiB,CACrB,sBAAsB,EAAE,MAAM,GAAG,UAAU,GAAG,aAAa,EAC3D,oBAAoB,EAAE,MAAM,EAC5B,MAAM,EAAE,kBAAkB,EAC1B,eAAe,EAAE,OAAO,EAAE,EAC1B,gBAAgB,CAAC,EAAE;QACjB,EAAE,EAAE,MAAM,CAAC;QACX,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,kBAAkB,EAAE,MAAM,CAAC;QAC3B,qBAAqB,EAAE,MAAM,CAAC;QAC9B,eAAe,EAAE,MAAM,CAAC;QACxB,cAAc,EAAE,MAAM,CAAC;QACvB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,sBAAsB,CAAC,EAAE,MAAM,CAAC;QAChC,gBAAgB,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;KACzC,GACA,OAAO,CAAC,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"claude-sdk-manager.d.ts","sourceRoot":"","sources":["../../src/components/claude-sdk-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAMH,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC1D,OAAO,EAAE,yBAAyB,EAAE,MAAM,kCAAkC,CAAC;AAC7E,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAuBrF,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,iBAAiB,CAA4B;gBAGnD,MAAM,EAAE,UAAU,EAClB,iBAAiB,EAAE,yBAAyB;IAiB9C,OAAO,CAAC,sBAAsB;IAKxB,iBAAiB,CACrB,sBAAsB,EAAE,MAAM,GAAG,UAAU,GAAG,aAAa,EAC3D,oBAAoB,EAAE,MAAM,EAC5B,MAAM,EAAE,kBAAkB,EAC1B,eAAe,EAAE,OAAO,EAAE,EAC1B,gBAAgB,CAAC,EAAE;QACjB,EAAE,EAAE,MAAM,CAAC;QACX,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,kBAAkB,EAAE,MAAM,CAAC;QAC3B,qBAAqB,EAAE,MAAM,CAAC;QAC9B,eAAe,EAAE,MAAM,CAAC;QACxB,cAAc,EAAE,MAAM,CAAC;QACvB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,sBAAsB,CAAC,EAAE,MAAM,CAAC;QAChC,gBAAgB,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;KACzC,GACA,OAAO,CAAC,mBAAmB,CAAC;IAyZzB,gBAAgB,CACpB,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,mBAAmB,EAC5B,gBAAgB,GAAE,OAAe,EACjC,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC;IAmCV,kBAAkB,CACtB,sBAAsB,EAAE,MAAM,GAAG,UAAU,GAAG,aAAa,EAC3D,oBAAoB,EAAE,MAAM,EAC5B,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,kBAAkB,EAC1B,gBAAgB,CAAC,EAAE,GAAG,EACtB,aAAa,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC,MAAM,CAAC;YAuCJ,qBAAqB;IA2C7B,eAAe,CACnB,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,GAAG,EACZ,MAAM,CAAC,EAAE,kBAAkB,EAC3B,sBAAsB,CAAC,EAAE,MAAM,GAAG,UAAU,GAAG,aAAa,EAC5D,oBAAoB,CAAC,EAAE,MAAM,EAC7B,YAAY,CAAC,EAAE,GAAG,EAClB,uBAAuB,CAAC,EAAE,MAAM,GAC/B,OAAO,CAAC,IAAI,CAAC;YAoIF,iBAAiB;YA2BjB,wBAAwB;IA2BtC,OAAO,CAAC,aAAa;IAerB;;OAEG;IACH,OAAO,CAAC,eAAe;IA2CvB;;;;OAIG;IACH,OAAO,CAAC,wBAAwB;YAyBlB,qBAAqB;CA0dpC"}
|
|
@@ -334,11 +334,25 @@ export class ClaudeManager {
|
|
|
334
334
|
// Send initial messages
|
|
335
335
|
try {
|
|
336
336
|
for (const message of initialMessages) {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
337
|
+
// Check if the message content contains multimodal content blocks (e.g., images)
|
|
338
|
+
const contentBlocks = this.normalizeToContentBlocks(message.content);
|
|
339
|
+
if (contentBlocks && Array.isArray(contentBlocks) && contentBlocks.some(b => b.type === 'image')) {
|
|
340
|
+
// Send as multimodal message with content array
|
|
341
|
+
// The SDK types may not include multimodal support, but the underlying API does
|
|
342
|
+
console.log('[ClaudeManager] Sending multimodal message with', contentBlocks.length, 'content blocks');
|
|
343
|
+
conversation.send({
|
|
344
|
+
type: "user",
|
|
345
|
+
content: contentBlocks,
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
// Fallback to text-only message
|
|
350
|
+
const initialText = this.normalizeToText(message.content);
|
|
351
|
+
conversation.send({
|
|
352
|
+
type: "text",
|
|
353
|
+
text: initialText,
|
|
354
|
+
});
|
|
355
|
+
}
|
|
342
356
|
}
|
|
343
357
|
console.log(`Started conversation for ${conversationObjectType} ${conversationObjectId} in workspace ${workspacePath}`);
|
|
344
358
|
// Return the conversation context directly
|
|
@@ -625,6 +639,33 @@ export class ClaudeManager {
|
|
|
625
639
|
return String(value);
|
|
626
640
|
}
|
|
627
641
|
}
|
|
642
|
+
/**
|
|
643
|
+
* Normalize arbitrary content shapes into content blocks array for multimodal support.
|
|
644
|
+
* Returns the array of content blocks if the content contains image blocks,
|
|
645
|
+
* otherwise returns null to indicate text-only content.
|
|
646
|
+
*/
|
|
647
|
+
normalizeToContentBlocks(value) {
|
|
648
|
+
// If already an array, check if it contains image blocks
|
|
649
|
+
if (Array.isArray(value)) {
|
|
650
|
+
const hasImages = value.some(b => b && b.type === 'image');
|
|
651
|
+
if (hasImages) {
|
|
652
|
+
// Return the content blocks as-is, they're already in the right format
|
|
653
|
+
return value;
|
|
654
|
+
}
|
|
655
|
+
return null;
|
|
656
|
+
}
|
|
657
|
+
// Check if it's a wrapped content object
|
|
658
|
+
if (typeof value === 'object' && value !== null) {
|
|
659
|
+
// Check for nested content array
|
|
660
|
+
if (Array.isArray(value.content)) {
|
|
661
|
+
const hasImages = value.content.some((b) => b && b.type === 'image');
|
|
662
|
+
if (hasImages) {
|
|
663
|
+
return value.content;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
return null;
|
|
668
|
+
}
|
|
628
669
|
async handleStreamedMessage(context, message, sessionId) {
|
|
629
670
|
/*
|
|
630
671
|
* SDK tool call payload reference (observed shapes)
|