zam-core 0.3.12 → 0.4.0
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/.agents/skills/zam/SKILL.md +97 -5
- package/dist/cli/index.js +1278 -377
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +148 -2
- package/dist/index.js +538 -204
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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 readFileSync14 } from "fs";
|
|
5
|
+
import { dirname as dirname10, join as join22 } from "path";
|
|
6
6
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
7
|
-
import { Command as
|
|
7
|
+
import { Command as Command24 } from "commander";
|
|
8
8
|
|
|
9
9
|
// src/cli/commands/agent.ts
|
|
10
10
|
import { existsSync as existsSync11 } from "fs";
|
|
11
|
-
import { join as
|
|
11
|
+
import { join as join11 } from "path";
|
|
12
12
|
import { Command } from "commander";
|
|
13
13
|
|
|
14
14
|
// src/kernel/analytics/stats.ts
|
|
@@ -703,6 +703,66 @@ function isRemoteDatabasePath(dbPath) {
|
|
|
703
703
|
function isDatabaseProvider(value) {
|
|
704
704
|
return value === "local" || value === "native" || value === "remote";
|
|
705
705
|
}
|
|
706
|
+
function cwdRequiresTursoCredentials() {
|
|
707
|
+
try {
|
|
708
|
+
const configPath = join2(process.cwd(), ".zam", "config.yaml");
|
|
709
|
+
if (existsSync2(configPath)) {
|
|
710
|
+
const configText = readFileSync2(configPath, "utf-8");
|
|
711
|
+
return /[\s\S]*turso:[\s\S]*url:/m.test(configText);
|
|
712
|
+
}
|
|
713
|
+
} catch (_e) {
|
|
714
|
+
}
|
|
715
|
+
return false;
|
|
716
|
+
}
|
|
717
|
+
function resolveDatabaseTarget(options = {}) {
|
|
718
|
+
const configuredCloud = options.useConfiguredCloud !== false && !options.dbPath && !options.syncUrl ? getTursoCredentials() : null;
|
|
719
|
+
if (cwdRequiresTursoCredentials() && !configuredCloud && options.useConfiguredCloud !== false && !options.dbPath && !options.syncUrl) {
|
|
720
|
+
throw new Error(
|
|
721
|
+
"Turso cloud database is configured in .zam/config.yaml but missing local credentials. Run: zam connector setup turso"
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
const dbPath = configuredCloud?.url ?? options.dbPath ?? DEFAULT_DB_PATH;
|
|
725
|
+
const isRemote = isRemoteDatabasePath(dbPath);
|
|
726
|
+
const isEmbeddedReplica = Boolean(options.syncUrl);
|
|
727
|
+
const provider = resolveProvider(options, configuredCloud?.mode, isRemote);
|
|
728
|
+
return {
|
|
729
|
+
dbPath,
|
|
730
|
+
provider,
|
|
731
|
+
isRemote,
|
|
732
|
+
isEmbeddedReplica,
|
|
733
|
+
configuredCloud
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
function getDatabaseTargetInfo(options = {}) {
|
|
737
|
+
const target = resolveDatabaseTarget(options);
|
|
738
|
+
if (target.isEmbeddedReplica) {
|
|
739
|
+
return {
|
|
740
|
+
kind: "turso-replica",
|
|
741
|
+
provider: target.provider,
|
|
742
|
+
location: target.dbPath,
|
|
743
|
+
syncUrl: options.syncUrl
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
if (target.provider === "remote" && target.isRemote) {
|
|
747
|
+
return {
|
|
748
|
+
kind: "turso-remote",
|
|
749
|
+
provider: target.provider,
|
|
750
|
+
location: target.dbPath
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
if (target.isRemote) {
|
|
754
|
+
return {
|
|
755
|
+
kind: "turso-native",
|
|
756
|
+
provider: target.provider,
|
|
757
|
+
location: target.dbPath
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
return {
|
|
761
|
+
kind: "local",
|
|
762
|
+
provider: target.provider,
|
|
763
|
+
location: target.dbPath
|
|
764
|
+
};
|
|
765
|
+
}
|
|
706
766
|
function openLocalSqlite(dbPath) {
|
|
707
767
|
const mod = require2("better-sqlite3");
|
|
708
768
|
const BetterSqlite3 = "default" in mod ? mod.default : mod;
|
|
@@ -720,27 +780,7 @@ function loadLibsql() {
|
|
|
720
780
|
}
|
|
721
781
|
}
|
|
722
782
|
async function openDatabase(options = {}) {
|
|
723
|
-
const
|
|
724
|
-
let requiresTurso = false;
|
|
725
|
-
try {
|
|
726
|
-
const configPath = join2(process.cwd(), ".zam", "config.yaml");
|
|
727
|
-
if (existsSync2(configPath)) {
|
|
728
|
-
const configText = readFileSync2(configPath, "utf-8");
|
|
729
|
-
if (/[\s\S]*turso:[\s\S]*url:/m.test(configText)) {
|
|
730
|
-
requiresTurso = true;
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
} catch (_e) {
|
|
734
|
-
}
|
|
735
|
-
if (requiresTurso && !configuredCloud && options.useConfiguredCloud !== false && !options.dbPath && !options.syncUrl) {
|
|
736
|
-
throw new Error(
|
|
737
|
-
"Turso cloud database is configured in .zam/config.yaml but missing local credentials. Run: zam connector setup turso"
|
|
738
|
-
);
|
|
739
|
-
}
|
|
740
|
-
const dbPath = configuredCloud?.url ?? options.dbPath ?? DEFAULT_DB_PATH;
|
|
741
|
-
const isRemote = isRemoteDatabasePath(dbPath);
|
|
742
|
-
const isEmbeddedReplica = Boolean(options.syncUrl);
|
|
743
|
-
const provider = resolveProvider(options, configuredCloud?.mode, isRemote);
|
|
783
|
+
const { dbPath, provider, isRemote, isEmbeddedReplica, configuredCloud } = resolveDatabaseTarget(options);
|
|
744
784
|
const shouldInitialize = options.initialize === true || !isRemote && !isEmbeddedReplica && !existsSync2(dbPath);
|
|
745
785
|
if (provider === "remote") {
|
|
746
786
|
const url = isRemote ? dbPath : options.syncUrl;
|
|
@@ -1360,8 +1400,17 @@ async function deleteCardForUser(db, tokenId, userId) {
|
|
|
1360
1400
|
await db.prepare("DELETE FROM cards WHERE id = ?").run(card.id);
|
|
1361
1401
|
return { card, impact };
|
|
1362
1402
|
}
|
|
1363
|
-
async function getDueCards(db, userId, now) {
|
|
1403
|
+
async function getDueCards(db, userId, now, domain) {
|
|
1364
1404
|
const cutoff = now ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
1405
|
+
if (domain) {
|
|
1406
|
+
return await db.prepare(
|
|
1407
|
+
`SELECT c.*, t.slug, t.concept, t.domain, t.bloom_level
|
|
1408
|
+
FROM cards c
|
|
1409
|
+
JOIN tokens t ON t.id = c.token_id
|
|
1410
|
+
WHERE c.user_id = ? AND c.blocked = 0 AND c.due_at <= ? AND t.domain = ?
|
|
1411
|
+
ORDER BY t.bloom_level ASC, c.due_at ASC`
|
|
1412
|
+
).all(userId, cutoff, domain);
|
|
1413
|
+
}
|
|
1365
1414
|
return await db.prepare(
|
|
1366
1415
|
`SELECT c.*, t.slug, t.concept, t.domain, t.bloom_level
|
|
1367
1416
|
FROM cards c
|
|
@@ -2077,6 +2126,415 @@ function getMonitorLogStats(sessionId) {
|
|
|
2077
2126
|
return { exists: true, sizeBytes: stat.size, lineCount };
|
|
2078
2127
|
}
|
|
2079
2128
|
|
|
2129
|
+
// src/kernel/observation/observer-sidecar-policy.ts
|
|
2130
|
+
import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
2131
|
+
import { join as join6 } from "path";
|
|
2132
|
+
|
|
2133
|
+
// src/kernel/observation/policy.ts
|
|
2134
|
+
var OBSERVER_POLICY_VERSION = 1;
|
|
2135
|
+
var DEFAULT_OBSERVER_POLICY = {
|
|
2136
|
+
version: OBSERVER_POLICY_VERSION,
|
|
2137
|
+
scope: "window",
|
|
2138
|
+
allowlist: [],
|
|
2139
|
+
denylist: [],
|
|
2140
|
+
consent: "per-session",
|
|
2141
|
+
retention: "none",
|
|
2142
|
+
redactWindowTitles: true,
|
|
2143
|
+
audioOptIn: false
|
|
2144
|
+
};
|
|
2145
|
+
var BUILT_IN_SENSITIVE_MATCHERS = [
|
|
2146
|
+
// Password managers
|
|
2147
|
+
"1password",
|
|
2148
|
+
"bitwarden",
|
|
2149
|
+
"keepass",
|
|
2150
|
+
"lastpass",
|
|
2151
|
+
"dashlane",
|
|
2152
|
+
"nordpass",
|
|
2153
|
+
"enpass",
|
|
2154
|
+
"proton pass",
|
|
2155
|
+
// Authentication / credential / UAC surfaces
|
|
2156
|
+
"credentialuibroker",
|
|
2157
|
+
"consentux",
|
|
2158
|
+
"logonui",
|
|
2159
|
+
"windowssecurity",
|
|
2160
|
+
"authenticator",
|
|
2161
|
+
// Conservative banking/title hints
|
|
2162
|
+
"online banking",
|
|
2163
|
+
"onlinebanking"
|
|
2164
|
+
];
|
|
2165
|
+
function parseList(raw) {
|
|
2166
|
+
if (!raw) return [];
|
|
2167
|
+
return raw.split(",").map((entry) => entry.trim().toLowerCase()).filter((entry) => entry.length > 0);
|
|
2168
|
+
}
|
|
2169
|
+
function parseObserverList(raw) {
|
|
2170
|
+
return parseList(raw);
|
|
2171
|
+
}
|
|
2172
|
+
function parseScope(raw, defaultScope) {
|
|
2173
|
+
if (raw === "off" || raw === "window" || raw === "fullscreen") {
|
|
2174
|
+
return raw;
|
|
2175
|
+
}
|
|
2176
|
+
return defaultScope ?? DEFAULT_OBSERVER_POLICY.scope;
|
|
2177
|
+
}
|
|
2178
|
+
function parseConsent(raw, defaultConsent) {
|
|
2179
|
+
if (raw === "per-capture" || raw === "per-session" || raw === "standing") {
|
|
2180
|
+
return raw;
|
|
2181
|
+
}
|
|
2182
|
+
return defaultConsent ?? DEFAULT_OBSERVER_POLICY.consent;
|
|
2183
|
+
}
|
|
2184
|
+
function parseRetention(raw) {
|
|
2185
|
+
return raw === "none" || raw === "session" || raw === "persist" ? raw : DEFAULT_OBSERVER_POLICY.retention;
|
|
2186
|
+
}
|
|
2187
|
+
function parseBool(raw, fallback) {
|
|
2188
|
+
if (raw === void 0) return fallback;
|
|
2189
|
+
return raw === "true";
|
|
2190
|
+
}
|
|
2191
|
+
function parseObserverPolicy(raw, defaults) {
|
|
2192
|
+
return {
|
|
2193
|
+
version: OBSERVER_POLICY_VERSION,
|
|
2194
|
+
scope: parseScope(raw["observer.scope"], defaults?.scope),
|
|
2195
|
+
allowlist: parseList(raw["observer.allowlist"]),
|
|
2196
|
+
denylist: parseList(raw["observer.denylist"]),
|
|
2197
|
+
consent: parseConsent(raw["observer.consent"], defaults?.consent),
|
|
2198
|
+
retention: parseRetention(raw["observer.retention"]),
|
|
2199
|
+
redactWindowTitles: parseBool(
|
|
2200
|
+
raw["observer.redact_titles"],
|
|
2201
|
+
DEFAULT_OBSERVER_POLICY.redactWindowTitles
|
|
2202
|
+
),
|
|
2203
|
+
audioOptIn: parseBool(
|
|
2204
|
+
raw["observer.audio"],
|
|
2205
|
+
DEFAULT_OBSERVER_POLICY.audioOptIn
|
|
2206
|
+
)
|
|
2207
|
+
};
|
|
2208
|
+
}
|
|
2209
|
+
function getDefaultsForSymbiosisMode(mode) {
|
|
2210
|
+
if (mode === "autonomy") {
|
|
2211
|
+
return { scope: "fullscreen", consent: "standing" };
|
|
2212
|
+
}
|
|
2213
|
+
return { scope: "window", consent: "per-session" };
|
|
2214
|
+
}
|
|
2215
|
+
async function resolveActiveSymbiosisMode(db) {
|
|
2216
|
+
const userId = await getSetting(db, "user.id") || "default";
|
|
2217
|
+
const activeSession = await db.prepare(
|
|
2218
|
+
"SELECT id FROM sessions WHERE user_id = ? AND completed_at IS NULL ORDER BY started_at DESC LIMIT 1"
|
|
2219
|
+
).get(userId);
|
|
2220
|
+
if (activeSession) {
|
|
2221
|
+
const stepToken = await db.prepare(
|
|
2222
|
+
`SELECT t.symbiosis_mode, t.domain
|
|
2223
|
+
FROM session_steps s
|
|
2224
|
+
JOIN tokens t ON t.id = s.token_id
|
|
2225
|
+
WHERE s.session_id = ?
|
|
2226
|
+
ORDER BY s.created_at DESC, s.id DESC
|
|
2227
|
+
LIMIT 1`
|
|
2228
|
+
).get(activeSession.id);
|
|
2229
|
+
if (stepToken) {
|
|
2230
|
+
if (stepToken.symbiosis_mode) {
|
|
2231
|
+
return stepToken.symbiosis_mode;
|
|
2232
|
+
}
|
|
2233
|
+
if (stepToken.domain) {
|
|
2234
|
+
const competence = await getDomainCompetence(db, userId);
|
|
2235
|
+
const comp = competence.find((c) => c.domain === stepToken.domain);
|
|
2236
|
+
if (comp) {
|
|
2237
|
+
return comp.suggestedMode;
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
const lastReviewToken = await db.prepare(
|
|
2243
|
+
`SELECT t.symbiosis_mode, t.domain
|
|
2244
|
+
FROM review_logs r
|
|
2245
|
+
JOIN tokens t ON t.id = r.token_id
|
|
2246
|
+
WHERE r.user_id = ?
|
|
2247
|
+
ORDER BY r.reviewed_at DESC, r.id DESC
|
|
2248
|
+
LIMIT 1`
|
|
2249
|
+
).get(userId);
|
|
2250
|
+
if (lastReviewToken) {
|
|
2251
|
+
if (lastReviewToken.symbiosis_mode) {
|
|
2252
|
+
return lastReviewToken.symbiosis_mode;
|
|
2253
|
+
}
|
|
2254
|
+
if (lastReviewToken.domain) {
|
|
2255
|
+
const competence = await getDomainCompetence(db, userId);
|
|
2256
|
+
const comp = competence.find((c) => c.domain === lastReviewToken.domain);
|
|
2257
|
+
if (comp) {
|
|
2258
|
+
return comp.suggestedMode;
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
return "shadowing";
|
|
2263
|
+
}
|
|
2264
|
+
async function resolveObserverPolicy(db) {
|
|
2265
|
+
const [scope, allowlist, denylist, consent, retention, redactTitles, audio] = await Promise.all([
|
|
2266
|
+
getSetting(db, "observer.scope"),
|
|
2267
|
+
getSetting(db, "observer.allowlist"),
|
|
2268
|
+
getSetting(db, "observer.denylist"),
|
|
2269
|
+
getSetting(db, "observer.consent"),
|
|
2270
|
+
getSetting(db, "observer.retention"),
|
|
2271
|
+
getSetting(db, "observer.redact_titles"),
|
|
2272
|
+
getSetting(db, "observer.audio")
|
|
2273
|
+
]);
|
|
2274
|
+
const mode = await resolveActiveSymbiosisMode(db);
|
|
2275
|
+
const defaults = getDefaultsForSymbiosisMode(mode);
|
|
2276
|
+
return parseObserverPolicy(
|
|
2277
|
+
{
|
|
2278
|
+
"observer.scope": scope,
|
|
2279
|
+
"observer.allowlist": allowlist,
|
|
2280
|
+
"observer.denylist": denylist,
|
|
2281
|
+
"observer.consent": consent,
|
|
2282
|
+
"observer.retention": retention,
|
|
2283
|
+
"observer.redact_titles": redactTitles,
|
|
2284
|
+
"observer.audio": audio
|
|
2285
|
+
},
|
|
2286
|
+
defaults
|
|
2287
|
+
);
|
|
2288
|
+
}
|
|
2289
|
+
function haystacks(...values) {
|
|
2290
|
+
return values.filter((v) => typeof v === "string" && v.length > 0).map((v) => v.toLowerCase());
|
|
2291
|
+
}
|
|
2292
|
+
function deny(reason, denialReason) {
|
|
2293
|
+
return { allowed: false, reason, denialReason };
|
|
2294
|
+
}
|
|
2295
|
+
function matchBuiltInSensitive(processName, windowTitle) {
|
|
2296
|
+
const fields = haystacks(processName, windowTitle);
|
|
2297
|
+
for (const matcher of BUILT_IN_SENSITIVE_MATCHERS) {
|
|
2298
|
+
if (fields.some((field) => field.includes(matcher))) return matcher;
|
|
2299
|
+
}
|
|
2300
|
+
return null;
|
|
2301
|
+
}
|
|
2302
|
+
function matchDenylist(policy, processName, windowTitle) {
|
|
2303
|
+
const fields = haystacks(processName, windowTitle);
|
|
2304
|
+
for (const matcher of policy.denylist) {
|
|
2305
|
+
if (fields.some((field) => field.includes(matcher))) return matcher;
|
|
2306
|
+
}
|
|
2307
|
+
return null;
|
|
2308
|
+
}
|
|
2309
|
+
function inAllowlist(policy, processName) {
|
|
2310
|
+
if (policy.allowlist.length === 0) return true;
|
|
2311
|
+
if (!processName) return false;
|
|
2312
|
+
const name = processName.toLowerCase();
|
|
2313
|
+
return policy.allowlist.some((entry) => name.includes(entry));
|
|
2314
|
+
}
|
|
2315
|
+
function decidePreCapture(policy, request) {
|
|
2316
|
+
if (policy.scope === "off") {
|
|
2317
|
+
return deny("observer is disabled (observer.scope=off)", "scope-off");
|
|
2318
|
+
}
|
|
2319
|
+
if (policy.scope === "window" && !request.hasExplicitTarget) {
|
|
2320
|
+
return deny(
|
|
2321
|
+
"window scope requires a target: pass --process-name or --hwnd, or set observer.scope=fullscreen",
|
|
2322
|
+
"scope-requires-target"
|
|
2323
|
+
);
|
|
2324
|
+
}
|
|
2325
|
+
const requested = request.requestedProcessName;
|
|
2326
|
+
if (requested) {
|
|
2327
|
+
const sensitive = matchBuiltInSensitive(requested, null);
|
|
2328
|
+
if (sensitive) {
|
|
2329
|
+
return deny(`refusing sensitive surface (${sensitive})`, "sensitive");
|
|
2330
|
+
}
|
|
2331
|
+
const denied = matchDenylist(policy, requested, null);
|
|
2332
|
+
if (denied) {
|
|
2333
|
+
return deny(`process is denylisted (${denied})`, "denylisted");
|
|
2334
|
+
}
|
|
2335
|
+
if (policy.scope === "window" && !inAllowlist(policy, requested)) {
|
|
2336
|
+
return deny(
|
|
2337
|
+
`process not in observer.allowlist (${requested.toLowerCase()})`,
|
|
2338
|
+
"not-allowlisted"
|
|
2339
|
+
);
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
return { allowed: true };
|
|
2343
|
+
}
|
|
2344
|
+
function decidePostCapture(policy, target) {
|
|
2345
|
+
const isFullscreen = target.method.toLowerCase().includes("fullscreen");
|
|
2346
|
+
if (policy.scope === "window" && isFullscreen) {
|
|
2347
|
+
return deny(
|
|
2348
|
+
"window scope but capture fell back to fullscreen (target window not resolved)",
|
|
2349
|
+
"scope-requires-target"
|
|
2350
|
+
);
|
|
2351
|
+
}
|
|
2352
|
+
const sensitive = matchBuiltInSensitive(
|
|
2353
|
+
target.processName,
|
|
2354
|
+
target.windowTitle
|
|
2355
|
+
);
|
|
2356
|
+
if (sensitive) {
|
|
2357
|
+
return deny(`refusing sensitive surface (${sensitive})`, "sensitive");
|
|
2358
|
+
}
|
|
2359
|
+
const denied = matchDenylist(policy, target.processName, target.windowTitle);
|
|
2360
|
+
if (denied) {
|
|
2361
|
+
return deny(`captured window is denylisted (${denied})`, "denylisted");
|
|
2362
|
+
}
|
|
2363
|
+
if (policy.scope === "window" && !inAllowlist(policy, target.processName)) {
|
|
2364
|
+
return deny(
|
|
2365
|
+
`captured window not in observer.allowlist (${target.processName ?? "unknown"})`,
|
|
2366
|
+
"not-allowlisted"
|
|
2367
|
+
);
|
|
2368
|
+
}
|
|
2369
|
+
return { allowed: true };
|
|
2370
|
+
}
|
|
2371
|
+
async function isObserverPolicyConfigured(db) {
|
|
2372
|
+
const settings = await getAllSettings(db);
|
|
2373
|
+
return Object.keys(settings).some((key) => key.startsWith("observer."));
|
|
2374
|
+
}
|
|
2375
|
+
var OBSERVER_POLICY_UNSET_HINT = "Observer policy is at defaults (scope=window: only a targeted window is captured). Configure with `zam observer status|grant|revoke` or `zam settings set observer.scope <off|window|fullscreen>`.";
|
|
2376
|
+
|
|
2377
|
+
// src/kernel/observation/ui-observer-io.ts
|
|
2378
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync5 } from "fs";
|
|
2379
|
+
import { homedir as homedir4 } from "os";
|
|
2380
|
+
import { join as join5 } from "path";
|
|
2381
|
+
|
|
2382
|
+
// src/kernel/observation/ui-observer.ts
|
|
2383
|
+
var UI_OBSERVATION_PROTOCOL_VERSION = 1;
|
|
2384
|
+
var OBSERVATION_KINDS = /* @__PURE__ */ new Set([
|
|
2385
|
+
"progress",
|
|
2386
|
+
"step-completed",
|
|
2387
|
+
"error",
|
|
2388
|
+
"help-seeking",
|
|
2389
|
+
"uncertain",
|
|
2390
|
+
"privacy-pause",
|
|
2391
|
+
"heartbeat"
|
|
2392
|
+
]);
|
|
2393
|
+
var ACTION_TYPES = /* @__PURE__ */ new Set([
|
|
2394
|
+
"click",
|
|
2395
|
+
"shortcut",
|
|
2396
|
+
"typing",
|
|
2397
|
+
"scroll",
|
|
2398
|
+
"window-change"
|
|
2399
|
+
]);
|
|
2400
|
+
var EVIDENCE_TYPES = /* @__PURE__ */ new Set([
|
|
2401
|
+
"uia",
|
|
2402
|
+
"keyframe",
|
|
2403
|
+
"clip",
|
|
2404
|
+
"window"
|
|
2405
|
+
]);
|
|
2406
|
+
function isUiObservationReport(value) {
|
|
2407
|
+
if (!isRecord(value)) return false;
|
|
2408
|
+
if (value.version !== UI_OBSERVATION_PROTOCOL_VERSION) return false;
|
|
2409
|
+
if (!isNonEmptyString(value.sessionId)) return false;
|
|
2410
|
+
if (!isNonNegativeInteger(value.sequence)) return false;
|
|
2411
|
+
if (!isNonEmptyString(value.observedFrom)) return false;
|
|
2412
|
+
if (!isNonEmptyString(value.observedTo)) return false;
|
|
2413
|
+
if (typeof value.kind !== "string" || !OBSERVATION_KINDS.has(value.kind)) {
|
|
2414
|
+
return false;
|
|
2415
|
+
}
|
|
2416
|
+
if (!isApplication(value.application)) return false;
|
|
2417
|
+
if (!isNonEmptyString(value.summary)) return false;
|
|
2418
|
+
if (!Array.isArray(value.actions) || !value.actions.every(isObservedAction)) {
|
|
2419
|
+
return false;
|
|
2420
|
+
}
|
|
2421
|
+
if (!Array.isArray(value.evidence) || !value.evidence.every(isEvidenceRef)) {
|
|
2422
|
+
return false;
|
|
2423
|
+
}
|
|
2424
|
+
if (!Array.isArray(value.candidateTokens) || !value.candidateTokens.every(isCandidateToken)) {
|
|
2425
|
+
return false;
|
|
2426
|
+
}
|
|
2427
|
+
return isConfidence(value.confidence);
|
|
2428
|
+
}
|
|
2429
|
+
function parseUiObservationLog(jsonl) {
|
|
2430
|
+
const reports = [];
|
|
2431
|
+
for (const line of jsonl.split("\n")) {
|
|
2432
|
+
const trimmed = line.trim();
|
|
2433
|
+
if (!trimmed) continue;
|
|
2434
|
+
try {
|
|
2435
|
+
const value = JSON.parse(trimmed);
|
|
2436
|
+
if (isUiObservationReport(value)) {
|
|
2437
|
+
reports.push(value);
|
|
2438
|
+
}
|
|
2439
|
+
} catch {
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2442
|
+
return reports.sort((left, right) => left.sequence - right.sequence);
|
|
2443
|
+
}
|
|
2444
|
+
function isApplication(value) {
|
|
2445
|
+
if (!isRecord(value) || !isNonEmptyString(value.processName)) return false;
|
|
2446
|
+
if (value.processId !== void 0 && !isNonNegativeInteger(value.processId)) {
|
|
2447
|
+
return false;
|
|
2448
|
+
}
|
|
2449
|
+
return value.windowTitle === void 0 || typeof value.windowTitle === "string";
|
|
2450
|
+
}
|
|
2451
|
+
function isObservedAction(value) {
|
|
2452
|
+
if (!isRecord(value) || typeof value.type !== "string") return false;
|
|
2453
|
+
if (!ACTION_TYPES.has(value.type)) return false;
|
|
2454
|
+
if (value.target !== void 0 && typeof value.target !== "string") {
|
|
2455
|
+
return false;
|
|
2456
|
+
}
|
|
2457
|
+
return value.result === void 0 || typeof value.result === "string";
|
|
2458
|
+
}
|
|
2459
|
+
function isEvidenceRef(value) {
|
|
2460
|
+
if (!isRecord(value) || typeof value.type !== "string") return false;
|
|
2461
|
+
return EVIDENCE_TYPES.has(value.type) && isNonEmptyString(value.ref) && typeof value.redacted === "boolean";
|
|
2462
|
+
}
|
|
2463
|
+
function isCandidateToken(value) {
|
|
2464
|
+
return isRecord(value) && isNonEmptyString(value.slug) && isConfidence(value.confidence) && isNonEmptyString(value.rationale);
|
|
2465
|
+
}
|
|
2466
|
+
function isRecord(value) {
|
|
2467
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2468
|
+
}
|
|
2469
|
+
function isNonEmptyString(value) {
|
|
2470
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
2471
|
+
}
|
|
2472
|
+
function isNonNegativeInteger(value) {
|
|
2473
|
+
return Number.isInteger(value) && Number(value) >= 0;
|
|
2474
|
+
}
|
|
2475
|
+
function isConfidence(value) {
|
|
2476
|
+
return typeof value === "number" && value >= 0 && value <= 1;
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
// src/kernel/observation/ui-observer-io.ts
|
|
2480
|
+
var DEFAULT_UI_OBSERVER_DIR = join5(homedir4(), ".zam", "observer");
|
|
2481
|
+
function getUiObserverDir() {
|
|
2482
|
+
return process.env.ZAM_OBSERVER_DIR || DEFAULT_UI_OBSERVER_DIR;
|
|
2483
|
+
}
|
|
2484
|
+
function getUiObservationPath(sessionId) {
|
|
2485
|
+
if (!/^[A-Za-z0-9_-]+$/.test(sessionId)) {
|
|
2486
|
+
throw new Error(`Invalid observer session ID: ${sessionId}`);
|
|
2487
|
+
}
|
|
2488
|
+
return join5(getUiObserverDir(), `${sessionId}.reports.jsonl`);
|
|
2489
|
+
}
|
|
2490
|
+
function ensureUiObserverDir() {
|
|
2491
|
+
const dir = getUiObserverDir();
|
|
2492
|
+
if (!existsSync5(dir)) {
|
|
2493
|
+
mkdirSync4(dir, { recursive: true, mode: 448 });
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2496
|
+
function uiObservationLogExists(sessionId) {
|
|
2497
|
+
return existsSync5(getUiObservationPath(sessionId));
|
|
2498
|
+
}
|
|
2499
|
+
function readUiObservationLog(sessionId) {
|
|
2500
|
+
const path = getUiObservationPath(sessionId);
|
|
2501
|
+
if (!existsSync5(path)) return [];
|
|
2502
|
+
return parseUiObservationLog(readFileSync5(path, "utf8"));
|
|
2503
|
+
}
|
|
2504
|
+
function appendUiObservationReport(report) {
|
|
2505
|
+
if (!isUiObservationReport(report)) {
|
|
2506
|
+
throw new Error("Invalid UI observation report");
|
|
2507
|
+
}
|
|
2508
|
+
ensureUiObserverDir();
|
|
2509
|
+
appendFileSync2(
|
|
2510
|
+
getUiObservationPath(report.sessionId),
|
|
2511
|
+
`${JSON.stringify(report)}
|
|
2512
|
+
`,
|
|
2513
|
+
{ encoding: "utf8", mode: 384 }
|
|
2514
|
+
);
|
|
2515
|
+
}
|
|
2516
|
+
|
|
2517
|
+
// src/kernel/observation/observer-sidecar-policy.ts
|
|
2518
|
+
var SIDECAR_POLICY_FILE = "policy.json";
|
|
2519
|
+
function toSidecarPrivacyPolicy(policy) {
|
|
2520
|
+
return {
|
|
2521
|
+
allowProcesses: [...policy.allowlist],
|
|
2522
|
+
denyProcesses: [...policy.denylist],
|
|
2523
|
+
denyTitleMarkers: [...policy.denylist]
|
|
2524
|
+
};
|
|
2525
|
+
}
|
|
2526
|
+
async function syncObserverSidecarPolicy(db, dir = getUiObserverDir()) {
|
|
2527
|
+
const policy = toSidecarPrivacyPolicy(await resolveObserverPolicy(db));
|
|
2528
|
+
mkdirSync5(dir, { recursive: true, mode: 448 });
|
|
2529
|
+
const path = join6(dir, SIDECAR_POLICY_FILE);
|
|
2530
|
+
writeFileSync3(path, `${JSON.stringify(policy, null, 2)}
|
|
2531
|
+
`, {
|
|
2532
|
+
encoding: "utf8",
|
|
2533
|
+
mode: 384
|
|
2534
|
+
});
|
|
2535
|
+
return { path, policy };
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2080
2538
|
// src/kernel/observation/session-synthesis.ts
|
|
2081
2539
|
import { ulid as ulid7 } from "ulid";
|
|
2082
2540
|
|
|
@@ -2309,182 +2767,42 @@ async function cascadeBlock(db, userId, tokenSlug) {
|
|
|
2309
2767
|
"UPDATE cards SET blocked = 0, due_at = ? WHERE token_id = ? AND user_id = ?"
|
|
2310
2768
|
).run(now, prereq.requires_id, userId);
|
|
2311
2769
|
}
|
|
2312
|
-
}
|
|
2313
|
-
surfaced.push({
|
|
2314
|
-
slug: prereq.slug,
|
|
2315
|
-
concept: prereq.concept,
|
|
2316
|
-
bloomLevel: prereq.bloom_level
|
|
2317
|
-
});
|
|
2318
|
-
}
|
|
2319
|
-
return {
|
|
2320
|
-
blockedSlug: tokenSlug,
|
|
2321
|
-
prerequisites: surfaced
|
|
2322
|
-
};
|
|
2323
|
-
}
|
|
2324
|
-
async function unblockReady(db, userId) {
|
|
2325
|
-
const blockedCards = await db.prepare(
|
|
2326
|
-
`SELECT c.token_id, t.slug, t.concept
|
|
2327
|
-
FROM cards c
|
|
2328
|
-
JOIN tokens t ON t.id = c.token_id
|
|
2329
|
-
WHERE c.user_id = ? AND c.blocked = 1`
|
|
2330
|
-
).all(userId);
|
|
2331
|
-
const unblocked = [];
|
|
2332
|
-
for (const card of blockedCards) {
|
|
2333
|
-
const totalPrereqs = await db.prepare("SELECT COUNT(*) as n FROM prerequisites WHERE token_id = ?").get(card.token_id);
|
|
2334
|
-
const metPrereqs = await db.prepare(
|
|
2335
|
-
`SELECT COUNT(*) as n FROM cards c
|
|
2336
|
-
JOIN prerequisites p ON p.requires_id = c.token_id
|
|
2337
|
-
WHERE p.token_id = ? AND c.user_id = ? AND c.reps >= 1 AND c.blocked = 0`
|
|
2338
|
-
).get(card.token_id, userId);
|
|
2339
|
-
if (totalPrereqs.n === 0 || metPrereqs.n === totalPrereqs.n) {
|
|
2340
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2341
|
-
await db.prepare(
|
|
2342
|
-
"UPDATE cards SET blocked = 0, due_at = ? WHERE token_id = ? AND user_id = ?"
|
|
2343
|
-
).run(now, card.token_id, userId);
|
|
2344
|
-
unblocked.push({ slug: card.slug, concept: card.concept });
|
|
2345
|
-
}
|
|
2346
|
-
}
|
|
2347
|
-
return { unblocked };
|
|
2348
|
-
}
|
|
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"));
|
|
2770
|
+
}
|
|
2771
|
+
surfaced.push({
|
|
2772
|
+
slug: prereq.slug,
|
|
2773
|
+
concept: prereq.concept,
|
|
2774
|
+
bloomLevel: prereq.bloom_level
|
|
2775
|
+
});
|
|
2776
|
+
}
|
|
2777
|
+
return {
|
|
2778
|
+
blockedSlug: tokenSlug,
|
|
2779
|
+
prerequisites: surfaced
|
|
2780
|
+
};
|
|
2476
2781
|
}
|
|
2477
|
-
function
|
|
2478
|
-
|
|
2479
|
-
|
|
2782
|
+
async function unblockReady(db, userId) {
|
|
2783
|
+
const blockedCards = await db.prepare(
|
|
2784
|
+
`SELECT c.token_id, t.slug, t.concept
|
|
2785
|
+
FROM cards c
|
|
2786
|
+
JOIN tokens t ON t.id = c.token_id
|
|
2787
|
+
WHERE c.user_id = ? AND c.blocked = 1`
|
|
2788
|
+
).all(userId);
|
|
2789
|
+
const unblocked = [];
|
|
2790
|
+
for (const card of blockedCards) {
|
|
2791
|
+
const totalPrereqs = await db.prepare("SELECT COUNT(*) as n FROM prerequisites WHERE token_id = ?").get(card.token_id);
|
|
2792
|
+
const metPrereqs = await db.prepare(
|
|
2793
|
+
`SELECT COUNT(*) as n FROM cards c
|
|
2794
|
+
JOIN prerequisites p ON p.requires_id = c.token_id
|
|
2795
|
+
WHERE p.token_id = ? AND c.user_id = ? AND c.reps >= 1 AND c.blocked = 0`
|
|
2796
|
+
).get(card.token_id, userId);
|
|
2797
|
+
if (totalPrereqs.n === 0 || metPrereqs.n === totalPrereqs.n) {
|
|
2798
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2799
|
+
await db.prepare(
|
|
2800
|
+
"UPDATE cards SET blocked = 0, due_at = ? WHERE token_id = ? AND user_id = ?"
|
|
2801
|
+
).run(now, card.token_id, userId);
|
|
2802
|
+
unblocked.push({ slug: card.slug, concept: card.concept });
|
|
2803
|
+
}
|
|
2480
2804
|
}
|
|
2481
|
-
|
|
2482
|
-
appendFileSync2(
|
|
2483
|
-
getUiObservationPath(report.sessionId),
|
|
2484
|
-
`${JSON.stringify(report)}
|
|
2485
|
-
`,
|
|
2486
|
-
{ encoding: "utf8", mode: 384 }
|
|
2487
|
-
);
|
|
2805
|
+
return { unblocked };
|
|
2488
2806
|
}
|
|
2489
2807
|
|
|
2490
2808
|
// src/kernel/observation/ui-observer-synthesis.ts
|
|
@@ -3291,7 +3609,7 @@ function generatePrompt(input8) {
|
|
|
3291
3609
|
|
|
3292
3610
|
// src/kernel/recall/reference-resolver.ts
|
|
3293
3611
|
import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
|
|
3294
|
-
import { dirname as dirname3, join as
|
|
3612
|
+
import { dirname as dirname3, join as join7, resolve } from "path";
|
|
3295
3613
|
var DEFAULT_REVIEW_CONTEXT_MAX_CHARS = 6e3;
|
|
3296
3614
|
function htmlToText(html) {
|
|
3297
3615
|
let content = html;
|
|
@@ -3352,8 +3670,8 @@ async function resolveReference(sourceLink) {
|
|
|
3352
3670
|
const filePath = anchorIndex2 !== -1 ? fullPathWithAnchor.slice(0, anchorIndex2) : fullPathWithAnchor;
|
|
3353
3671
|
const anchor2 = anchorIndex2 !== -1 ? fullPathWithAnchor.slice(anchorIndex2) : "";
|
|
3354
3672
|
const parentDir = dirname3(process.cwd());
|
|
3355
|
-
const localRepoPath =
|
|
3356
|
-
const localFilePath =
|
|
3673
|
+
const localRepoPath = join7(parentDir, repo);
|
|
3674
|
+
const localFilePath = join7(localRepoPath, filePath);
|
|
3357
3675
|
if (existsSync6(localFilePath)) {
|
|
3358
3676
|
try {
|
|
3359
3677
|
let fileContent = readFileSync6(localFilePath, "utf-8");
|
|
@@ -3667,12 +3985,12 @@ import {
|
|
|
3667
3985
|
appendFileSync as appendFileSync3,
|
|
3668
3986
|
copyFileSync,
|
|
3669
3987
|
existsSync as existsSync7,
|
|
3670
|
-
mkdirSync as
|
|
3988
|
+
mkdirSync as mkdirSync6,
|
|
3671
3989
|
readFileSync as readFileSync7,
|
|
3672
|
-
writeFileSync as
|
|
3990
|
+
writeFileSync as writeFileSync4
|
|
3673
3991
|
} from "fs";
|
|
3674
3992
|
import { homedir as homedir5 } from "os";
|
|
3675
|
-
import { join as
|
|
3993
|
+
import { join as join8 } from "path";
|
|
3676
3994
|
import { fileURLToPath } from "url";
|
|
3677
3995
|
var HOME = homedir5();
|
|
3678
3996
|
function getPackageSkillPath(agent = "default") {
|
|
@@ -3680,15 +3998,15 @@ function getPackageSkillPath(agent = "default") {
|
|
|
3680
3998
|
fileURLToPath(new URL("../../..", import.meta.url)),
|
|
3681
3999
|
fileURLToPath(new URL("../..", import.meta.url)),
|
|
3682
4000
|
fileURLToPath(new URL("..", import.meta.url))
|
|
3683
|
-
].find((candidate) => existsSync7(
|
|
4001
|
+
].find((candidate) => existsSync7(join8(candidate, "package.json"))) ?? "";
|
|
3684
4002
|
if (!packageRoot2) return "";
|
|
3685
4003
|
if (agent === "codex") {
|
|
3686
|
-
const codexPath =
|
|
4004
|
+
const codexPath = join8(packageRoot2, ".agents", "skills", "zam", "SKILL.md");
|
|
3687
4005
|
if (existsSync7(codexPath)) return codexPath;
|
|
3688
4006
|
return "";
|
|
3689
4007
|
}
|
|
3690
4008
|
if (agent === "claude") {
|
|
3691
|
-
const claudePath =
|
|
4009
|
+
const claudePath = join8(
|
|
3692
4010
|
packageRoot2,
|
|
3693
4011
|
".claude",
|
|
3694
4012
|
"skills",
|
|
@@ -3698,9 +4016,9 @@ function getPackageSkillPath(agent = "default") {
|
|
|
3698
4016
|
if (existsSync7(claudePath)) return claudePath;
|
|
3699
4017
|
return "";
|
|
3700
4018
|
}
|
|
3701
|
-
let path =
|
|
4019
|
+
let path = join8(packageRoot2, ".agent", "skills", "zam", "SKILL.md");
|
|
3702
4020
|
if (existsSync7(path)) return path;
|
|
3703
|
-
path =
|
|
4021
|
+
path = join8(packageRoot2, ".claude", "skills", "zam", "SKILL.md");
|
|
3704
4022
|
if (existsSync7(path)) return path;
|
|
3705
4023
|
return "";
|
|
3706
4024
|
}
|
|
@@ -3713,16 +4031,16 @@ function distributeGlobalSkills(home = HOME) {
|
|
|
3713
4031
|
console.warn("Could not find ZAM source SKILL.md in the package folder.");
|
|
3714
4032
|
return results;
|
|
3715
4033
|
}
|
|
3716
|
-
const claudeSkillsDir =
|
|
4034
|
+
const claudeSkillsDir = join8(home, ".claude", "skills", "zam");
|
|
3717
4035
|
try {
|
|
3718
4036
|
if (!claudeSourceSkill) {
|
|
3719
4037
|
throw new Error("Claude skill source not found");
|
|
3720
4038
|
}
|
|
3721
|
-
|
|
3722
|
-
copyFileSync(claudeSourceSkill,
|
|
4039
|
+
mkdirSync6(claudeSkillsDir, { recursive: true });
|
|
4040
|
+
copyFileSync(claudeSourceSkill, join8(claudeSkillsDir, "SKILL.md"));
|
|
3723
4041
|
results.push({
|
|
3724
4042
|
name: "Claude Code Global",
|
|
3725
|
-
path:
|
|
4043
|
+
path: join8(claudeSkillsDir, "SKILL.md"),
|
|
3726
4044
|
success: true
|
|
3727
4045
|
});
|
|
3728
4046
|
} catch (_err) {
|
|
@@ -3732,13 +4050,13 @@ function distributeGlobalSkills(home = HOME) {
|
|
|
3732
4050
|
success: false
|
|
3733
4051
|
});
|
|
3734
4052
|
}
|
|
3735
|
-
const geminiSkillsDir =
|
|
4053
|
+
const geminiSkillsDir = join8(home, ".gemini", "skills", "zam");
|
|
3736
4054
|
try {
|
|
3737
|
-
|
|
3738
|
-
copyFileSync(sourceSkill,
|
|
4055
|
+
mkdirSync6(geminiSkillsDir, { recursive: true });
|
|
4056
|
+
copyFileSync(sourceSkill, join8(geminiSkillsDir, "SKILL.md"));
|
|
3739
4057
|
results.push({
|
|
3740
4058
|
name: "Gemini CLI Global",
|
|
3741
|
-
path:
|
|
4059
|
+
path: join8(geminiSkillsDir, "SKILL.md"),
|
|
3742
4060
|
success: true
|
|
3743
4061
|
});
|
|
3744
4062
|
} catch (_err) {
|
|
@@ -3748,16 +4066,16 @@ function distributeGlobalSkills(home = HOME) {
|
|
|
3748
4066
|
success: false
|
|
3749
4067
|
});
|
|
3750
4068
|
}
|
|
3751
|
-
const codexSkillsDir =
|
|
4069
|
+
const codexSkillsDir = join8(home, ".agents", "skills", "zam");
|
|
3752
4070
|
try {
|
|
3753
4071
|
if (!codexSourceSkill) {
|
|
3754
4072
|
throw new Error("Codex skill source not found");
|
|
3755
4073
|
}
|
|
3756
|
-
|
|
3757
|
-
copyFileSync(codexSourceSkill,
|
|
4074
|
+
mkdirSync6(codexSkillsDir, { recursive: true });
|
|
4075
|
+
copyFileSync(codexSourceSkill, join8(codexSkillsDir, "SKILL.md"));
|
|
3758
4076
|
results.push({
|
|
3759
4077
|
name: "Codex Global",
|
|
3760
|
-
path:
|
|
4078
|
+
path: join8(codexSkillsDir, "SKILL.md"),
|
|
3761
4079
|
success: true
|
|
3762
4080
|
});
|
|
3763
4081
|
} catch (_err) {
|
|
@@ -3767,13 +4085,13 @@ function distributeGlobalSkills(home = HOME) {
|
|
|
3767
4085
|
success: false
|
|
3768
4086
|
});
|
|
3769
4087
|
}
|
|
3770
|
-
const gooseSkillsDir =
|
|
4088
|
+
const gooseSkillsDir = join8(home, ".goose", "skills", "zam");
|
|
3771
4089
|
try {
|
|
3772
|
-
|
|
3773
|
-
copyFileSync(sourceSkill,
|
|
4090
|
+
mkdirSync6(gooseSkillsDir, { recursive: true });
|
|
4091
|
+
copyFileSync(sourceSkill, join8(gooseSkillsDir, "SKILL.md"));
|
|
3774
4092
|
results.push({
|
|
3775
4093
|
name: "Goose Global",
|
|
3776
|
-
path:
|
|
4094
|
+
path: join8(gooseSkillsDir, "SKILL.md"),
|
|
3777
4095
|
success: true
|
|
3778
4096
|
});
|
|
3779
4097
|
} catch (_err) {
|
|
@@ -3822,7 +4140,7 @@ function installHook(file, hook, oldHook) {
|
|
|
3822
4140
|
return { success: true, alreadyHooked: true };
|
|
3823
4141
|
}
|
|
3824
4142
|
if (content.includes(oldHook.trim())) {
|
|
3825
|
-
|
|
4143
|
+
writeFileSync4(file, content.replace(oldHook.trim(), hook.trim()), "utf8");
|
|
3826
4144
|
} else {
|
|
3827
4145
|
appendFileSync3(file, hook);
|
|
3828
4146
|
}
|
|
@@ -3833,24 +4151,24 @@ function installHook(file, hook, oldHook) {
|
|
|
3833
4151
|
}
|
|
3834
4152
|
function injectShellHooks(home = HOME) {
|
|
3835
4153
|
const results = [];
|
|
3836
|
-
const zshrc =
|
|
4154
|
+
const zshrc = join8(home, ".zshrc");
|
|
3837
4155
|
if (existsSync7(zshrc)) {
|
|
3838
4156
|
const status = installHook(zshrc, posixHook("zsh"), POSIX_OLD_HOOK);
|
|
3839
4157
|
results.push({ shell: "zsh", file: zshrc, ...status });
|
|
3840
4158
|
}
|
|
3841
|
-
const bashrc =
|
|
4159
|
+
const bashrc = join8(home, ".bashrc");
|
|
3842
4160
|
if (existsSync7(bashrc)) {
|
|
3843
4161
|
const status = installHook(bashrc, posixHook("bash"), POSIX_OLD_HOOK);
|
|
3844
4162
|
results.push({ shell: "bash", file: bashrc, ...status });
|
|
3845
4163
|
}
|
|
3846
4164
|
const pwshDirs = [
|
|
3847
|
-
|
|
3848
|
-
|
|
4165
|
+
join8(home, "Documents", "PowerShell"),
|
|
4166
|
+
join8(home, "Documents", "WindowsPowerShell")
|
|
3849
4167
|
];
|
|
3850
4168
|
for (const dir of pwshDirs) {
|
|
3851
|
-
const profileFile =
|
|
4169
|
+
const profileFile = join8(dir, "Microsoft.PowerShell_profile.ps1");
|
|
3852
4170
|
try {
|
|
3853
|
-
|
|
4171
|
+
mkdirSync6(dir, { recursive: true });
|
|
3854
4172
|
const status = installHook(
|
|
3855
4173
|
profileFile,
|
|
3856
4174
|
POWERSHELL_HOOK,
|
|
@@ -4082,10 +4400,10 @@ function t(locale, key, params = {}) {
|
|
|
4082
4400
|
}
|
|
4083
4401
|
|
|
4084
4402
|
// src/kernel/system/install-config.ts
|
|
4085
|
-
import { existsSync as existsSync8, mkdirSync as
|
|
4403
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync7, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
|
|
4086
4404
|
import { homedir as homedir6 } from "os";
|
|
4087
|
-
import { dirname as dirname4, join as
|
|
4088
|
-
var DEFAULT_CONFIG_PATH =
|
|
4405
|
+
import { dirname as dirname4, join as join9 } from "path";
|
|
4406
|
+
var DEFAULT_CONFIG_PATH = join9(homedir6(), ".zam", "config.json");
|
|
4089
4407
|
function loadInstallConfig(path = DEFAULT_CONFIG_PATH) {
|
|
4090
4408
|
if (!existsSync8(path)) return {};
|
|
4091
4409
|
try {
|
|
@@ -4096,8 +4414,8 @@ function loadInstallConfig(path = DEFAULT_CONFIG_PATH) {
|
|
|
4096
4414
|
}
|
|
4097
4415
|
function saveInstallConfig(config, path = DEFAULT_CONFIG_PATH) {
|
|
4098
4416
|
const dir = dirname4(path);
|
|
4099
|
-
if (!existsSync8(dir))
|
|
4100
|
-
|
|
4417
|
+
if (!existsSync8(dir)) mkdirSync7(dir, { recursive: true });
|
|
4418
|
+
writeFileSync5(path, `${JSON.stringify(config, null, 2)}
|
|
4101
4419
|
`, "utf-8");
|
|
4102
4420
|
}
|
|
4103
4421
|
function getInstallMode(path = DEFAULT_CONFIG_PATH) {
|
|
@@ -4130,7 +4448,7 @@ function detectSyncProvider(dir) {
|
|
|
4130
4448
|
import { execFileSync, execSync } from "child_process";
|
|
4131
4449
|
import { existsSync as existsSync9 } from "fs";
|
|
4132
4450
|
import { homedir as homedir7 } from "os";
|
|
4133
|
-
import { join as
|
|
4451
|
+
import { join as join10 } from "path";
|
|
4134
4452
|
function hasCommand(cmd) {
|
|
4135
4453
|
try {
|
|
4136
4454
|
const checkCmd2 = process.platform === "win32" ? `where ${cmd}` : `which ${cmd}`;
|
|
@@ -4175,7 +4493,7 @@ function installOllama() {
|
|
|
4175
4493
|
const isMac = process.platform === "darwin";
|
|
4176
4494
|
const isWin = process.platform === "win32";
|
|
4177
4495
|
const hasOllama = hasCommand("ollama") || isMac && existsSync9("/Applications/Ollama.app") || isWin && existsSync9(
|
|
4178
|
-
|
|
4496
|
+
join10(homedir7(), "AppData", "Local", "Programs", "Ollama", "ollama.exe")
|
|
4179
4497
|
);
|
|
4180
4498
|
if (hasOllama) {
|
|
4181
4499
|
return { success: true, message: "Ollama is already installed." };
|
|
@@ -4235,7 +4553,7 @@ function installOllama() {
|
|
|
4235
4553
|
function resolveOllamaCommand() {
|
|
4236
4554
|
if (hasCommand("ollama")) return "ollama";
|
|
4237
4555
|
const candidates = process.platform === "win32" ? [
|
|
4238
|
-
|
|
4556
|
+
join10(
|
|
4239
4557
|
homedir7(),
|
|
4240
4558
|
"AppData",
|
|
4241
4559
|
"Local",
|
|
@@ -4541,7 +4859,7 @@ var C = {
|
|
|
4541
4859
|
};
|
|
4542
4860
|
var SUPPORTED_AGENTS = ["opencode"];
|
|
4543
4861
|
function agentsMdPresent(cwd = process.cwd()) {
|
|
4544
|
-
return existsSync11(
|
|
4862
|
+
return existsSync11(join11(cwd, "AGENTS.md"));
|
|
4545
4863
|
}
|
|
4546
4864
|
function printStatus() {
|
|
4547
4865
|
const installed = hasCommand("opencode");
|
|
@@ -4582,9 +4900,11 @@ var statusCmd = new Command("status").description("Show whether the agent is ins
|
|
|
4582
4900
|
var agentCommand = new Command("agent").description("Provision and inspect the agent that drives ZAM sessions").addCommand(installCmd).addCommand(statusCmd).action(printStatus);
|
|
4583
4901
|
|
|
4584
4902
|
// src/cli/commands/bridge.ts
|
|
4585
|
-
import {
|
|
4586
|
-
import {
|
|
4587
|
-
import {
|
|
4903
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
4904
|
+
import { randomBytes } from "crypto";
|
|
4905
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync10, rmSync as rmSync2 } from "fs";
|
|
4906
|
+
import { homedir as homedir8, tmpdir } from "os";
|
|
4907
|
+
import { join as join12 } from "path";
|
|
4588
4908
|
import { Command as Command2 } from "commander";
|
|
4589
4909
|
|
|
4590
4910
|
// src/cli/llm/client.ts
|
|
@@ -5505,15 +5825,16 @@ function parseTokenUpdates(opts) {
|
|
|
5505
5825
|
var bridgeCommand = new Command2("bridge").description(
|
|
5506
5826
|
"Machine-readable JSON protocol for AI integration"
|
|
5507
5827
|
);
|
|
5508
|
-
bridgeCommand.command("check-due").description("Check due cards for a user (JSON)").option("--user <id>", "User ID (default: whoami)").action(async (opts) => {
|
|
5828
|
+
bridgeCommand.command("check-due").description("Check due cards for a user (JSON)").option("--user <id>", "User ID (default: whoami)").option("--domain <domain>", "Filter by knowledge domain").action(async (opts) => {
|
|
5509
5829
|
await withDb2(async (db) => {
|
|
5510
5830
|
const userId = await resolveUser(opts, db, { json: true });
|
|
5511
|
-
const dueCards = await getDueCards(db, userId);
|
|
5831
|
+
const dueCards = await getDueCards(db, userId, void 0, opts.domain);
|
|
5512
5832
|
const domains = [
|
|
5513
5833
|
...new Set(dueCards.map((c) => c.domain).filter(Boolean))
|
|
5514
5834
|
].sort();
|
|
5515
5835
|
jsonOut2({
|
|
5516
5836
|
userId,
|
|
5837
|
+
domain: opts.domain ?? null,
|
|
5517
5838
|
dueCount: dueCards.length,
|
|
5518
5839
|
domains,
|
|
5519
5840
|
cards: dueCards.map((c) => ({
|
|
@@ -5720,13 +6041,15 @@ bridgeCommand.command("start-session").description("Start a ZAM learning session
|
|
|
5720
6041
|
task: opts.task,
|
|
5721
6042
|
execution_context: context
|
|
5722
6043
|
});
|
|
6044
|
+
const observerPolicyHint = context === "ui" && !await isObserverPolicyConfigured(db) ? OBSERVER_POLICY_UNSET_HINT : void 0;
|
|
5723
6045
|
jsonOut2({
|
|
5724
6046
|
id: session.id,
|
|
5725
6047
|
userId: session.user_id,
|
|
5726
6048
|
task: session.task,
|
|
5727
6049
|
executionContext: session.execution_context,
|
|
5728
6050
|
startedAt: session.started_at,
|
|
5729
|
-
completedAt: session.completed_at
|
|
6051
|
+
completedAt: session.completed_at,
|
|
6052
|
+
...observerPolicyHint ? { observerPolicyHint } : {}
|
|
5730
6053
|
});
|
|
5731
6054
|
});
|
|
5732
6055
|
});
|
|
@@ -5849,7 +6172,8 @@ bridgeCommand.command("add-token").description("Create a token + card from JSON
|
|
|
5849
6172
|
bloom_level: data?.bloom_level ?? 1,
|
|
5850
6173
|
context: data?.context,
|
|
5851
6174
|
symbiosis_mode: data?.symbiosis_mode,
|
|
5852
|
-
source_link: data?.source_link ?? null
|
|
6175
|
+
source_link: data?.source_link ?? null,
|
|
6176
|
+
question: data?.question ?? null
|
|
5853
6177
|
});
|
|
5854
6178
|
const card = await ensureCard(db, token.id, userId);
|
|
5855
6179
|
jsonOut2({
|
|
@@ -5884,7 +6208,7 @@ bridgeCommand.command("discover-skills").description(
|
|
|
5884
6208
|
"20"
|
|
5885
6209
|
).action(async (opts) => {
|
|
5886
6210
|
try {
|
|
5887
|
-
const monitorDir =
|
|
6211
|
+
const monitorDir = join12(homedir8(), ".zam", "monitor");
|
|
5888
6212
|
let files;
|
|
5889
6213
|
try {
|
|
5890
6214
|
files = readdirSync2(monitorDir).filter((f) => f.endsWith(".jsonl"));
|
|
@@ -5897,7 +6221,7 @@ bridgeCommand.command("discover-skills").description(
|
|
|
5897
6221
|
return;
|
|
5898
6222
|
}
|
|
5899
6223
|
const limit = Number(opts.limit);
|
|
5900
|
-
const sorted = files.map((f) => ({ name: f, path:
|
|
6224
|
+
const sorted = files.map((f) => ({ name: f, path: join12(monitorDir, f) })).sort((a, b) => b.name.localeCompare(a.name)).slice(0, limit);
|
|
5901
6225
|
const sessionCommands = /* @__PURE__ */ new Map();
|
|
5902
6226
|
for (const file of sorted) {
|
|
5903
6227
|
const sessionId = file.name.replace(".jsonl", "");
|
|
@@ -6007,6 +6331,468 @@ bridgeCommand.command("observe-ui-snapshot").description(
|
|
|
6007
6331
|
jsonOut2(report);
|
|
6008
6332
|
});
|
|
6009
6333
|
});
|
|
6334
|
+
function resolveWindowsPowerShell() {
|
|
6335
|
+
try {
|
|
6336
|
+
execFileSync2("where.exe", ["pwsh.exe"], { stdio: "ignore" });
|
|
6337
|
+
return "pwsh";
|
|
6338
|
+
} catch {
|
|
6339
|
+
return "powershell";
|
|
6340
|
+
}
|
|
6341
|
+
}
|
|
6342
|
+
function captureScreenshot(outputPath, hwnd, processName) {
|
|
6343
|
+
const platform = process.platform;
|
|
6344
|
+
if (hwnd && !/^(0x)?[0-9a-fA-F]+$/.test(hwnd)) {
|
|
6345
|
+
throw new Error(`Invalid HWND format: ${hwnd}`);
|
|
6346
|
+
}
|
|
6347
|
+
if (processName && !/^[a-zA-Z0-9\-_.]+$/.test(processName)) {
|
|
6348
|
+
throw new Error(`Invalid process name format: ${processName}`);
|
|
6349
|
+
}
|
|
6350
|
+
if (platform === "win32") {
|
|
6351
|
+
const stdout = execFileSync2(
|
|
6352
|
+
resolveWindowsPowerShell(),
|
|
6353
|
+
[
|
|
6354
|
+
"-NoProfile",
|
|
6355
|
+
"-Command",
|
|
6356
|
+
`
|
|
6357
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
6358
|
+
Add-Type -AssemblyName System.Drawing
|
|
6359
|
+
|
|
6360
|
+
$code = @'
|
|
6361
|
+
using System;
|
|
6362
|
+
using System.Runtime.InteropServices;
|
|
6363
|
+
|
|
6364
|
+
public class Win32 {
|
|
6365
|
+
public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
|
|
6366
|
+
|
|
6367
|
+
[DllImport("user32.dll")]
|
|
6368
|
+
public static extern bool SetProcessDPIAware();
|
|
6369
|
+
|
|
6370
|
+
[DllImport("user32.dll")]
|
|
6371
|
+
public static extern bool SetProcessDpiAwarenessContext(IntPtr dpiContext);
|
|
6372
|
+
|
|
6373
|
+
[DllImport("user32.dll")]
|
|
6374
|
+
public static extern bool GetWindowRect(IntPtr hWnd, out RECT rect);
|
|
6375
|
+
|
|
6376
|
+
[DllImport("user32.dll")]
|
|
6377
|
+
public static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
|
|
6378
|
+
|
|
6379
|
+
[DllImport("user32.dll")]
|
|
6380
|
+
public static extern bool EnumChildWindows(IntPtr hWnd, EnumWindowsProc lpEnumFunc, IntPtr lParam);
|
|
6381
|
+
|
|
6382
|
+
[DllImport("user32.dll")]
|
|
6383
|
+
public static extern bool IsWindowVisible(IntPtr hWnd);
|
|
6384
|
+
|
|
6385
|
+
[DllImport("user32.dll", SetLastError=true)]
|
|
6386
|
+
public static extern int GetWindowText(IntPtr hWnd, System.Text.StringBuilder lpString, int nMaxCount);
|
|
6387
|
+
|
|
6388
|
+
[DllImport("user32.dll", SetLastError=true)]
|
|
6389
|
+
public static extern int GetWindowTextLength(IntPtr hWnd);
|
|
6390
|
+
|
|
6391
|
+
[DllImport("user32.dll")]
|
|
6392
|
+
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
|
|
6393
|
+
|
|
6394
|
+
[DllImport("user32.dll")]
|
|
6395
|
+
public static extern bool SetForegroundWindow(IntPtr hWnd);
|
|
6396
|
+
|
|
6397
|
+
[DllImport("user32.dll")]
|
|
6398
|
+
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
|
|
6399
|
+
|
|
6400
|
+
[DllImport("user32.dll")]
|
|
6401
|
+
public static extern bool IsIconic(IntPtr hWnd);
|
|
6402
|
+
|
|
6403
|
+
[DllImport("user32.dll")]
|
|
6404
|
+
public static extern bool PrintWindow(IntPtr hWnd, IntPtr hdcBlt, uint nFlags);
|
|
6405
|
+
|
|
6406
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
6407
|
+
public struct RECT {
|
|
6408
|
+
public int Left;
|
|
6409
|
+
public int Top;
|
|
6410
|
+
public int Right;
|
|
6411
|
+
public int Bottom;
|
|
6412
|
+
}
|
|
6413
|
+
}
|
|
6414
|
+
'@
|
|
6415
|
+
Add-Type -TypeDefinition $code
|
|
6416
|
+
|
|
6417
|
+
try {
|
|
6418
|
+
# DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = -4. Without this, Windows
|
|
6419
|
+
# can return logical window bounds while PrintWindow renders physical
|
|
6420
|
+
# pixels, causing high-DPI captures to crop the right/bottom edge.
|
|
6421
|
+
[Win32]::SetProcessDpiAwarenessContext([IntPtr](-4)) | Out-Null
|
|
6422
|
+
} catch {
|
|
6423
|
+
try { [Win32]::SetProcessDPIAware() | Out-Null } catch {}
|
|
6424
|
+
}
|
|
6425
|
+
|
|
6426
|
+
function Get-WindowTitle([IntPtr]$hWnd) {
|
|
6427
|
+
$length = [Win32]::GetWindowTextLength($hWnd)
|
|
6428
|
+
$capacity = [Math]::Max(1, $length + 1)
|
|
6429
|
+
$builder = New-Object System.Text.StringBuilder $capacity
|
|
6430
|
+
[Win32]::GetWindowText($hWnd, $builder, $builder.Capacity) | Out-Null
|
|
6431
|
+
$builder.ToString()
|
|
6432
|
+
}
|
|
6433
|
+
|
|
6434
|
+
function Get-WindowProcess([IntPtr]$hWnd) {
|
|
6435
|
+
[uint32]$processId = 0
|
|
6436
|
+
[Win32]::GetWindowThreadProcessId($hWnd, [ref]$processId) | Out-Null
|
|
6437
|
+
if ($processId -eq 0) { return $null }
|
|
6438
|
+
Get-Process -Id $processId -ErrorAction SilentlyContinue
|
|
6439
|
+
}
|
|
6440
|
+
|
|
6441
|
+
function Get-VisibleTopLevelWindows {
|
|
6442
|
+
$script:windowCandidates = New-Object System.Collections.ArrayList
|
|
6443
|
+
$callback = [Win32+EnumWindowsProc]{
|
|
6444
|
+
param([IntPtr]$candidateHwnd, [IntPtr]$lParam)
|
|
6445
|
+
if ([Win32]::IsWindowVisible($candidateHwnd)) {
|
|
6446
|
+
$candidateRect = New-Object Win32+RECT
|
|
6447
|
+
if ([Win32]::GetWindowRect($candidateHwnd, [ref]$candidateRect)) {
|
|
6448
|
+
$candidateWidth = $candidateRect.Right - $candidateRect.Left
|
|
6449
|
+
$candidateHeight = $candidateRect.Bottom - $candidateRect.Top
|
|
6450
|
+
if ($candidateWidth -gt 0 -and $candidateHeight -gt 0) {
|
|
6451
|
+
[void]$script:windowCandidates.Add($candidateHwnd)
|
|
6452
|
+
}
|
|
6453
|
+
}
|
|
6454
|
+
}
|
|
6455
|
+
return $true
|
|
6456
|
+
}
|
|
6457
|
+
[Win32]::EnumWindows($callback, [IntPtr]::Zero) | Out-Null
|
|
6458
|
+
$windows = $script:windowCandidates
|
|
6459
|
+
Remove-Variable -Name windowCandidates -Scope Script -ErrorAction SilentlyContinue
|
|
6460
|
+
$windows
|
|
6461
|
+
}
|
|
6462
|
+
|
|
6463
|
+
function Find-TopLevelWindowByProcessName([string]$name) {
|
|
6464
|
+
foreach ($candidateHwnd in Get-VisibleTopLevelWindows) {
|
|
6465
|
+
$candidateProcess = Get-WindowProcess $candidateHwnd
|
|
6466
|
+
if ($candidateProcess -and $candidateProcess.ProcessName -ieq $name) {
|
|
6467
|
+
return [pscustomobject]@{
|
|
6468
|
+
Hwnd = $candidateHwnd
|
|
6469
|
+
MatchedBy = "process-top-level-window"
|
|
6470
|
+
}
|
|
6471
|
+
}
|
|
6472
|
+
|
|
6473
|
+
$script:desiredChildProcessName = $name
|
|
6474
|
+
$script:foundChildProcessWindow = $false
|
|
6475
|
+
$childCallback = [Win32+EnumWindowsProc]{
|
|
6476
|
+
param([IntPtr]$childHwnd, [IntPtr]$lParam)
|
|
6477
|
+
$childProcess = Get-WindowProcess $childHwnd
|
|
6478
|
+
if ($childProcess -and $childProcess.ProcessName -ieq $script:desiredChildProcessName) {
|
|
6479
|
+
$script:foundChildProcessWindow = $true
|
|
6480
|
+
return $false
|
|
6481
|
+
}
|
|
6482
|
+
return $true
|
|
6483
|
+
}
|
|
6484
|
+
[Win32]::EnumChildWindows($candidateHwnd, $childCallback, [IntPtr]::Zero) | Out-Null
|
|
6485
|
+
$foundChild = $script:foundChildProcessWindow
|
|
6486
|
+
Remove-Variable -Name desiredChildProcessName -Scope Script -ErrorAction SilentlyContinue
|
|
6487
|
+
Remove-Variable -Name foundChildProcessWindow -Scope Script -ErrorAction SilentlyContinue
|
|
6488
|
+
|
|
6489
|
+
if ($foundChild) {
|
|
6490
|
+
return [pscustomobject]@{
|
|
6491
|
+
Hwnd = $candidateHwnd
|
|
6492
|
+
MatchedBy = "process-child-window"
|
|
6493
|
+
}
|
|
6494
|
+
}
|
|
6495
|
+
}
|
|
6496
|
+
|
|
6497
|
+
return $null
|
|
6498
|
+
}
|
|
6499
|
+
|
|
6500
|
+
function New-CaptureTarget([IntPtr]$hWnd, [string]$matchedBy) {
|
|
6501
|
+
$target = [ordered]@{
|
|
6502
|
+
requestedHwnd = if ($targetHwnd -ne '') { $targetHwnd } else { $null }
|
|
6503
|
+
requestedProcessName = if ($processName -ne '') { $processName } else { $null }
|
|
6504
|
+
matchedBy = $matchedBy
|
|
6505
|
+
hwnd = $null
|
|
6506
|
+
processId = $null
|
|
6507
|
+
processName = $null
|
|
6508
|
+
windowTitle = $null
|
|
6509
|
+
bounds = $null
|
|
6510
|
+
}
|
|
6511
|
+
|
|
6512
|
+
if ($hWnd -ne [IntPtr]::Zero) {
|
|
6513
|
+
$target.hwnd = $hWnd.ToInt64()
|
|
6514
|
+
$target.windowTitle = Get-WindowTitle $hWnd
|
|
6515
|
+
|
|
6516
|
+
$windowProcess = Get-WindowProcess $hWnd
|
|
6517
|
+
if ($windowProcess) {
|
|
6518
|
+
$target.processId = $windowProcess.Id
|
|
6519
|
+
$target.processName = $windowProcess.ProcessName
|
|
6520
|
+
}
|
|
6521
|
+
|
|
6522
|
+
$targetRect = New-Object Win32+RECT
|
|
6523
|
+
if ([Win32]::GetWindowRect($hWnd, [ref]$targetRect)) {
|
|
6524
|
+
$target.bounds = [ordered]@{
|
|
6525
|
+
left = $targetRect.Left
|
|
6526
|
+
top = $targetRect.Top
|
|
6527
|
+
right = $targetRect.Right
|
|
6528
|
+
bottom = $targetRect.Bottom
|
|
6529
|
+
width = $targetRect.Right - $targetRect.Left
|
|
6530
|
+
height = $targetRect.Bottom - $targetRect.Top
|
|
6531
|
+
}
|
|
6532
|
+
}
|
|
6533
|
+
}
|
|
6534
|
+
|
|
6535
|
+
$target
|
|
6536
|
+
}
|
|
6537
|
+
|
|
6538
|
+
function Write-CaptureResult([string]$method, [IntPtr]$hWnd, [string]$matchedBy) {
|
|
6539
|
+
$result = [ordered]@{
|
|
6540
|
+
method = $method
|
|
6541
|
+
target = New-CaptureTarget $hWnd $matchedBy
|
|
6542
|
+
}
|
|
6543
|
+
Write-Output ("CAPTURE_RESULT:" + ($result | ConvertTo-Json -Compress -Depth 6))
|
|
6544
|
+
}
|
|
6545
|
+
|
|
6546
|
+
$hwndVal = [IntPtr]::Zero
|
|
6547
|
+
$matchedBy = "fullscreen-fallback"
|
|
6548
|
+
$targetHwnd = '${hwnd || ""}'
|
|
6549
|
+
$processName = '${processName || ""}'
|
|
6550
|
+
|
|
6551
|
+
if ($targetHwnd -ne '') {
|
|
6552
|
+
if ($targetHwnd.StartsWith("0x")) {
|
|
6553
|
+
$hwndVal = [IntPtr][Convert]::ToInt64($targetHwnd, 16)
|
|
6554
|
+
} else {
|
|
6555
|
+
$hwndVal = [IntPtr][Convert]::ToInt64($targetHwnd, 10)
|
|
6556
|
+
}
|
|
6557
|
+
$matchedBy = "hwnd"
|
|
6558
|
+
} elseif ($processName -ne '') {
|
|
6559
|
+
$proc = Get-Process -Name $processName -ErrorAction SilentlyContinue | Where-Object {$_.MainWindowHandle -ne 0} | Select-Object -First 1
|
|
6560
|
+
if ($proc) {
|
|
6561
|
+
$hwndVal = $proc.MainWindowHandle
|
|
6562
|
+
$matchedBy = "process-main-window"
|
|
6563
|
+
} else {
|
|
6564
|
+
$proc = Get-Process -Name $processName -ErrorAction SilentlyContinue | Select-Object -First 1
|
|
6565
|
+
if ($proc) {
|
|
6566
|
+
$hwndVal = $proc.MainWindowHandle
|
|
6567
|
+
$matchedBy = "process-zero-main-window"
|
|
6568
|
+
}
|
|
6569
|
+
}
|
|
6570
|
+
|
|
6571
|
+
if ($hwndVal -eq [IntPtr]::Zero) {
|
|
6572
|
+
$windowMatch = Find-TopLevelWindowByProcessName $processName
|
|
6573
|
+
if ($windowMatch) {
|
|
6574
|
+
$hwndVal = $windowMatch.Hwnd
|
|
6575
|
+
$matchedBy = $windowMatch.MatchedBy
|
|
6576
|
+
}
|
|
6577
|
+
}
|
|
6578
|
+
}
|
|
6579
|
+
|
|
6580
|
+
if ($hwndVal -ne [IntPtr]::Zero) {
|
|
6581
|
+
if ([Win32]::IsIconic($hwndVal)) {
|
|
6582
|
+
[Win32]::ShowWindow($hwndVal, 9) | Out-Null # SW_RESTORE = 9
|
|
6583
|
+
Start-Sleep -Milliseconds 250
|
|
6584
|
+
}
|
|
6585
|
+
|
|
6586
|
+
$rect = New-Object Win32+RECT
|
|
6587
|
+
if ([Win32]::GetWindowRect($hwndVal, [ref]$rect)) {
|
|
6588
|
+
$width = $rect.Right - $rect.Left
|
|
6589
|
+
$height = $rect.Bottom - $rect.Top
|
|
6590
|
+
if ($width -gt 0 -and $height -gt 0) {
|
|
6591
|
+
$bitmap = New-Object System.Drawing.Bitmap($width, $height)
|
|
6592
|
+
$graphics = [System.Drawing.Graphics]::FromImage($bitmap)
|
|
6593
|
+
# PrintWindow renders the target window directly, regardless of
|
|
6594
|
+
# z-order, so an occluded or background window is still captured
|
|
6595
|
+
# correctly. SetForegroundWindow from a background process is
|
|
6596
|
+
# blocked by Windows, so CopyFromScreen would grab whatever sits
|
|
6597
|
+
# on top. PW_RENDERFULLCONTENT (0x2) handles modern/UWP windows.
|
|
6598
|
+
$hdc = $graphics.GetHdc()
|
|
6599
|
+
$printed = [Win32]::PrintWindow($hwndVal, $hdc, 2)
|
|
6600
|
+
$graphics.ReleaseHdc($hdc)
|
|
6601
|
+
$method = "printwindow"
|
|
6602
|
+
|
|
6603
|
+
# Black-frame guard: PrintWindow can return a near-black frame on
|
|
6604
|
+
# some hardware-accelerated / DirectComposition surfaces. Sample a
|
|
6605
|
+
# sparse grid; if it is essentially black, fall back to a foreground
|
|
6606
|
+
# CopyFromScreen grab so the capture self-heals on those drivers.
|
|
6607
|
+
$needFallback = -not $printed
|
|
6608
|
+
if (-not $needFallback) {
|
|
6609
|
+
$sum = 0.0
|
|
6610
|
+
$cnt = 0
|
|
6611
|
+
$stepX = [Math]::Max(1, [int]($width / 12))
|
|
6612
|
+
$stepY = [Math]::Max(1, [int]($height / 12))
|
|
6613
|
+
for ($sy = 0; $sy -lt $height; $sy += $stepY) {
|
|
6614
|
+
for ($sx = 0; $sx -lt $width; $sx += $stepX) {
|
|
6615
|
+
$px = $bitmap.GetPixel($sx, $sy)
|
|
6616
|
+
$sum += ($px.R + $px.G + $px.B) / 3.0
|
|
6617
|
+
$cnt++
|
|
6618
|
+
}
|
|
6619
|
+
}
|
|
6620
|
+
if ($cnt -gt 0 -and ($sum / $cnt) -lt 6) { $needFallback = $true }
|
|
6621
|
+
}
|
|
6622
|
+
|
|
6623
|
+
if ($needFallback) {
|
|
6624
|
+
[Win32]::SetForegroundWindow($hwndVal) | Out-Null
|
|
6625
|
+
Start-Sleep -Milliseconds 250
|
|
6626
|
+
$graphics.CopyFromScreen($rect.Left, $rect.Top, 0, 0, $bitmap.Size)
|
|
6627
|
+
$method = "copyfromscreen"
|
|
6628
|
+
}
|
|
6629
|
+
$bitmap.Save('${outputPath.replace(/\\/g, "\\\\")}', [System.Drawing.Imaging.ImageFormat]::Png)
|
|
6630
|
+
$graphics.Dispose()
|
|
6631
|
+
$bitmap.Dispose()
|
|
6632
|
+
Write-CaptureResult $method $hwndVal $matchedBy
|
|
6633
|
+
exit 0
|
|
6634
|
+
}
|
|
6635
|
+
}
|
|
6636
|
+
}
|
|
6637
|
+
|
|
6638
|
+
# Fallback: full primary screen
|
|
6639
|
+
$screen = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds
|
|
6640
|
+
$bitmap = New-Object System.Drawing.Bitmap($screen.Width, $screen.Height)
|
|
6641
|
+
$graphics = [System.Drawing.Graphics]::FromImage($bitmap)
|
|
6642
|
+
$graphics.CopyFromScreen($screen.Location, [System.Drawing.Point]::Empty, $screen.Size)
|
|
6643
|
+
$bitmap.Save('${outputPath.replace(/\\/g, "\\\\")}', [System.Drawing.Imaging.ImageFormat]::Png)
|
|
6644
|
+
$graphics.Dispose()
|
|
6645
|
+
$bitmap.Dispose()
|
|
6646
|
+
Write-CaptureResult "fullscreen" $hwndVal $matchedBy
|
|
6647
|
+
`.trim()
|
|
6648
|
+
],
|
|
6649
|
+
{ stdio: "pipe", encoding: "utf8" }
|
|
6650
|
+
);
|
|
6651
|
+
const resultMatch = /CAPTURE_RESULT:(\{.*\})/.exec(stdout ?? "");
|
|
6652
|
+
if (resultMatch) {
|
|
6653
|
+
const parsed = JSON.parse(resultMatch[1]);
|
|
6654
|
+
return parsed;
|
|
6655
|
+
}
|
|
6656
|
+
const methodMatch = /CAPTURE_METHOD:(\w+)/.exec(stdout ?? "");
|
|
6657
|
+
return {
|
|
6658
|
+
method: methodMatch ? methodMatch[1] : "unknown",
|
|
6659
|
+
target: null
|
|
6660
|
+
};
|
|
6661
|
+
} else if (platform === "darwin") {
|
|
6662
|
+
if (hwnd) {
|
|
6663
|
+
const parsedHwnd = hwnd.startsWith("0x") ? parseInt(hwnd, 16) : parseInt(hwnd, 10);
|
|
6664
|
+
execFileSync2("screencapture", ["-l", String(parsedHwnd), outputPath], {
|
|
6665
|
+
stdio: "pipe"
|
|
6666
|
+
});
|
|
6667
|
+
return { method: "screencapture-window", target: null };
|
|
6668
|
+
} else if (processName) {
|
|
6669
|
+
try {
|
|
6670
|
+
const windowId = execFileSync2(
|
|
6671
|
+
"osascript",
|
|
6672
|
+
[
|
|
6673
|
+
"-e",
|
|
6674
|
+
`tell application "System Events" to get id of window 1 of process "${processName}"`
|
|
6675
|
+
],
|
|
6676
|
+
{ encoding: "utf8", stdio: ["pipe", "pipe", "ignore"] }
|
|
6677
|
+
).trim();
|
|
6678
|
+
if (windowId && /^\d+$/.test(windowId)) {
|
|
6679
|
+
execFileSync2("screencapture", ["-l", windowId, outputPath], {
|
|
6680
|
+
stdio: "pipe"
|
|
6681
|
+
});
|
|
6682
|
+
return { method: "screencapture-window", target: null };
|
|
6683
|
+
}
|
|
6684
|
+
} catch {
|
|
6685
|
+
}
|
|
6686
|
+
execFileSync2("screencapture", ["-x", outputPath], { stdio: "pipe" });
|
|
6687
|
+
return { method: "screencapture-full", target: null };
|
|
6688
|
+
} else {
|
|
6689
|
+
execFileSync2("screencapture", ["-x", outputPath], { stdio: "pipe" });
|
|
6690
|
+
return { method: "screencapture-full", target: null };
|
|
6691
|
+
}
|
|
6692
|
+
} else {
|
|
6693
|
+
throw new Error(
|
|
6694
|
+
`Screen capture not supported on platform: ${platform}. Use zam-observer or provide --image.`
|
|
6695
|
+
);
|
|
6696
|
+
}
|
|
6697
|
+
}
|
|
6698
|
+
bridgeCommand.command("capture-ui").description("Capture a screenshot for agent-side vision analysis (JSON)").option("--session <id>", "ZAM session ID (for metadata)").option("--output <path>", "PNG output path (defaults to temp file)").option("--image <path>", "Skip capture; return an existing image instead").option("--hwnd <hwnd>", "Window handle (decimal or hex) to capture").option("--process-name <name>", "Process name to capture").action(async (opts) => {
|
|
6699
|
+
await withDb2(async (db) => {
|
|
6700
|
+
const policy = await resolveObserverPolicy(db);
|
|
6701
|
+
const permission = {
|
|
6702
|
+
scope: policy.scope,
|
|
6703
|
+
consent: policy.consent,
|
|
6704
|
+
retention: policy.retention
|
|
6705
|
+
};
|
|
6706
|
+
const isProvided = Boolean(opts.image);
|
|
6707
|
+
if (!isProvided) {
|
|
6708
|
+
const pre = decidePreCapture(policy, {
|
|
6709
|
+
hasExplicitTarget: Boolean(opts.hwnd || opts.processName),
|
|
6710
|
+
requestedProcessName: opts.processName ?? null
|
|
6711
|
+
});
|
|
6712
|
+
if (!pre.allowed) {
|
|
6713
|
+
jsonOut2({
|
|
6714
|
+
sessionId: opts.session ?? null,
|
|
6715
|
+
granted: false,
|
|
6716
|
+
denied: true,
|
|
6717
|
+
denialReason: pre.denialReason,
|
|
6718
|
+
reason: pre.reason,
|
|
6719
|
+
capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6720
|
+
platform: process.platform,
|
|
6721
|
+
permission: { ...permission, granted: false }
|
|
6722
|
+
});
|
|
6723
|
+
return;
|
|
6724
|
+
}
|
|
6725
|
+
}
|
|
6726
|
+
const outputPath = opts.image ?? opts.output ?? join12(tmpdir(), `zam-capture-${randomBytes(4).toString("hex")}.png`);
|
|
6727
|
+
const captureResult = isProvided ? { method: "provided", target: null } : captureScreenshot(outputPath, opts.hwnd, opts.processName);
|
|
6728
|
+
if (!isProvided) {
|
|
6729
|
+
const post = decidePostCapture(policy, {
|
|
6730
|
+
method: captureResult.method,
|
|
6731
|
+
processName: captureResult.target?.processName ?? null,
|
|
6732
|
+
windowTitle: captureResult.target?.windowTitle ?? null
|
|
6733
|
+
});
|
|
6734
|
+
if (!post.allowed) {
|
|
6735
|
+
if (!opts.output) {
|
|
6736
|
+
try {
|
|
6737
|
+
rmSync2(outputPath, { force: true });
|
|
6738
|
+
} catch {
|
|
6739
|
+
}
|
|
6740
|
+
}
|
|
6741
|
+
jsonOut2({
|
|
6742
|
+
sessionId: opts.session ?? null,
|
|
6743
|
+
granted: false,
|
|
6744
|
+
denied: true,
|
|
6745
|
+
denialReason: post.denialReason,
|
|
6746
|
+
reason: post.reason,
|
|
6747
|
+
capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6748
|
+
platform: process.platform,
|
|
6749
|
+
permission: { ...permission, granted: false }
|
|
6750
|
+
});
|
|
6751
|
+
return;
|
|
6752
|
+
}
|
|
6753
|
+
}
|
|
6754
|
+
const imageBytes = readFileSync10(outputPath);
|
|
6755
|
+
const base64 = imageBytes.toString("base64");
|
|
6756
|
+
jsonOut2({
|
|
6757
|
+
sessionId: opts.session ?? null,
|
|
6758
|
+
granted: true,
|
|
6759
|
+
imagePath: outputPath,
|
|
6760
|
+
base64,
|
|
6761
|
+
mimeType: "image/png",
|
|
6762
|
+
captureMethod: captureResult.method,
|
|
6763
|
+
captureTarget: captureResult.target,
|
|
6764
|
+
capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6765
|
+
platform: process.platform,
|
|
6766
|
+
permission: { ...permission, granted: true }
|
|
6767
|
+
});
|
|
6768
|
+
});
|
|
6769
|
+
});
|
|
6770
|
+
bridgeCommand.command("get-observer-policy").description(
|
|
6771
|
+
"Report the resolved observer policy so an agent can check before capturing (JSON)"
|
|
6772
|
+
).action(async () => {
|
|
6773
|
+
await withDb2(async (db) => {
|
|
6774
|
+
const policy = await resolveObserverPolicy(db);
|
|
6775
|
+
jsonOut2({
|
|
6776
|
+
scope: policy.scope,
|
|
6777
|
+
consent: policy.consent,
|
|
6778
|
+
retention: policy.retention,
|
|
6779
|
+
allowlist: policy.allowlist,
|
|
6780
|
+
denylist: policy.denylist,
|
|
6781
|
+
redactWindowTitles: policy.redactWindowTitles,
|
|
6782
|
+
audioOptIn: policy.audioOptIn,
|
|
6783
|
+
builtInSensitiveAlwaysRefused: true,
|
|
6784
|
+
builtInSensitiveMatchers: [...BUILT_IN_SENSITIVE_MATCHERS]
|
|
6785
|
+
});
|
|
6786
|
+
});
|
|
6787
|
+
});
|
|
6788
|
+
bridgeCommand.command("sync-observer-policy").description(
|
|
6789
|
+
"Write the resolved observer policy to the native sidecar file (JSON)"
|
|
6790
|
+
).action(async () => {
|
|
6791
|
+
await withDb2(async (db) => {
|
|
6792
|
+
const { path, policy } = await syncObserverSidecarPolicy(db);
|
|
6793
|
+
jsonOut2({ synced: true, path, policy });
|
|
6794
|
+
});
|
|
6795
|
+
});
|
|
6010
6796
|
bridgeCommand.command("check-llm").description("Check if LLM is enabled and online (JSON)").action(async () => {
|
|
6011
6797
|
await withDb2(async (db) => {
|
|
6012
6798
|
const { enabled, url, model, apiKey } = await getLlmConfig(db);
|
|
@@ -6348,10 +7134,10 @@ import { Command as Command3 } from "commander";
|
|
|
6348
7134
|
var cardCommand = new Command3("card").description(
|
|
6349
7135
|
"Manage spaced-repetition cards"
|
|
6350
7136
|
);
|
|
6351
|
-
cardCommand.command("due").description("Show due tokens for a user").option("--user <id>", "User ID (default: whoami)").option("--json", "Output as JSON").option("--summary", "Show only counts per domain (no slugs or concepts)").action(async (opts) => {
|
|
7137
|
+
cardCommand.command("due").description("Show due tokens for a user").option("--user <id>", "User ID (default: whoami)").option("--domain <domain>", "Filter by knowledge domain").option("--json", "Output as JSON").option("--summary", "Show only counts per domain (no slugs or concepts)").action(async (opts) => {
|
|
6352
7138
|
await withDb(async (db) => {
|
|
6353
7139
|
const userId = await resolveUser(opts, db);
|
|
6354
|
-
const dueCards = await getDueCards(db, userId);
|
|
7140
|
+
const dueCards = await getDueCards(db, userId, void 0, opts.domain);
|
|
6355
7141
|
if (opts.json) {
|
|
6356
7142
|
console.log(JSON.stringify(dueCards, null, 2));
|
|
6357
7143
|
return;
|
|
@@ -6688,26 +7474,26 @@ async function setupTurso(urlArg, tokenArg, mode) {
|
|
|
6688
7474
|
|
|
6689
7475
|
// src/cli/commands/git-sync.ts
|
|
6690
7476
|
import { execSync as execSync4 } from "child_process";
|
|
6691
|
-
import { chmodSync as chmodSync2, existsSync as existsSync13, writeFileSync as
|
|
6692
|
-
import { join as
|
|
7477
|
+
import { chmodSync as chmodSync2, existsSync as existsSync13, writeFileSync as writeFileSync6 } from "fs";
|
|
7478
|
+
import { join as join13 } from "path";
|
|
6693
7479
|
import { Command as Command5 } from "commander";
|
|
6694
7480
|
function installHook2() {
|
|
6695
|
-
const gitDir =
|
|
7481
|
+
const gitDir = join13(process.cwd(), ".git");
|
|
6696
7482
|
if (!existsSync13(gitDir)) {
|
|
6697
7483
|
console.error(
|
|
6698
7484
|
"Error: Current directory is not the root of a Git repository."
|
|
6699
7485
|
);
|
|
6700
7486
|
process.exit(1);
|
|
6701
7487
|
}
|
|
6702
|
-
const hooksDir =
|
|
6703
|
-
const hookPath =
|
|
7488
|
+
const hooksDir = join13(gitDir, "hooks");
|
|
7489
|
+
const hookPath = join13(hooksDir, "post-commit");
|
|
6704
7490
|
const hookContent = `#!/bin/sh
|
|
6705
7491
|
# ZAM Spaced Repetition Auto-Stale Hook
|
|
6706
7492
|
# Triggered automatically on git commits to decay modified concept cards.
|
|
6707
7493
|
zam git-sync --commit HEAD --quiet
|
|
6708
7494
|
`;
|
|
6709
7495
|
try {
|
|
6710
|
-
|
|
7496
|
+
writeFileSync6(hookPath, hookContent, { encoding: "utf-8", flag: "w" });
|
|
6711
7497
|
try {
|
|
6712
7498
|
chmodSync2(hookPath, "755");
|
|
6713
7499
|
} catch (_e) {
|
|
@@ -6809,7 +7595,7 @@ ZAM Auto-Stale Complete: Scanned ${changedFiles.length} file(s).`
|
|
|
6809
7595
|
});
|
|
6810
7596
|
|
|
6811
7597
|
// src/cli/commands/goal.ts
|
|
6812
|
-
import { existsSync as existsSync14, mkdirSync as
|
|
7598
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync8 } from "fs";
|
|
6813
7599
|
import { resolve as resolve3 } from "path";
|
|
6814
7600
|
import { input as input2 } from "@inquirer/prompts";
|
|
6815
7601
|
import { Command as Command6 } from "commander";
|
|
@@ -6935,7 +7721,7 @@ ${"\u2500".repeat(50)}`);
|
|
|
6935
7721
|
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) => {
|
|
6936
7722
|
const goalsDir = await resolveGoalsDir();
|
|
6937
7723
|
if (!existsSync14(goalsDir)) {
|
|
6938
|
-
|
|
7724
|
+
mkdirSync8(goalsDir, { recursive: true });
|
|
6939
7725
|
}
|
|
6940
7726
|
let slug = opts.slug;
|
|
6941
7727
|
let title = opts.title;
|
|
@@ -6994,9 +7780,9 @@ goalCommand.command("status <slug> <status>").description("Update a goal's statu
|
|
|
6994
7780
|
});
|
|
6995
7781
|
|
|
6996
7782
|
// src/cli/commands/init.ts
|
|
6997
|
-
import { existsSync as existsSync15, mkdirSync as
|
|
7783
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync9, writeFileSync as writeFileSync7 } from "fs";
|
|
6998
7784
|
import { homedir as homedir9 } from "os";
|
|
6999
|
-
import { join as
|
|
7785
|
+
import { join as join14 } from "path";
|
|
7000
7786
|
import { confirm, input as input3 } from "@inquirer/prompts";
|
|
7001
7787
|
import { Command as Command7 } from "commander";
|
|
7002
7788
|
var HOME2 = homedir9();
|
|
@@ -7004,12 +7790,12 @@ function printLine(char = "\u2550", len = 60, color = "\x1B[36m") {
|
|
|
7004
7790
|
console.log(`${color}${char.repeat(len)}\x1B[0m`);
|
|
7005
7791
|
}
|
|
7006
7792
|
function bootstrapSandboxWorkspace(workspaceDir) {
|
|
7007
|
-
|
|
7008
|
-
|
|
7009
|
-
|
|
7010
|
-
const worldviewFile =
|
|
7793
|
+
mkdirSync9(join14(workspaceDir, "beliefs"), { recursive: true });
|
|
7794
|
+
mkdirSync9(join14(workspaceDir, "goals"), { recursive: true });
|
|
7795
|
+
mkdirSync9(join14(workspaceDir, "skills"), { recursive: true });
|
|
7796
|
+
const worldviewFile = join14(workspaceDir, "beliefs", "worldview.md");
|
|
7011
7797
|
if (!existsSync15(worldviewFile)) {
|
|
7012
|
-
|
|
7798
|
+
writeFileSync7(
|
|
7013
7799
|
worldviewFile,
|
|
7014
7800
|
`# Personal Worldview
|
|
7015
7801
|
|
|
@@ -7021,9 +7807,9 @@ Here, I declare the core concepts and principles I want to master.
|
|
|
7021
7807
|
"utf8"
|
|
7022
7808
|
);
|
|
7023
7809
|
}
|
|
7024
|
-
const goalsFile =
|
|
7810
|
+
const goalsFile = join14(workspaceDir, "goals", "goals.md");
|
|
7025
7811
|
if (!existsSync15(goalsFile)) {
|
|
7026
|
-
|
|
7812
|
+
writeFileSync7(
|
|
7027
7813
|
goalsFile,
|
|
7028
7814
|
`# Personal Goals
|
|
7029
7815
|
|
|
@@ -7045,7 +7831,7 @@ var initCommand = new Command7("init").description("Launch the guided interactiv
|
|
|
7045
7831
|
);
|
|
7046
7832
|
printLine();
|
|
7047
7833
|
console.log("\n\x1B[1m[1/5] Setting up Local Workspace Sandbox\x1B[0m");
|
|
7048
|
-
const defaultWorkspace =
|
|
7834
|
+
const defaultWorkspace = join14(HOME2, "Documents", "zam");
|
|
7049
7835
|
const workspacePath = await input3({
|
|
7050
7836
|
message: "Choose your ZAM workspace directory:",
|
|
7051
7837
|
default: defaultWorkspace
|
|
@@ -7742,10 +8528,10 @@ ${"\u2550".repeat(50)}`);
|
|
|
7742
8528
|
});
|
|
7743
8529
|
|
|
7744
8530
|
// src/cli/commands/monitor.ts
|
|
7745
|
-
import { execFileSync as
|
|
7746
|
-
import { unlinkSync, writeFileSync as
|
|
7747
|
-
import { tmpdir } from "os";
|
|
7748
|
-
import { basename as basename3, join as
|
|
8531
|
+
import { execFileSync as execFileSync3, execSync as execSync5 } from "child_process";
|
|
8532
|
+
import { unlinkSync, writeFileSync as writeFileSync8 } from "fs";
|
|
8533
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
8534
|
+
import { basename as basename3, join as join15 } from "path";
|
|
7749
8535
|
import { Command as Command9 } from "commander";
|
|
7750
8536
|
function isPowerShellShell(shell) {
|
|
7751
8537
|
return shell === "pwsh" || shell === "powershell";
|
|
@@ -7896,11 +8682,23 @@ monitorCommand.command("status").description("Show monitoring status for a sessi
|
|
|
7896
8682
|
console.log(` To: ${result.timeSpan.end}`);
|
|
7897
8683
|
}
|
|
7898
8684
|
});
|
|
8685
|
+
function selectWindowsExecutable(results, pathext = process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD") {
|
|
8686
|
+
if (results.length === 0) return null;
|
|
8687
|
+
const extensions = pathext.split(";").map((ext) => ext.trim().toLowerCase()).filter(Boolean);
|
|
8688
|
+
const runnable = results.find(
|
|
8689
|
+
(result) => extensions.some((ext) => result.toLowerCase().endsWith(ext))
|
|
8690
|
+
);
|
|
8691
|
+
return runnable ?? results[0];
|
|
8692
|
+
}
|
|
7899
8693
|
function findExecutable(command) {
|
|
7900
8694
|
try {
|
|
7901
8695
|
const lookup = process.platform === "win32" ? `where.exe ${command}` : `command -v ${command}`;
|
|
7902
|
-
const
|
|
7903
|
-
|
|
8696
|
+
const results = execSync5(lookup, { encoding: "utf-8" }).split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
8697
|
+
if (results.length === 0) return null;
|
|
8698
|
+
if (process.platform === "win32") {
|
|
8699
|
+
return selectWindowsExecutable(results);
|
|
8700
|
+
}
|
|
8701
|
+
return results[0];
|
|
7904
8702
|
} catch {
|
|
7905
8703
|
return null;
|
|
7906
8704
|
}
|
|
@@ -7910,8 +8708,8 @@ function resolveZamInvocation(shell) {
|
|
|
7910
8708
|
if (installed) {
|
|
7911
8709
|
return isPowerShellShell(shell) ? `& ${psSingleQuoted2(installed)}` : installed;
|
|
7912
8710
|
}
|
|
7913
|
-
const projectRoot =
|
|
7914
|
-
const cliSource =
|
|
8711
|
+
const projectRoot = join15(import.meta.dirname, "..", "..", "..");
|
|
8712
|
+
const cliSource = join15(projectRoot, "src/cli/index.ts");
|
|
7915
8713
|
if (isPowerShellShell(shell)) {
|
|
7916
8714
|
return `& npx --prefix ${psSingleQuoted2(projectRoot)} tsx ${psSingleQuoted2(cliSource)}`;
|
|
7917
8715
|
}
|
|
@@ -8001,9 +8799,9 @@ end tell` : `tell application "Terminal"
|
|
|
8001
8799
|
activate
|
|
8002
8800
|
do script "${escaped}"
|
|
8003
8801
|
end tell`;
|
|
8004
|
-
const tmpFile =
|
|
8802
|
+
const tmpFile = join15(tmpdir2(), `zam-monitor-${sessionId}.scpt`);
|
|
8005
8803
|
try {
|
|
8006
|
-
|
|
8804
|
+
writeFileSync8(tmpFile, appleScript);
|
|
8007
8805
|
execSync5(`osascript ${JSON.stringify(tmpFile)}`, { stdio: "ignore" });
|
|
8008
8806
|
console.log(
|
|
8009
8807
|
`Opened ${useIterm ? "iTerm2" : "Terminal.app"} window with monitoring for session ${sessionId}`
|
|
@@ -8030,9 +8828,10 @@ function openWindowsPowerShell(shellSetup, sessionId, dir, requestedShell) {
|
|
|
8030
8828
|
`-FilePath ${psSingleQuoted2(executable)}`,
|
|
8031
8829
|
`-ArgumentList @('-NoExit','-NoProfile','-Command',${psSingleQuoted2(shellSetup)})`
|
|
8032
8830
|
].join(" ");
|
|
8831
|
+
const launcher = findExecutable("pwsh.exe") ? "pwsh.exe" : "powershell.exe";
|
|
8033
8832
|
try {
|
|
8034
|
-
|
|
8035
|
-
|
|
8833
|
+
execFileSync3(
|
|
8834
|
+
launcher,
|
|
8036
8835
|
["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", startCommand],
|
|
8037
8836
|
{
|
|
8038
8837
|
stdio: "ignore"
|
|
@@ -8051,10 +8850,73 @@ Run this manually in a new PowerShell terminal:
|
|
|
8051
8850
|
}
|
|
8052
8851
|
}
|
|
8053
8852
|
|
|
8853
|
+
// src/cli/commands/observer.ts
|
|
8854
|
+
import { Command as Command10 } from "commander";
|
|
8855
|
+
var observerCommand = new Command10("observer").description(
|
|
8856
|
+
"Configure what the UI observer may capture (Layer 2 policy)"
|
|
8857
|
+
);
|
|
8858
|
+
function applyObserverListChange(current, entry, op) {
|
|
8859
|
+
const normalized = entry.trim().toLowerCase();
|
|
8860
|
+
const list = parseObserverList(current);
|
|
8861
|
+
const next = op === "add" ? [.../* @__PURE__ */ new Set([...list, normalized])] : list.filter((item) => item !== normalized);
|
|
8862
|
+
return next.join(",");
|
|
8863
|
+
}
|
|
8864
|
+
observerCommand.command("status").description("Show the active observer policy").option("--json", "Output as JSON").action(async (opts) => {
|
|
8865
|
+
await withDb(async (db) => {
|
|
8866
|
+
const policy = await resolveObserverPolicy(db);
|
|
8867
|
+
if (opts.json) {
|
|
8868
|
+
console.log(JSON.stringify(policy, null, 2));
|
|
8869
|
+
return;
|
|
8870
|
+
}
|
|
8871
|
+
console.log("Observer policy:\n");
|
|
8872
|
+
console.log(` Scope: ${policy.scope}`);
|
|
8873
|
+
console.log(` Consent: ${policy.consent}`);
|
|
8874
|
+
console.log(` Retention: ${policy.retention}`);
|
|
8875
|
+
console.log(
|
|
8876
|
+
` Allowlist: ${policy.allowlist.length ? policy.allowlist.join(", ") : "(any targeted window)"}`
|
|
8877
|
+
);
|
|
8878
|
+
console.log(
|
|
8879
|
+
` Denylist: ${policy.denylist.length ? policy.denylist.join(", ") : "(none)"}`
|
|
8880
|
+
);
|
|
8881
|
+
console.log(` Redact titles: ${policy.redactWindowTitles}`);
|
|
8882
|
+
console.log(
|
|
8883
|
+
"\nBuilt-in sensitive surfaces (password managers, auth/UAC dialogs, banking) are always refused and cannot be allowlisted."
|
|
8884
|
+
);
|
|
8885
|
+
});
|
|
8886
|
+
});
|
|
8887
|
+
observerCommand.command("grant <process>").description(
|
|
8888
|
+
"Allow the observer to capture a process (adds it to observer.allowlist)"
|
|
8889
|
+
).action(async (processName) => {
|
|
8890
|
+
await withDb(async (db) => {
|
|
8891
|
+
const next = applyObserverListChange(
|
|
8892
|
+
await getSetting(db, "observer.allowlist"),
|
|
8893
|
+
processName,
|
|
8894
|
+
"add"
|
|
8895
|
+
);
|
|
8896
|
+
await setSetting(db, "observer.allowlist", next);
|
|
8897
|
+
await syncObserverSidecarPolicy(db);
|
|
8898
|
+
console.log(`Granted: ${processName.trim().toLowerCase()}`);
|
|
8899
|
+
console.log(`observer.allowlist = ${next || "(empty)"}`);
|
|
8900
|
+
});
|
|
8901
|
+
});
|
|
8902
|
+
observerCommand.command("revoke <process>").description("Remove a process from observer.allowlist").action(async (processName) => {
|
|
8903
|
+
await withDb(async (db) => {
|
|
8904
|
+
const next = applyObserverListChange(
|
|
8905
|
+
await getSetting(db, "observer.allowlist"),
|
|
8906
|
+
processName,
|
|
8907
|
+
"remove"
|
|
8908
|
+
);
|
|
8909
|
+
await setSetting(db, "observer.allowlist", next);
|
|
8910
|
+
await syncObserverSidecarPolicy(db);
|
|
8911
|
+
console.log(`Revoked: ${processName.trim().toLowerCase()}`);
|
|
8912
|
+
console.log(`observer.allowlist = ${next || "(empty)"}`);
|
|
8913
|
+
});
|
|
8914
|
+
});
|
|
8915
|
+
|
|
8054
8916
|
// src/cli/commands/profile.ts
|
|
8055
8917
|
import { homedir as homedir10 } from "os";
|
|
8056
|
-
import { dirname as dirname5, join as
|
|
8057
|
-
import { Command as
|
|
8918
|
+
import { dirname as dirname5, join as join16, resolve as resolve4 } from "path";
|
|
8919
|
+
import { Command as Command11 } from "commander";
|
|
8058
8920
|
var C2 = {
|
|
8059
8921
|
reset: "\x1B[0m",
|
|
8060
8922
|
bold: "\x1B[1m",
|
|
@@ -8063,7 +8925,7 @@ var C2 = {
|
|
|
8063
8925
|
green: "\x1B[32m"
|
|
8064
8926
|
};
|
|
8065
8927
|
function defaultPersonalDir() {
|
|
8066
|
-
return
|
|
8928
|
+
return join16(homedir10(), "Documents", "zam");
|
|
8067
8929
|
}
|
|
8068
8930
|
function render(profile) {
|
|
8069
8931
|
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}`;
|
|
@@ -8074,7 +8936,7 @@ function render(profile) {
|
|
|
8074
8936
|
console.log(` data dir: ${C2.cyan}${profile.dataDir}${C2.reset}`);
|
|
8075
8937
|
console.log(` database: ${C2.cyan}${profile.dbPath}${C2.reset}`);
|
|
8076
8938
|
}
|
|
8077
|
-
var profileCommand = new
|
|
8939
|
+
var profileCommand = new Command11("profile").description("Show or change this machine's ZAM install profile").option("--mode <mode>", "Set install mode: developer | default").option("--dir <path>", "Set the personal-content folder").option("--json", "Output as JSON").action(async (opts) => {
|
|
8078
8940
|
if (opts.mode && opts.mode !== "developer" && opts.mode !== "default") {
|
|
8079
8941
|
console.error(`Invalid --mode: ${opts.mode}. Use developer or default.`);
|
|
8080
8942
|
process.exit(1);
|
|
@@ -8110,8 +8972,8 @@ var profileCommand = new Command10("profile").description("Show or change this m
|
|
|
8110
8972
|
});
|
|
8111
8973
|
|
|
8112
8974
|
// src/cli/commands/review.ts
|
|
8113
|
-
import { Command as
|
|
8114
|
-
var reviewCommand = new
|
|
8975
|
+
import { Command as Command12 } from "commander";
|
|
8976
|
+
var reviewCommand = new Command12("review").description("Start an interactive review session").option("--user <id>", "User ID (default: whoami)").option("--max-new <n>", "Maximum new cards", "10").option("--max-reviews <n>", "Maximum review cards", "50").option("--no-resolve", "Skip resolving source_link into inline context").action(async (opts) => {
|
|
8115
8977
|
let db;
|
|
8116
8978
|
try {
|
|
8117
8979
|
db = await openDatabase();
|
|
@@ -8224,10 +9086,10 @@ ${"\u2550".repeat(50)}`);
|
|
|
8224
9086
|
});
|
|
8225
9087
|
|
|
8226
9088
|
// src/cli/commands/session.ts
|
|
8227
|
-
import { readFileSync as
|
|
9089
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
8228
9090
|
import { input as input6, select as select2 } from "@inquirer/prompts";
|
|
8229
|
-
import { Command as
|
|
8230
|
-
var sessionCommand = new
|
|
9091
|
+
import { Command as Command13 } from "commander";
|
|
9092
|
+
var sessionCommand = new Command13("session").description(
|
|
8231
9093
|
"Manage learning sessions"
|
|
8232
9094
|
);
|
|
8233
9095
|
sessionCommand.command("start").description("Start a new learning session (review \u2192 task)").option("--user <id>", "User ID (default: whoami)").option("--task <description>", "Task description (interactive if omitted)").option(
|
|
@@ -8279,11 +9141,18 @@ sessionCommand.command("start").description("Start a new learning session (revie
|
|
|
8279
9141
|
task,
|
|
8280
9142
|
execution_context: opts.context
|
|
8281
9143
|
});
|
|
9144
|
+
const observerHint = opts.context === "ui" && !await isObserverPolicyConfigured(db) ? OBSERVER_POLICY_UNSET_HINT : null;
|
|
8282
9145
|
await db.close();
|
|
8283
9146
|
if (opts.quiet) {
|
|
8284
9147
|
console.log(session.id);
|
|
8285
9148
|
} else if (opts.json) {
|
|
8286
|
-
console.log(
|
|
9149
|
+
console.log(
|
|
9150
|
+
JSON.stringify(
|
|
9151
|
+
observerHint ? { ...session, observerPolicyHint: observerHint } : session,
|
|
9152
|
+
null,
|
|
9153
|
+
2
|
|
9154
|
+
)
|
|
9155
|
+
);
|
|
8287
9156
|
} else {
|
|
8288
9157
|
console.log(`
|
|
8289
9158
|
Session started: ${session.id}`);
|
|
@@ -8291,6 +9160,10 @@ Session started: ${session.id}`);
|
|
|
8291
9160
|
console.log(` Task: ${session.task}`);
|
|
8292
9161
|
console.log(` Context: ${session.execution_context}`);
|
|
8293
9162
|
console.log(` Started: ${session.started_at}`);
|
|
9163
|
+
if (observerHint) {
|
|
9164
|
+
console.log(`
|
|
9165
|
+
${observerHint}`);
|
|
9166
|
+
}
|
|
8294
9167
|
}
|
|
8295
9168
|
} catch (err) {
|
|
8296
9169
|
await db?.close();
|
|
@@ -8405,7 +9278,7 @@ function loadPatternFile(path) {
|
|
|
8405
9278
|
if (!path) return [];
|
|
8406
9279
|
let parsed;
|
|
8407
9280
|
try {
|
|
8408
|
-
parsed = JSON.parse(
|
|
9281
|
+
parsed = JSON.parse(readFileSync11(path, "utf-8"));
|
|
8409
9282
|
} catch (err) {
|
|
8410
9283
|
throw new Error(
|
|
8411
9284
|
`Cannot read synthesis patterns from ${path}: ${err.message}`
|
|
@@ -8596,8 +9469,8 @@ sessionCommand.command("end").description("End a session and show summary").requ
|
|
|
8596
9469
|
|
|
8597
9470
|
// src/cli/commands/settings.ts
|
|
8598
9471
|
import { existsSync as existsSync16 } from "fs";
|
|
8599
|
-
import { Command as
|
|
8600
|
-
var settingsCommand = new
|
|
9472
|
+
import { Command as Command14 } from "commander";
|
|
9473
|
+
var settingsCommand = new Command14("settings").description(
|
|
8601
9474
|
"Manage user settings"
|
|
8602
9475
|
);
|
|
8603
9476
|
var BOOLEAN_SETTING_KEYS = /* @__PURE__ */ new Set(["llm.enabled", "llm.vision.enabled"]);
|
|
@@ -8651,6 +9524,9 @@ settingsCommand.command("set <key> <value>").description("Set a setting").option
|
|
|
8651
9524
|
await withDb(async (db) => {
|
|
8652
9525
|
const parsedVal = normalizeSettingValue(key, value);
|
|
8653
9526
|
await setSetting(db, key, parsedVal);
|
|
9527
|
+
if (key.startsWith("observer.")) {
|
|
9528
|
+
await syncObserverSidecarPolicy(db);
|
|
9529
|
+
}
|
|
8654
9530
|
if (!opts.quiet) {
|
|
8655
9531
|
console.log(`Set ${key} = ${parsedVal}`);
|
|
8656
9532
|
}
|
|
@@ -8659,6 +9535,9 @@ settingsCommand.command("set <key> <value>").description("Set a setting").option
|
|
|
8659
9535
|
settingsCommand.command("delete <key>").description("Delete a setting").option("--quiet", "Suppress output").action(async (key, opts) => {
|
|
8660
9536
|
await withDb(async (db) => {
|
|
8661
9537
|
const deleted = await deleteSetting(db, key);
|
|
9538
|
+
if (key.startsWith("observer.")) {
|
|
9539
|
+
await syncObserverSidecarPolicy(db);
|
|
9540
|
+
}
|
|
8662
9541
|
if (!opts.quiet) {
|
|
8663
9542
|
if (deleted) {
|
|
8664
9543
|
console.log(`Deleted: ${key}`);
|
|
@@ -8766,32 +9645,32 @@ settingsCommand.command("repos").description("Show or set Personal, Team, and Or
|
|
|
8766
9645
|
});
|
|
8767
9646
|
|
|
8768
9647
|
// src/cli/commands/setup.ts
|
|
8769
|
-
import { copyFileSync as copyFileSync2, existsSync as existsSync17, mkdirSync as
|
|
8770
|
-
import { basename as basename4, dirname as dirname6, join as
|
|
9648
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync17, mkdirSync as mkdirSync10, writeFileSync as writeFileSync9 } from "fs";
|
|
9649
|
+
import { basename as basename4, dirname as dirname6, join as join17 } from "path";
|
|
8771
9650
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
8772
|
-
import { Command as
|
|
9651
|
+
import { Command as Command15 } from "commander";
|
|
8773
9652
|
var packageRoot = [
|
|
8774
9653
|
fileURLToPath2(new URL("../..", import.meta.url)),
|
|
8775
9654
|
fileURLToPath2(new URL("../../..", import.meta.url))
|
|
8776
|
-
].find((candidate) => existsSync17(
|
|
9655
|
+
].find((candidate) => existsSync17(join17(candidate, "package.json"))) ?? fileURLToPath2(new URL("../..", import.meta.url));
|
|
8777
9656
|
var SKILL_PAIRS = [
|
|
8778
9657
|
{
|
|
8779
|
-
from:
|
|
8780
|
-
to:
|
|
9658
|
+
from: join17(packageRoot, ".claude", "skills", "zam", "SKILL.md"),
|
|
9659
|
+
to: join17(".claude", "skills", "zam", "SKILL.md")
|
|
8781
9660
|
},
|
|
8782
9661
|
{
|
|
8783
|
-
from:
|
|
8784
|
-
to:
|
|
9662
|
+
from: join17(packageRoot, ".agent", "skills", "zam", "SKILL.md"),
|
|
9663
|
+
to: join17(".agent", "skills", "zam", "SKILL.md")
|
|
8785
9664
|
},
|
|
8786
9665
|
{
|
|
8787
|
-
from:
|
|
8788
|
-
to:
|
|
9666
|
+
from: join17(packageRoot, ".agents", "skills", "zam", "SKILL.md"),
|
|
9667
|
+
to: join17(".agents", "skills", "zam", "SKILL.md")
|
|
8789
9668
|
}
|
|
8790
9669
|
];
|
|
8791
9670
|
function copySkills(force, cwd = process.cwd()) {
|
|
8792
9671
|
let anyAction = false;
|
|
8793
9672
|
for (const { from, to } of SKILL_PAIRS) {
|
|
8794
|
-
const dest =
|
|
9673
|
+
const dest = join17(cwd, to);
|
|
8795
9674
|
if (!existsSync17(from)) {
|
|
8796
9675
|
console.warn(` warn source not found, skipping: ${from}`);
|
|
8797
9676
|
continue;
|
|
@@ -8800,7 +9679,7 @@ function copySkills(force, cwd = process.cwd()) {
|
|
|
8800
9679
|
console.log(` skip ${to} (already present \xE2\u20AC\u201D use --force to update)`);
|
|
8801
9680
|
continue;
|
|
8802
9681
|
}
|
|
8803
|
-
|
|
9682
|
+
mkdirSync10(dirname6(dest), { recursive: true });
|
|
8804
9683
|
copyFileSync2(from, dest);
|
|
8805
9684
|
console.log(` copy ${to}`);
|
|
8806
9685
|
anyAction = true;
|
|
@@ -8811,13 +9690,25 @@ function copySkills(force, cwd = process.cwd()) {
|
|
|
8811
9690
|
);
|
|
8812
9691
|
}
|
|
8813
9692
|
}
|
|
9693
|
+
function formatDatabaseInitTarget(target) {
|
|
9694
|
+
switch (target.kind) {
|
|
9695
|
+
case "local":
|
|
9696
|
+
return `ZAM database at ${target.location} (local SQLite)`;
|
|
9697
|
+
case "turso-remote":
|
|
9698
|
+
return `ZAM database via Turso remote at ${target.location}`;
|
|
9699
|
+
case "turso-native":
|
|
9700
|
+
return `ZAM database via Turso native driver at ${target.location}`;
|
|
9701
|
+
case "turso-replica":
|
|
9702
|
+
return `ZAM database replica at ${target.location} syncing from ${target.syncUrl}`;
|
|
9703
|
+
}
|
|
9704
|
+
}
|
|
8814
9705
|
async function initDatabase(skipInit) {
|
|
8815
9706
|
if (skipInit) return;
|
|
8816
9707
|
try {
|
|
8817
|
-
const
|
|
9708
|
+
const target = getDatabaseTargetInfo();
|
|
8818
9709
|
const db = await openDatabaseWithSync({ initialize: true });
|
|
8819
9710
|
await db.close();
|
|
8820
|
-
console.log(` init
|
|
9711
|
+
console.log(` init ${formatDatabaseInitTarget(target)}`);
|
|
8821
9712
|
} catch (err) {
|
|
8822
9713
|
const msg = err.message;
|
|
8823
9714
|
if (!msg.includes("already")) {
|
|
@@ -8829,13 +9720,13 @@ async function initDatabase(skipInit) {
|
|
|
8829
9720
|
}
|
|
8830
9721
|
function writeClaudeMd(skipClaudeMd, cwd = process.cwd()) {
|
|
8831
9722
|
if (skipClaudeMd) return;
|
|
8832
|
-
const dest =
|
|
9723
|
+
const dest = join17(cwd, "CLAUDE.md");
|
|
8833
9724
|
if (existsSync17(dest)) {
|
|
8834
9725
|
console.log(` skip CLAUDE.md (already present)`);
|
|
8835
9726
|
return;
|
|
8836
9727
|
}
|
|
8837
9728
|
const name = basename4(cwd);
|
|
8838
|
-
|
|
9729
|
+
writeFileSync9(
|
|
8839
9730
|
dest,
|
|
8840
9731
|
`# ZAM Personal Kernel \xE2\u20AC\u201D ${name}
|
|
8841
9732
|
|
|
@@ -8863,13 +9754,13 @@ Use \`zam connector setup turso\` to store cloud credentials in
|
|
|
8863
9754
|
}
|
|
8864
9755
|
function writeAgentsMd(skipAgentsMd, cwd = process.cwd()) {
|
|
8865
9756
|
if (skipAgentsMd) return;
|
|
8866
|
-
const dest =
|
|
9757
|
+
const dest = join17(cwd, "AGENTS.md");
|
|
8867
9758
|
if (existsSync17(dest)) {
|
|
8868
9759
|
console.log(` skip AGENTS.md (already present)`);
|
|
8869
9760
|
return;
|
|
8870
9761
|
}
|
|
8871
9762
|
const name = basename4(cwd);
|
|
8872
|
-
|
|
9763
|
+
writeFileSync9(
|
|
8873
9764
|
dest,
|
|
8874
9765
|
`# ZAM Personal Kernel - ${name}
|
|
8875
9766
|
|
|
@@ -8902,7 +9793,7 @@ Codex discovers repository skills under \`.agents/skills/\`. Run
|
|
|
8902
9793
|
);
|
|
8903
9794
|
console.log(` write AGENTS.md`);
|
|
8904
9795
|
}
|
|
8905
|
-
var setupCommand = new
|
|
9796
|
+
var setupCommand = new Command15("setup").description(
|
|
8906
9797
|
"Distribute ZAM skill files into this personal instance and initialize the database"
|
|
8907
9798
|
).option(
|
|
8908
9799
|
"--force",
|
|
@@ -8923,8 +9814,8 @@ var setupCommand = new Command14("setup").description(
|
|
|
8923
9814
|
);
|
|
8924
9815
|
|
|
8925
9816
|
// src/cli/commands/skill.ts
|
|
8926
|
-
import { Command as
|
|
8927
|
-
var skillCommand = new
|
|
9817
|
+
import { Command as Command16 } from "commander";
|
|
9818
|
+
var skillCommand = new Command16("skill").description(
|
|
8928
9819
|
"Manage agent skill entries (task recipes)"
|
|
8929
9820
|
);
|
|
8930
9821
|
skillCommand.command("list").description("List all agent skills").option("--json", "Output as JSON").action(async (opts) => {
|
|
@@ -9009,10 +9900,10 @@ skillCommand.command("add").description("Register a new agent skill").requiredOp
|
|
|
9009
9900
|
});
|
|
9010
9901
|
|
|
9011
9902
|
// src/cli/commands/snapshot.ts
|
|
9012
|
-
import { existsSync as existsSync18, mkdirSync as
|
|
9903
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync11, readFileSync as readFileSync12, writeFileSync as writeFileSync10 } from "fs";
|
|
9013
9904
|
import { homedir as homedir11 } from "os";
|
|
9014
|
-
import { dirname as dirname7, join as
|
|
9015
|
-
import { Command as
|
|
9905
|
+
import { dirname as dirname7, join as join18 } from "path";
|
|
9906
|
+
import { Command as Command17 } from "commander";
|
|
9016
9907
|
function defaultOutName() {
|
|
9017
9908
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d+Z$/, "").replace(/:/g, "-");
|
|
9018
9909
|
return `zam-snapshot-${stamp}.sql`;
|
|
@@ -9026,12 +9917,12 @@ function summarize(tables) {
|
|
|
9026
9917
|
}
|
|
9027
9918
|
return { total, nonEmpty };
|
|
9028
9919
|
}
|
|
9029
|
-
var exportCmd = new
|
|
9920
|
+
var exportCmd = new Command17("export").description("Write a portable SQL-text snapshot of the database").option("--out <file>", "Output file (use - for stdout)").action(async (opts) => {
|
|
9030
9921
|
let db;
|
|
9031
9922
|
try {
|
|
9032
9923
|
db = await openDatabaseWithSync({ initialize: true });
|
|
9033
9924
|
const snapshot = await exportSnapshot(db);
|
|
9034
|
-
const personalDir = await getSetting(db, "personal.workspace_dir") ||
|
|
9925
|
+
const personalDir = await getSetting(db, "personal.workspace_dir") || join18(homedir11(), "Documents", "zam");
|
|
9035
9926
|
await db.close();
|
|
9036
9927
|
db = void 0;
|
|
9037
9928
|
const manifest = verifySnapshot(snapshot);
|
|
@@ -9040,12 +9931,12 @@ var exportCmd = new Command16("export").description("Write a portable SQL-text s
|
|
|
9040
9931
|
process.stdout.write(snapshot);
|
|
9041
9932
|
return;
|
|
9042
9933
|
}
|
|
9043
|
-
const out = opts.out ??
|
|
9934
|
+
const out = opts.out ?? join18(personalDir, "snapshots", defaultOutName());
|
|
9044
9935
|
const dir = dirname7(out);
|
|
9045
9936
|
if (dir && dir !== "." && !existsSync18(dir)) {
|
|
9046
|
-
|
|
9937
|
+
mkdirSync11(dir, { recursive: true });
|
|
9047
9938
|
}
|
|
9048
|
-
|
|
9939
|
+
writeFileSync10(out, snapshot, "utf-8");
|
|
9049
9940
|
console.log(`Snapshot written: ${out}`);
|
|
9050
9941
|
console.log(
|
|
9051
9942
|
` ${total} row(s)${nonEmpty.length ? ` \u2014 ${nonEmpty.join(", ")}` : ""}`
|
|
@@ -9056,14 +9947,14 @@ var exportCmd = new Command16("export").description("Write a portable SQL-text s
|
|
|
9056
9947
|
process.exit(1);
|
|
9057
9948
|
}
|
|
9058
9949
|
});
|
|
9059
|
-
var importCmd = new
|
|
9950
|
+
var importCmd = new Command17("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) => {
|
|
9060
9951
|
let db;
|
|
9061
9952
|
try {
|
|
9062
9953
|
if (!existsSync18(file)) {
|
|
9063
9954
|
console.error(`Error: Snapshot file not found: ${file}`);
|
|
9064
9955
|
process.exit(1);
|
|
9065
9956
|
}
|
|
9066
|
-
const snapshot =
|
|
9957
|
+
const snapshot = readFileSync12(file, "utf-8");
|
|
9067
9958
|
db = await openDatabaseWithSync({ initialize: true });
|
|
9068
9959
|
const result = await importSnapshot(db, snapshot, { force: opts.force });
|
|
9069
9960
|
await db.close();
|
|
@@ -9079,13 +9970,13 @@ var importCmd = new Command16("import").description("Restore a snapshot into the
|
|
|
9079
9970
|
process.exit(1);
|
|
9080
9971
|
}
|
|
9081
9972
|
});
|
|
9082
|
-
var verifyCmd = new
|
|
9973
|
+
var verifyCmd = new Command17("verify").description("Check a snapshot's manifest and checksum without importing").argument("<file>", "Snapshot file to verify").action((file) => {
|
|
9083
9974
|
try {
|
|
9084
9975
|
if (!existsSync18(file)) {
|
|
9085
9976
|
console.error(`Error: Snapshot file not found: ${file}`);
|
|
9086
9977
|
process.exit(1);
|
|
9087
9978
|
}
|
|
9088
|
-
const manifest = verifySnapshot(
|
|
9979
|
+
const manifest = verifySnapshot(readFileSync12(file, "utf-8"));
|
|
9089
9980
|
const { total, nonEmpty } = summarize(manifest.tables);
|
|
9090
9981
|
console.log(`Valid snapshot (format v${manifest.version})`);
|
|
9091
9982
|
console.log(` created: ${manifest.createdAt}`);
|
|
@@ -9097,11 +9988,11 @@ var verifyCmd = new Command16("verify").description("Check a snapshot's manifest
|
|
|
9097
9988
|
process.exit(1);
|
|
9098
9989
|
}
|
|
9099
9990
|
});
|
|
9100
|
-
var snapshotCommand = new
|
|
9991
|
+
var snapshotCommand = new Command17("snapshot").description("Export, import, or verify a portable database snapshot").addCommand(exportCmd).addCommand(importCmd).addCommand(verifyCmd);
|
|
9101
9992
|
|
|
9102
9993
|
// src/cli/commands/stats.ts
|
|
9103
|
-
import { Command as
|
|
9104
|
-
var statsCommand = new
|
|
9994
|
+
import { Command as Command18 } from "commander";
|
|
9995
|
+
var statsCommand = new Command18("stats").description("Show learning dashboard for a user").option("--user <id>", "User ID (default: whoami)").option("--json", "Output as JSON").action(async (opts) => {
|
|
9105
9996
|
await withDb(async (db) => {
|
|
9106
9997
|
const userId = await resolveUser(opts, db);
|
|
9107
9998
|
const stats = await getUserStats(db, userId);
|
|
@@ -9137,11 +10028,11 @@ var statsCommand = new Command17("stats").description("Show learning dashboard f
|
|
|
9137
10028
|
});
|
|
9138
10029
|
|
|
9139
10030
|
// src/cli/commands/token.ts
|
|
9140
|
-
import { Command as
|
|
9141
|
-
var tokenCommand = new
|
|
10031
|
+
import { Command as Command19 } from "commander";
|
|
10032
|
+
var tokenCommand = new Command19("token").description(
|
|
9142
10033
|
"Manage knowledge tokens"
|
|
9143
10034
|
);
|
|
9144
|
-
tokenCommand.command("register").description("Register a new knowledge token").requiredOption("--slug <slug>", "Unique token slug").requiredOption("--concept <concept>", "Concept description").option("--domain <domain>", "Knowledge domain", "").option("--bloom <level>", "Bloom taxonomy level (1-5)", "1").option("--source-link <link>", "Source file path or reference URL", "").option("--question <question>", "Specific question prompt for recall", "").option("--json", "Output as JSON").action(async (opts) => {
|
|
10035
|
+
tokenCommand.command("register").description("Register a new knowledge token").requiredOption("--slug <slug>", "Unique token slug").requiredOption("--concept <concept>", "Concept description").option("--domain <domain>", "Knowledge domain", "").option("--bloom <level>", "Bloom taxonomy level (1-5)", "1").option("--source-link <link>", "Source file path or reference URL", "").option("--question <question>", "Specific question prompt for recall", "").option("--json", "Output as JSON").option("--quiet", "Suppress output (exit code only)").action(async (opts) => {
|
|
9145
10036
|
await withDb(async (db) => {
|
|
9146
10037
|
let question = opts.question || null;
|
|
9147
10038
|
if (!question) {
|
|
@@ -9159,6 +10050,7 @@ tokenCommand.command("register").description("Register a new knowledge token").r
|
|
|
9159
10050
|
source_link: opts.sourceLink || null,
|
|
9160
10051
|
question
|
|
9161
10052
|
});
|
|
10053
|
+
if (opts.quiet) return;
|
|
9162
10054
|
if (opts.json) {
|
|
9163
10055
|
console.log(JSON.stringify(token, null, 2));
|
|
9164
10056
|
} else {
|
|
@@ -9173,9 +10065,10 @@ tokenCommand.command("register").description("Register a new knowledge token").r
|
|
|
9173
10065
|
}
|
|
9174
10066
|
});
|
|
9175
10067
|
});
|
|
9176
|
-
tokenCommand.command("find").description("Fuzzy search for tokens").requiredOption("--query <query>", "Search query").option("--json", "Output as JSON").action(async (opts) => {
|
|
10068
|
+
tokenCommand.command("find").description("Fuzzy search for tokens").requiredOption("--query <query>", "Search query").option("--json", "Output as JSON").option("--quiet", "Suppress output (exit code only)").action(async (opts) => {
|
|
9177
10069
|
await withDb(async (db) => {
|
|
9178
10070
|
const results = await findTokens(db, opts.query);
|
|
10071
|
+
if (opts.quiet) return;
|
|
9179
10072
|
if (opts.json) {
|
|
9180
10073
|
console.log(JSON.stringify(results, null, 2));
|
|
9181
10074
|
return;
|
|
@@ -9197,12 +10090,13 @@ tokenCommand.command("find").description("Fuzzy search for tokens").requiredOpti
|
|
|
9197
10090
|
}
|
|
9198
10091
|
});
|
|
9199
10092
|
});
|
|
9200
|
-
tokenCommand.command("list").description("List all tokens").option("--domain <domain>", "Filter by domain").option("--json", "Output as JSON").action(async (opts) => {
|
|
10093
|
+
tokenCommand.command("list").description("List all tokens").option("--domain <domain>", "Filter by domain").option("--json", "Output as JSON").option("--quiet", "Suppress output (exit code only)").action(async (opts) => {
|
|
9201
10094
|
await withDb(async (db) => {
|
|
9202
10095
|
const tokens = await listTokens(
|
|
9203
10096
|
db,
|
|
9204
10097
|
opts.domain ? { domain: opts.domain } : void 0
|
|
9205
10098
|
);
|
|
10099
|
+
if (opts.quiet) return;
|
|
9206
10100
|
if (opts.json) {
|
|
9207
10101
|
console.log(JSON.stringify(tokens, null, 2));
|
|
9208
10102
|
return;
|
|
@@ -9230,7 +10124,7 @@ tokenCommand.command("edit").description("Edit a token's mutable fields").requir
|
|
|
9230
10124
|
).option(
|
|
9231
10125
|
"--source-link <link>",
|
|
9232
10126
|
"Updated source file path or reference URL (blank allowed)"
|
|
9233
|
-
).option("--question <question>", "Updated question text (blank allowed)").option("--json", "Output as JSON").action(async (opts) => {
|
|
10127
|
+
).option("--question <question>", "Updated question text (blank allowed)").option("--json", "Output as JSON").option("--quiet", "Suppress output (exit code only)").action(async (opts) => {
|
|
9234
10128
|
await withDb(async (db) => {
|
|
9235
10129
|
const updates = {};
|
|
9236
10130
|
if (opts.concept !== void 0) updates.concept = opts.concept;
|
|
@@ -9253,6 +10147,7 @@ tokenCommand.command("edit").description("Edit a token's mutable fields").requir
|
|
|
9253
10147
|
updates.symbiosis_mode = opts.mode === "none" ? null : opts.mode;
|
|
9254
10148
|
}
|
|
9255
10149
|
const token = await updateToken(db, opts.slug, updates);
|
|
10150
|
+
if (opts.quiet) return;
|
|
9256
10151
|
if (opts.json) {
|
|
9257
10152
|
jsonOut(token);
|
|
9258
10153
|
return;
|
|
@@ -9267,7 +10162,7 @@ tokenCommand.command("edit").description("Edit a token's mutable fields").requir
|
|
|
9267
10162
|
console.log(` Source: ${token.source_link || "(none)"}`);
|
|
9268
10163
|
});
|
|
9269
10164
|
});
|
|
9270
|
-
tokenCommand.command("prereq").description("Add a prerequisite edge between tokens").requiredOption("--token <slug>", "Token that requires a prerequisite").requiredOption("--requires <slug>", "Required prerequisite token").option("--json", "Output as JSON").action(async (opts) => {
|
|
10165
|
+
tokenCommand.command("prereq").description("Add a prerequisite edge between tokens").requiredOption("--token <slug>", "Token that requires a prerequisite").requiredOption("--requires <slug>", "Required prerequisite token").option("--json", "Output as JSON").option("--quiet", "Suppress output (exit code only)").action(async (opts) => {
|
|
9271
10166
|
await withDb(async (db) => {
|
|
9272
10167
|
const token = await getTokenBySlug(db, opts.token);
|
|
9273
10168
|
if (!token) {
|
|
@@ -9280,6 +10175,7 @@ tokenCommand.command("prereq").description("Add a prerequisite edge between toke
|
|
|
9280
10175
|
process.exit(1);
|
|
9281
10176
|
}
|
|
9282
10177
|
await addPrerequisite(db, token.id, requires.id);
|
|
10178
|
+
if (opts.quiet) return;
|
|
9283
10179
|
if (opts.json) {
|
|
9284
10180
|
console.log(
|
|
9285
10181
|
JSON.stringify(
|
|
@@ -9297,9 +10193,10 @@ tokenCommand.command("prereq").description("Add a prerequisite edge between toke
|
|
|
9297
10193
|
});
|
|
9298
10194
|
tokenCommand.command("deprecate").description(
|
|
9299
10195
|
"Mark a token as deprecated (excluded from reviews, not deleted)"
|
|
9300
|
-
).requiredOption("--slug <slug>", "Token slug to deprecate").option("--json", "Output as JSON").action(async (opts) => {
|
|
10196
|
+
).requiredOption("--slug <slug>", "Token slug to deprecate").option("--json", "Output as JSON").option("--quiet", "Suppress output (exit code only)").action(async (opts) => {
|
|
9301
10197
|
await withDb(async (db) => {
|
|
9302
10198
|
const token = await deprecateToken(db, opts.slug);
|
|
10199
|
+
if (opts.quiet) return;
|
|
9303
10200
|
if (opts.json) {
|
|
9304
10201
|
console.log(JSON.stringify(token, null, 2));
|
|
9305
10202
|
} else {
|
|
@@ -9309,7 +10206,7 @@ tokenCommand.command("deprecate").description(
|
|
|
9309
10206
|
}
|
|
9310
10207
|
});
|
|
9311
10208
|
});
|
|
9312
|
-
tokenCommand.command("delete").description("Hard-delete a token and its dependent learning data").requiredOption("--slug <slug>", "Token slug to delete").option("--force", "Actually delete the token").option("--json", "Output as JSON").action(async (opts) => {
|
|
10209
|
+
tokenCommand.command("delete").description("Hard-delete a token and its dependent learning data").requiredOption("--slug <slug>", "Token slug to delete").option("--force", "Actually delete the token").option("--json", "Output as JSON").option("--quiet", "Suppress output (exit code only)").action(async (opts) => {
|
|
9313
10210
|
await withDb(async (db) => {
|
|
9314
10211
|
const impact = await getTokenDeleteImpact(db, opts.slug);
|
|
9315
10212
|
if (!opts.force) {
|
|
@@ -9319,6 +10216,7 @@ tokenCommand.command("delete").description("Hard-delete a token and its dependen
|
|
|
9319
10216
|
requiresForce: true,
|
|
9320
10217
|
impact
|
|
9321
10218
|
};
|
|
10219
|
+
if (opts.quiet) return;
|
|
9322
10220
|
if (opts.json) {
|
|
9323
10221
|
jsonOut(preview);
|
|
9324
10222
|
return;
|
|
@@ -9339,6 +10237,7 @@ tokenCommand.command("delete").description("Hard-delete a token and its dependen
|
|
|
9339
10237
|
return;
|
|
9340
10238
|
}
|
|
9341
10239
|
const result = await deleteToken(db, opts.slug);
|
|
10240
|
+
if (opts.quiet) return;
|
|
9342
10241
|
if (opts.json) {
|
|
9343
10242
|
jsonOut({
|
|
9344
10243
|
slug: result.token.slug,
|
|
@@ -9354,7 +10253,7 @@ tokenCommand.command("delete").description("Hard-delete a token and its dependen
|
|
|
9354
10253
|
console.log(` Agent skills updated: ${result.impact.agent_skills}`);
|
|
9355
10254
|
});
|
|
9356
10255
|
});
|
|
9357
|
-
tokenCommand.command("status").description("Show full status of a token for a user").requiredOption("--token <slug>", "Token slug").option("--user <id>", "User ID (default: whoami)").option("--json", "Output as JSON").action(async (opts) => {
|
|
10256
|
+
tokenCommand.command("status").description("Show full status of a token for a user").requiredOption("--token <slug>", "Token slug").option("--user <id>", "User ID (default: whoami)").option("--json", "Output as JSON").option("--quiet", "Suppress output (exit code only)").action(async (opts) => {
|
|
9358
10257
|
await withDb(async (db) => {
|
|
9359
10258
|
const userId = await resolveUser(opts, db);
|
|
9360
10259
|
const token = await getTokenBySlug(db, opts.token);
|
|
@@ -9371,6 +10270,7 @@ tokenCommand.command("status").description("Show full status of a token for a us
|
|
|
9371
10270
|
prerequisites: prereqs,
|
|
9372
10271
|
dependents
|
|
9373
10272
|
};
|
|
10273
|
+
if (opts.quiet) return;
|
|
9374
10274
|
if (opts.json) {
|
|
9375
10275
|
console.log(JSON.stringify(status, null, 2));
|
|
9376
10276
|
return;
|
|
@@ -9418,9 +10318,9 @@ tokenCommand.command("status").description("Show full status of a token for a us
|
|
|
9418
10318
|
import { spawn as spawn2, spawnSync } from "child_process";
|
|
9419
10319
|
import { existsSync as existsSync19 } from "fs";
|
|
9420
10320
|
import { homedir as homedir12 } from "os";
|
|
9421
|
-
import { dirname as dirname8, join as
|
|
10321
|
+
import { dirname as dirname8, join as join19 } from "path";
|
|
9422
10322
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
9423
|
-
import { Command as
|
|
10323
|
+
import { Command as Command20 } from "commander";
|
|
9424
10324
|
var C3 = {
|
|
9425
10325
|
reset: "\x1B[0m",
|
|
9426
10326
|
red: "\x1B[31m",
|
|
@@ -9434,8 +10334,8 @@ function findDesktopDir() {
|
|
|
9434
10334
|
for (const start of starts) {
|
|
9435
10335
|
let dir = start;
|
|
9436
10336
|
for (let i = 0; i < 10; i++) {
|
|
9437
|
-
if (existsSync19(
|
|
9438
|
-
return
|
|
10337
|
+
if (existsSync19(join19(dir, "desktop", "src-tauri", "tauri.conf.json"))) {
|
|
10338
|
+
return join19(dir, "desktop");
|
|
9439
10339
|
}
|
|
9440
10340
|
const parent = dirname8(dir);
|
|
9441
10341
|
if (parent === dir) break;
|
|
@@ -9445,18 +10345,18 @@ function findDesktopDir() {
|
|
|
9445
10345
|
return null;
|
|
9446
10346
|
}
|
|
9447
10347
|
function findBuiltApp(desktopDir) {
|
|
9448
|
-
const releaseDir =
|
|
10348
|
+
const releaseDir = join19(desktopDir, "src-tauri", "target", "release");
|
|
9449
10349
|
if (process.platform === "win32") {
|
|
9450
10350
|
for (const name of ["ZAM.exe", "zam.exe", "zam-desktop.exe"]) {
|
|
9451
|
-
const p =
|
|
10351
|
+
const p = join19(releaseDir, name);
|
|
9452
10352
|
if (existsSync19(p)) return p;
|
|
9453
10353
|
}
|
|
9454
10354
|
} else if (process.platform === "darwin") {
|
|
9455
|
-
const app =
|
|
10355
|
+
const app = join19(releaseDir, "bundle", "macos", "ZAM.app");
|
|
9456
10356
|
if (existsSync19(app)) return app;
|
|
9457
10357
|
} else {
|
|
9458
10358
|
for (const name of ["zam", "ZAM", "zam-desktop"]) {
|
|
9459
|
-
const p =
|
|
10359
|
+
const p = join19(releaseDir, name);
|
|
9460
10360
|
if (existsSync19(p)) return p;
|
|
9461
10361
|
}
|
|
9462
10362
|
}
|
|
@@ -9464,10 +10364,10 @@ function findBuiltApp(desktopDir) {
|
|
|
9464
10364
|
}
|
|
9465
10365
|
function findInstalledApp() {
|
|
9466
10366
|
const candidates = process.platform === "win32" ? [
|
|
9467
|
-
process.env.LOCALAPPDATA &&
|
|
9468
|
-
process.env.ProgramFiles &&
|
|
9469
|
-
process.env["ProgramFiles(x86)"] &&
|
|
9470
|
-
] : process.platform === "darwin" ? ["/Applications/ZAM.app",
|
|
10367
|
+
process.env.LOCALAPPDATA && join19(process.env.LOCALAPPDATA, "Programs", "ZAM", "ZAM.exe"),
|
|
10368
|
+
process.env.ProgramFiles && join19(process.env.ProgramFiles, "ZAM", "ZAM.exe"),
|
|
10369
|
+
process.env["ProgramFiles(x86)"] && join19(process.env["ProgramFiles(x86)"], "ZAM", "ZAM.exe")
|
|
10370
|
+
] : process.platform === "darwin" ? ["/Applications/ZAM.app", join19(homedir12(), "Applications", "ZAM.app")] : ["/opt/ZAM/zam", "/usr/bin/zam-desktop"];
|
|
9471
10371
|
return candidates.find((candidate) => candidate && existsSync19(candidate)) || null;
|
|
9472
10372
|
}
|
|
9473
10373
|
function runNpm(args, opts) {
|
|
@@ -9479,7 +10379,7 @@ function runNpm(args, opts) {
|
|
|
9479
10379
|
return res.status ?? 1;
|
|
9480
10380
|
}
|
|
9481
10381
|
function ensureDesktopDeps(desktopDir) {
|
|
9482
|
-
if (existsSync19(
|
|
10382
|
+
if (existsSync19(join19(desktopDir, "node_modules"))) return true;
|
|
9483
10383
|
console.log(
|
|
9484
10384
|
`${C3.cyan}Installing desktop dependencies (one-time)...${C3.reset}`
|
|
9485
10385
|
);
|
|
@@ -9505,7 +10405,7 @@ function requireRust() {
|
|
|
9505
10405
|
function hasMsvcBuildTools() {
|
|
9506
10406
|
if (process.platform !== "win32") return true;
|
|
9507
10407
|
const pf86 = process.env["ProgramFiles(x86)"] || "C:\\Program Files (x86)";
|
|
9508
|
-
const vswhere =
|
|
10408
|
+
const vswhere = join19(
|
|
9509
10409
|
pf86,
|
|
9510
10410
|
"Microsoft Visual Studio",
|
|
9511
10411
|
"Installer",
|
|
@@ -9543,7 +10443,7 @@ function requireMsvcOnWindows() {
|
|
|
9543
10443
|
return false;
|
|
9544
10444
|
}
|
|
9545
10445
|
function warnIfCliMissing(repoRoot) {
|
|
9546
|
-
if (!existsSync19(
|
|
10446
|
+
if (!existsSync19(join19(repoRoot, "dist", "cli", "index.js"))) {
|
|
9547
10447
|
console.warn(
|
|
9548
10448
|
`${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}`
|
|
9549
10449
|
);
|
|
@@ -9600,7 +10500,7 @@ function createShortcuts(appPath, repoRoot) {
|
|
|
9600
10500
|
console.error(`${C3.red}\u2717 Could not create shortcuts.${C3.reset}`);
|
|
9601
10501
|
}
|
|
9602
10502
|
}
|
|
9603
|
-
var uiCommand = new
|
|
10503
|
+
var uiCommand = new Command20("ui").description("Launch the ZAM Desktop GUI (Active-Recall Studio)").option("--dev", "Run in hot-reload development mode (needs Rust)").option(
|
|
9604
10504
|
"--build",
|
|
9605
10505
|
"Build the native installer for your OS (needs Rust, one-time)"
|
|
9606
10506
|
).option("--shortcut", "Create Desktop + Start-menu shortcuts to the GUI").action((opts) => {
|
|
@@ -9627,7 +10527,7 @@ var uiCommand = new Command19("ui").description("Launch the ZAM Desktop GUI (Act
|
|
|
9627
10527
|
);
|
|
9628
10528
|
const code = runNpm(["run", "tauri", "build"], { cwd: desktopDir });
|
|
9629
10529
|
if (code === 0) {
|
|
9630
|
-
const bundle =
|
|
10530
|
+
const bundle = join19(
|
|
9631
10531
|
desktopDir,
|
|
9632
10532
|
"src-tauri",
|
|
9633
10533
|
"target",
|
|
@@ -9703,10 +10603,10 @@ ${C3.dim}After that, 'zam ui' launches it directly.${C3.reset}`
|
|
|
9703
10603
|
});
|
|
9704
10604
|
|
|
9705
10605
|
// src/cli/commands/update.ts
|
|
9706
|
-
import { readFileSync as
|
|
9707
|
-
import { dirname as dirname9, join as
|
|
10606
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
10607
|
+
import { dirname as dirname9, join as join20 } from "path";
|
|
9708
10608
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
9709
|
-
import { Command as
|
|
10609
|
+
import { Command as Command21 } from "commander";
|
|
9710
10610
|
var GITHUB_REPO = "zam-os/zam";
|
|
9711
10611
|
var CHANNELS = [
|
|
9712
10612
|
"developer",
|
|
@@ -9727,7 +10627,7 @@ function currentVersion() {
|
|
|
9727
10627
|
for (const up of ["..", "../..", "../../.."]) {
|
|
9728
10628
|
try {
|
|
9729
10629
|
const pkg2 = JSON.parse(
|
|
9730
|
-
|
|
10630
|
+
readFileSync13(join20(here, up, "package.json"), "utf-8")
|
|
9731
10631
|
);
|
|
9732
10632
|
if (pkg2.version) return pkg2.version;
|
|
9733
10633
|
} catch {
|
|
@@ -9773,7 +10673,7 @@ function render2(decision) {
|
|
|
9773
10673
|
);
|
|
9774
10674
|
}
|
|
9775
10675
|
}
|
|
9776
|
-
var checkCmd = new
|
|
10676
|
+
var checkCmd = new Command21("check").description("Check whether a newer ZAM has been released").option(
|
|
9777
10677
|
"--latest <version>",
|
|
9778
10678
|
"Compare against this version instead of fetching"
|
|
9779
10679
|
).option("--channel <channel>", "Override the detected install channel").option("--json", "Output as JSON").action(
|
|
@@ -9804,11 +10704,11 @@ var checkCmd = new Command20("check").description("Check whether a newer ZAM has
|
|
|
9804
10704
|
}
|
|
9805
10705
|
}
|
|
9806
10706
|
);
|
|
9807
|
-
var updateCommand = new
|
|
10707
|
+
var updateCommand = new Command21("update").description("Check for ZAM updates").addCommand(checkCmd);
|
|
9808
10708
|
|
|
9809
10709
|
// src/cli/commands/whoami.ts
|
|
9810
|
-
import { Command as
|
|
9811
|
-
var whoamiCommand = new
|
|
10710
|
+
import { Command as Command22 } from "commander";
|
|
10711
|
+
var whoamiCommand = new Command22("whoami").description("Show or set the default user identity").option("--set <id>", "Set the default user ID").option("--clear", "Remove the default user ID").option("--json", "Output as JSON").action(async (opts) => {
|
|
9812
10712
|
await withDb(async (db) => {
|
|
9813
10713
|
if (opts.set) {
|
|
9814
10714
|
await setSetting(db, "user.id", opts.set);
|
|
@@ -9845,10 +10745,10 @@ var whoamiCommand = new Command21("whoami").description("Show or set the default
|
|
|
9845
10745
|
|
|
9846
10746
|
// src/cli/commands/workspace.ts
|
|
9847
10747
|
import { execSync as execSync6 } from "child_process";
|
|
9848
|
-
import { existsSync as existsSync20, writeFileSync as
|
|
9849
|
-
import { join as
|
|
10748
|
+
import { existsSync as existsSync20, writeFileSync as writeFileSync11 } from "fs";
|
|
10749
|
+
import { join as join21 } from "path";
|
|
9850
10750
|
import { confirm as confirm3, input as input7 } from "@inquirer/prompts";
|
|
9851
|
-
import { Command as
|
|
10751
|
+
import { Command as Command23 } from "commander";
|
|
9852
10752
|
function runGit(cwd, args) {
|
|
9853
10753
|
try {
|
|
9854
10754
|
return execSync6(`git ${args}`, {
|
|
@@ -9860,7 +10760,7 @@ function runGit(cwd, args) {
|
|
|
9860
10760
|
throw new Error(`Git command failed: ${err.message}`);
|
|
9861
10761
|
}
|
|
9862
10762
|
}
|
|
9863
|
-
var workspaceCommand = new
|
|
10763
|
+
var workspaceCommand = new Command23("workspace").description(
|
|
9864
10764
|
"Manage your ZAM learning workspace"
|
|
9865
10765
|
);
|
|
9866
10766
|
workspaceCommand.command("publish").description("Publish your local workspace sandbox to GitHub").action(async () => {
|
|
@@ -9893,15 +10793,15 @@ Active workspace: \x1B[36m${workspaceDir}\x1B[0m`);
|
|
|
9893
10793
|
);
|
|
9894
10794
|
process.exit(1);
|
|
9895
10795
|
}
|
|
9896
|
-
const gitignorePath =
|
|
10796
|
+
const gitignorePath = join21(workspaceDir, ".gitignore");
|
|
9897
10797
|
if (!existsSync20(gitignorePath)) {
|
|
9898
|
-
|
|
10798
|
+
writeFileSync11(
|
|
9899
10799
|
gitignorePath,
|
|
9900
10800
|
"node_modules/\n.agent/\n.agents/\n.claude/\n.gemini/\n.goose/\n*.log\n",
|
|
9901
10801
|
"utf8"
|
|
9902
10802
|
);
|
|
9903
10803
|
}
|
|
9904
|
-
const hasGitRepo = existsSync20(
|
|
10804
|
+
const hasGitRepo = existsSync20(join21(workspaceDir, ".git"));
|
|
9905
10805
|
if (!hasGitRepo) {
|
|
9906
10806
|
console.log("Initializing local Git repository...");
|
|
9907
10807
|
runGit(workspaceDir, "init -b main");
|
|
@@ -9997,9 +10897,9 @@ Active workspace: \x1B[36m${workspaceDir}\x1B[0m`);
|
|
|
9997
10897
|
// src/cli/index.ts
|
|
9998
10898
|
var __dirname = dirname10(fileURLToPath5(import.meta.url));
|
|
9999
10899
|
var pkg = JSON.parse(
|
|
10000
|
-
|
|
10900
|
+
readFileSync14(join22(__dirname, "..", "..", "package.json"), "utf-8")
|
|
10001
10901
|
);
|
|
10002
|
-
var program = new
|
|
10902
|
+
var program = new Command24();
|
|
10003
10903
|
program.name("zam").description(
|
|
10004
10904
|
"The Symbiotic Learning Kernel: Elevating Human Intelligence through AI Collaboration."
|
|
10005
10905
|
).version(pkg.version);
|
|
@@ -10015,6 +10915,7 @@ program.addCommand(uiCommand);
|
|
|
10015
10915
|
program.addCommand(bridgeCommand);
|
|
10016
10916
|
program.addCommand(skillCommand);
|
|
10017
10917
|
program.addCommand(monitorCommand);
|
|
10918
|
+
program.addCommand(observerCommand);
|
|
10018
10919
|
program.addCommand(settingsCommand);
|
|
10019
10920
|
program.addCommand(whoamiCommand);
|
|
10020
10921
|
program.addCommand(connectorCommand);
|