zam-core 0.3.11 → 0.3.12
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/dist/cli/index.js +893 -195
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +74 -1
- package/dist/index.js +356 -81
- package/dist/index.js.map +1 -1
- package/package.json +9 -2
- package/templates/personal/beliefs/README.md +1 -1
- package/templates/personal/foundations/README.md +19 -0
package/dist/cli/index.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli/index.ts
|
|
4
|
-
import { readFileSync as
|
|
5
|
-
import { dirname as dirname10, join as
|
|
4
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
5
|
+
import { dirname as dirname10, join as join21 } from "path";
|
|
6
6
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
7
7
|
import { Command as Command23 } from "commander";
|
|
8
8
|
|
|
9
9
|
// src/cli/commands/agent.ts
|
|
10
|
-
import { existsSync as
|
|
11
|
-
import { join as
|
|
10
|
+
import { existsSync as existsSync11 } from "fs";
|
|
11
|
+
import { join as join10 } from "path";
|
|
12
12
|
import { Command } from "commander";
|
|
13
13
|
|
|
14
14
|
// src/kernel/analytics/stats.ts
|
|
@@ -1891,11 +1891,11 @@ function analyzeObservation(commands, tokenPatterns) {
|
|
|
1891
1891
|
const ratings = [];
|
|
1892
1892
|
for (const tp of tokenPatterns) {
|
|
1893
1893
|
const matchIndices = [];
|
|
1894
|
-
const
|
|
1894
|
+
const matchedTexts2 = [];
|
|
1895
1895
|
for (let i = 0; i < commands.length; i++) {
|
|
1896
1896
|
if (matchesToken(commands[i].command, tp.patterns)) {
|
|
1897
1897
|
matchIndices.push(i);
|
|
1898
|
-
|
|
1898
|
+
matchedTexts2.push(commands[i].command);
|
|
1899
1899
|
matchedSet.add(i);
|
|
1900
1900
|
}
|
|
1901
1901
|
}
|
|
@@ -1985,7 +1985,7 @@ function analyzeObservation(commands, tokenPatterns) {
|
|
|
1985
1985
|
medianGapMs,
|
|
1986
1986
|
thinkingGapMs
|
|
1987
1987
|
},
|
|
1988
|
-
matchedCommandTexts:
|
|
1988
|
+
matchedCommandTexts: matchedTexts2
|
|
1989
1989
|
});
|
|
1990
1990
|
}
|
|
1991
1991
|
const unmatchedCommands = [];
|
|
@@ -2347,6 +2347,240 @@ async function unblockReady(db, userId) {
|
|
|
2347
2347
|
return { unblocked };
|
|
2348
2348
|
}
|
|
2349
2349
|
|
|
2350
|
+
// src/kernel/observation/ui-observer-io.ts
|
|
2351
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync5 } from "fs";
|
|
2352
|
+
import { homedir as homedir4 } from "os";
|
|
2353
|
+
import { join as join5 } from "path";
|
|
2354
|
+
|
|
2355
|
+
// src/kernel/observation/ui-observer.ts
|
|
2356
|
+
var UI_OBSERVATION_PROTOCOL_VERSION = 1;
|
|
2357
|
+
var OBSERVATION_KINDS = /* @__PURE__ */ new Set([
|
|
2358
|
+
"progress",
|
|
2359
|
+
"step-completed",
|
|
2360
|
+
"error",
|
|
2361
|
+
"help-seeking",
|
|
2362
|
+
"uncertain",
|
|
2363
|
+
"privacy-pause",
|
|
2364
|
+
"heartbeat"
|
|
2365
|
+
]);
|
|
2366
|
+
var ACTION_TYPES = /* @__PURE__ */ new Set([
|
|
2367
|
+
"click",
|
|
2368
|
+
"shortcut",
|
|
2369
|
+
"typing",
|
|
2370
|
+
"scroll",
|
|
2371
|
+
"window-change"
|
|
2372
|
+
]);
|
|
2373
|
+
var EVIDENCE_TYPES = /* @__PURE__ */ new Set([
|
|
2374
|
+
"uia",
|
|
2375
|
+
"keyframe",
|
|
2376
|
+
"clip",
|
|
2377
|
+
"window"
|
|
2378
|
+
]);
|
|
2379
|
+
function isUiObservationReport(value) {
|
|
2380
|
+
if (!isRecord(value)) return false;
|
|
2381
|
+
if (value.version !== UI_OBSERVATION_PROTOCOL_VERSION) return false;
|
|
2382
|
+
if (!isNonEmptyString(value.sessionId)) return false;
|
|
2383
|
+
if (!isNonNegativeInteger(value.sequence)) return false;
|
|
2384
|
+
if (!isNonEmptyString(value.observedFrom)) return false;
|
|
2385
|
+
if (!isNonEmptyString(value.observedTo)) return false;
|
|
2386
|
+
if (typeof value.kind !== "string" || !OBSERVATION_KINDS.has(value.kind)) {
|
|
2387
|
+
return false;
|
|
2388
|
+
}
|
|
2389
|
+
if (!isApplication(value.application)) return false;
|
|
2390
|
+
if (!isNonEmptyString(value.summary)) return false;
|
|
2391
|
+
if (!Array.isArray(value.actions) || !value.actions.every(isObservedAction)) {
|
|
2392
|
+
return false;
|
|
2393
|
+
}
|
|
2394
|
+
if (!Array.isArray(value.evidence) || !value.evidence.every(isEvidenceRef)) {
|
|
2395
|
+
return false;
|
|
2396
|
+
}
|
|
2397
|
+
if (!Array.isArray(value.candidateTokens) || !value.candidateTokens.every(isCandidateToken)) {
|
|
2398
|
+
return false;
|
|
2399
|
+
}
|
|
2400
|
+
return isConfidence(value.confidence);
|
|
2401
|
+
}
|
|
2402
|
+
function parseUiObservationLog(jsonl) {
|
|
2403
|
+
const reports = [];
|
|
2404
|
+
for (const line of jsonl.split("\n")) {
|
|
2405
|
+
const trimmed = line.trim();
|
|
2406
|
+
if (!trimmed) continue;
|
|
2407
|
+
try {
|
|
2408
|
+
const value = JSON.parse(trimmed);
|
|
2409
|
+
if (isUiObservationReport(value)) {
|
|
2410
|
+
reports.push(value);
|
|
2411
|
+
}
|
|
2412
|
+
} catch {
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
return reports.sort((left, right) => left.sequence - right.sequence);
|
|
2416
|
+
}
|
|
2417
|
+
function isApplication(value) {
|
|
2418
|
+
if (!isRecord(value) || !isNonEmptyString(value.processName)) return false;
|
|
2419
|
+
if (value.processId !== void 0 && !isNonNegativeInteger(value.processId)) {
|
|
2420
|
+
return false;
|
|
2421
|
+
}
|
|
2422
|
+
return value.windowTitle === void 0 || typeof value.windowTitle === "string";
|
|
2423
|
+
}
|
|
2424
|
+
function isObservedAction(value) {
|
|
2425
|
+
if (!isRecord(value) || typeof value.type !== "string") return false;
|
|
2426
|
+
if (!ACTION_TYPES.has(value.type)) return false;
|
|
2427
|
+
if (value.target !== void 0 && typeof value.target !== "string") {
|
|
2428
|
+
return false;
|
|
2429
|
+
}
|
|
2430
|
+
return value.result === void 0 || typeof value.result === "string";
|
|
2431
|
+
}
|
|
2432
|
+
function isEvidenceRef(value) {
|
|
2433
|
+
if (!isRecord(value) || typeof value.type !== "string") return false;
|
|
2434
|
+
return EVIDENCE_TYPES.has(value.type) && isNonEmptyString(value.ref) && typeof value.redacted === "boolean";
|
|
2435
|
+
}
|
|
2436
|
+
function isCandidateToken(value) {
|
|
2437
|
+
return isRecord(value) && isNonEmptyString(value.slug) && isConfidence(value.confidence) && isNonEmptyString(value.rationale);
|
|
2438
|
+
}
|
|
2439
|
+
function isRecord(value) {
|
|
2440
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2441
|
+
}
|
|
2442
|
+
function isNonEmptyString(value) {
|
|
2443
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
2444
|
+
}
|
|
2445
|
+
function isNonNegativeInteger(value) {
|
|
2446
|
+
return Number.isInteger(value) && Number(value) >= 0;
|
|
2447
|
+
}
|
|
2448
|
+
function isConfidence(value) {
|
|
2449
|
+
return typeof value === "number" && value >= 0 && value <= 1;
|
|
2450
|
+
}
|
|
2451
|
+
|
|
2452
|
+
// src/kernel/observation/ui-observer-io.ts
|
|
2453
|
+
var DEFAULT_UI_OBSERVER_DIR = join5(homedir4(), ".zam", "observer");
|
|
2454
|
+
function getUiObserverDir() {
|
|
2455
|
+
return process.env.ZAM_OBSERVER_DIR || DEFAULT_UI_OBSERVER_DIR;
|
|
2456
|
+
}
|
|
2457
|
+
function getUiObservationPath(sessionId) {
|
|
2458
|
+
if (!/^[A-Za-z0-9_-]+$/.test(sessionId)) {
|
|
2459
|
+
throw new Error(`Invalid observer session ID: ${sessionId}`);
|
|
2460
|
+
}
|
|
2461
|
+
return join5(getUiObserverDir(), `${sessionId}.reports.jsonl`);
|
|
2462
|
+
}
|
|
2463
|
+
function ensureUiObserverDir() {
|
|
2464
|
+
const dir = getUiObserverDir();
|
|
2465
|
+
if (!existsSync5(dir)) {
|
|
2466
|
+
mkdirSync4(dir, { recursive: true, mode: 448 });
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
function uiObservationLogExists(sessionId) {
|
|
2470
|
+
return existsSync5(getUiObservationPath(sessionId));
|
|
2471
|
+
}
|
|
2472
|
+
function readUiObservationLog(sessionId) {
|
|
2473
|
+
const path = getUiObservationPath(sessionId);
|
|
2474
|
+
if (!existsSync5(path)) return [];
|
|
2475
|
+
return parseUiObservationLog(readFileSync5(path, "utf8"));
|
|
2476
|
+
}
|
|
2477
|
+
function appendUiObservationReport(report) {
|
|
2478
|
+
if (!isUiObservationReport(report)) {
|
|
2479
|
+
throw new Error("Invalid UI observation report");
|
|
2480
|
+
}
|
|
2481
|
+
ensureUiObserverDir();
|
|
2482
|
+
appendFileSync2(
|
|
2483
|
+
getUiObservationPath(report.sessionId),
|
|
2484
|
+
`${JSON.stringify(report)}
|
|
2485
|
+
`,
|
|
2486
|
+
{ encoding: "utf8", mode: 384 }
|
|
2487
|
+
);
|
|
2488
|
+
}
|
|
2489
|
+
|
|
2490
|
+
// src/kernel/observation/ui-observer-synthesis.ts
|
|
2491
|
+
var MIN_UI_CONFIDENCE = 0.6;
|
|
2492
|
+
function kindToRating(kind) {
|
|
2493
|
+
switch (kind) {
|
|
2494
|
+
case "step-completed":
|
|
2495
|
+
return 4;
|
|
2496
|
+
case "progress":
|
|
2497
|
+
return 3;
|
|
2498
|
+
case "error":
|
|
2499
|
+
case "help-seeking":
|
|
2500
|
+
return 2;
|
|
2501
|
+
default:
|
|
2502
|
+
return null;
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
function toSynthesisConfidence(confidence) {
|
|
2506
|
+
return confidence >= 0.85 ? "high" : "medium";
|
|
2507
|
+
}
|
|
2508
|
+
function confidenceRank(confidence) {
|
|
2509
|
+
return confidence === "high" ? 2 : 1;
|
|
2510
|
+
}
|
|
2511
|
+
function buildEvidence(report) {
|
|
2512
|
+
return {
|
|
2513
|
+
matchedCommands: report.actions.length,
|
|
2514
|
+
helpSeeking: report.kind === "help-seeking",
|
|
2515
|
+
errorCount: report.kind === "error" ? 1 : 0,
|
|
2516
|
+
selfCorrections: 0,
|
|
2517
|
+
medianGapMs: null,
|
|
2518
|
+
thinkingGapMs: null
|
|
2519
|
+
};
|
|
2520
|
+
}
|
|
2521
|
+
function matchedTexts(report) {
|
|
2522
|
+
const texts = [report.summary];
|
|
2523
|
+
for (const action of report.actions) {
|
|
2524
|
+
const label = [action.type, action.target, action.result].filter(Boolean).join(" ");
|
|
2525
|
+
if (label) texts.push(label);
|
|
2526
|
+
}
|
|
2527
|
+
return texts;
|
|
2528
|
+
}
|
|
2529
|
+
function buildUiSynthesisCandidates(reports, tokens, applied, minConfidence) {
|
|
2530
|
+
const minRank = confidenceRank(minConfidence);
|
|
2531
|
+
const bestBySlug = /* @__PURE__ */ new Map();
|
|
2532
|
+
let skippedLowConfidence = 0;
|
|
2533
|
+
for (const report of reports) {
|
|
2534
|
+
if (report.confidence < MIN_UI_CONFIDENCE) continue;
|
|
2535
|
+
const fallbackRating = kindToRating(report.kind);
|
|
2536
|
+
const candidates2 = report.candidateTokens.length > 0 ? report.candidateTokens.map((candidate) => ({
|
|
2537
|
+
slug: candidate.slug,
|
|
2538
|
+
confidence: candidate.confidence,
|
|
2539
|
+
rating: fallbackRating
|
|
2540
|
+
})) : [];
|
|
2541
|
+
for (const candidate of candidates2) {
|
|
2542
|
+
const token = tokens.get(candidate.slug);
|
|
2543
|
+
if (!token || applied.has(token.id)) continue;
|
|
2544
|
+
const synthesisConfidence = toSynthesisConfidence(candidate.confidence);
|
|
2545
|
+
if (confidenceRank(synthesisConfidence) < minRank) {
|
|
2546
|
+
skippedLowConfidence++;
|
|
2547
|
+
continue;
|
|
2548
|
+
}
|
|
2549
|
+
const inferredRating = candidate.rating ?? 3;
|
|
2550
|
+
const existing = bestBySlug.get(candidate.slug);
|
|
2551
|
+
if (existing && existing.reportConfidence >= candidate.confidence) {
|
|
2552
|
+
continue;
|
|
2553
|
+
}
|
|
2554
|
+
bestBySlug.set(candidate.slug, {
|
|
2555
|
+
token,
|
|
2556
|
+
inferredRating,
|
|
2557
|
+
confidence: synthesisConfidence,
|
|
2558
|
+
evidence: buildEvidence(report),
|
|
2559
|
+
matchedCommandTexts: matchedTexts(report),
|
|
2560
|
+
reportConfidence: candidate.confidence
|
|
2561
|
+
});
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
const candidates = [...bestBySlug.values()].sort((left, right) => right.reportConfidence - left.reportConfidence).map((entry) => ({
|
|
2565
|
+
tokenId: entry.token.id,
|
|
2566
|
+
tokenSlug: entry.token.slug,
|
|
2567
|
+
concept: entry.token.concept,
|
|
2568
|
+
domain: entry.token.domain,
|
|
2569
|
+
inferredRating: entry.inferredRating,
|
|
2570
|
+
confidence: entry.confidence,
|
|
2571
|
+
evidence: entry.evidence,
|
|
2572
|
+
matchedCommandTexts: entry.matchedCommandTexts
|
|
2573
|
+
}));
|
|
2574
|
+
return { candidates, skippedLowConfidence };
|
|
2575
|
+
}
|
|
2576
|
+
function uiObservationTimeSpan(reports) {
|
|
2577
|
+
if (reports.length === 0) return null;
|
|
2578
|
+
const start = reports[0].observedFrom;
|
|
2579
|
+
const end = reports[reports.length - 1].observedTo;
|
|
2580
|
+
const durationMs = Math.max(0, Date.parse(end) - Date.parse(start));
|
|
2581
|
+
return { start, end, durationMs };
|
|
2582
|
+
}
|
|
2583
|
+
|
|
2350
2584
|
// src/kernel/observation/session-synthesis.ts
|
|
2351
2585
|
function parseSynthesisRow(row) {
|
|
2352
2586
|
return {
|
|
@@ -2354,7 +2588,7 @@ function parseSynthesisRow(row) {
|
|
|
2354
2588
|
evidence: JSON.parse(row.evidence)
|
|
2355
2589
|
};
|
|
2356
2590
|
}
|
|
2357
|
-
function
|
|
2591
|
+
function confidenceRank2(confidence) {
|
|
2358
2592
|
return confidence === "high" ? 2 : confidence === "medium" ? 1 : 0;
|
|
2359
2593
|
}
|
|
2360
2594
|
function normalizeSkillStep(step) {
|
|
@@ -2416,20 +2650,50 @@ async function prepareSessionSynthesis(db, input8) {
|
|
|
2416
2650
|
validPatterns.push(pattern);
|
|
2417
2651
|
tokens.set(pattern.slug, token);
|
|
2418
2652
|
}
|
|
2419
|
-
const commands = input8.commands ?? pairCommands(readMonitorLog(input8.sessionId));
|
|
2420
|
-
const analysis = analyzeObservation(commands, validPatterns);
|
|
2421
2653
|
const applied = new Set(
|
|
2422
2654
|
(await getSessionSynthesisRecords(db, input8.sessionId)).map(
|
|
2423
2655
|
(record) => record.token_id
|
|
2424
2656
|
)
|
|
2425
2657
|
);
|
|
2426
|
-
const
|
|
2658
|
+
const minConfidence = input8.minConfidence ?? "medium";
|
|
2659
|
+
if (session.execution_context === "ui") {
|
|
2660
|
+
const reports = readUiObservationLog(input8.sessionId);
|
|
2661
|
+
for (const report of reports) {
|
|
2662
|
+
for (const candidate of report.candidateTokens) {
|
|
2663
|
+
if (tokens.has(candidate.slug)) continue;
|
|
2664
|
+
const token = await getTokenBySlug(db, candidate.slug);
|
|
2665
|
+
if (token && !token.deprecated_at) {
|
|
2666
|
+
tokens.set(candidate.slug, token);
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
const { candidates: candidates2, skippedLowConfidence: skippedLowConfidence2 } = buildUiSynthesisCandidates(
|
|
2671
|
+
reports,
|
|
2672
|
+
tokens,
|
|
2673
|
+
applied,
|
|
2674
|
+
minConfidence
|
|
2675
|
+
);
|
|
2676
|
+
return {
|
|
2677
|
+
sessionId: session.id,
|
|
2678
|
+
userId: session.user_id,
|
|
2679
|
+
patternCount: validPatterns.length,
|
|
2680
|
+
commandCount: reports.length,
|
|
2681
|
+
alreadyApplied: applied.size,
|
|
2682
|
+
skippedLowConfidence: skippedLowConfidence2,
|
|
2683
|
+
candidates: candidates2,
|
|
2684
|
+
unmatchedCommands: [],
|
|
2685
|
+
timeSpan: uiObservationTimeSpan(reports)
|
|
2686
|
+
};
|
|
2687
|
+
}
|
|
2688
|
+
const commands = input8.commands ?? pairCommands(readMonitorLog(input8.sessionId));
|
|
2689
|
+
const analysis = analyzeObservation(commands, validPatterns);
|
|
2690
|
+
const minRank = confidenceRank2(minConfidence);
|
|
2427
2691
|
let skippedLowConfidence = 0;
|
|
2428
2692
|
const candidates = [];
|
|
2429
2693
|
for (const rating of analysis.ratings) {
|
|
2430
2694
|
const token = tokens.get(rating.tokenSlug);
|
|
2431
2695
|
if (!token || rating.rating == null || applied.has(token.id)) continue;
|
|
2432
|
-
if (
|
|
2696
|
+
if (confidenceRank2(rating.confidence) < minRank) {
|
|
2433
2697
|
skippedLowConfidence++;
|
|
2434
2698
|
continue;
|
|
2435
2699
|
}
|
|
@@ -3026,8 +3290,8 @@ function generatePrompt(input8) {
|
|
|
3026
3290
|
}
|
|
3027
3291
|
|
|
3028
3292
|
// src/kernel/recall/reference-resolver.ts
|
|
3029
|
-
import { existsSync as
|
|
3030
|
-
import { dirname as dirname3, join as
|
|
3293
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
|
|
3294
|
+
import { dirname as dirname3, join as join6, resolve } from "path";
|
|
3031
3295
|
var DEFAULT_REVIEW_CONTEXT_MAX_CHARS = 6e3;
|
|
3032
3296
|
function htmlToText(html) {
|
|
3033
3297
|
let content = html;
|
|
@@ -3088,11 +3352,11 @@ async function resolveReference(sourceLink) {
|
|
|
3088
3352
|
const filePath = anchorIndex2 !== -1 ? fullPathWithAnchor.slice(0, anchorIndex2) : fullPathWithAnchor;
|
|
3089
3353
|
const anchor2 = anchorIndex2 !== -1 ? fullPathWithAnchor.slice(anchorIndex2) : "";
|
|
3090
3354
|
const parentDir = dirname3(process.cwd());
|
|
3091
|
-
const localRepoPath =
|
|
3092
|
-
const localFilePath =
|
|
3093
|
-
if (
|
|
3355
|
+
const localRepoPath = join6(parentDir, repo);
|
|
3356
|
+
const localFilePath = join6(localRepoPath, filePath);
|
|
3357
|
+
if (existsSync6(localFilePath)) {
|
|
3094
3358
|
try {
|
|
3095
|
-
let fileContent =
|
|
3359
|
+
let fileContent = readFileSync6(localFilePath, "utf-8");
|
|
3096
3360
|
if (anchor2) {
|
|
3097
3361
|
fileContent = extractLines(fileContent, anchor2);
|
|
3098
3362
|
}
|
|
@@ -3146,9 +3410,9 @@ Link: ${cleaned}`,
|
|
|
3146
3410
|
const relativePath = anchorIndex !== -1 ? cleaned.slice(0, anchorIndex) : cleaned;
|
|
3147
3411
|
const anchor = anchorIndex !== -1 ? cleaned.slice(anchorIndex) : "";
|
|
3148
3412
|
const absolutePath = resolve(process.cwd(), relativePath);
|
|
3149
|
-
if (
|
|
3413
|
+
if (existsSync6(absolutePath)) {
|
|
3150
3414
|
try {
|
|
3151
|
-
let fileContent =
|
|
3415
|
+
let fileContent = readFileSync6(absolutePath, "utf-8");
|
|
3152
3416
|
if (anchor) {
|
|
3153
3417
|
fileContent = extractLines(fileContent, anchor);
|
|
3154
3418
|
}
|
|
@@ -3400,44 +3664,44 @@ function intersperseNew(reviews, newCards, interval) {
|
|
|
3400
3664
|
|
|
3401
3665
|
// src/kernel/system/hooks.ts
|
|
3402
3666
|
import {
|
|
3403
|
-
appendFileSync as
|
|
3667
|
+
appendFileSync as appendFileSync3,
|
|
3404
3668
|
copyFileSync,
|
|
3405
|
-
existsSync as
|
|
3406
|
-
mkdirSync as
|
|
3407
|
-
readFileSync as
|
|
3669
|
+
existsSync as existsSync7,
|
|
3670
|
+
mkdirSync as mkdirSync5,
|
|
3671
|
+
readFileSync as readFileSync7,
|
|
3408
3672
|
writeFileSync as writeFileSync3
|
|
3409
3673
|
} from "fs";
|
|
3410
|
-
import { homedir as
|
|
3411
|
-
import { join as
|
|
3674
|
+
import { homedir as homedir5 } from "os";
|
|
3675
|
+
import { join as join7 } from "path";
|
|
3412
3676
|
import { fileURLToPath } from "url";
|
|
3413
|
-
var HOME =
|
|
3677
|
+
var HOME = homedir5();
|
|
3414
3678
|
function getPackageSkillPath(agent = "default") {
|
|
3415
3679
|
const packageRoot2 = [
|
|
3416
3680
|
fileURLToPath(new URL("../../..", import.meta.url)),
|
|
3417
3681
|
fileURLToPath(new URL("../..", import.meta.url)),
|
|
3418
3682
|
fileURLToPath(new URL("..", import.meta.url))
|
|
3419
|
-
].find((candidate) =>
|
|
3683
|
+
].find((candidate) => existsSync7(join7(candidate, "package.json"))) ?? "";
|
|
3420
3684
|
if (!packageRoot2) return "";
|
|
3421
3685
|
if (agent === "codex") {
|
|
3422
|
-
const codexPath =
|
|
3423
|
-
if (
|
|
3686
|
+
const codexPath = join7(packageRoot2, ".agents", "skills", "zam", "SKILL.md");
|
|
3687
|
+
if (existsSync7(codexPath)) return codexPath;
|
|
3424
3688
|
return "";
|
|
3425
3689
|
}
|
|
3426
3690
|
if (agent === "claude") {
|
|
3427
|
-
const claudePath =
|
|
3691
|
+
const claudePath = join7(
|
|
3428
3692
|
packageRoot2,
|
|
3429
3693
|
".claude",
|
|
3430
3694
|
"skills",
|
|
3431
3695
|
"zam",
|
|
3432
3696
|
"SKILL.md"
|
|
3433
3697
|
);
|
|
3434
|
-
if (
|
|
3698
|
+
if (existsSync7(claudePath)) return claudePath;
|
|
3435
3699
|
return "";
|
|
3436
3700
|
}
|
|
3437
|
-
let path =
|
|
3438
|
-
if (
|
|
3439
|
-
path =
|
|
3440
|
-
if (
|
|
3701
|
+
let path = join7(packageRoot2, ".agent", "skills", "zam", "SKILL.md");
|
|
3702
|
+
if (existsSync7(path)) return path;
|
|
3703
|
+
path = join7(packageRoot2, ".claude", "skills", "zam", "SKILL.md");
|
|
3704
|
+
if (existsSync7(path)) return path;
|
|
3441
3705
|
return "";
|
|
3442
3706
|
}
|
|
3443
3707
|
function distributeGlobalSkills(home = HOME) {
|
|
@@ -3449,16 +3713,16 @@ function distributeGlobalSkills(home = HOME) {
|
|
|
3449
3713
|
console.warn("Could not find ZAM source SKILL.md in the package folder.");
|
|
3450
3714
|
return results;
|
|
3451
3715
|
}
|
|
3452
|
-
const claudeSkillsDir =
|
|
3716
|
+
const claudeSkillsDir = join7(home, ".claude", "skills", "zam");
|
|
3453
3717
|
try {
|
|
3454
3718
|
if (!claudeSourceSkill) {
|
|
3455
3719
|
throw new Error("Claude skill source not found");
|
|
3456
3720
|
}
|
|
3457
|
-
|
|
3458
|
-
copyFileSync(claudeSourceSkill,
|
|
3721
|
+
mkdirSync5(claudeSkillsDir, { recursive: true });
|
|
3722
|
+
copyFileSync(claudeSourceSkill, join7(claudeSkillsDir, "SKILL.md"));
|
|
3459
3723
|
results.push({
|
|
3460
3724
|
name: "Claude Code Global",
|
|
3461
|
-
path:
|
|
3725
|
+
path: join7(claudeSkillsDir, "SKILL.md"),
|
|
3462
3726
|
success: true
|
|
3463
3727
|
});
|
|
3464
3728
|
} catch (_err) {
|
|
@@ -3468,13 +3732,13 @@ function distributeGlobalSkills(home = HOME) {
|
|
|
3468
3732
|
success: false
|
|
3469
3733
|
});
|
|
3470
3734
|
}
|
|
3471
|
-
const geminiSkillsDir =
|
|
3735
|
+
const geminiSkillsDir = join7(home, ".gemini", "skills", "zam");
|
|
3472
3736
|
try {
|
|
3473
|
-
|
|
3474
|
-
copyFileSync(sourceSkill,
|
|
3737
|
+
mkdirSync5(geminiSkillsDir, { recursive: true });
|
|
3738
|
+
copyFileSync(sourceSkill, join7(geminiSkillsDir, "SKILL.md"));
|
|
3475
3739
|
results.push({
|
|
3476
3740
|
name: "Gemini CLI Global",
|
|
3477
|
-
path:
|
|
3741
|
+
path: join7(geminiSkillsDir, "SKILL.md"),
|
|
3478
3742
|
success: true
|
|
3479
3743
|
});
|
|
3480
3744
|
} catch (_err) {
|
|
@@ -3484,16 +3748,16 @@ function distributeGlobalSkills(home = HOME) {
|
|
|
3484
3748
|
success: false
|
|
3485
3749
|
});
|
|
3486
3750
|
}
|
|
3487
|
-
const codexSkillsDir =
|
|
3751
|
+
const codexSkillsDir = join7(home, ".agents", "skills", "zam");
|
|
3488
3752
|
try {
|
|
3489
3753
|
if (!codexSourceSkill) {
|
|
3490
3754
|
throw new Error("Codex skill source not found");
|
|
3491
3755
|
}
|
|
3492
|
-
|
|
3493
|
-
copyFileSync(codexSourceSkill,
|
|
3756
|
+
mkdirSync5(codexSkillsDir, { recursive: true });
|
|
3757
|
+
copyFileSync(codexSourceSkill, join7(codexSkillsDir, "SKILL.md"));
|
|
3494
3758
|
results.push({
|
|
3495
3759
|
name: "Codex Global",
|
|
3496
|
-
path:
|
|
3760
|
+
path: join7(codexSkillsDir, "SKILL.md"),
|
|
3497
3761
|
success: true
|
|
3498
3762
|
});
|
|
3499
3763
|
} catch (_err) {
|
|
@@ -3503,13 +3767,13 @@ function distributeGlobalSkills(home = HOME) {
|
|
|
3503
3767
|
success: false
|
|
3504
3768
|
});
|
|
3505
3769
|
}
|
|
3506
|
-
const gooseSkillsDir =
|
|
3770
|
+
const gooseSkillsDir = join7(home, ".goose", "skills", "zam");
|
|
3507
3771
|
try {
|
|
3508
|
-
|
|
3509
|
-
copyFileSync(sourceSkill,
|
|
3772
|
+
mkdirSync5(gooseSkillsDir, { recursive: true });
|
|
3773
|
+
copyFileSync(sourceSkill, join7(gooseSkillsDir, "SKILL.md"));
|
|
3510
3774
|
results.push({
|
|
3511
3775
|
name: "Goose Global",
|
|
3512
|
-
path:
|
|
3776
|
+
path: join7(gooseSkillsDir, "SKILL.md"),
|
|
3513
3777
|
success: true
|
|
3514
3778
|
});
|
|
3515
3779
|
} catch (_err) {
|
|
@@ -3553,14 +3817,14 @@ function Start-ZamMonitor {
|
|
|
3553
3817
|
`;
|
|
3554
3818
|
function installHook(file, hook, oldHook) {
|
|
3555
3819
|
try {
|
|
3556
|
-
const content =
|
|
3820
|
+
const content = existsSync7(file) ? readFileSync7(file, "utf8") : "";
|
|
3557
3821
|
if (content.includes(HOOK_MARKER)) {
|
|
3558
3822
|
return { success: true, alreadyHooked: true };
|
|
3559
3823
|
}
|
|
3560
3824
|
if (content.includes(oldHook.trim())) {
|
|
3561
3825
|
writeFileSync3(file, content.replace(oldHook.trim(), hook.trim()), "utf8");
|
|
3562
3826
|
} else {
|
|
3563
|
-
|
|
3827
|
+
appendFileSync3(file, hook);
|
|
3564
3828
|
}
|
|
3565
3829
|
return { success: true, alreadyHooked: false };
|
|
3566
3830
|
} catch {
|
|
@@ -3569,24 +3833,24 @@ function installHook(file, hook, oldHook) {
|
|
|
3569
3833
|
}
|
|
3570
3834
|
function injectShellHooks(home = HOME) {
|
|
3571
3835
|
const results = [];
|
|
3572
|
-
const zshrc =
|
|
3573
|
-
if (
|
|
3836
|
+
const zshrc = join7(home, ".zshrc");
|
|
3837
|
+
if (existsSync7(zshrc)) {
|
|
3574
3838
|
const status = installHook(zshrc, posixHook("zsh"), POSIX_OLD_HOOK);
|
|
3575
3839
|
results.push({ shell: "zsh", file: zshrc, ...status });
|
|
3576
3840
|
}
|
|
3577
|
-
const bashrc =
|
|
3578
|
-
if (
|
|
3841
|
+
const bashrc = join7(home, ".bashrc");
|
|
3842
|
+
if (existsSync7(bashrc)) {
|
|
3579
3843
|
const status = installHook(bashrc, posixHook("bash"), POSIX_OLD_HOOK);
|
|
3580
3844
|
results.push({ shell: "bash", file: bashrc, ...status });
|
|
3581
3845
|
}
|
|
3582
3846
|
const pwshDirs = [
|
|
3583
|
-
|
|
3584
|
-
|
|
3847
|
+
join7(home, "Documents", "PowerShell"),
|
|
3848
|
+
join7(home, "Documents", "WindowsPowerShell")
|
|
3585
3849
|
];
|
|
3586
3850
|
for (const dir of pwshDirs) {
|
|
3587
|
-
const profileFile =
|
|
3851
|
+
const profileFile = join7(dir, "Microsoft.PowerShell_profile.ps1");
|
|
3588
3852
|
try {
|
|
3589
|
-
|
|
3853
|
+
mkdirSync5(dir, { recursive: true });
|
|
3590
3854
|
const status = installHook(
|
|
3591
3855
|
profileFile,
|
|
3592
3856
|
POWERSHELL_HOOK,
|
|
@@ -3818,21 +4082,21 @@ function t(locale, key, params = {}) {
|
|
|
3818
4082
|
}
|
|
3819
4083
|
|
|
3820
4084
|
// src/kernel/system/install-config.ts
|
|
3821
|
-
import { existsSync as
|
|
3822
|
-
import { homedir as
|
|
3823
|
-
import { dirname as dirname4, join as
|
|
3824
|
-
var DEFAULT_CONFIG_PATH =
|
|
4085
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
|
|
4086
|
+
import { homedir as homedir6 } from "os";
|
|
4087
|
+
import { dirname as dirname4, join as join8 } from "path";
|
|
4088
|
+
var DEFAULT_CONFIG_PATH = join8(homedir6(), ".zam", "config.json");
|
|
3825
4089
|
function loadInstallConfig(path = DEFAULT_CONFIG_PATH) {
|
|
3826
|
-
if (!
|
|
4090
|
+
if (!existsSync8(path)) return {};
|
|
3827
4091
|
try {
|
|
3828
|
-
return JSON.parse(
|
|
4092
|
+
return JSON.parse(readFileSync8(path, "utf-8"));
|
|
3829
4093
|
} catch {
|
|
3830
4094
|
return {};
|
|
3831
4095
|
}
|
|
3832
4096
|
}
|
|
3833
4097
|
function saveInstallConfig(config, path = DEFAULT_CONFIG_PATH) {
|
|
3834
4098
|
const dir = dirname4(path);
|
|
3835
|
-
if (!
|
|
4099
|
+
if (!existsSync8(dir)) mkdirSync6(dir, { recursive: true });
|
|
3836
4100
|
writeFileSync4(path, `${JSON.stringify(config, null, 2)}
|
|
3837
4101
|
`, "utf-8");
|
|
3838
4102
|
}
|
|
@@ -3864,9 +4128,9 @@ function detectSyncProvider(dir) {
|
|
|
3864
4128
|
|
|
3865
4129
|
// src/kernel/system/installer.ts
|
|
3866
4130
|
import { execFileSync, execSync } from "child_process";
|
|
3867
|
-
import { existsSync as
|
|
3868
|
-
import { homedir as
|
|
3869
|
-
import { join as
|
|
4131
|
+
import { existsSync as existsSync9 } from "fs";
|
|
4132
|
+
import { homedir as homedir7 } from "os";
|
|
4133
|
+
import { join as join9 } from "path";
|
|
3870
4134
|
function hasCommand(cmd) {
|
|
3871
4135
|
try {
|
|
3872
4136
|
const checkCmd2 = process.platform === "win32" ? `where ${cmd}` : `which ${cmd}`;
|
|
@@ -3883,7 +4147,7 @@ function installFastFlowLM() {
|
|
|
3883
4147
|
message: "FastFlowLM is only supported on Windows."
|
|
3884
4148
|
};
|
|
3885
4149
|
}
|
|
3886
|
-
const hasFlm = hasCommand("flm") ||
|
|
4150
|
+
const hasFlm = hasCommand("flm") || existsSync9("C:\\Program Files\\flm\\flm.exe");
|
|
3887
4151
|
if (hasFlm) {
|
|
3888
4152
|
return { success: true, message: "FastFlowLM is already installed." };
|
|
3889
4153
|
}
|
|
@@ -3910,8 +4174,8 @@ function installFastFlowLM() {
|
|
|
3910
4174
|
function installOllama() {
|
|
3911
4175
|
const isMac = process.platform === "darwin";
|
|
3912
4176
|
const isWin = process.platform === "win32";
|
|
3913
|
-
const hasOllama = hasCommand("ollama") || isMac &&
|
|
3914
|
-
|
|
4177
|
+
const hasOllama = hasCommand("ollama") || isMac && existsSync9("/Applications/Ollama.app") || isWin && existsSync9(
|
|
4178
|
+
join9(homedir7(), "AppData", "Local", "Programs", "Ollama", "ollama.exe")
|
|
3915
4179
|
);
|
|
3916
4180
|
if (hasOllama) {
|
|
3917
4181
|
return { success: true, message: "Ollama is already installed." };
|
|
@@ -3971,8 +4235,8 @@ function installOllama() {
|
|
|
3971
4235
|
function resolveOllamaCommand() {
|
|
3972
4236
|
if (hasCommand("ollama")) return "ollama";
|
|
3973
4237
|
const candidates = process.platform === "win32" ? [
|
|
3974
|
-
|
|
3975
|
-
|
|
4238
|
+
join9(
|
|
4239
|
+
homedir7(),
|
|
3976
4240
|
"AppData",
|
|
3977
4241
|
"Local",
|
|
3978
4242
|
"Programs",
|
|
@@ -3980,7 +4244,7 @@ function resolveOllamaCommand() {
|
|
|
3980
4244
|
"ollama.exe"
|
|
3981
4245
|
)
|
|
3982
4246
|
] : process.platform === "darwin" ? ["/Applications/Ollama.app/Contents/Resources/ollama"] : [];
|
|
3983
|
-
return candidates.find((candidate) =>
|
|
4247
|
+
return candidates.find((candidate) => existsSync9(candidate));
|
|
3984
4248
|
}
|
|
3985
4249
|
function prepareLocalModel(runner, model) {
|
|
3986
4250
|
if (runner === "fastflowlm") {
|
|
@@ -4168,7 +4432,7 @@ function getSystemProfile() {
|
|
|
4168
4432
|
}
|
|
4169
4433
|
|
|
4170
4434
|
// src/kernel/system/repos.ts
|
|
4171
|
-
import { existsSync as
|
|
4435
|
+
import { existsSync as existsSync10 } from "fs";
|
|
4172
4436
|
import { resolve as resolve2 } from "path";
|
|
4173
4437
|
async function getRepoPaths(db) {
|
|
4174
4438
|
const personalSetting = await getSetting(db, "repo.personal") || await getSetting(db, "personal.workspace_dir");
|
|
@@ -4277,7 +4541,7 @@ var C = {
|
|
|
4277
4541
|
};
|
|
4278
4542
|
var SUPPORTED_AGENTS = ["opencode"];
|
|
4279
4543
|
function agentsMdPresent(cwd = process.cwd()) {
|
|
4280
|
-
return
|
|
4544
|
+
return existsSync11(join10(cwd, "AGENTS.md"));
|
|
4281
4545
|
}
|
|
4282
4546
|
function printStatus() {
|
|
4283
4547
|
const installed = hasCommand("opencode");
|
|
@@ -4319,13 +4583,13 @@ var agentCommand = new Command("agent").description("Provision and inspect the a
|
|
|
4319
4583
|
|
|
4320
4584
|
// src/cli/commands/bridge.ts
|
|
4321
4585
|
import { readdirSync as readdirSync2 } from "fs";
|
|
4322
|
-
import { homedir as
|
|
4323
|
-
import { join as
|
|
4586
|
+
import { homedir as homedir8 } from "os";
|
|
4587
|
+
import { join as join11 } from "path";
|
|
4324
4588
|
import { Command as Command2 } from "commander";
|
|
4325
4589
|
|
|
4326
4590
|
// src/cli/llm/client.ts
|
|
4327
4591
|
import { spawn } from "child_process";
|
|
4328
|
-
import { existsSync as
|
|
4592
|
+
import { existsSync as existsSync12 } from "fs";
|
|
4329
4593
|
var DEFAULT_LLM_URL = "http://localhost:8000/v1";
|
|
4330
4594
|
var DEFAULT_LLM_MODEL = "qwen3.5:4b";
|
|
4331
4595
|
var DEFAULT_LLM_API_KEY = "sk-none";
|
|
@@ -4338,6 +4602,16 @@ async function getLlmConfig(db) {
|
|
|
4338
4602
|
locale: await getSetting(db, "system.locale") || "en"
|
|
4339
4603
|
};
|
|
4340
4604
|
}
|
|
4605
|
+
async function getVisionConfig(db) {
|
|
4606
|
+
const base = await getLlmConfig(db);
|
|
4607
|
+
return {
|
|
4608
|
+
enabled: await getSetting(db, "llm.vision.enabled") === "true",
|
|
4609
|
+
url: await getSetting(db, "llm.vision.url") || base.url,
|
|
4610
|
+
model: await getSetting(db, "llm.vision.model") || base.model,
|
|
4611
|
+
apiKey: await getSetting(db, "llm.vision.api_key") || base.apiKey,
|
|
4612
|
+
locale: base.locale
|
|
4613
|
+
};
|
|
4614
|
+
}
|
|
4341
4615
|
var LANGUAGE_NAMES = {
|
|
4342
4616
|
en: "English",
|
|
4343
4617
|
de: "German",
|
|
@@ -4531,6 +4805,38 @@ async function getAvailableModels(url, apiKey = DEFAULT_LLM_API_KEY) {
|
|
|
4531
4805
|
return [];
|
|
4532
4806
|
}
|
|
4533
4807
|
}
|
|
4808
|
+
async function checkVisionReadiness(db) {
|
|
4809
|
+
const { enabled, url, model, apiKey } = await getVisionConfig(db);
|
|
4810
|
+
const visionModelSetting = await getSetting(db, "llm.vision.model");
|
|
4811
|
+
const visionModelExplicit = !!visionModelSetting;
|
|
4812
|
+
let online = false;
|
|
4813
|
+
let availableModels = [];
|
|
4814
|
+
let modelAvailable = false;
|
|
4815
|
+
if (enabled) {
|
|
4816
|
+
online = await isLlmOnline(url);
|
|
4817
|
+
if (online) {
|
|
4818
|
+
availableModels = await getAvailableModels(url, apiKey);
|
|
4819
|
+
modelAvailable = availableModels.length === 0 || availableModels.some(
|
|
4820
|
+
(candidate) => candidate.toLowerCase() === model.toLowerCase()
|
|
4821
|
+
);
|
|
4822
|
+
}
|
|
4823
|
+
}
|
|
4824
|
+
let warning;
|
|
4825
|
+
if (enabled && online && modelAvailable && !visionModelExplicit) {
|
|
4826
|
+
warning = `No explicit vision model configured (llm.vision.model). Falling back to base model "${model}", which may not support image input. Set a multimodal model: zam settings set llm.vision.model <model>`;
|
|
4827
|
+
}
|
|
4828
|
+
return {
|
|
4829
|
+
enabled,
|
|
4830
|
+
online,
|
|
4831
|
+
url,
|
|
4832
|
+
model,
|
|
4833
|
+
modelAvailable,
|
|
4834
|
+
availableModels,
|
|
4835
|
+
usable: enabled && online && modelAvailable,
|
|
4836
|
+
visionModelExplicit,
|
|
4837
|
+
warning
|
|
4838
|
+
};
|
|
4839
|
+
}
|
|
4534
4840
|
function detectRunner(url, model) {
|
|
4535
4841
|
let runner = "unknown";
|
|
4536
4842
|
let port = "8000";
|
|
@@ -4551,7 +4857,7 @@ function spawnLocalRunner(url, model) {
|
|
|
4551
4857
|
const { runner, port } = detectRunner(url, model);
|
|
4552
4858
|
try {
|
|
4553
4859
|
if (runner === "fastflowlm") {
|
|
4554
|
-
const flmExe =
|
|
4860
|
+
const flmExe = existsSync12("C:\\Program Files\\flm\\flm.exe") ? "C:\\Program Files\\flm\\flm.exe" : "flm";
|
|
4555
4861
|
if (!hasCommand("flm") && flmExe === "flm") return;
|
|
4556
4862
|
spawn(flmExe, ["serve", model, "--port", port], {
|
|
4557
4863
|
detached: true,
|
|
@@ -4572,7 +4878,7 @@ function spawnLocalRunner(url, model) {
|
|
|
4572
4878
|
async function startLocalRunner(url, model, locale) {
|
|
4573
4879
|
const { runner, port } = detectRunner(url, model);
|
|
4574
4880
|
if (runner === "fastflowlm") {
|
|
4575
|
-
const flmExe =
|
|
4881
|
+
const flmExe = existsSync12("C:\\Program Files\\flm\\flm.exe") ? "C:\\Program Files\\flm\\flm.exe" : "flm";
|
|
4576
4882
|
if (!hasCommand("flm") && flmExe === "flm") {
|
|
4577
4883
|
console.warn(
|
|
4578
4884
|
"\x1B[31m\u2717 FastFlowLM is configured but could not be found on the system.\x1B[0m"
|
|
@@ -4839,6 +5145,266 @@ async function ensureHighQualityQuestion(db, token) {
|
|
|
4839
5145
|
return null;
|
|
4840
5146
|
}
|
|
4841
5147
|
|
|
5148
|
+
// src/cli/llm/vision.ts
|
|
5149
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
5150
|
+
import { basename as basename2 } from "path";
|
|
5151
|
+
var LANGUAGE_NAMES2 = {
|
|
5152
|
+
en: "English",
|
|
5153
|
+
de: "German",
|
|
5154
|
+
es: "Spanish",
|
|
5155
|
+
fr: "French",
|
|
5156
|
+
pt: "Portuguese",
|
|
5157
|
+
zh: "Chinese",
|
|
5158
|
+
ja: "Japanese"
|
|
5159
|
+
};
|
|
5160
|
+
var OBSERVATION_KINDS2 = /* @__PURE__ */ new Set([
|
|
5161
|
+
"progress",
|
|
5162
|
+
"step-completed",
|
|
5163
|
+
"error",
|
|
5164
|
+
"help-seeking",
|
|
5165
|
+
"uncertain",
|
|
5166
|
+
"privacy-pause",
|
|
5167
|
+
"heartbeat"
|
|
5168
|
+
]);
|
|
5169
|
+
var ACTION_TYPES2 = /* @__PURE__ */ new Set([
|
|
5170
|
+
"click",
|
|
5171
|
+
"shortcut",
|
|
5172
|
+
"typing",
|
|
5173
|
+
"scroll",
|
|
5174
|
+
"window-change"
|
|
5175
|
+
]);
|
|
5176
|
+
async function observeUiSnapshotViaLLM(db, input8) {
|
|
5177
|
+
const cfg = await getVisionConfig(db);
|
|
5178
|
+
if (!cfg.enabled) {
|
|
5179
|
+
throw new Error(
|
|
5180
|
+
"Vision observation is disabled in settings (llm.vision.enabled)"
|
|
5181
|
+
);
|
|
5182
|
+
}
|
|
5183
|
+
const imageBytes = readFileSync9(input8.imagePath);
|
|
5184
|
+
const imageUrl = `data:image/png;base64,${imageBytes.toString("base64")}`;
|
|
5185
|
+
const model = input8.model ?? cfg.model;
|
|
5186
|
+
const content = await requestVisionDraft({
|
|
5187
|
+
url: cfg.url,
|
|
5188
|
+
apiKey: cfg.apiKey || DEFAULT_LLM_API_KEY,
|
|
5189
|
+
model,
|
|
5190
|
+
locale: cfg.locale,
|
|
5191
|
+
imageUrl,
|
|
5192
|
+
input: input8
|
|
5193
|
+
});
|
|
5194
|
+
let draft;
|
|
5195
|
+
try {
|
|
5196
|
+
draft = extractDraft(content);
|
|
5197
|
+
} catch {
|
|
5198
|
+
return uncertainReport(
|
|
5199
|
+
input8,
|
|
5200
|
+
"Vision model returned output that could not be parsed as JSON."
|
|
5201
|
+
);
|
|
5202
|
+
}
|
|
5203
|
+
const report = buildReport(input8, draft);
|
|
5204
|
+
if (!isUiObservationReport(report)) {
|
|
5205
|
+
return uncertainReport(
|
|
5206
|
+
input8,
|
|
5207
|
+
"Vision model returned an invalid UI report draft."
|
|
5208
|
+
);
|
|
5209
|
+
}
|
|
5210
|
+
return report;
|
|
5211
|
+
}
|
|
5212
|
+
async function requestVisionDraft(args) {
|
|
5213
|
+
const language = LANGUAGE_NAMES2[args.locale] ?? "English";
|
|
5214
|
+
const schema = `{
|
|
5215
|
+
"kind": "progress | step-completed | error | help-seeking | uncertain",
|
|
5216
|
+
"summary": "short factual UI summary in ${language}",
|
|
5217
|
+
"actions": [{"type": "click | shortcut | typing | scroll | window-change", "target": "optional UI target", "result": "optional visible result"}],
|
|
5218
|
+
"candidateTokens": [{"slug": "optional-kebab-case-skill-token", "confidence": 0.0, "rationale": "why this token may matter"}],
|
|
5219
|
+
"confidence": 0.0
|
|
5220
|
+
}`;
|
|
5221
|
+
const res = await fetchWithInteractiveTimeout(
|
|
5222
|
+
`${args.url}/chat/completions`,
|
|
5223
|
+
{
|
|
5224
|
+
method: "POST",
|
|
5225
|
+
headers: {
|
|
5226
|
+
"Content-Type": "application/json",
|
|
5227
|
+
Authorization: `Bearer ${args.apiKey}`
|
|
5228
|
+
},
|
|
5229
|
+
body: JSON.stringify({
|
|
5230
|
+
model: args.model,
|
|
5231
|
+
messages: [
|
|
5232
|
+
{
|
|
5233
|
+
role: "system",
|
|
5234
|
+
content: "You are ZAM's UI observer. Return only strict JSON. Do not include markdown, prose, or fields outside the requested schema."
|
|
5235
|
+
},
|
|
5236
|
+
{
|
|
5237
|
+
role: "user",
|
|
5238
|
+
content: [
|
|
5239
|
+
{
|
|
5240
|
+
type: "text",
|
|
5241
|
+
text: `Observe this Windows application snapshot for a learning session.
|
|
5242
|
+
Application process: ${args.input.application.processName}
|
|
5243
|
+
Window title: ${args.input.application.windowTitle ?? "(unknown)"}
|
|
5244
|
+
|
|
5245
|
+
Return this JSON draft only:
|
|
5246
|
+
${schema}`
|
|
5247
|
+
},
|
|
5248
|
+
{
|
|
5249
|
+
type: "image_url",
|
|
5250
|
+
image_url: { url: args.imageUrl }
|
|
5251
|
+
}
|
|
5252
|
+
]
|
|
5253
|
+
}
|
|
5254
|
+
],
|
|
5255
|
+
temperature: 0,
|
|
5256
|
+
max_tokens: args.input.maxTokens ?? 450
|
|
5257
|
+
}),
|
|
5258
|
+
locale: args.locale,
|
|
5259
|
+
hardTimeoutMs: args.input.hardTimeoutMs ?? 18e4
|
|
5260
|
+
}
|
|
5261
|
+
);
|
|
5262
|
+
if (!res.ok) {
|
|
5263
|
+
const errorText = await res.text().catch(() => "");
|
|
5264
|
+
if (errorText.includes("image") && (errorText.includes("not support") || errorText.includes("unsupported"))) {
|
|
5265
|
+
throw new Error(
|
|
5266
|
+
`Vision model "${args.model}" does not support image input. Set a multimodal model: zam settings set llm.vision.model <model>`
|
|
5267
|
+
);
|
|
5268
|
+
}
|
|
5269
|
+
throw new Error(
|
|
5270
|
+
`Vision LLM request failed: ${res.statusText} (${res.status}) - ${errorText}`
|
|
5271
|
+
);
|
|
5272
|
+
}
|
|
5273
|
+
const data = await res.json();
|
|
5274
|
+
if (data.error !== void 0) {
|
|
5275
|
+
const errorMsg = formatModelError(data.error);
|
|
5276
|
+
if (errorMsg.includes("image") && (errorMsg.includes("not support") || errorMsg.includes("unsupported"))) {
|
|
5277
|
+
throw new Error(
|
|
5278
|
+
`Vision model "${args.model}" does not support image input. Set a multimodal model: zam settings set llm.vision.model <model>`
|
|
5279
|
+
);
|
|
5280
|
+
}
|
|
5281
|
+
throw new Error(`Vision model failed: ${errorMsg}`);
|
|
5282
|
+
}
|
|
5283
|
+
const content = data.choices?.[0]?.message?.content;
|
|
5284
|
+
if (!content || typeof content !== "string") {
|
|
5285
|
+
throw new Error("Empty response from vision model");
|
|
5286
|
+
}
|
|
5287
|
+
return content.trim();
|
|
5288
|
+
}
|
|
5289
|
+
function extractDraft(content) {
|
|
5290
|
+
const fenced = content.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
5291
|
+
const candidate = (fenced?.[1] ?? content).trim();
|
|
5292
|
+
try {
|
|
5293
|
+
return JSON.parse(candidate);
|
|
5294
|
+
} catch {
|
|
5295
|
+
const start = candidate.indexOf("{");
|
|
5296
|
+
const end = candidate.lastIndexOf("}");
|
|
5297
|
+
if (start === -1 || end <= start) throw new Error("no JSON object found");
|
|
5298
|
+
return JSON.parse(
|
|
5299
|
+
candidate.slice(start, end + 1)
|
|
5300
|
+
);
|
|
5301
|
+
}
|
|
5302
|
+
}
|
|
5303
|
+
function buildReport(input8, draft) {
|
|
5304
|
+
return {
|
|
5305
|
+
version: UI_OBSERVATION_PROTOCOL_VERSION,
|
|
5306
|
+
sessionId: input8.sessionId,
|
|
5307
|
+
sequence: input8.sequence,
|
|
5308
|
+
observedFrom: input8.observedFrom,
|
|
5309
|
+
observedTo: input8.observedTo,
|
|
5310
|
+
kind: parseKind(draft.kind),
|
|
5311
|
+
application: input8.application,
|
|
5312
|
+
summary: parseSummary(draft.summary),
|
|
5313
|
+
actions: parseActions(draft.actions),
|
|
5314
|
+
evidence: [
|
|
5315
|
+
{
|
|
5316
|
+
type: "keyframe",
|
|
5317
|
+
ref: input8.evidenceRef ?? basename2(input8.imagePath),
|
|
5318
|
+
redacted: input8.redacted ?? false
|
|
5319
|
+
}
|
|
5320
|
+
],
|
|
5321
|
+
candidateTokens: parseCandidateTokens(draft.candidateTokens),
|
|
5322
|
+
confidence: parseConfidence(draft.confidence, 0.35)
|
|
5323
|
+
};
|
|
5324
|
+
}
|
|
5325
|
+
function uncertainReport(input8, summary) {
|
|
5326
|
+
return {
|
|
5327
|
+
version: UI_OBSERVATION_PROTOCOL_VERSION,
|
|
5328
|
+
sessionId: input8.sessionId,
|
|
5329
|
+
sequence: input8.sequence,
|
|
5330
|
+
observedFrom: input8.observedFrom,
|
|
5331
|
+
observedTo: input8.observedTo,
|
|
5332
|
+
kind: "uncertain",
|
|
5333
|
+
application: input8.application,
|
|
5334
|
+
summary,
|
|
5335
|
+
actions: [],
|
|
5336
|
+
evidence: [
|
|
5337
|
+
{
|
|
5338
|
+
type: "keyframe",
|
|
5339
|
+
ref: input8.evidenceRef ?? basename2(input8.imagePath),
|
|
5340
|
+
redacted: input8.redacted ?? false
|
|
5341
|
+
}
|
|
5342
|
+
],
|
|
5343
|
+
candidateTokens: [],
|
|
5344
|
+
confidence: 0.1
|
|
5345
|
+
};
|
|
5346
|
+
}
|
|
5347
|
+
function parseKind(value) {
|
|
5348
|
+
if (typeof value === "string" && OBSERVATION_KINDS2.has(value)) {
|
|
5349
|
+
return value;
|
|
5350
|
+
}
|
|
5351
|
+
return "uncertain";
|
|
5352
|
+
}
|
|
5353
|
+
function parseSummary(value) {
|
|
5354
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
5355
|
+
return value.trim();
|
|
5356
|
+
}
|
|
5357
|
+
return "Vision model did not provide a factual UI summary.";
|
|
5358
|
+
}
|
|
5359
|
+
function parseActions(value) {
|
|
5360
|
+
if (!Array.isArray(value)) return [];
|
|
5361
|
+
return value.flatMap((item) => {
|
|
5362
|
+
if (!isRecord2(item) || typeof item.type !== "string") return [];
|
|
5363
|
+
if (!ACTION_TYPES2.has(item.type)) return [];
|
|
5364
|
+
const action = { type: item.type };
|
|
5365
|
+
if (typeof item.target === "string" && item.target.trim()) {
|
|
5366
|
+
action.target = item.target.trim();
|
|
5367
|
+
}
|
|
5368
|
+
if (typeof item.result === "string" && item.result.trim()) {
|
|
5369
|
+
action.result = item.result.trim();
|
|
5370
|
+
}
|
|
5371
|
+
return [action];
|
|
5372
|
+
});
|
|
5373
|
+
}
|
|
5374
|
+
function parseCandidateTokens(value) {
|
|
5375
|
+
if (!Array.isArray(value)) return [];
|
|
5376
|
+
return value.flatMap((item) => {
|
|
5377
|
+
if (!isRecord2(item)) return [];
|
|
5378
|
+
if (typeof item.slug !== "string" || !/^[A-Za-z0-9._-]+$/.test(item.slug)) {
|
|
5379
|
+
return [];
|
|
5380
|
+
}
|
|
5381
|
+
if (typeof item.rationale !== "string" || !item.rationale.trim()) {
|
|
5382
|
+
return [];
|
|
5383
|
+
}
|
|
5384
|
+
return [
|
|
5385
|
+
{
|
|
5386
|
+
slug: item.slug,
|
|
5387
|
+
confidence: parseConfidence(item.confidence, 0.2),
|
|
5388
|
+
rationale: item.rationale.trim()
|
|
5389
|
+
}
|
|
5390
|
+
];
|
|
5391
|
+
});
|
|
5392
|
+
}
|
|
5393
|
+
function parseConfidence(value, fallback) {
|
|
5394
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
|
|
5395
|
+
return Math.max(0, Math.min(1, value));
|
|
5396
|
+
}
|
|
5397
|
+
function formatModelError(error) {
|
|
5398
|
+
if (typeof error === "string") return error;
|
|
5399
|
+
if (isRecord2(error) && typeof error.message === "string") {
|
|
5400
|
+
return error.message;
|
|
5401
|
+
}
|
|
5402
|
+
return JSON.stringify(error);
|
|
5403
|
+
}
|
|
5404
|
+
function isRecord2(value) {
|
|
5405
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5406
|
+
}
|
|
5407
|
+
|
|
4842
5408
|
// src/cli/commands/resolve-user.ts
|
|
4843
5409
|
async function ensureDefaultUser(db, preferredUserId) {
|
|
4844
5410
|
const stored = await getSetting(db, "user.id");
|
|
@@ -4892,6 +5458,13 @@ function jsonError(message) {
|
|
|
4892
5458
|
console.log(JSON.stringify({ error: message }, null, 2));
|
|
4893
5459
|
process.exit(1);
|
|
4894
5460
|
}
|
|
5461
|
+
function parseNonNegativeIntegerOption(name, value) {
|
|
5462
|
+
const parsed = Number(value);
|
|
5463
|
+
if (!Number.isInteger(parsed) || parsed < 0) {
|
|
5464
|
+
jsonError(`${name} must be a non-negative integer`);
|
|
5465
|
+
}
|
|
5466
|
+
return parsed;
|
|
5467
|
+
}
|
|
4895
5468
|
async function withDb2(fn) {
|
|
4896
5469
|
await withDb(fn, jsonError);
|
|
4897
5470
|
}
|
|
@@ -5131,6 +5704,45 @@ bridgeCommand.command("get-skill").description("Get an agent skill by slug (JSON
|
|
|
5131
5704
|
});
|
|
5132
5705
|
});
|
|
5133
5706
|
});
|
|
5707
|
+
bridgeCommand.command("start-session").description("Start a ZAM learning session (JSON)").requiredOption("--task <task>", "Session task description").option(
|
|
5708
|
+
"--context <context>",
|
|
5709
|
+
"Execution context: shell | ui | reallife",
|
|
5710
|
+
"shell"
|
|
5711
|
+
).option("--user <id>", "User ID (default: whoami)").action(async (opts) => {
|
|
5712
|
+
const context = opts.context;
|
|
5713
|
+
if (!["shell", "ui", "reallife"].includes(context)) {
|
|
5714
|
+
jsonError("context must be shell, ui, or reallife");
|
|
5715
|
+
}
|
|
5716
|
+
await withDb2(async (db) => {
|
|
5717
|
+
const userId = await resolveUser(opts, db, { json: true });
|
|
5718
|
+
const session = await startSession(db, {
|
|
5719
|
+
user_id: userId,
|
|
5720
|
+
task: opts.task,
|
|
5721
|
+
execution_context: context
|
|
5722
|
+
});
|
|
5723
|
+
jsonOut2({
|
|
5724
|
+
id: session.id,
|
|
5725
|
+
userId: session.user_id,
|
|
5726
|
+
task: session.task,
|
|
5727
|
+
executionContext: session.execution_context,
|
|
5728
|
+
startedAt: session.started_at,
|
|
5729
|
+
completedAt: session.completed_at
|
|
5730
|
+
});
|
|
5731
|
+
});
|
|
5732
|
+
});
|
|
5733
|
+
bridgeCommand.command("end-session").description("Complete an active ZAM learning session (JSON)").requiredOption("--session <id>", "Session ID").action(async (opts) => {
|
|
5734
|
+
await withDb2(async (db) => {
|
|
5735
|
+
const session = await endSession(db, opts.session);
|
|
5736
|
+
jsonOut2({
|
|
5737
|
+
id: session.id,
|
|
5738
|
+
userId: session.user_id,
|
|
5739
|
+
task: session.task,
|
|
5740
|
+
executionContext: session.execution_context,
|
|
5741
|
+
startedAt: session.started_at,
|
|
5742
|
+
completedAt: session.completed_at
|
|
5743
|
+
});
|
|
5744
|
+
});
|
|
5745
|
+
});
|
|
5134
5746
|
bridgeCommand.command("get-monitor").description("Read monitor log for a session (JSON)").requiredOption("--session <id>", "Session ID").action((opts) => {
|
|
5135
5747
|
if (!monitorLogExists(opts.session)) {
|
|
5136
5748
|
jsonOut2({
|
|
@@ -5272,7 +5884,7 @@ bridgeCommand.command("discover-skills").description(
|
|
|
5272
5884
|
"20"
|
|
5273
5885
|
).action(async (opts) => {
|
|
5274
5886
|
try {
|
|
5275
|
-
const monitorDir =
|
|
5887
|
+
const monitorDir = join11(homedir8(), ".zam", "monitor");
|
|
5276
5888
|
let files;
|
|
5277
5889
|
try {
|
|
5278
5890
|
files = readdirSync2(monitorDir).filter((f) => f.endsWith(".jsonl"));
|
|
@@ -5285,7 +5897,7 @@ bridgeCommand.command("discover-skills").description(
|
|
|
5285
5897
|
return;
|
|
5286
5898
|
}
|
|
5287
5899
|
const limit = Number(opts.limit);
|
|
5288
|
-
const sorted = files.map((f) => ({ name: f, path:
|
|
5900
|
+
const sorted = files.map((f) => ({ name: f, path: join11(monitorDir, f) })).sort((a, b) => b.name.localeCompare(a.name)).slice(0, limit);
|
|
5289
5901
|
const sessionCommands = /* @__PURE__ */ new Map();
|
|
5290
5902
|
for (const file of sorted) {
|
|
5291
5903
|
const sessionId = file.name.replace(".jsonl", "");
|
|
@@ -5320,6 +5932,81 @@ bridgeCommand.command("discover-skills").description(
|
|
|
5320
5932
|
jsonError(err.message);
|
|
5321
5933
|
}
|
|
5322
5934
|
});
|
|
5935
|
+
bridgeCommand.command("observe-ui-watch").description(
|
|
5936
|
+
"Poll live UI observer watch reports for a ZAM learning session (JSON)"
|
|
5937
|
+
).requiredOption(
|
|
5938
|
+
"--session <id>",
|
|
5939
|
+
"ZAM session ID (also the observer log key)"
|
|
5940
|
+
).option("--after <n>", "Only return observations after this sequence").option("--limit <n>", "Maximum observations to return", "100").action(async (opts) => {
|
|
5941
|
+
await withDb2(async (db) => {
|
|
5942
|
+
const session = await db.prepare("SELECT id, execution_context FROM sessions WHERE id = ?").get(opts.session);
|
|
5943
|
+
if (!session) {
|
|
5944
|
+
jsonError(`Session not found: ${opts.session}`);
|
|
5945
|
+
}
|
|
5946
|
+
const after = opts.after === void 0 ? void 0 : parseNonNegativeIntegerOption("after", opts.after);
|
|
5947
|
+
const limit = parseNonNegativeIntegerOption("limit", opts.limit);
|
|
5948
|
+
const observations = readUiObservationLog(opts.session).filter((report) => after === void 0 || report.sequence > after).slice(0, limit);
|
|
5949
|
+
const last = observations[observations.length - 1];
|
|
5950
|
+
jsonOut2({
|
|
5951
|
+
sessionId: opts.session,
|
|
5952
|
+
executionContext: session.execution_context,
|
|
5953
|
+
observationSource: "ui",
|
|
5954
|
+
logExists: uiObservationLogExists(opts.session),
|
|
5955
|
+
after: after ?? null,
|
|
5956
|
+
count: observations.length,
|
|
5957
|
+
nextSequence: last?.sequence ?? after ?? null,
|
|
5958
|
+
observations
|
|
5959
|
+
});
|
|
5960
|
+
});
|
|
5961
|
+
});
|
|
5962
|
+
bridgeCommand.command("get-observations").description("Read UI observer reports for a session (JSON)").requiredOption("--session <id>", "Observer session ID").option("--after <n>", "Only return observations after this sequence").option("--limit <n>", "Maximum observations to return", "100").action((opts) => {
|
|
5963
|
+
try {
|
|
5964
|
+
const after = opts.after === void 0 ? void 0 : parseNonNegativeIntegerOption("after", opts.after);
|
|
5965
|
+
const limit = parseNonNegativeIntegerOption("limit", opts.limit);
|
|
5966
|
+
const observations = readUiObservationLog(opts.session).filter((report) => after === void 0 || report.sequence > after).slice(0, limit);
|
|
5967
|
+
const last = observations[observations.length - 1];
|
|
5968
|
+
jsonOut2({
|
|
5969
|
+
sessionId: opts.session,
|
|
5970
|
+
after: after ?? null,
|
|
5971
|
+
count: observations.length,
|
|
5972
|
+
nextSequence: last?.sequence ?? after ?? null,
|
|
5973
|
+
observations
|
|
5974
|
+
});
|
|
5975
|
+
} catch (err) {
|
|
5976
|
+
jsonError(err.message);
|
|
5977
|
+
}
|
|
5978
|
+
});
|
|
5979
|
+
bridgeCommand.command("observe-ui-snapshot").description(
|
|
5980
|
+
"Analyze a captured UI snapshot with the configured vision LLM (JSON)"
|
|
5981
|
+
).requiredOption("--session <id>", "Observer session ID").requiredOption("--sequence <n>", "Monotonic observation sequence number").requiredOption("--image <path>", "PNG snapshot path").requiredOption("--observed-from <iso>", "Observation window start time").requiredOption("--observed-to <iso>", "Observation window end time").requiredOption("--process-name <name>", "Observed application process name").option("--process-id <n>", "Observed application process ID").option("--window-title <title>", "Observed window title").option("--evidence-ref <ref>", "Evidence reference to put in the report").option("--model <model>", "Override configured LLM model for this request").option("--max-tokens <n>", "Model response token budget").option("--timeout <ms>", "Hard request timeout in milliseconds").option("--redacted", "Mark the snapshot evidence as redacted").option("--write-log", "Append the generated report to the session JSONL").action(async (opts) => {
|
|
5982
|
+
const sequence = parseNonNegativeIntegerOption("sequence", opts.sequence);
|
|
5983
|
+
const processId = opts.processId === void 0 ? void 0 : parseNonNegativeIntegerOption("process-id", opts.processId);
|
|
5984
|
+
const maxTokens = opts.maxTokens === void 0 ? void 0 : parseNonNegativeIntegerOption("max-tokens", opts.maxTokens);
|
|
5985
|
+
const hardTimeoutMs = opts.timeout === void 0 ? void 0 : parseNonNegativeIntegerOption("timeout", opts.timeout);
|
|
5986
|
+
await withDb2(async (db) => {
|
|
5987
|
+
const report = await observeUiSnapshotViaLLM(db, {
|
|
5988
|
+
sessionId: opts.session,
|
|
5989
|
+
sequence,
|
|
5990
|
+
observedFrom: opts.observedFrom,
|
|
5991
|
+
observedTo: opts.observedTo,
|
|
5992
|
+
imagePath: opts.image,
|
|
5993
|
+
application: {
|
|
5994
|
+
processName: opts.processName,
|
|
5995
|
+
processId,
|
|
5996
|
+
windowTitle: opts.windowTitle
|
|
5997
|
+
},
|
|
5998
|
+
evidenceRef: opts.evidenceRef,
|
|
5999
|
+
redacted: opts.redacted === true,
|
|
6000
|
+
model: opts.model,
|
|
6001
|
+
maxTokens,
|
|
6002
|
+
hardTimeoutMs
|
|
6003
|
+
});
|
|
6004
|
+
if (opts.writeLog === true) {
|
|
6005
|
+
appendUiObservationReport(report);
|
|
6006
|
+
}
|
|
6007
|
+
jsonOut2(report);
|
|
6008
|
+
});
|
|
6009
|
+
});
|
|
5323
6010
|
bridgeCommand.command("check-llm").description("Check if LLM is enabled and online (JSON)").action(async () => {
|
|
5324
6011
|
await withDb2(async (db) => {
|
|
5325
6012
|
const { enabled, url, model, apiKey } = await getLlmConfig(db);
|
|
@@ -5345,6 +6032,13 @@ bridgeCommand.command("check-llm").description("Check if LLM is enabled and onli
|
|
|
5345
6032
|
});
|
|
5346
6033
|
});
|
|
5347
6034
|
});
|
|
6035
|
+
bridgeCommand.command("check-vision").description(
|
|
6036
|
+
"Check if UI observer vision analysis is enabled and ready (JSON)"
|
|
6037
|
+
).action(async () => {
|
|
6038
|
+
await withDb2(async (db) => {
|
|
6039
|
+
jsonOut2(await checkVisionReadiness(db));
|
|
6040
|
+
});
|
|
6041
|
+
});
|
|
5348
6042
|
bridgeCommand.command("ensure-llm").description(
|
|
5349
6043
|
"Start the local LLM server if needed and report readiness (JSON)"
|
|
5350
6044
|
).option(
|
|
@@ -5532,7 +6226,7 @@ bridgeCommand.command("get-neighborhood").description(
|
|
|
5532
6226
|
bridgeCommand.command("serve").description("Start the persistent JSON-RPC stdin/stdout server").option("--stdin", "Use stdin/stdout for communication").action(async (_opts) => {
|
|
5533
6227
|
isServeMode = true;
|
|
5534
6228
|
const {
|
|
5535
|
-
appendFileSync:
|
|
6229
|
+
appendFileSync: appendFileSync4,
|
|
5536
6230
|
existsSync: fileExists,
|
|
5537
6231
|
mkdirSync: makeDir
|
|
5538
6232
|
} = await import("fs");
|
|
@@ -5543,7 +6237,7 @@ bridgeCommand.command("serve").description("Start the persistent JSON-RPC stdin/
|
|
|
5543
6237
|
const logDiag = (msg) => {
|
|
5544
6238
|
try {
|
|
5545
6239
|
if (!fileExists(logDir)) makeDir(logDir, { recursive: true });
|
|
5546
|
-
|
|
6240
|
+
appendFileSync4(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}
|
|
5547
6241
|
`);
|
|
5548
6242
|
} catch {
|
|
5549
6243
|
}
|
|
@@ -5994,19 +6688,19 @@ async function setupTurso(urlArg, tokenArg, mode) {
|
|
|
5994
6688
|
|
|
5995
6689
|
// src/cli/commands/git-sync.ts
|
|
5996
6690
|
import { execSync as execSync4 } from "child_process";
|
|
5997
|
-
import { chmodSync as chmodSync2, existsSync as
|
|
5998
|
-
import { join as
|
|
6691
|
+
import { chmodSync as chmodSync2, existsSync as existsSync13, writeFileSync as writeFileSync5 } from "fs";
|
|
6692
|
+
import { join as join12 } from "path";
|
|
5999
6693
|
import { Command as Command5 } from "commander";
|
|
6000
6694
|
function installHook2() {
|
|
6001
|
-
const gitDir =
|
|
6002
|
-
if (!
|
|
6695
|
+
const gitDir = join12(process.cwd(), ".git");
|
|
6696
|
+
if (!existsSync13(gitDir)) {
|
|
6003
6697
|
console.error(
|
|
6004
6698
|
"Error: Current directory is not the root of a Git repository."
|
|
6005
6699
|
);
|
|
6006
6700
|
process.exit(1);
|
|
6007
6701
|
}
|
|
6008
|
-
const hooksDir =
|
|
6009
|
-
const hookPath =
|
|
6702
|
+
const hooksDir = join12(gitDir, "hooks");
|
|
6703
|
+
const hookPath = join12(hooksDir, "post-commit");
|
|
6010
6704
|
const hookContent = `#!/bin/sh
|
|
6011
6705
|
# ZAM Spaced Repetition Auto-Stale Hook
|
|
6012
6706
|
# Triggered automatically on git commits to decay modified concept cards.
|
|
@@ -6115,7 +6809,7 @@ ZAM Auto-Stale Complete: Scanned ${changedFiles.length} file(s).`
|
|
|
6115
6809
|
});
|
|
6116
6810
|
|
|
6117
6811
|
// src/cli/commands/goal.ts
|
|
6118
|
-
import { existsSync as
|
|
6812
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync7 } from "fs";
|
|
6119
6813
|
import { resolve as resolve3 } from "path";
|
|
6120
6814
|
import { input as input2 } from "@inquirer/prompts";
|
|
6121
6815
|
import { Command as Command6 } from "commander";
|
|
@@ -6139,7 +6833,7 @@ goalCommand.command("list").description("List all goals").option(
|
|
|
6139
6833
|
"Filter by status (active, completed, paused, abandoned)"
|
|
6140
6834
|
).option("--tree", "Show goals as a tree with parent/child relationships").option("--json", "Output as JSON").action(async (opts) => {
|
|
6141
6835
|
const goalsDir = await resolveGoalsDir();
|
|
6142
|
-
if (!
|
|
6836
|
+
if (!existsSync14(goalsDir)) {
|
|
6143
6837
|
console.error(`Goals directory not found: ${goalsDir}`);
|
|
6144
6838
|
console.error(
|
|
6145
6839
|
"Set it with: zam settings set personal.goals_dir /path/to/goals"
|
|
@@ -6240,8 +6934,8 @@ ${"\u2500".repeat(50)}`);
|
|
|
6240
6934
|
});
|
|
6241
6935
|
goalCommand.command("create").description("Create a new goal").option("--slug <slug>", "Goal slug (used as filename)").option("--title <title>", "Goal title").option("--parent <slug>", "Parent goal slug").option("--description <text>", "Goal description").option("--json", "Output as JSON").action(async (opts) => {
|
|
6242
6936
|
const goalsDir = await resolveGoalsDir();
|
|
6243
|
-
if (!
|
|
6244
|
-
|
|
6937
|
+
if (!existsSync14(goalsDir)) {
|
|
6938
|
+
mkdirSync7(goalsDir, { recursive: true });
|
|
6245
6939
|
}
|
|
6246
6940
|
let slug = opts.slug;
|
|
6247
6941
|
let title = opts.title;
|
|
@@ -6300,21 +6994,21 @@ goalCommand.command("status <slug> <status>").description("Update a goal's statu
|
|
|
6300
6994
|
});
|
|
6301
6995
|
|
|
6302
6996
|
// src/cli/commands/init.ts
|
|
6303
|
-
import { existsSync as
|
|
6304
|
-
import { homedir as
|
|
6305
|
-
import { join as
|
|
6997
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync8, writeFileSync as writeFileSync6 } from "fs";
|
|
6998
|
+
import { homedir as homedir9 } from "os";
|
|
6999
|
+
import { join as join13 } from "path";
|
|
6306
7000
|
import { confirm, input as input3 } from "@inquirer/prompts";
|
|
6307
7001
|
import { Command as Command7 } from "commander";
|
|
6308
|
-
var HOME2 =
|
|
7002
|
+
var HOME2 = homedir9();
|
|
6309
7003
|
function printLine(char = "\u2550", len = 60, color = "\x1B[36m") {
|
|
6310
7004
|
console.log(`${color}${char.repeat(len)}\x1B[0m`);
|
|
6311
7005
|
}
|
|
6312
7006
|
function bootstrapSandboxWorkspace(workspaceDir) {
|
|
6313
|
-
|
|
6314
|
-
|
|
6315
|
-
|
|
6316
|
-
const worldviewFile =
|
|
6317
|
-
if (!
|
|
7007
|
+
mkdirSync8(join13(workspaceDir, "beliefs"), { recursive: true });
|
|
7008
|
+
mkdirSync8(join13(workspaceDir, "goals"), { recursive: true });
|
|
7009
|
+
mkdirSync8(join13(workspaceDir, "skills"), { recursive: true });
|
|
7010
|
+
const worldviewFile = join13(workspaceDir, "beliefs", "worldview.md");
|
|
7011
|
+
if (!existsSync15(worldviewFile)) {
|
|
6318
7012
|
writeFileSync6(
|
|
6319
7013
|
worldviewFile,
|
|
6320
7014
|
`# Personal Worldview
|
|
@@ -6327,8 +7021,8 @@ Here, I declare the core concepts and principles I want to master.
|
|
|
6327
7021
|
"utf8"
|
|
6328
7022
|
);
|
|
6329
7023
|
}
|
|
6330
|
-
const goalsFile =
|
|
6331
|
-
if (!
|
|
7024
|
+
const goalsFile = join13(workspaceDir, "goals", "goals.md");
|
|
7025
|
+
if (!existsSync15(goalsFile)) {
|
|
6332
7026
|
writeFileSync6(
|
|
6333
7027
|
goalsFile,
|
|
6334
7028
|
`# Personal Goals
|
|
@@ -6351,7 +7045,7 @@ var initCommand = new Command7("init").description("Launch the guided interactiv
|
|
|
6351
7045
|
);
|
|
6352
7046
|
printLine();
|
|
6353
7047
|
console.log("\n\x1B[1m[1/5] Setting up Local Workspace Sandbox\x1B[0m");
|
|
6354
|
-
const defaultWorkspace =
|
|
7048
|
+
const defaultWorkspace = join13(HOME2, "Documents", "zam");
|
|
6355
7049
|
const workspacePath = await input3({
|
|
6356
7050
|
message: "Choose your ZAM workspace directory:",
|
|
6357
7051
|
default: defaultWorkspace
|
|
@@ -7051,7 +7745,7 @@ ${"\u2550".repeat(50)}`);
|
|
|
7051
7745
|
import { execFileSync as execFileSync2, execSync as execSync5 } from "child_process";
|
|
7052
7746
|
import { unlinkSync, writeFileSync as writeFileSync7 } from "fs";
|
|
7053
7747
|
import { tmpdir } from "os";
|
|
7054
|
-
import { basename as
|
|
7748
|
+
import { basename as basename3, join as join14 } from "path";
|
|
7055
7749
|
import { Command as Command9 } from "commander";
|
|
7056
7750
|
function isPowerShellShell(shell) {
|
|
7057
7751
|
return shell === "pwsh" || shell === "powershell";
|
|
@@ -7073,7 +7767,7 @@ function detectShell() {
|
|
|
7073
7767
|
if (process.platform === "win32")
|
|
7074
7768
|
return findExecutable("pwsh.exe") ? "pwsh" : "powershell";
|
|
7075
7769
|
const shell = process.env.SHELL ?? "";
|
|
7076
|
-
return
|
|
7770
|
+
return basename3(shell) === "bash" ? "bash" : "zsh";
|
|
7077
7771
|
}
|
|
7078
7772
|
var monitorCommand = new Command9("monitor").description(
|
|
7079
7773
|
"Shell observation for real-time task monitoring"
|
|
@@ -7216,8 +7910,8 @@ function resolveZamInvocation(shell) {
|
|
|
7216
7910
|
if (installed) {
|
|
7217
7911
|
return isPowerShellShell(shell) ? `& ${psSingleQuoted2(installed)}` : installed;
|
|
7218
7912
|
}
|
|
7219
|
-
const projectRoot =
|
|
7220
|
-
const cliSource =
|
|
7913
|
+
const projectRoot = join14(import.meta.dirname, "..", "..", "..");
|
|
7914
|
+
const cliSource = join14(projectRoot, "src/cli/index.ts");
|
|
7221
7915
|
if (isPowerShellShell(shell)) {
|
|
7222
7916
|
return `& npx --prefix ${psSingleQuoted2(projectRoot)} tsx ${psSingleQuoted2(cliSource)}`;
|
|
7223
7917
|
}
|
|
@@ -7307,7 +8001,7 @@ end tell` : `tell application "Terminal"
|
|
|
7307
8001
|
activate
|
|
7308
8002
|
do script "${escaped}"
|
|
7309
8003
|
end tell`;
|
|
7310
|
-
const tmpFile =
|
|
8004
|
+
const tmpFile = join14(tmpdir(), `zam-monitor-${sessionId}.scpt`);
|
|
7311
8005
|
try {
|
|
7312
8006
|
writeFileSync7(tmpFile, appleScript);
|
|
7313
8007
|
execSync5(`osascript ${JSON.stringify(tmpFile)}`, { stdio: "ignore" });
|
|
@@ -7358,8 +8052,8 @@ Run this manually in a new PowerShell terminal:
|
|
|
7358
8052
|
}
|
|
7359
8053
|
|
|
7360
8054
|
// src/cli/commands/profile.ts
|
|
7361
|
-
import { homedir as
|
|
7362
|
-
import { dirname as dirname5, join as
|
|
8055
|
+
import { homedir as homedir10 } from "os";
|
|
8056
|
+
import { dirname as dirname5, join as join15, resolve as resolve4 } from "path";
|
|
7363
8057
|
import { Command as Command10 } from "commander";
|
|
7364
8058
|
var C2 = {
|
|
7365
8059
|
reset: "\x1B[0m",
|
|
@@ -7369,7 +8063,7 @@ var C2 = {
|
|
|
7369
8063
|
green: "\x1B[32m"
|
|
7370
8064
|
};
|
|
7371
8065
|
function defaultPersonalDir() {
|
|
7372
|
-
return
|
|
8066
|
+
return join15(homedir10(), "Documents", "zam");
|
|
7373
8067
|
}
|
|
7374
8068
|
function render(profile) {
|
|
7375
8069
|
const sync = profile.syncProvider ? `${C2.green}${profile.syncProvider}${C2.reset} ${C2.dim}(good for cross-device snapshots)${C2.reset}` : `${C2.dim}local folder (use a synced folder or snapshots to move between machines)${C2.reset}`;
|
|
@@ -7530,7 +8224,7 @@ ${"\u2550".repeat(50)}`);
|
|
|
7530
8224
|
});
|
|
7531
8225
|
|
|
7532
8226
|
// src/cli/commands/session.ts
|
|
7533
|
-
import { readFileSync as
|
|
8227
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
7534
8228
|
import { input as input6, select as select2 } from "@inquirer/prompts";
|
|
7535
8229
|
import { Command as Command12 } from "commander";
|
|
7536
8230
|
var sessionCommand = new Command12("session").description(
|
|
@@ -7711,7 +8405,7 @@ function loadPatternFile(path) {
|
|
|
7711
8405
|
if (!path) return [];
|
|
7712
8406
|
let parsed;
|
|
7713
8407
|
try {
|
|
7714
|
-
parsed = JSON.parse(
|
|
8408
|
+
parsed = JSON.parse(readFileSync10(path, "utf-8"));
|
|
7715
8409
|
} catch (err) {
|
|
7716
8410
|
throw new Error(
|
|
7717
8411
|
`Cannot read synthesis patterns from ${path}: ${err.message}`
|
|
@@ -7901,11 +8595,23 @@ sessionCommand.command("end").description("End a session and show summary").requ
|
|
|
7901
8595
|
});
|
|
7902
8596
|
|
|
7903
8597
|
// src/cli/commands/settings.ts
|
|
7904
|
-
import { existsSync as
|
|
8598
|
+
import { existsSync as existsSync16 } from "fs";
|
|
7905
8599
|
import { Command as Command13 } from "commander";
|
|
7906
8600
|
var settingsCommand = new Command13("settings").description(
|
|
7907
8601
|
"Manage user settings"
|
|
7908
8602
|
);
|
|
8603
|
+
var BOOLEAN_SETTING_KEYS = /* @__PURE__ */ new Set(["llm.enabled", "llm.vision.enabled"]);
|
|
8604
|
+
function normalizeSettingValue(key, value) {
|
|
8605
|
+
if (!BOOLEAN_SETTING_KEYS.has(key)) return value;
|
|
8606
|
+
const lower = value.toLowerCase();
|
|
8607
|
+
if (lower === "on" || lower === "enable" || lower === "enabled" || lower === "true") {
|
|
8608
|
+
return "true";
|
|
8609
|
+
}
|
|
8610
|
+
if (lower === "off" || lower === "disable" || lower === "disabled" || lower === "false") {
|
|
8611
|
+
return "false";
|
|
8612
|
+
}
|
|
8613
|
+
return value;
|
|
8614
|
+
}
|
|
7909
8615
|
settingsCommand.command("show").description("Show all settings").option("--json", "Output as JSON").action(async (opts) => {
|
|
7910
8616
|
await withDb(async (db) => {
|
|
7911
8617
|
if (opts.json) {
|
|
@@ -7943,15 +8649,7 @@ settingsCommand.command("get <key>").description("Get a single setting").option(
|
|
|
7943
8649
|
});
|
|
7944
8650
|
settingsCommand.command("set <key> <value>").description("Set a setting").option("--quiet", "Suppress output").action(async (key, value, opts) => {
|
|
7945
8651
|
await withDb(async (db) => {
|
|
7946
|
-
|
|
7947
|
-
if (key === "llm.enabled") {
|
|
7948
|
-
const lower = value.toLowerCase();
|
|
7949
|
-
if (lower === "on" || lower === "enable" || lower === "enabled" || lower === "true") {
|
|
7950
|
-
parsedVal = "true";
|
|
7951
|
-
} else if (lower === "off" || lower === "disable" || lower === "disabled" || lower === "false") {
|
|
7952
|
-
parsedVal = "false";
|
|
7953
|
-
}
|
|
7954
|
-
}
|
|
8652
|
+
const parsedVal = normalizeSettingValue(key, value);
|
|
7955
8653
|
await setSetting(db, key, parsedVal);
|
|
7956
8654
|
if (!opts.quiet) {
|
|
7957
8655
|
console.log(`Set ${key} = ${parsedVal}`);
|
|
@@ -8056,7 +8754,7 @@ settingsCommand.command("repos").description("Show or set Personal, Team, and Or
|
|
|
8056
8754
|
console.log("\nValidation:");
|
|
8057
8755
|
for (const [name, path] of Object.entries(paths)) {
|
|
8058
8756
|
if (path) {
|
|
8059
|
-
const exists =
|
|
8757
|
+
const exists = existsSync16(path);
|
|
8060
8758
|
console.log(
|
|
8061
8759
|
` ${name.padEnd(9)}: ${exists ? "\x1B[32m\u2713 Valid folder\x1B[0m" : "\x1B[31m\u2717 Directory does not exist\x1B[0m"}`
|
|
8062
8760
|
);
|
|
@@ -8068,41 +8766,41 @@ settingsCommand.command("repos").description("Show or set Personal, Team, and Or
|
|
|
8068
8766
|
});
|
|
8069
8767
|
|
|
8070
8768
|
// src/cli/commands/setup.ts
|
|
8071
|
-
import { copyFileSync as copyFileSync2, existsSync as
|
|
8072
|
-
import { basename as
|
|
8769
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync17, mkdirSync as mkdirSync9, writeFileSync as writeFileSync8 } from "fs";
|
|
8770
|
+
import { basename as basename4, dirname as dirname6, join as join16 } from "path";
|
|
8073
8771
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
8074
8772
|
import { Command as Command14 } from "commander";
|
|
8075
8773
|
var packageRoot = [
|
|
8076
8774
|
fileURLToPath2(new URL("../..", import.meta.url)),
|
|
8077
8775
|
fileURLToPath2(new URL("../../..", import.meta.url))
|
|
8078
|
-
].find((candidate) =>
|
|
8776
|
+
].find((candidate) => existsSync17(join16(candidate, "package.json"))) ?? fileURLToPath2(new URL("../..", import.meta.url));
|
|
8079
8777
|
var SKILL_PAIRS = [
|
|
8080
8778
|
{
|
|
8081
|
-
from:
|
|
8082
|
-
to:
|
|
8779
|
+
from: join16(packageRoot, ".claude", "skills", "zam", "SKILL.md"),
|
|
8780
|
+
to: join16(".claude", "skills", "zam", "SKILL.md")
|
|
8083
8781
|
},
|
|
8084
8782
|
{
|
|
8085
|
-
from:
|
|
8086
|
-
to:
|
|
8783
|
+
from: join16(packageRoot, ".agent", "skills", "zam", "SKILL.md"),
|
|
8784
|
+
to: join16(".agent", "skills", "zam", "SKILL.md")
|
|
8087
8785
|
},
|
|
8088
8786
|
{
|
|
8089
|
-
from:
|
|
8090
|
-
to:
|
|
8787
|
+
from: join16(packageRoot, ".agents", "skills", "zam", "SKILL.md"),
|
|
8788
|
+
to: join16(".agents", "skills", "zam", "SKILL.md")
|
|
8091
8789
|
}
|
|
8092
8790
|
];
|
|
8093
8791
|
function copySkills(force, cwd = process.cwd()) {
|
|
8094
8792
|
let anyAction = false;
|
|
8095
8793
|
for (const { from, to } of SKILL_PAIRS) {
|
|
8096
|
-
const dest =
|
|
8097
|
-
if (!
|
|
8794
|
+
const dest = join16(cwd, to);
|
|
8795
|
+
if (!existsSync17(from)) {
|
|
8098
8796
|
console.warn(` warn source not found, skipping: ${from}`);
|
|
8099
8797
|
continue;
|
|
8100
8798
|
}
|
|
8101
|
-
if (
|
|
8799
|
+
if (existsSync17(dest) && !force) {
|
|
8102
8800
|
console.log(` skip ${to} (already present \xE2\u20AC\u201D use --force to update)`);
|
|
8103
8801
|
continue;
|
|
8104
8802
|
}
|
|
8105
|
-
|
|
8803
|
+
mkdirSync9(dirname6(dest), { recursive: true });
|
|
8106
8804
|
copyFileSync2(from, dest);
|
|
8107
8805
|
console.log(` copy ${to}`);
|
|
8108
8806
|
anyAction = true;
|
|
@@ -8131,12 +8829,12 @@ async function initDatabase(skipInit) {
|
|
|
8131
8829
|
}
|
|
8132
8830
|
function writeClaudeMd(skipClaudeMd, cwd = process.cwd()) {
|
|
8133
8831
|
if (skipClaudeMd) return;
|
|
8134
|
-
const dest =
|
|
8135
|
-
if (
|
|
8832
|
+
const dest = join16(cwd, "CLAUDE.md");
|
|
8833
|
+
if (existsSync17(dest)) {
|
|
8136
8834
|
console.log(` skip CLAUDE.md (already present)`);
|
|
8137
8835
|
return;
|
|
8138
8836
|
}
|
|
8139
|
-
const name =
|
|
8837
|
+
const name = basename4(cwd);
|
|
8140
8838
|
writeFileSync8(
|
|
8141
8839
|
dest,
|
|
8142
8840
|
`# ZAM Personal Kernel \xE2\u20AC\u201D ${name}
|
|
@@ -8165,12 +8863,12 @@ Use \`zam connector setup turso\` to store cloud credentials in
|
|
|
8165
8863
|
}
|
|
8166
8864
|
function writeAgentsMd(skipAgentsMd, cwd = process.cwd()) {
|
|
8167
8865
|
if (skipAgentsMd) return;
|
|
8168
|
-
const dest =
|
|
8169
|
-
if (
|
|
8866
|
+
const dest = join16(cwd, "AGENTS.md");
|
|
8867
|
+
if (existsSync17(dest)) {
|
|
8170
8868
|
console.log(` skip AGENTS.md (already present)`);
|
|
8171
8869
|
return;
|
|
8172
8870
|
}
|
|
8173
|
-
const name =
|
|
8871
|
+
const name = basename4(cwd);
|
|
8174
8872
|
writeFileSync8(
|
|
8175
8873
|
dest,
|
|
8176
8874
|
`# ZAM Personal Kernel - ${name}
|
|
@@ -8311,9 +9009,9 @@ skillCommand.command("add").description("Register a new agent skill").requiredOp
|
|
|
8311
9009
|
});
|
|
8312
9010
|
|
|
8313
9011
|
// src/cli/commands/snapshot.ts
|
|
8314
|
-
import { existsSync as
|
|
8315
|
-
import { homedir as
|
|
8316
|
-
import { dirname as dirname7, join as
|
|
9012
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync10, readFileSync as readFileSync11, writeFileSync as writeFileSync9 } from "fs";
|
|
9013
|
+
import { homedir as homedir11 } from "os";
|
|
9014
|
+
import { dirname as dirname7, join as join17 } from "path";
|
|
8317
9015
|
import { Command as Command16 } from "commander";
|
|
8318
9016
|
function defaultOutName() {
|
|
8319
9017
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d+Z$/, "").replace(/:/g, "-");
|
|
@@ -8333,7 +9031,7 @@ var exportCmd = new Command16("export").description("Write a portable SQL-text s
|
|
|
8333
9031
|
try {
|
|
8334
9032
|
db = await openDatabaseWithSync({ initialize: true });
|
|
8335
9033
|
const snapshot = await exportSnapshot(db);
|
|
8336
|
-
const personalDir = await getSetting(db, "personal.workspace_dir") ||
|
|
9034
|
+
const personalDir = await getSetting(db, "personal.workspace_dir") || join17(homedir11(), "Documents", "zam");
|
|
8337
9035
|
await db.close();
|
|
8338
9036
|
db = void 0;
|
|
8339
9037
|
const manifest = verifySnapshot(snapshot);
|
|
@@ -8342,10 +9040,10 @@ var exportCmd = new Command16("export").description("Write a portable SQL-text s
|
|
|
8342
9040
|
process.stdout.write(snapshot);
|
|
8343
9041
|
return;
|
|
8344
9042
|
}
|
|
8345
|
-
const out = opts.out ??
|
|
9043
|
+
const out = opts.out ?? join17(personalDir, "snapshots", defaultOutName());
|
|
8346
9044
|
const dir = dirname7(out);
|
|
8347
|
-
if (dir && dir !== "." && !
|
|
8348
|
-
|
|
9045
|
+
if (dir && dir !== "." && !existsSync18(dir)) {
|
|
9046
|
+
mkdirSync10(dir, { recursive: true });
|
|
8349
9047
|
}
|
|
8350
9048
|
writeFileSync9(out, snapshot, "utf-8");
|
|
8351
9049
|
console.log(`Snapshot written: ${out}`);
|
|
@@ -8361,11 +9059,11 @@ var exportCmd = new Command16("export").description("Write a portable SQL-text s
|
|
|
8361
9059
|
var importCmd = new Command16("import").description("Restore a snapshot into the database").argument("<file>", "Snapshot file to restore").option("--force", "Overwrite a non-empty database", false).action(async (file, opts) => {
|
|
8362
9060
|
let db;
|
|
8363
9061
|
try {
|
|
8364
|
-
if (!
|
|
9062
|
+
if (!existsSync18(file)) {
|
|
8365
9063
|
console.error(`Error: Snapshot file not found: ${file}`);
|
|
8366
9064
|
process.exit(1);
|
|
8367
9065
|
}
|
|
8368
|
-
const snapshot =
|
|
9066
|
+
const snapshot = readFileSync11(file, "utf-8");
|
|
8369
9067
|
db = await openDatabaseWithSync({ initialize: true });
|
|
8370
9068
|
const result = await importSnapshot(db, snapshot, { force: opts.force });
|
|
8371
9069
|
await db.close();
|
|
@@ -8383,11 +9081,11 @@ var importCmd = new Command16("import").description("Restore a snapshot into the
|
|
|
8383
9081
|
});
|
|
8384
9082
|
var verifyCmd = new Command16("verify").description("Check a snapshot's manifest and checksum without importing").argument("<file>", "Snapshot file to verify").action((file) => {
|
|
8385
9083
|
try {
|
|
8386
|
-
if (!
|
|
9084
|
+
if (!existsSync18(file)) {
|
|
8387
9085
|
console.error(`Error: Snapshot file not found: ${file}`);
|
|
8388
9086
|
process.exit(1);
|
|
8389
9087
|
}
|
|
8390
|
-
const manifest = verifySnapshot(
|
|
9088
|
+
const manifest = verifySnapshot(readFileSync11(file, "utf-8"));
|
|
8391
9089
|
const { total, nonEmpty } = summarize(manifest.tables);
|
|
8392
9090
|
console.log(`Valid snapshot (format v${manifest.version})`);
|
|
8393
9091
|
console.log(` created: ${manifest.createdAt}`);
|
|
@@ -8718,9 +9416,9 @@ tokenCommand.command("status").description("Show full status of a token for a us
|
|
|
8718
9416
|
|
|
8719
9417
|
// src/cli/commands/ui.ts
|
|
8720
9418
|
import { spawn as spawn2, spawnSync } from "child_process";
|
|
8721
|
-
import { existsSync as
|
|
8722
|
-
import { homedir as
|
|
8723
|
-
import { dirname as dirname8, join as
|
|
9419
|
+
import { existsSync as existsSync19 } from "fs";
|
|
9420
|
+
import { homedir as homedir12 } from "os";
|
|
9421
|
+
import { dirname as dirname8, join as join18 } from "path";
|
|
8724
9422
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
8725
9423
|
import { Command as Command19 } from "commander";
|
|
8726
9424
|
var C3 = {
|
|
@@ -8736,8 +9434,8 @@ function findDesktopDir() {
|
|
|
8736
9434
|
for (const start of starts) {
|
|
8737
9435
|
let dir = start;
|
|
8738
9436
|
for (let i = 0; i < 10; i++) {
|
|
8739
|
-
if (
|
|
8740
|
-
return
|
|
9437
|
+
if (existsSync19(join18(dir, "desktop", "src-tauri", "tauri.conf.json"))) {
|
|
9438
|
+
return join18(dir, "desktop");
|
|
8741
9439
|
}
|
|
8742
9440
|
const parent = dirname8(dir);
|
|
8743
9441
|
if (parent === dir) break;
|
|
@@ -8747,30 +9445,30 @@ function findDesktopDir() {
|
|
|
8747
9445
|
return null;
|
|
8748
9446
|
}
|
|
8749
9447
|
function findBuiltApp(desktopDir) {
|
|
8750
|
-
const releaseDir =
|
|
9448
|
+
const releaseDir = join18(desktopDir, "src-tauri", "target", "release");
|
|
8751
9449
|
if (process.platform === "win32") {
|
|
8752
9450
|
for (const name of ["ZAM.exe", "zam.exe", "zam-desktop.exe"]) {
|
|
8753
|
-
const p =
|
|
8754
|
-
if (
|
|
9451
|
+
const p = join18(releaseDir, name);
|
|
9452
|
+
if (existsSync19(p)) return p;
|
|
8755
9453
|
}
|
|
8756
9454
|
} else if (process.platform === "darwin") {
|
|
8757
|
-
const app =
|
|
8758
|
-
if (
|
|
9455
|
+
const app = join18(releaseDir, "bundle", "macos", "ZAM.app");
|
|
9456
|
+
if (existsSync19(app)) return app;
|
|
8759
9457
|
} else {
|
|
8760
9458
|
for (const name of ["zam", "ZAM", "zam-desktop"]) {
|
|
8761
|
-
const p =
|
|
8762
|
-
if (
|
|
9459
|
+
const p = join18(releaseDir, name);
|
|
9460
|
+
if (existsSync19(p)) return p;
|
|
8763
9461
|
}
|
|
8764
9462
|
}
|
|
8765
9463
|
return null;
|
|
8766
9464
|
}
|
|
8767
9465
|
function findInstalledApp() {
|
|
8768
9466
|
const candidates = process.platform === "win32" ? [
|
|
8769
|
-
process.env.LOCALAPPDATA &&
|
|
8770
|
-
process.env.ProgramFiles &&
|
|
8771
|
-
process.env["ProgramFiles(x86)"] &&
|
|
8772
|
-
] : process.platform === "darwin" ? ["/Applications/ZAM.app",
|
|
8773
|
-
return candidates.find((candidate) => candidate &&
|
|
9467
|
+
process.env.LOCALAPPDATA && join18(process.env.LOCALAPPDATA, "Programs", "ZAM", "ZAM.exe"),
|
|
9468
|
+
process.env.ProgramFiles && join18(process.env.ProgramFiles, "ZAM", "ZAM.exe"),
|
|
9469
|
+
process.env["ProgramFiles(x86)"] && join18(process.env["ProgramFiles(x86)"], "ZAM", "ZAM.exe")
|
|
9470
|
+
] : process.platform === "darwin" ? ["/Applications/ZAM.app", join18(homedir12(), "Applications", "ZAM.app")] : ["/opt/ZAM/zam", "/usr/bin/zam-desktop"];
|
|
9471
|
+
return candidates.find((candidate) => candidate && existsSync19(candidate)) || null;
|
|
8774
9472
|
}
|
|
8775
9473
|
function runNpm(args, opts) {
|
|
8776
9474
|
const res = spawnSync("npm", args, {
|
|
@@ -8781,7 +9479,7 @@ function runNpm(args, opts) {
|
|
|
8781
9479
|
return res.status ?? 1;
|
|
8782
9480
|
}
|
|
8783
9481
|
function ensureDesktopDeps(desktopDir) {
|
|
8784
|
-
if (
|
|
9482
|
+
if (existsSync19(join18(desktopDir, "node_modules"))) return true;
|
|
8785
9483
|
console.log(
|
|
8786
9484
|
`${C3.cyan}Installing desktop dependencies (one-time)...${C3.reset}`
|
|
8787
9485
|
);
|
|
@@ -8807,13 +9505,13 @@ function requireRust() {
|
|
|
8807
9505
|
function hasMsvcBuildTools() {
|
|
8808
9506
|
if (process.platform !== "win32") return true;
|
|
8809
9507
|
const pf86 = process.env["ProgramFiles(x86)"] || "C:\\Program Files (x86)";
|
|
8810
|
-
const vswhere =
|
|
9508
|
+
const vswhere = join18(
|
|
8811
9509
|
pf86,
|
|
8812
9510
|
"Microsoft Visual Studio",
|
|
8813
9511
|
"Installer",
|
|
8814
9512
|
"vswhere.exe"
|
|
8815
9513
|
);
|
|
8816
|
-
if (!
|
|
9514
|
+
if (!existsSync19(vswhere)) return false;
|
|
8817
9515
|
const res = spawnSync(
|
|
8818
9516
|
vswhere,
|
|
8819
9517
|
[
|
|
@@ -8845,7 +9543,7 @@ function requireMsvcOnWindows() {
|
|
|
8845
9543
|
return false;
|
|
8846
9544
|
}
|
|
8847
9545
|
function warnIfCliMissing(repoRoot) {
|
|
8848
|
-
if (!
|
|
9546
|
+
if (!existsSync19(join18(repoRoot, "dist", "cli", "index.js"))) {
|
|
8849
9547
|
console.warn(
|
|
8850
9548
|
`${C3.yellow}\u26A0 CLI build not found (dist/cli/index.js). The GUI bridge needs it \u2014 run 'npm run build' at the repo root.${C3.reset}`
|
|
8851
9549
|
);
|
|
@@ -8908,7 +9606,7 @@ var uiCommand = new Command19("ui").description("Launch the ZAM Desktop GUI (Act
|
|
|
8908
9606
|
).option("--shortcut", "Create Desktop + Start-menu shortcuts to the GUI").action((opts) => {
|
|
8909
9607
|
const installedApp = findInstalledApp();
|
|
8910
9608
|
if (!opts.dev && !opts.build && !opts.shortcut && installedApp) {
|
|
8911
|
-
launchApp(installedApp,
|
|
9609
|
+
launchApp(installedApp, homedir12());
|
|
8912
9610
|
return;
|
|
8913
9611
|
}
|
|
8914
9612
|
const desktopDir = findDesktopDir();
|
|
@@ -8929,7 +9627,7 @@ var uiCommand = new Command19("ui").description("Launch the ZAM Desktop GUI (Act
|
|
|
8929
9627
|
);
|
|
8930
9628
|
const code = runNpm(["run", "tauri", "build"], { cwd: desktopDir });
|
|
8931
9629
|
if (code === 0) {
|
|
8932
|
-
const bundle =
|
|
9630
|
+
const bundle = join18(
|
|
8933
9631
|
desktopDir,
|
|
8934
9632
|
"src-tauri",
|
|
8935
9633
|
"target",
|
|
@@ -9005,8 +9703,8 @@ ${C3.dim}After that, 'zam ui' launches it directly.${C3.reset}`
|
|
|
9005
9703
|
});
|
|
9006
9704
|
|
|
9007
9705
|
// src/cli/commands/update.ts
|
|
9008
|
-
import { readFileSync as
|
|
9009
|
-
import { dirname as dirname9, join as
|
|
9706
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
9707
|
+
import { dirname as dirname9, join as join19 } from "path";
|
|
9010
9708
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
9011
9709
|
import { Command as Command20 } from "commander";
|
|
9012
9710
|
var GITHUB_REPO = "zam-os/zam";
|
|
@@ -9029,7 +9727,7 @@ function currentVersion() {
|
|
|
9029
9727
|
for (const up of ["..", "../..", "../../.."]) {
|
|
9030
9728
|
try {
|
|
9031
9729
|
const pkg2 = JSON.parse(
|
|
9032
|
-
|
|
9730
|
+
readFileSync12(join19(here, up, "package.json"), "utf-8")
|
|
9033
9731
|
);
|
|
9034
9732
|
if (pkg2.version) return pkg2.version;
|
|
9035
9733
|
} catch {
|
|
@@ -9147,8 +9845,8 @@ var whoamiCommand = new Command21("whoami").description("Show or set the default
|
|
|
9147
9845
|
|
|
9148
9846
|
// src/cli/commands/workspace.ts
|
|
9149
9847
|
import { execSync as execSync6 } from "child_process";
|
|
9150
|
-
import { existsSync as
|
|
9151
|
-
import { join as
|
|
9848
|
+
import { existsSync as existsSync20, writeFileSync as writeFileSync10 } from "fs";
|
|
9849
|
+
import { join as join20 } from "path";
|
|
9152
9850
|
import { confirm as confirm3, input as input7 } from "@inquirer/prompts";
|
|
9153
9851
|
import { Command as Command22 } from "commander";
|
|
9154
9852
|
function runGit(cwd, args) {
|
|
@@ -9181,7 +9879,7 @@ workspaceCommand.command("publish").description("Publish your local workspace sa
|
|
|
9181
9879
|
);
|
|
9182
9880
|
process.exit(1);
|
|
9183
9881
|
}
|
|
9184
|
-
if (!
|
|
9882
|
+
if (!existsSync20(workspaceDir)) {
|
|
9185
9883
|
console.error(
|
|
9186
9884
|
`\x1B[31m\u2717 Workspace directory does not exist: ${workspaceDir}\x1B[0m`
|
|
9187
9885
|
);
|
|
@@ -9195,15 +9893,15 @@ Active workspace: \x1B[36m${workspaceDir}\x1B[0m`);
|
|
|
9195
9893
|
);
|
|
9196
9894
|
process.exit(1);
|
|
9197
9895
|
}
|
|
9198
|
-
const gitignorePath =
|
|
9199
|
-
if (!
|
|
9896
|
+
const gitignorePath = join20(workspaceDir, ".gitignore");
|
|
9897
|
+
if (!existsSync20(gitignorePath)) {
|
|
9200
9898
|
writeFileSync10(
|
|
9201
9899
|
gitignorePath,
|
|
9202
9900
|
"node_modules/\n.agent/\n.agents/\n.claude/\n.gemini/\n.goose/\n*.log\n",
|
|
9203
9901
|
"utf8"
|
|
9204
9902
|
);
|
|
9205
9903
|
}
|
|
9206
|
-
const hasGitRepo =
|
|
9904
|
+
const hasGitRepo = existsSync20(join20(workspaceDir, ".git"));
|
|
9207
9905
|
if (!hasGitRepo) {
|
|
9208
9906
|
console.log("Initializing local Git repository...");
|
|
9209
9907
|
runGit(workspaceDir, "init -b main");
|
|
@@ -9299,7 +9997,7 @@ Active workspace: \x1B[36m${workspaceDir}\x1B[0m`);
|
|
|
9299
9997
|
// src/cli/index.ts
|
|
9300
9998
|
var __dirname = dirname10(fileURLToPath5(import.meta.url));
|
|
9301
9999
|
var pkg = JSON.parse(
|
|
9302
|
-
|
|
10000
|
+
readFileSync13(join21(__dirname, "..", "..", "package.json"), "utf-8")
|
|
9303
10001
|
);
|
|
9304
10002
|
var program = new Command23();
|
|
9305
10003
|
program.name("zam").description(
|