zam-core 0.3.13 → 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 +1246 -364
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +145 -1
- package/dist/index.js +528 -203
- 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;
|
|
@@ -2086,6 +2126,415 @@ function getMonitorLogStats(sessionId) {
|
|
|
2086
2126
|
return { exists: true, sizeBytes: stat.size, lineCount };
|
|
2087
2127
|
}
|
|
2088
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
|
+
|
|
2089
2538
|
// src/kernel/observation/session-synthesis.ts
|
|
2090
2539
|
import { ulid as ulid7 } from "ulid";
|
|
2091
2540
|
|
|
@@ -2317,183 +2766,43 @@ async function cascadeBlock(db, userId, tokenSlug) {
|
|
|
2317
2766
|
await db.prepare(
|
|
2318
2767
|
"UPDATE cards SET blocked = 0, due_at = ? WHERE token_id = ? AND user_id = ?"
|
|
2319
2768
|
).run(now, prereq.requires_id, userId);
|
|
2320
|
-
}
|
|
2321
|
-
}
|
|
2322
|
-
surfaced.push({
|
|
2323
|
-
slug: prereq.slug,
|
|
2324
|
-
concept: prereq.concept,
|
|
2325
|
-
bloomLevel: prereq.bloom_level
|
|
2326
|
-
});
|
|
2327
|
-
}
|
|
2328
|
-
return {
|
|
2329
|
-
blockedSlug: tokenSlug,
|
|
2330
|
-
prerequisites: surfaced
|
|
2331
|
-
};
|
|
2332
|
-
}
|
|
2333
|
-
async function unblockReady(db, userId) {
|
|
2334
|
-
const blockedCards = await db.prepare(
|
|
2335
|
-
`SELECT c.token_id, t.slug, t.concept
|
|
2336
|
-
FROM cards c
|
|
2337
|
-
JOIN tokens t ON t.id = c.token_id
|
|
2338
|
-
WHERE c.user_id = ? AND c.blocked = 1`
|
|
2339
|
-
).all(userId);
|
|
2340
|
-
const unblocked = [];
|
|
2341
|
-
for (const card of blockedCards) {
|
|
2342
|
-
const totalPrereqs = await db.prepare("SELECT COUNT(*) as n FROM prerequisites WHERE token_id = ?").get(card.token_id);
|
|
2343
|
-
const metPrereqs = await db.prepare(
|
|
2344
|
-
`SELECT COUNT(*) as n FROM cards c
|
|
2345
|
-
JOIN prerequisites p ON p.requires_id = c.token_id
|
|
2346
|
-
WHERE p.token_id = ? AND c.user_id = ? AND c.reps >= 1 AND c.blocked = 0`
|
|
2347
|
-
).get(card.token_id, userId);
|
|
2348
|
-
if (totalPrereqs.n === 0 || metPrereqs.n === totalPrereqs.n) {
|
|
2349
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2350
|
-
await db.prepare(
|
|
2351
|
-
"UPDATE cards SET blocked = 0, due_at = ? WHERE token_id = ? AND user_id = ?"
|
|
2352
|
-
).run(now, card.token_id, userId);
|
|
2353
|
-
unblocked.push({ slug: card.slug, concept: card.concept });
|
|
2354
|
-
}
|
|
2355
|
-
}
|
|
2356
|
-
return { unblocked };
|
|
2357
|
-
}
|
|
2358
|
-
|
|
2359
|
-
// src/kernel/observation/ui-observer-io.ts
|
|
2360
|
-
import { appendFileSync as appendFileSync2, existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync5 } from "fs";
|
|
2361
|
-
import { homedir as homedir4 } from "os";
|
|
2362
|
-
import { join as join5 } from "path";
|
|
2363
|
-
|
|
2364
|
-
// src/kernel/observation/ui-observer.ts
|
|
2365
|
-
var UI_OBSERVATION_PROTOCOL_VERSION = 1;
|
|
2366
|
-
var OBSERVATION_KINDS = /* @__PURE__ */ new Set([
|
|
2367
|
-
"progress",
|
|
2368
|
-
"step-completed",
|
|
2369
|
-
"error",
|
|
2370
|
-
"help-seeking",
|
|
2371
|
-
"uncertain",
|
|
2372
|
-
"privacy-pause",
|
|
2373
|
-
"heartbeat"
|
|
2374
|
-
]);
|
|
2375
|
-
var ACTION_TYPES = /* @__PURE__ */ new Set([
|
|
2376
|
-
"click",
|
|
2377
|
-
"shortcut",
|
|
2378
|
-
"typing",
|
|
2379
|
-
"scroll",
|
|
2380
|
-
"window-change"
|
|
2381
|
-
]);
|
|
2382
|
-
var EVIDENCE_TYPES = /* @__PURE__ */ new Set([
|
|
2383
|
-
"uia",
|
|
2384
|
-
"keyframe",
|
|
2385
|
-
"clip",
|
|
2386
|
-
"window"
|
|
2387
|
-
]);
|
|
2388
|
-
function isUiObservationReport(value) {
|
|
2389
|
-
if (!isRecord(value)) return false;
|
|
2390
|
-
if (value.version !== UI_OBSERVATION_PROTOCOL_VERSION) return false;
|
|
2391
|
-
if (!isNonEmptyString(value.sessionId)) return false;
|
|
2392
|
-
if (!isNonNegativeInteger(value.sequence)) return false;
|
|
2393
|
-
if (!isNonEmptyString(value.observedFrom)) return false;
|
|
2394
|
-
if (!isNonEmptyString(value.observedTo)) return false;
|
|
2395
|
-
if (typeof value.kind !== "string" || !OBSERVATION_KINDS.has(value.kind)) {
|
|
2396
|
-
return false;
|
|
2397
|
-
}
|
|
2398
|
-
if (!isApplication(value.application)) return false;
|
|
2399
|
-
if (!isNonEmptyString(value.summary)) return false;
|
|
2400
|
-
if (!Array.isArray(value.actions) || !value.actions.every(isObservedAction)) {
|
|
2401
|
-
return false;
|
|
2402
|
-
}
|
|
2403
|
-
if (!Array.isArray(value.evidence) || !value.evidence.every(isEvidenceRef)) {
|
|
2404
|
-
return false;
|
|
2405
|
-
}
|
|
2406
|
-
if (!Array.isArray(value.candidateTokens) || !value.candidateTokens.every(isCandidateToken)) {
|
|
2407
|
-
return false;
|
|
2408
|
-
}
|
|
2409
|
-
return isConfidence(value.confidence);
|
|
2410
|
-
}
|
|
2411
|
-
function parseUiObservationLog(jsonl) {
|
|
2412
|
-
const reports = [];
|
|
2413
|
-
for (const line of jsonl.split("\n")) {
|
|
2414
|
-
const trimmed = line.trim();
|
|
2415
|
-
if (!trimmed) continue;
|
|
2416
|
-
try {
|
|
2417
|
-
const value = JSON.parse(trimmed);
|
|
2418
|
-
if (isUiObservationReport(value)) {
|
|
2419
|
-
reports.push(value);
|
|
2420
|
-
}
|
|
2421
|
-
} catch {
|
|
2422
|
-
}
|
|
2423
|
-
}
|
|
2424
|
-
return reports.sort((left, right) => left.sequence - right.sequence);
|
|
2425
|
-
}
|
|
2426
|
-
function isApplication(value) {
|
|
2427
|
-
if (!isRecord(value) || !isNonEmptyString(value.processName)) return false;
|
|
2428
|
-
if (value.processId !== void 0 && !isNonNegativeInteger(value.processId)) {
|
|
2429
|
-
return false;
|
|
2430
|
-
}
|
|
2431
|
-
return value.windowTitle === void 0 || typeof value.windowTitle === "string";
|
|
2432
|
-
}
|
|
2433
|
-
function isObservedAction(value) {
|
|
2434
|
-
if (!isRecord(value) || typeof value.type !== "string") return false;
|
|
2435
|
-
if (!ACTION_TYPES.has(value.type)) return false;
|
|
2436
|
-
if (value.target !== void 0 && typeof value.target !== "string") {
|
|
2437
|
-
return false;
|
|
2438
|
-
}
|
|
2439
|
-
return value.result === void 0 || typeof value.result === "string";
|
|
2440
|
-
}
|
|
2441
|
-
function isEvidenceRef(value) {
|
|
2442
|
-
if (!isRecord(value) || typeof value.type !== "string") return false;
|
|
2443
|
-
return EVIDENCE_TYPES.has(value.type) && isNonEmptyString(value.ref) && typeof value.redacted === "boolean";
|
|
2444
|
-
}
|
|
2445
|
-
function isCandidateToken(value) {
|
|
2446
|
-
return isRecord(value) && isNonEmptyString(value.slug) && isConfidence(value.confidence) && isNonEmptyString(value.rationale);
|
|
2447
|
-
}
|
|
2448
|
-
function isRecord(value) {
|
|
2449
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2450
|
-
}
|
|
2451
|
-
function isNonEmptyString(value) {
|
|
2452
|
-
return typeof value === "string" && value.trim().length > 0;
|
|
2453
|
-
}
|
|
2454
|
-
function isNonNegativeInteger(value) {
|
|
2455
|
-
return Number.isInteger(value) && Number(value) >= 0;
|
|
2456
|
-
}
|
|
2457
|
-
function isConfidence(value) {
|
|
2458
|
-
return typeof value === "number" && value >= 0 && value <= 1;
|
|
2459
|
-
}
|
|
2460
|
-
|
|
2461
|
-
// src/kernel/observation/ui-observer-io.ts
|
|
2462
|
-
var DEFAULT_UI_OBSERVER_DIR = join5(homedir4(), ".zam", "observer");
|
|
2463
|
-
function getUiObserverDir() {
|
|
2464
|
-
return process.env.ZAM_OBSERVER_DIR || DEFAULT_UI_OBSERVER_DIR;
|
|
2465
|
-
}
|
|
2466
|
-
function getUiObservationPath(sessionId) {
|
|
2467
|
-
if (!/^[A-Za-z0-9_-]+$/.test(sessionId)) {
|
|
2468
|
-
throw new Error(`Invalid observer session ID: ${sessionId}`);
|
|
2469
|
-
}
|
|
2470
|
-
return join5(getUiObserverDir(), `${sessionId}.reports.jsonl`);
|
|
2471
|
-
}
|
|
2472
|
-
function ensureUiObserverDir() {
|
|
2473
|
-
const dir = getUiObserverDir();
|
|
2474
|
-
if (!existsSync5(dir)) {
|
|
2475
|
-
mkdirSync4(dir, { recursive: true, mode: 448 });
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2771
|
+
surfaced.push({
|
|
2772
|
+
slug: prereq.slug,
|
|
2773
|
+
concept: prereq.concept,
|
|
2774
|
+
bloomLevel: prereq.bloom_level
|
|
2775
|
+
});
|
|
2476
2776
|
}
|
|
2777
|
+
return {
|
|
2778
|
+
blockedSlug: tokenSlug,
|
|
2779
|
+
prerequisites: surfaced
|
|
2780
|
+
};
|
|
2477
2781
|
}
|
|
2478
|
-
function
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
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
|
+
}
|
|
2489
2804
|
}
|
|
2490
|
-
|
|
2491
|
-
appendFileSync2(
|
|
2492
|
-
getUiObservationPath(report.sessionId),
|
|
2493
|
-
`${JSON.stringify(report)}
|
|
2494
|
-
`,
|
|
2495
|
-
{ encoding: "utf8", mode: 384 }
|
|
2496
|
-
);
|
|
2805
|
+
return { unblocked };
|
|
2497
2806
|
}
|
|
2498
2807
|
|
|
2499
2808
|
// src/kernel/observation/ui-observer-synthesis.ts
|
|
@@ -3300,7 +3609,7 @@ function generatePrompt(input8) {
|
|
|
3300
3609
|
|
|
3301
3610
|
// src/kernel/recall/reference-resolver.ts
|
|
3302
3611
|
import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
|
|
3303
|
-
import { dirname as dirname3, join as
|
|
3612
|
+
import { dirname as dirname3, join as join7, resolve } from "path";
|
|
3304
3613
|
var DEFAULT_REVIEW_CONTEXT_MAX_CHARS = 6e3;
|
|
3305
3614
|
function htmlToText(html) {
|
|
3306
3615
|
let content = html;
|
|
@@ -3361,8 +3670,8 @@ async function resolveReference(sourceLink) {
|
|
|
3361
3670
|
const filePath = anchorIndex2 !== -1 ? fullPathWithAnchor.slice(0, anchorIndex2) : fullPathWithAnchor;
|
|
3362
3671
|
const anchor2 = anchorIndex2 !== -1 ? fullPathWithAnchor.slice(anchorIndex2) : "";
|
|
3363
3672
|
const parentDir = dirname3(process.cwd());
|
|
3364
|
-
const localRepoPath =
|
|
3365
|
-
const localFilePath =
|
|
3673
|
+
const localRepoPath = join7(parentDir, repo);
|
|
3674
|
+
const localFilePath = join7(localRepoPath, filePath);
|
|
3366
3675
|
if (existsSync6(localFilePath)) {
|
|
3367
3676
|
try {
|
|
3368
3677
|
let fileContent = readFileSync6(localFilePath, "utf-8");
|
|
@@ -3676,12 +3985,12 @@ import {
|
|
|
3676
3985
|
appendFileSync as appendFileSync3,
|
|
3677
3986
|
copyFileSync,
|
|
3678
3987
|
existsSync as existsSync7,
|
|
3679
|
-
mkdirSync as
|
|
3988
|
+
mkdirSync as mkdirSync6,
|
|
3680
3989
|
readFileSync as readFileSync7,
|
|
3681
|
-
writeFileSync as
|
|
3990
|
+
writeFileSync as writeFileSync4
|
|
3682
3991
|
} from "fs";
|
|
3683
3992
|
import { homedir as homedir5 } from "os";
|
|
3684
|
-
import { join as
|
|
3993
|
+
import { join as join8 } from "path";
|
|
3685
3994
|
import { fileURLToPath } from "url";
|
|
3686
3995
|
var HOME = homedir5();
|
|
3687
3996
|
function getPackageSkillPath(agent = "default") {
|
|
@@ -3689,15 +3998,15 @@ function getPackageSkillPath(agent = "default") {
|
|
|
3689
3998
|
fileURLToPath(new URL("../../..", import.meta.url)),
|
|
3690
3999
|
fileURLToPath(new URL("../..", import.meta.url)),
|
|
3691
4000
|
fileURLToPath(new URL("..", import.meta.url))
|
|
3692
|
-
].find((candidate) => existsSync7(
|
|
4001
|
+
].find((candidate) => existsSync7(join8(candidate, "package.json"))) ?? "";
|
|
3693
4002
|
if (!packageRoot2) return "";
|
|
3694
4003
|
if (agent === "codex") {
|
|
3695
|
-
const codexPath =
|
|
4004
|
+
const codexPath = join8(packageRoot2, ".agents", "skills", "zam", "SKILL.md");
|
|
3696
4005
|
if (existsSync7(codexPath)) return codexPath;
|
|
3697
4006
|
return "";
|
|
3698
4007
|
}
|
|
3699
4008
|
if (agent === "claude") {
|
|
3700
|
-
const claudePath =
|
|
4009
|
+
const claudePath = join8(
|
|
3701
4010
|
packageRoot2,
|
|
3702
4011
|
".claude",
|
|
3703
4012
|
"skills",
|
|
@@ -3707,9 +4016,9 @@ function getPackageSkillPath(agent = "default") {
|
|
|
3707
4016
|
if (existsSync7(claudePath)) return claudePath;
|
|
3708
4017
|
return "";
|
|
3709
4018
|
}
|
|
3710
|
-
let path =
|
|
4019
|
+
let path = join8(packageRoot2, ".agent", "skills", "zam", "SKILL.md");
|
|
3711
4020
|
if (existsSync7(path)) return path;
|
|
3712
|
-
path =
|
|
4021
|
+
path = join8(packageRoot2, ".claude", "skills", "zam", "SKILL.md");
|
|
3713
4022
|
if (existsSync7(path)) return path;
|
|
3714
4023
|
return "";
|
|
3715
4024
|
}
|
|
@@ -3722,16 +4031,16 @@ function distributeGlobalSkills(home = HOME) {
|
|
|
3722
4031
|
console.warn("Could not find ZAM source SKILL.md in the package folder.");
|
|
3723
4032
|
return results;
|
|
3724
4033
|
}
|
|
3725
|
-
const claudeSkillsDir =
|
|
4034
|
+
const claudeSkillsDir = join8(home, ".claude", "skills", "zam");
|
|
3726
4035
|
try {
|
|
3727
4036
|
if (!claudeSourceSkill) {
|
|
3728
4037
|
throw new Error("Claude skill source not found");
|
|
3729
4038
|
}
|
|
3730
|
-
|
|
3731
|
-
copyFileSync(claudeSourceSkill,
|
|
4039
|
+
mkdirSync6(claudeSkillsDir, { recursive: true });
|
|
4040
|
+
copyFileSync(claudeSourceSkill, join8(claudeSkillsDir, "SKILL.md"));
|
|
3732
4041
|
results.push({
|
|
3733
4042
|
name: "Claude Code Global",
|
|
3734
|
-
path:
|
|
4043
|
+
path: join8(claudeSkillsDir, "SKILL.md"),
|
|
3735
4044
|
success: true
|
|
3736
4045
|
});
|
|
3737
4046
|
} catch (_err) {
|
|
@@ -3741,13 +4050,13 @@ function distributeGlobalSkills(home = HOME) {
|
|
|
3741
4050
|
success: false
|
|
3742
4051
|
});
|
|
3743
4052
|
}
|
|
3744
|
-
const geminiSkillsDir =
|
|
4053
|
+
const geminiSkillsDir = join8(home, ".gemini", "skills", "zam");
|
|
3745
4054
|
try {
|
|
3746
|
-
|
|
3747
|
-
copyFileSync(sourceSkill,
|
|
4055
|
+
mkdirSync6(geminiSkillsDir, { recursive: true });
|
|
4056
|
+
copyFileSync(sourceSkill, join8(geminiSkillsDir, "SKILL.md"));
|
|
3748
4057
|
results.push({
|
|
3749
4058
|
name: "Gemini CLI Global",
|
|
3750
|
-
path:
|
|
4059
|
+
path: join8(geminiSkillsDir, "SKILL.md"),
|
|
3751
4060
|
success: true
|
|
3752
4061
|
});
|
|
3753
4062
|
} catch (_err) {
|
|
@@ -3757,16 +4066,16 @@ function distributeGlobalSkills(home = HOME) {
|
|
|
3757
4066
|
success: false
|
|
3758
4067
|
});
|
|
3759
4068
|
}
|
|
3760
|
-
const codexSkillsDir =
|
|
4069
|
+
const codexSkillsDir = join8(home, ".agents", "skills", "zam");
|
|
3761
4070
|
try {
|
|
3762
4071
|
if (!codexSourceSkill) {
|
|
3763
4072
|
throw new Error("Codex skill source not found");
|
|
3764
4073
|
}
|
|
3765
|
-
|
|
3766
|
-
copyFileSync(codexSourceSkill,
|
|
4074
|
+
mkdirSync6(codexSkillsDir, { recursive: true });
|
|
4075
|
+
copyFileSync(codexSourceSkill, join8(codexSkillsDir, "SKILL.md"));
|
|
3767
4076
|
results.push({
|
|
3768
4077
|
name: "Codex Global",
|
|
3769
|
-
path:
|
|
4078
|
+
path: join8(codexSkillsDir, "SKILL.md"),
|
|
3770
4079
|
success: true
|
|
3771
4080
|
});
|
|
3772
4081
|
} catch (_err) {
|
|
@@ -3776,13 +4085,13 @@ function distributeGlobalSkills(home = HOME) {
|
|
|
3776
4085
|
success: false
|
|
3777
4086
|
});
|
|
3778
4087
|
}
|
|
3779
|
-
const gooseSkillsDir =
|
|
4088
|
+
const gooseSkillsDir = join8(home, ".goose", "skills", "zam");
|
|
3780
4089
|
try {
|
|
3781
|
-
|
|
3782
|
-
copyFileSync(sourceSkill,
|
|
4090
|
+
mkdirSync6(gooseSkillsDir, { recursive: true });
|
|
4091
|
+
copyFileSync(sourceSkill, join8(gooseSkillsDir, "SKILL.md"));
|
|
3783
4092
|
results.push({
|
|
3784
4093
|
name: "Goose Global",
|
|
3785
|
-
path:
|
|
4094
|
+
path: join8(gooseSkillsDir, "SKILL.md"),
|
|
3786
4095
|
success: true
|
|
3787
4096
|
});
|
|
3788
4097
|
} catch (_err) {
|
|
@@ -3831,7 +4140,7 @@ function installHook(file, hook, oldHook) {
|
|
|
3831
4140
|
return { success: true, alreadyHooked: true };
|
|
3832
4141
|
}
|
|
3833
4142
|
if (content.includes(oldHook.trim())) {
|
|
3834
|
-
|
|
4143
|
+
writeFileSync4(file, content.replace(oldHook.trim(), hook.trim()), "utf8");
|
|
3835
4144
|
} else {
|
|
3836
4145
|
appendFileSync3(file, hook);
|
|
3837
4146
|
}
|
|
@@ -3842,24 +4151,24 @@ function installHook(file, hook, oldHook) {
|
|
|
3842
4151
|
}
|
|
3843
4152
|
function injectShellHooks(home = HOME) {
|
|
3844
4153
|
const results = [];
|
|
3845
|
-
const zshrc =
|
|
4154
|
+
const zshrc = join8(home, ".zshrc");
|
|
3846
4155
|
if (existsSync7(zshrc)) {
|
|
3847
4156
|
const status = installHook(zshrc, posixHook("zsh"), POSIX_OLD_HOOK);
|
|
3848
4157
|
results.push({ shell: "zsh", file: zshrc, ...status });
|
|
3849
4158
|
}
|
|
3850
|
-
const bashrc =
|
|
4159
|
+
const bashrc = join8(home, ".bashrc");
|
|
3851
4160
|
if (existsSync7(bashrc)) {
|
|
3852
4161
|
const status = installHook(bashrc, posixHook("bash"), POSIX_OLD_HOOK);
|
|
3853
4162
|
results.push({ shell: "bash", file: bashrc, ...status });
|
|
3854
4163
|
}
|
|
3855
4164
|
const pwshDirs = [
|
|
3856
|
-
|
|
3857
|
-
|
|
4165
|
+
join8(home, "Documents", "PowerShell"),
|
|
4166
|
+
join8(home, "Documents", "WindowsPowerShell")
|
|
3858
4167
|
];
|
|
3859
4168
|
for (const dir of pwshDirs) {
|
|
3860
|
-
const profileFile =
|
|
4169
|
+
const profileFile = join8(dir, "Microsoft.PowerShell_profile.ps1");
|
|
3861
4170
|
try {
|
|
3862
|
-
|
|
4171
|
+
mkdirSync6(dir, { recursive: true });
|
|
3863
4172
|
const status = installHook(
|
|
3864
4173
|
profileFile,
|
|
3865
4174
|
POWERSHELL_HOOK,
|
|
@@ -4091,10 +4400,10 @@ function t(locale, key, params = {}) {
|
|
|
4091
4400
|
}
|
|
4092
4401
|
|
|
4093
4402
|
// src/kernel/system/install-config.ts
|
|
4094
|
-
import { existsSync as existsSync8, mkdirSync as
|
|
4403
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync7, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
|
|
4095
4404
|
import { homedir as homedir6 } from "os";
|
|
4096
|
-
import { dirname as dirname4, join as
|
|
4097
|
-
var DEFAULT_CONFIG_PATH =
|
|
4405
|
+
import { dirname as dirname4, join as join9 } from "path";
|
|
4406
|
+
var DEFAULT_CONFIG_PATH = join9(homedir6(), ".zam", "config.json");
|
|
4098
4407
|
function loadInstallConfig(path = DEFAULT_CONFIG_PATH) {
|
|
4099
4408
|
if (!existsSync8(path)) return {};
|
|
4100
4409
|
try {
|
|
@@ -4105,8 +4414,8 @@ function loadInstallConfig(path = DEFAULT_CONFIG_PATH) {
|
|
|
4105
4414
|
}
|
|
4106
4415
|
function saveInstallConfig(config, path = DEFAULT_CONFIG_PATH) {
|
|
4107
4416
|
const dir = dirname4(path);
|
|
4108
|
-
if (!existsSync8(dir))
|
|
4109
|
-
|
|
4417
|
+
if (!existsSync8(dir)) mkdirSync7(dir, { recursive: true });
|
|
4418
|
+
writeFileSync5(path, `${JSON.stringify(config, null, 2)}
|
|
4110
4419
|
`, "utf-8");
|
|
4111
4420
|
}
|
|
4112
4421
|
function getInstallMode(path = DEFAULT_CONFIG_PATH) {
|
|
@@ -4139,7 +4448,7 @@ function detectSyncProvider(dir) {
|
|
|
4139
4448
|
import { execFileSync, execSync } from "child_process";
|
|
4140
4449
|
import { existsSync as existsSync9 } from "fs";
|
|
4141
4450
|
import { homedir as homedir7 } from "os";
|
|
4142
|
-
import { join as
|
|
4451
|
+
import { join as join10 } from "path";
|
|
4143
4452
|
function hasCommand(cmd) {
|
|
4144
4453
|
try {
|
|
4145
4454
|
const checkCmd2 = process.platform === "win32" ? `where ${cmd}` : `which ${cmd}`;
|
|
@@ -4184,7 +4493,7 @@ function installOllama() {
|
|
|
4184
4493
|
const isMac = process.platform === "darwin";
|
|
4185
4494
|
const isWin = process.platform === "win32";
|
|
4186
4495
|
const hasOllama = hasCommand("ollama") || isMac && existsSync9("/Applications/Ollama.app") || isWin && existsSync9(
|
|
4187
|
-
|
|
4496
|
+
join10(homedir7(), "AppData", "Local", "Programs", "Ollama", "ollama.exe")
|
|
4188
4497
|
);
|
|
4189
4498
|
if (hasOllama) {
|
|
4190
4499
|
return { success: true, message: "Ollama is already installed." };
|
|
@@ -4244,7 +4553,7 @@ function installOllama() {
|
|
|
4244
4553
|
function resolveOllamaCommand() {
|
|
4245
4554
|
if (hasCommand("ollama")) return "ollama";
|
|
4246
4555
|
const candidates = process.platform === "win32" ? [
|
|
4247
|
-
|
|
4556
|
+
join10(
|
|
4248
4557
|
homedir7(),
|
|
4249
4558
|
"AppData",
|
|
4250
4559
|
"Local",
|
|
@@ -4550,7 +4859,7 @@ var C = {
|
|
|
4550
4859
|
};
|
|
4551
4860
|
var SUPPORTED_AGENTS = ["opencode"];
|
|
4552
4861
|
function agentsMdPresent(cwd = process.cwd()) {
|
|
4553
|
-
return existsSync11(
|
|
4862
|
+
return existsSync11(join11(cwd, "AGENTS.md"));
|
|
4554
4863
|
}
|
|
4555
4864
|
function printStatus() {
|
|
4556
4865
|
const installed = hasCommand("opencode");
|
|
@@ -4591,9 +4900,11 @@ var statusCmd = new Command("status").description("Show whether the agent is ins
|
|
|
4591
4900
|
var agentCommand = new Command("agent").description("Provision and inspect the agent that drives ZAM sessions").addCommand(installCmd).addCommand(statusCmd).action(printStatus);
|
|
4592
4901
|
|
|
4593
4902
|
// src/cli/commands/bridge.ts
|
|
4594
|
-
import {
|
|
4595
|
-
import {
|
|
4596
|
-
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";
|
|
4597
4908
|
import { Command as Command2 } from "commander";
|
|
4598
4909
|
|
|
4599
4910
|
// src/cli/llm/client.ts
|
|
@@ -5730,13 +6041,15 @@ bridgeCommand.command("start-session").description("Start a ZAM learning session
|
|
|
5730
6041
|
task: opts.task,
|
|
5731
6042
|
execution_context: context
|
|
5732
6043
|
});
|
|
6044
|
+
const observerPolicyHint = context === "ui" && !await isObserverPolicyConfigured(db) ? OBSERVER_POLICY_UNSET_HINT : void 0;
|
|
5733
6045
|
jsonOut2({
|
|
5734
6046
|
id: session.id,
|
|
5735
6047
|
userId: session.user_id,
|
|
5736
6048
|
task: session.task,
|
|
5737
6049
|
executionContext: session.execution_context,
|
|
5738
6050
|
startedAt: session.started_at,
|
|
5739
|
-
completedAt: session.completed_at
|
|
6051
|
+
completedAt: session.completed_at,
|
|
6052
|
+
...observerPolicyHint ? { observerPolicyHint } : {}
|
|
5740
6053
|
});
|
|
5741
6054
|
});
|
|
5742
6055
|
});
|
|
@@ -5859,7 +6172,8 @@ bridgeCommand.command("add-token").description("Create a token + card from JSON
|
|
|
5859
6172
|
bloom_level: data?.bloom_level ?? 1,
|
|
5860
6173
|
context: data?.context,
|
|
5861
6174
|
symbiosis_mode: data?.symbiosis_mode,
|
|
5862
|
-
source_link: data?.source_link ?? null
|
|
6175
|
+
source_link: data?.source_link ?? null,
|
|
6176
|
+
question: data?.question ?? null
|
|
5863
6177
|
});
|
|
5864
6178
|
const card = await ensureCard(db, token.id, userId);
|
|
5865
6179
|
jsonOut2({
|
|
@@ -5894,7 +6208,7 @@ bridgeCommand.command("discover-skills").description(
|
|
|
5894
6208
|
"20"
|
|
5895
6209
|
).action(async (opts) => {
|
|
5896
6210
|
try {
|
|
5897
|
-
const monitorDir =
|
|
6211
|
+
const monitorDir = join12(homedir8(), ".zam", "monitor");
|
|
5898
6212
|
let files;
|
|
5899
6213
|
try {
|
|
5900
6214
|
files = readdirSync2(monitorDir).filter((f) => f.endsWith(".jsonl"));
|
|
@@ -5907,7 +6221,7 @@ bridgeCommand.command("discover-skills").description(
|
|
|
5907
6221
|
return;
|
|
5908
6222
|
}
|
|
5909
6223
|
const limit = Number(opts.limit);
|
|
5910
|
-
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);
|
|
5911
6225
|
const sessionCommands = /* @__PURE__ */ new Map();
|
|
5912
6226
|
for (const file of sorted) {
|
|
5913
6227
|
const sessionId = file.name.replace(".jsonl", "");
|
|
@@ -6017,6 +6331,468 @@ bridgeCommand.command("observe-ui-snapshot").description(
|
|
|
6017
6331
|
jsonOut2(report);
|
|
6018
6332
|
});
|
|
6019
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
|
+
});
|
|
6020
6796
|
bridgeCommand.command("check-llm").description("Check if LLM is enabled and online (JSON)").action(async () => {
|
|
6021
6797
|
await withDb2(async (db) => {
|
|
6022
6798
|
const { enabled, url, model, apiKey } = await getLlmConfig(db);
|
|
@@ -6698,26 +7474,26 @@ async function setupTurso(urlArg, tokenArg, mode) {
|
|
|
6698
7474
|
|
|
6699
7475
|
// src/cli/commands/git-sync.ts
|
|
6700
7476
|
import { execSync as execSync4 } from "child_process";
|
|
6701
|
-
import { chmodSync as chmodSync2, existsSync as existsSync13, writeFileSync as
|
|
6702
|
-
import { join as
|
|
7477
|
+
import { chmodSync as chmodSync2, existsSync as existsSync13, writeFileSync as writeFileSync6 } from "fs";
|
|
7478
|
+
import { join as join13 } from "path";
|
|
6703
7479
|
import { Command as Command5 } from "commander";
|
|
6704
7480
|
function installHook2() {
|
|
6705
|
-
const gitDir =
|
|
7481
|
+
const gitDir = join13(process.cwd(), ".git");
|
|
6706
7482
|
if (!existsSync13(gitDir)) {
|
|
6707
7483
|
console.error(
|
|
6708
7484
|
"Error: Current directory is not the root of a Git repository."
|
|
6709
7485
|
);
|
|
6710
7486
|
process.exit(1);
|
|
6711
7487
|
}
|
|
6712
|
-
const hooksDir =
|
|
6713
|
-
const hookPath =
|
|
7488
|
+
const hooksDir = join13(gitDir, "hooks");
|
|
7489
|
+
const hookPath = join13(hooksDir, "post-commit");
|
|
6714
7490
|
const hookContent = `#!/bin/sh
|
|
6715
7491
|
# ZAM Spaced Repetition Auto-Stale Hook
|
|
6716
7492
|
# Triggered automatically on git commits to decay modified concept cards.
|
|
6717
7493
|
zam git-sync --commit HEAD --quiet
|
|
6718
7494
|
`;
|
|
6719
7495
|
try {
|
|
6720
|
-
|
|
7496
|
+
writeFileSync6(hookPath, hookContent, { encoding: "utf-8", flag: "w" });
|
|
6721
7497
|
try {
|
|
6722
7498
|
chmodSync2(hookPath, "755");
|
|
6723
7499
|
} catch (_e) {
|
|
@@ -6819,7 +7595,7 @@ ZAM Auto-Stale Complete: Scanned ${changedFiles.length} file(s).`
|
|
|
6819
7595
|
});
|
|
6820
7596
|
|
|
6821
7597
|
// src/cli/commands/goal.ts
|
|
6822
|
-
import { existsSync as existsSync14, mkdirSync as
|
|
7598
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync8 } from "fs";
|
|
6823
7599
|
import { resolve as resolve3 } from "path";
|
|
6824
7600
|
import { input as input2 } from "@inquirer/prompts";
|
|
6825
7601
|
import { Command as Command6 } from "commander";
|
|
@@ -6945,7 +7721,7 @@ ${"\u2500".repeat(50)}`);
|
|
|
6945
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) => {
|
|
6946
7722
|
const goalsDir = await resolveGoalsDir();
|
|
6947
7723
|
if (!existsSync14(goalsDir)) {
|
|
6948
|
-
|
|
7724
|
+
mkdirSync8(goalsDir, { recursive: true });
|
|
6949
7725
|
}
|
|
6950
7726
|
let slug = opts.slug;
|
|
6951
7727
|
let title = opts.title;
|
|
@@ -7004,9 +7780,9 @@ goalCommand.command("status <slug> <status>").description("Update a goal's statu
|
|
|
7004
7780
|
});
|
|
7005
7781
|
|
|
7006
7782
|
// src/cli/commands/init.ts
|
|
7007
|
-
import { existsSync as existsSync15, mkdirSync as
|
|
7783
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync9, writeFileSync as writeFileSync7 } from "fs";
|
|
7008
7784
|
import { homedir as homedir9 } from "os";
|
|
7009
|
-
import { join as
|
|
7785
|
+
import { join as join14 } from "path";
|
|
7010
7786
|
import { confirm, input as input3 } from "@inquirer/prompts";
|
|
7011
7787
|
import { Command as Command7 } from "commander";
|
|
7012
7788
|
var HOME2 = homedir9();
|
|
@@ -7014,12 +7790,12 @@ function printLine(char = "\u2550", len = 60, color = "\x1B[36m") {
|
|
|
7014
7790
|
console.log(`${color}${char.repeat(len)}\x1B[0m`);
|
|
7015
7791
|
}
|
|
7016
7792
|
function bootstrapSandboxWorkspace(workspaceDir) {
|
|
7017
|
-
|
|
7018
|
-
|
|
7019
|
-
|
|
7020
|
-
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");
|
|
7021
7797
|
if (!existsSync15(worldviewFile)) {
|
|
7022
|
-
|
|
7798
|
+
writeFileSync7(
|
|
7023
7799
|
worldviewFile,
|
|
7024
7800
|
`# Personal Worldview
|
|
7025
7801
|
|
|
@@ -7031,9 +7807,9 @@ Here, I declare the core concepts and principles I want to master.
|
|
|
7031
7807
|
"utf8"
|
|
7032
7808
|
);
|
|
7033
7809
|
}
|
|
7034
|
-
const goalsFile =
|
|
7810
|
+
const goalsFile = join14(workspaceDir, "goals", "goals.md");
|
|
7035
7811
|
if (!existsSync15(goalsFile)) {
|
|
7036
|
-
|
|
7812
|
+
writeFileSync7(
|
|
7037
7813
|
goalsFile,
|
|
7038
7814
|
`# Personal Goals
|
|
7039
7815
|
|
|
@@ -7055,7 +7831,7 @@ var initCommand = new Command7("init").description("Launch the guided interactiv
|
|
|
7055
7831
|
);
|
|
7056
7832
|
printLine();
|
|
7057
7833
|
console.log("\n\x1B[1m[1/5] Setting up Local Workspace Sandbox\x1B[0m");
|
|
7058
|
-
const defaultWorkspace =
|
|
7834
|
+
const defaultWorkspace = join14(HOME2, "Documents", "zam");
|
|
7059
7835
|
const workspacePath = await input3({
|
|
7060
7836
|
message: "Choose your ZAM workspace directory:",
|
|
7061
7837
|
default: defaultWorkspace
|
|
@@ -7752,10 +8528,10 @@ ${"\u2550".repeat(50)}`);
|
|
|
7752
8528
|
});
|
|
7753
8529
|
|
|
7754
8530
|
// src/cli/commands/monitor.ts
|
|
7755
|
-
import { execFileSync as
|
|
7756
|
-
import { unlinkSync, writeFileSync as
|
|
7757
|
-
import { tmpdir } from "os";
|
|
7758
|
-
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";
|
|
7759
8535
|
import { Command as Command9 } from "commander";
|
|
7760
8536
|
function isPowerShellShell(shell) {
|
|
7761
8537
|
return shell === "pwsh" || shell === "powershell";
|
|
@@ -7906,11 +8682,23 @@ monitorCommand.command("status").description("Show monitoring status for a sessi
|
|
|
7906
8682
|
console.log(` To: ${result.timeSpan.end}`);
|
|
7907
8683
|
}
|
|
7908
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
|
+
}
|
|
7909
8693
|
function findExecutable(command) {
|
|
7910
8694
|
try {
|
|
7911
8695
|
const lookup = process.platform === "win32" ? `where.exe ${command}` : `command -v ${command}`;
|
|
7912
|
-
const
|
|
7913
|
-
|
|
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];
|
|
7914
8702
|
} catch {
|
|
7915
8703
|
return null;
|
|
7916
8704
|
}
|
|
@@ -7920,8 +8708,8 @@ function resolveZamInvocation(shell) {
|
|
|
7920
8708
|
if (installed) {
|
|
7921
8709
|
return isPowerShellShell(shell) ? `& ${psSingleQuoted2(installed)}` : installed;
|
|
7922
8710
|
}
|
|
7923
|
-
const projectRoot =
|
|
7924
|
-
const cliSource =
|
|
8711
|
+
const projectRoot = join15(import.meta.dirname, "..", "..", "..");
|
|
8712
|
+
const cliSource = join15(projectRoot, "src/cli/index.ts");
|
|
7925
8713
|
if (isPowerShellShell(shell)) {
|
|
7926
8714
|
return `& npx --prefix ${psSingleQuoted2(projectRoot)} tsx ${psSingleQuoted2(cliSource)}`;
|
|
7927
8715
|
}
|
|
@@ -8011,9 +8799,9 @@ end tell` : `tell application "Terminal"
|
|
|
8011
8799
|
activate
|
|
8012
8800
|
do script "${escaped}"
|
|
8013
8801
|
end tell`;
|
|
8014
|
-
const tmpFile =
|
|
8802
|
+
const tmpFile = join15(tmpdir2(), `zam-monitor-${sessionId}.scpt`);
|
|
8015
8803
|
try {
|
|
8016
|
-
|
|
8804
|
+
writeFileSync8(tmpFile, appleScript);
|
|
8017
8805
|
execSync5(`osascript ${JSON.stringify(tmpFile)}`, { stdio: "ignore" });
|
|
8018
8806
|
console.log(
|
|
8019
8807
|
`Opened ${useIterm ? "iTerm2" : "Terminal.app"} window with monitoring for session ${sessionId}`
|
|
@@ -8040,9 +8828,10 @@ function openWindowsPowerShell(shellSetup, sessionId, dir, requestedShell) {
|
|
|
8040
8828
|
`-FilePath ${psSingleQuoted2(executable)}`,
|
|
8041
8829
|
`-ArgumentList @('-NoExit','-NoProfile','-Command',${psSingleQuoted2(shellSetup)})`
|
|
8042
8830
|
].join(" ");
|
|
8831
|
+
const launcher = findExecutable("pwsh.exe") ? "pwsh.exe" : "powershell.exe";
|
|
8043
8832
|
try {
|
|
8044
|
-
|
|
8045
|
-
|
|
8833
|
+
execFileSync3(
|
|
8834
|
+
launcher,
|
|
8046
8835
|
["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", startCommand],
|
|
8047
8836
|
{
|
|
8048
8837
|
stdio: "ignore"
|
|
@@ -8061,10 +8850,73 @@ Run this manually in a new PowerShell terminal:
|
|
|
8061
8850
|
}
|
|
8062
8851
|
}
|
|
8063
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
|
+
|
|
8064
8916
|
// src/cli/commands/profile.ts
|
|
8065
8917
|
import { homedir as homedir10 } from "os";
|
|
8066
|
-
import { dirname as dirname5, join as
|
|
8067
|
-
import { Command as
|
|
8918
|
+
import { dirname as dirname5, join as join16, resolve as resolve4 } from "path";
|
|
8919
|
+
import { Command as Command11 } from "commander";
|
|
8068
8920
|
var C2 = {
|
|
8069
8921
|
reset: "\x1B[0m",
|
|
8070
8922
|
bold: "\x1B[1m",
|
|
@@ -8073,7 +8925,7 @@ var C2 = {
|
|
|
8073
8925
|
green: "\x1B[32m"
|
|
8074
8926
|
};
|
|
8075
8927
|
function defaultPersonalDir() {
|
|
8076
|
-
return
|
|
8928
|
+
return join16(homedir10(), "Documents", "zam");
|
|
8077
8929
|
}
|
|
8078
8930
|
function render(profile) {
|
|
8079
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}`;
|
|
@@ -8084,7 +8936,7 @@ function render(profile) {
|
|
|
8084
8936
|
console.log(` data dir: ${C2.cyan}${profile.dataDir}${C2.reset}`);
|
|
8085
8937
|
console.log(` database: ${C2.cyan}${profile.dbPath}${C2.reset}`);
|
|
8086
8938
|
}
|
|
8087
|
-
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) => {
|
|
8088
8940
|
if (opts.mode && opts.mode !== "developer" && opts.mode !== "default") {
|
|
8089
8941
|
console.error(`Invalid --mode: ${opts.mode}. Use developer or default.`);
|
|
8090
8942
|
process.exit(1);
|
|
@@ -8120,8 +8972,8 @@ var profileCommand = new Command10("profile").description("Show or change this m
|
|
|
8120
8972
|
});
|
|
8121
8973
|
|
|
8122
8974
|
// src/cli/commands/review.ts
|
|
8123
|
-
import { Command as
|
|
8124
|
-
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) => {
|
|
8125
8977
|
let db;
|
|
8126
8978
|
try {
|
|
8127
8979
|
db = await openDatabase();
|
|
@@ -8234,10 +9086,10 @@ ${"\u2550".repeat(50)}`);
|
|
|
8234
9086
|
});
|
|
8235
9087
|
|
|
8236
9088
|
// src/cli/commands/session.ts
|
|
8237
|
-
import { readFileSync as
|
|
9089
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
8238
9090
|
import { input as input6, select as select2 } from "@inquirer/prompts";
|
|
8239
|
-
import { Command as
|
|
8240
|
-
var sessionCommand = new
|
|
9091
|
+
import { Command as Command13 } from "commander";
|
|
9092
|
+
var sessionCommand = new Command13("session").description(
|
|
8241
9093
|
"Manage learning sessions"
|
|
8242
9094
|
);
|
|
8243
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(
|
|
@@ -8289,11 +9141,18 @@ sessionCommand.command("start").description("Start a new learning session (revie
|
|
|
8289
9141
|
task,
|
|
8290
9142
|
execution_context: opts.context
|
|
8291
9143
|
});
|
|
9144
|
+
const observerHint = opts.context === "ui" && !await isObserverPolicyConfigured(db) ? OBSERVER_POLICY_UNSET_HINT : null;
|
|
8292
9145
|
await db.close();
|
|
8293
9146
|
if (opts.quiet) {
|
|
8294
9147
|
console.log(session.id);
|
|
8295
9148
|
} else if (opts.json) {
|
|
8296
|
-
console.log(
|
|
9149
|
+
console.log(
|
|
9150
|
+
JSON.stringify(
|
|
9151
|
+
observerHint ? { ...session, observerPolicyHint: observerHint } : session,
|
|
9152
|
+
null,
|
|
9153
|
+
2
|
|
9154
|
+
)
|
|
9155
|
+
);
|
|
8297
9156
|
} else {
|
|
8298
9157
|
console.log(`
|
|
8299
9158
|
Session started: ${session.id}`);
|
|
@@ -8301,6 +9160,10 @@ Session started: ${session.id}`);
|
|
|
8301
9160
|
console.log(` Task: ${session.task}`);
|
|
8302
9161
|
console.log(` Context: ${session.execution_context}`);
|
|
8303
9162
|
console.log(` Started: ${session.started_at}`);
|
|
9163
|
+
if (observerHint) {
|
|
9164
|
+
console.log(`
|
|
9165
|
+
${observerHint}`);
|
|
9166
|
+
}
|
|
8304
9167
|
}
|
|
8305
9168
|
} catch (err) {
|
|
8306
9169
|
await db?.close();
|
|
@@ -8415,7 +9278,7 @@ function loadPatternFile(path) {
|
|
|
8415
9278
|
if (!path) return [];
|
|
8416
9279
|
let parsed;
|
|
8417
9280
|
try {
|
|
8418
|
-
parsed = JSON.parse(
|
|
9281
|
+
parsed = JSON.parse(readFileSync11(path, "utf-8"));
|
|
8419
9282
|
} catch (err) {
|
|
8420
9283
|
throw new Error(
|
|
8421
9284
|
`Cannot read synthesis patterns from ${path}: ${err.message}`
|
|
@@ -8606,8 +9469,8 @@ sessionCommand.command("end").description("End a session and show summary").requ
|
|
|
8606
9469
|
|
|
8607
9470
|
// src/cli/commands/settings.ts
|
|
8608
9471
|
import { existsSync as existsSync16 } from "fs";
|
|
8609
|
-
import { Command as
|
|
8610
|
-
var settingsCommand = new
|
|
9472
|
+
import { Command as Command14 } from "commander";
|
|
9473
|
+
var settingsCommand = new Command14("settings").description(
|
|
8611
9474
|
"Manage user settings"
|
|
8612
9475
|
);
|
|
8613
9476
|
var BOOLEAN_SETTING_KEYS = /* @__PURE__ */ new Set(["llm.enabled", "llm.vision.enabled"]);
|
|
@@ -8661,6 +9524,9 @@ settingsCommand.command("set <key> <value>").description("Set a setting").option
|
|
|
8661
9524
|
await withDb(async (db) => {
|
|
8662
9525
|
const parsedVal = normalizeSettingValue(key, value);
|
|
8663
9526
|
await setSetting(db, key, parsedVal);
|
|
9527
|
+
if (key.startsWith("observer.")) {
|
|
9528
|
+
await syncObserverSidecarPolicy(db);
|
|
9529
|
+
}
|
|
8664
9530
|
if (!opts.quiet) {
|
|
8665
9531
|
console.log(`Set ${key} = ${parsedVal}`);
|
|
8666
9532
|
}
|
|
@@ -8669,6 +9535,9 @@ settingsCommand.command("set <key> <value>").description("Set a setting").option
|
|
|
8669
9535
|
settingsCommand.command("delete <key>").description("Delete a setting").option("--quiet", "Suppress output").action(async (key, opts) => {
|
|
8670
9536
|
await withDb(async (db) => {
|
|
8671
9537
|
const deleted = await deleteSetting(db, key);
|
|
9538
|
+
if (key.startsWith("observer.")) {
|
|
9539
|
+
await syncObserverSidecarPolicy(db);
|
|
9540
|
+
}
|
|
8672
9541
|
if (!opts.quiet) {
|
|
8673
9542
|
if (deleted) {
|
|
8674
9543
|
console.log(`Deleted: ${key}`);
|
|
@@ -8776,32 +9645,32 @@ settingsCommand.command("repos").description("Show or set Personal, Team, and Or
|
|
|
8776
9645
|
});
|
|
8777
9646
|
|
|
8778
9647
|
// src/cli/commands/setup.ts
|
|
8779
|
-
import { copyFileSync as copyFileSync2, existsSync as existsSync17, mkdirSync as
|
|
8780
|
-
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";
|
|
8781
9650
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
8782
|
-
import { Command as
|
|
9651
|
+
import { Command as Command15 } from "commander";
|
|
8783
9652
|
var packageRoot = [
|
|
8784
9653
|
fileURLToPath2(new URL("../..", import.meta.url)),
|
|
8785
9654
|
fileURLToPath2(new URL("../../..", import.meta.url))
|
|
8786
|
-
].find((candidate) => existsSync17(
|
|
9655
|
+
].find((candidate) => existsSync17(join17(candidate, "package.json"))) ?? fileURLToPath2(new URL("../..", import.meta.url));
|
|
8787
9656
|
var SKILL_PAIRS = [
|
|
8788
9657
|
{
|
|
8789
|
-
from:
|
|
8790
|
-
to:
|
|
9658
|
+
from: join17(packageRoot, ".claude", "skills", "zam", "SKILL.md"),
|
|
9659
|
+
to: join17(".claude", "skills", "zam", "SKILL.md")
|
|
8791
9660
|
},
|
|
8792
9661
|
{
|
|
8793
|
-
from:
|
|
8794
|
-
to:
|
|
9662
|
+
from: join17(packageRoot, ".agent", "skills", "zam", "SKILL.md"),
|
|
9663
|
+
to: join17(".agent", "skills", "zam", "SKILL.md")
|
|
8795
9664
|
},
|
|
8796
9665
|
{
|
|
8797
|
-
from:
|
|
8798
|
-
to:
|
|
9666
|
+
from: join17(packageRoot, ".agents", "skills", "zam", "SKILL.md"),
|
|
9667
|
+
to: join17(".agents", "skills", "zam", "SKILL.md")
|
|
8799
9668
|
}
|
|
8800
9669
|
];
|
|
8801
9670
|
function copySkills(force, cwd = process.cwd()) {
|
|
8802
9671
|
let anyAction = false;
|
|
8803
9672
|
for (const { from, to } of SKILL_PAIRS) {
|
|
8804
|
-
const dest =
|
|
9673
|
+
const dest = join17(cwd, to);
|
|
8805
9674
|
if (!existsSync17(from)) {
|
|
8806
9675
|
console.warn(` warn source not found, skipping: ${from}`);
|
|
8807
9676
|
continue;
|
|
@@ -8810,7 +9679,7 @@ function copySkills(force, cwd = process.cwd()) {
|
|
|
8810
9679
|
console.log(` skip ${to} (already present \xE2\u20AC\u201D use --force to update)`);
|
|
8811
9680
|
continue;
|
|
8812
9681
|
}
|
|
8813
|
-
|
|
9682
|
+
mkdirSync10(dirname6(dest), { recursive: true });
|
|
8814
9683
|
copyFileSync2(from, dest);
|
|
8815
9684
|
console.log(` copy ${to}`);
|
|
8816
9685
|
anyAction = true;
|
|
@@ -8821,13 +9690,25 @@ function copySkills(force, cwd = process.cwd()) {
|
|
|
8821
9690
|
);
|
|
8822
9691
|
}
|
|
8823
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
|
+
}
|
|
8824
9705
|
async function initDatabase(skipInit) {
|
|
8825
9706
|
if (skipInit) return;
|
|
8826
9707
|
try {
|
|
8827
|
-
const
|
|
9708
|
+
const target = getDatabaseTargetInfo();
|
|
8828
9709
|
const db = await openDatabaseWithSync({ initialize: true });
|
|
8829
9710
|
await db.close();
|
|
8830
|
-
console.log(` init
|
|
9711
|
+
console.log(` init ${formatDatabaseInitTarget(target)}`);
|
|
8831
9712
|
} catch (err) {
|
|
8832
9713
|
const msg = err.message;
|
|
8833
9714
|
if (!msg.includes("already")) {
|
|
@@ -8839,13 +9720,13 @@ async function initDatabase(skipInit) {
|
|
|
8839
9720
|
}
|
|
8840
9721
|
function writeClaudeMd(skipClaudeMd, cwd = process.cwd()) {
|
|
8841
9722
|
if (skipClaudeMd) return;
|
|
8842
|
-
const dest =
|
|
9723
|
+
const dest = join17(cwd, "CLAUDE.md");
|
|
8843
9724
|
if (existsSync17(dest)) {
|
|
8844
9725
|
console.log(` skip CLAUDE.md (already present)`);
|
|
8845
9726
|
return;
|
|
8846
9727
|
}
|
|
8847
9728
|
const name = basename4(cwd);
|
|
8848
|
-
|
|
9729
|
+
writeFileSync9(
|
|
8849
9730
|
dest,
|
|
8850
9731
|
`# ZAM Personal Kernel \xE2\u20AC\u201D ${name}
|
|
8851
9732
|
|
|
@@ -8873,13 +9754,13 @@ Use \`zam connector setup turso\` to store cloud credentials in
|
|
|
8873
9754
|
}
|
|
8874
9755
|
function writeAgentsMd(skipAgentsMd, cwd = process.cwd()) {
|
|
8875
9756
|
if (skipAgentsMd) return;
|
|
8876
|
-
const dest =
|
|
9757
|
+
const dest = join17(cwd, "AGENTS.md");
|
|
8877
9758
|
if (existsSync17(dest)) {
|
|
8878
9759
|
console.log(` skip AGENTS.md (already present)`);
|
|
8879
9760
|
return;
|
|
8880
9761
|
}
|
|
8881
9762
|
const name = basename4(cwd);
|
|
8882
|
-
|
|
9763
|
+
writeFileSync9(
|
|
8883
9764
|
dest,
|
|
8884
9765
|
`# ZAM Personal Kernel - ${name}
|
|
8885
9766
|
|
|
@@ -8912,7 +9793,7 @@ Codex discovers repository skills under \`.agents/skills/\`. Run
|
|
|
8912
9793
|
);
|
|
8913
9794
|
console.log(` write AGENTS.md`);
|
|
8914
9795
|
}
|
|
8915
|
-
var setupCommand = new
|
|
9796
|
+
var setupCommand = new Command15("setup").description(
|
|
8916
9797
|
"Distribute ZAM skill files into this personal instance and initialize the database"
|
|
8917
9798
|
).option(
|
|
8918
9799
|
"--force",
|
|
@@ -8933,8 +9814,8 @@ var setupCommand = new Command14("setup").description(
|
|
|
8933
9814
|
);
|
|
8934
9815
|
|
|
8935
9816
|
// src/cli/commands/skill.ts
|
|
8936
|
-
import { Command as
|
|
8937
|
-
var skillCommand = new
|
|
9817
|
+
import { Command as Command16 } from "commander";
|
|
9818
|
+
var skillCommand = new Command16("skill").description(
|
|
8938
9819
|
"Manage agent skill entries (task recipes)"
|
|
8939
9820
|
);
|
|
8940
9821
|
skillCommand.command("list").description("List all agent skills").option("--json", "Output as JSON").action(async (opts) => {
|
|
@@ -9019,10 +9900,10 @@ skillCommand.command("add").description("Register a new agent skill").requiredOp
|
|
|
9019
9900
|
});
|
|
9020
9901
|
|
|
9021
9902
|
// src/cli/commands/snapshot.ts
|
|
9022
|
-
import { existsSync as existsSync18, mkdirSync as
|
|
9903
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync11, readFileSync as readFileSync12, writeFileSync as writeFileSync10 } from "fs";
|
|
9023
9904
|
import { homedir as homedir11 } from "os";
|
|
9024
|
-
import { dirname as dirname7, join as
|
|
9025
|
-
import { Command as
|
|
9905
|
+
import { dirname as dirname7, join as join18 } from "path";
|
|
9906
|
+
import { Command as Command17 } from "commander";
|
|
9026
9907
|
function defaultOutName() {
|
|
9027
9908
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d+Z$/, "").replace(/:/g, "-");
|
|
9028
9909
|
return `zam-snapshot-${stamp}.sql`;
|
|
@@ -9036,12 +9917,12 @@ function summarize(tables) {
|
|
|
9036
9917
|
}
|
|
9037
9918
|
return { total, nonEmpty };
|
|
9038
9919
|
}
|
|
9039
|
-
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) => {
|
|
9040
9921
|
let db;
|
|
9041
9922
|
try {
|
|
9042
9923
|
db = await openDatabaseWithSync({ initialize: true });
|
|
9043
9924
|
const snapshot = await exportSnapshot(db);
|
|
9044
|
-
const personalDir = await getSetting(db, "personal.workspace_dir") ||
|
|
9925
|
+
const personalDir = await getSetting(db, "personal.workspace_dir") || join18(homedir11(), "Documents", "zam");
|
|
9045
9926
|
await db.close();
|
|
9046
9927
|
db = void 0;
|
|
9047
9928
|
const manifest = verifySnapshot(snapshot);
|
|
@@ -9050,12 +9931,12 @@ var exportCmd = new Command16("export").description("Write a portable SQL-text s
|
|
|
9050
9931
|
process.stdout.write(snapshot);
|
|
9051
9932
|
return;
|
|
9052
9933
|
}
|
|
9053
|
-
const out = opts.out ??
|
|
9934
|
+
const out = opts.out ?? join18(personalDir, "snapshots", defaultOutName());
|
|
9054
9935
|
const dir = dirname7(out);
|
|
9055
9936
|
if (dir && dir !== "." && !existsSync18(dir)) {
|
|
9056
|
-
|
|
9937
|
+
mkdirSync11(dir, { recursive: true });
|
|
9057
9938
|
}
|
|
9058
|
-
|
|
9939
|
+
writeFileSync10(out, snapshot, "utf-8");
|
|
9059
9940
|
console.log(`Snapshot written: ${out}`);
|
|
9060
9941
|
console.log(
|
|
9061
9942
|
` ${total} row(s)${nonEmpty.length ? ` \u2014 ${nonEmpty.join(", ")}` : ""}`
|
|
@@ -9066,14 +9947,14 @@ var exportCmd = new Command16("export").description("Write a portable SQL-text s
|
|
|
9066
9947
|
process.exit(1);
|
|
9067
9948
|
}
|
|
9068
9949
|
});
|
|
9069
|
-
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) => {
|
|
9070
9951
|
let db;
|
|
9071
9952
|
try {
|
|
9072
9953
|
if (!existsSync18(file)) {
|
|
9073
9954
|
console.error(`Error: Snapshot file not found: ${file}`);
|
|
9074
9955
|
process.exit(1);
|
|
9075
9956
|
}
|
|
9076
|
-
const snapshot =
|
|
9957
|
+
const snapshot = readFileSync12(file, "utf-8");
|
|
9077
9958
|
db = await openDatabaseWithSync({ initialize: true });
|
|
9078
9959
|
const result = await importSnapshot(db, snapshot, { force: opts.force });
|
|
9079
9960
|
await db.close();
|
|
@@ -9089,13 +9970,13 @@ var importCmd = new Command16("import").description("Restore a snapshot into the
|
|
|
9089
9970
|
process.exit(1);
|
|
9090
9971
|
}
|
|
9091
9972
|
});
|
|
9092
|
-
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) => {
|
|
9093
9974
|
try {
|
|
9094
9975
|
if (!existsSync18(file)) {
|
|
9095
9976
|
console.error(`Error: Snapshot file not found: ${file}`);
|
|
9096
9977
|
process.exit(1);
|
|
9097
9978
|
}
|
|
9098
|
-
const manifest = verifySnapshot(
|
|
9979
|
+
const manifest = verifySnapshot(readFileSync12(file, "utf-8"));
|
|
9099
9980
|
const { total, nonEmpty } = summarize(manifest.tables);
|
|
9100
9981
|
console.log(`Valid snapshot (format v${manifest.version})`);
|
|
9101
9982
|
console.log(` created: ${manifest.createdAt}`);
|
|
@@ -9107,11 +9988,11 @@ var verifyCmd = new Command16("verify").description("Check a snapshot's manifest
|
|
|
9107
9988
|
process.exit(1);
|
|
9108
9989
|
}
|
|
9109
9990
|
});
|
|
9110
|
-
var snapshotCommand = new
|
|
9991
|
+
var snapshotCommand = new Command17("snapshot").description("Export, import, or verify a portable database snapshot").addCommand(exportCmd).addCommand(importCmd).addCommand(verifyCmd);
|
|
9111
9992
|
|
|
9112
9993
|
// src/cli/commands/stats.ts
|
|
9113
|
-
import { Command as
|
|
9114
|
-
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) => {
|
|
9115
9996
|
await withDb(async (db) => {
|
|
9116
9997
|
const userId = await resolveUser(opts, db);
|
|
9117
9998
|
const stats = await getUserStats(db, userId);
|
|
@@ -9147,8 +10028,8 @@ var statsCommand = new Command17("stats").description("Show learning dashboard f
|
|
|
9147
10028
|
});
|
|
9148
10029
|
|
|
9149
10030
|
// src/cli/commands/token.ts
|
|
9150
|
-
import { Command as
|
|
9151
|
-
var tokenCommand = new
|
|
10031
|
+
import { Command as Command19 } from "commander";
|
|
10032
|
+
var tokenCommand = new Command19("token").description(
|
|
9152
10033
|
"Manage knowledge tokens"
|
|
9153
10034
|
);
|
|
9154
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) => {
|
|
@@ -9437,9 +10318,9 @@ tokenCommand.command("status").description("Show full status of a token for a us
|
|
|
9437
10318
|
import { spawn as spawn2, spawnSync } from "child_process";
|
|
9438
10319
|
import { existsSync as existsSync19 } from "fs";
|
|
9439
10320
|
import { homedir as homedir12 } from "os";
|
|
9440
|
-
import { dirname as dirname8, join as
|
|
10321
|
+
import { dirname as dirname8, join as join19 } from "path";
|
|
9441
10322
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
9442
|
-
import { Command as
|
|
10323
|
+
import { Command as Command20 } from "commander";
|
|
9443
10324
|
var C3 = {
|
|
9444
10325
|
reset: "\x1B[0m",
|
|
9445
10326
|
red: "\x1B[31m",
|
|
@@ -9453,8 +10334,8 @@ function findDesktopDir() {
|
|
|
9453
10334
|
for (const start of starts) {
|
|
9454
10335
|
let dir = start;
|
|
9455
10336
|
for (let i = 0; i < 10; i++) {
|
|
9456
|
-
if (existsSync19(
|
|
9457
|
-
return
|
|
10337
|
+
if (existsSync19(join19(dir, "desktop", "src-tauri", "tauri.conf.json"))) {
|
|
10338
|
+
return join19(dir, "desktop");
|
|
9458
10339
|
}
|
|
9459
10340
|
const parent = dirname8(dir);
|
|
9460
10341
|
if (parent === dir) break;
|
|
@@ -9464,18 +10345,18 @@ function findDesktopDir() {
|
|
|
9464
10345
|
return null;
|
|
9465
10346
|
}
|
|
9466
10347
|
function findBuiltApp(desktopDir) {
|
|
9467
|
-
const releaseDir =
|
|
10348
|
+
const releaseDir = join19(desktopDir, "src-tauri", "target", "release");
|
|
9468
10349
|
if (process.platform === "win32") {
|
|
9469
10350
|
for (const name of ["ZAM.exe", "zam.exe", "zam-desktop.exe"]) {
|
|
9470
|
-
const p =
|
|
10351
|
+
const p = join19(releaseDir, name);
|
|
9471
10352
|
if (existsSync19(p)) return p;
|
|
9472
10353
|
}
|
|
9473
10354
|
} else if (process.platform === "darwin") {
|
|
9474
|
-
const app =
|
|
10355
|
+
const app = join19(releaseDir, "bundle", "macos", "ZAM.app");
|
|
9475
10356
|
if (existsSync19(app)) return app;
|
|
9476
10357
|
} else {
|
|
9477
10358
|
for (const name of ["zam", "ZAM", "zam-desktop"]) {
|
|
9478
|
-
const p =
|
|
10359
|
+
const p = join19(releaseDir, name);
|
|
9479
10360
|
if (existsSync19(p)) return p;
|
|
9480
10361
|
}
|
|
9481
10362
|
}
|
|
@@ -9483,10 +10364,10 @@ function findBuiltApp(desktopDir) {
|
|
|
9483
10364
|
}
|
|
9484
10365
|
function findInstalledApp() {
|
|
9485
10366
|
const candidates = process.platform === "win32" ? [
|
|
9486
|
-
process.env.LOCALAPPDATA &&
|
|
9487
|
-
process.env.ProgramFiles &&
|
|
9488
|
-
process.env["ProgramFiles(x86)"] &&
|
|
9489
|
-
] : 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"];
|
|
9490
10371
|
return candidates.find((candidate) => candidate && existsSync19(candidate)) || null;
|
|
9491
10372
|
}
|
|
9492
10373
|
function runNpm(args, opts) {
|
|
@@ -9498,7 +10379,7 @@ function runNpm(args, opts) {
|
|
|
9498
10379
|
return res.status ?? 1;
|
|
9499
10380
|
}
|
|
9500
10381
|
function ensureDesktopDeps(desktopDir) {
|
|
9501
|
-
if (existsSync19(
|
|
10382
|
+
if (existsSync19(join19(desktopDir, "node_modules"))) return true;
|
|
9502
10383
|
console.log(
|
|
9503
10384
|
`${C3.cyan}Installing desktop dependencies (one-time)...${C3.reset}`
|
|
9504
10385
|
);
|
|
@@ -9524,7 +10405,7 @@ function requireRust() {
|
|
|
9524
10405
|
function hasMsvcBuildTools() {
|
|
9525
10406
|
if (process.platform !== "win32") return true;
|
|
9526
10407
|
const pf86 = process.env["ProgramFiles(x86)"] || "C:\\Program Files (x86)";
|
|
9527
|
-
const vswhere =
|
|
10408
|
+
const vswhere = join19(
|
|
9528
10409
|
pf86,
|
|
9529
10410
|
"Microsoft Visual Studio",
|
|
9530
10411
|
"Installer",
|
|
@@ -9562,7 +10443,7 @@ function requireMsvcOnWindows() {
|
|
|
9562
10443
|
return false;
|
|
9563
10444
|
}
|
|
9564
10445
|
function warnIfCliMissing(repoRoot) {
|
|
9565
|
-
if (!existsSync19(
|
|
10446
|
+
if (!existsSync19(join19(repoRoot, "dist", "cli", "index.js"))) {
|
|
9566
10447
|
console.warn(
|
|
9567
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}`
|
|
9568
10449
|
);
|
|
@@ -9619,7 +10500,7 @@ function createShortcuts(appPath, repoRoot) {
|
|
|
9619
10500
|
console.error(`${C3.red}\u2717 Could not create shortcuts.${C3.reset}`);
|
|
9620
10501
|
}
|
|
9621
10502
|
}
|
|
9622
|
-
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(
|
|
9623
10504
|
"--build",
|
|
9624
10505
|
"Build the native installer for your OS (needs Rust, one-time)"
|
|
9625
10506
|
).option("--shortcut", "Create Desktop + Start-menu shortcuts to the GUI").action((opts) => {
|
|
@@ -9646,7 +10527,7 @@ var uiCommand = new Command19("ui").description("Launch the ZAM Desktop GUI (Act
|
|
|
9646
10527
|
);
|
|
9647
10528
|
const code = runNpm(["run", "tauri", "build"], { cwd: desktopDir });
|
|
9648
10529
|
if (code === 0) {
|
|
9649
|
-
const bundle =
|
|
10530
|
+
const bundle = join19(
|
|
9650
10531
|
desktopDir,
|
|
9651
10532
|
"src-tauri",
|
|
9652
10533
|
"target",
|
|
@@ -9722,10 +10603,10 @@ ${C3.dim}After that, 'zam ui' launches it directly.${C3.reset}`
|
|
|
9722
10603
|
});
|
|
9723
10604
|
|
|
9724
10605
|
// src/cli/commands/update.ts
|
|
9725
|
-
import { readFileSync as
|
|
9726
|
-
import { dirname as dirname9, join as
|
|
10606
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
10607
|
+
import { dirname as dirname9, join as join20 } from "path";
|
|
9727
10608
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
9728
|
-
import { Command as
|
|
10609
|
+
import { Command as Command21 } from "commander";
|
|
9729
10610
|
var GITHUB_REPO = "zam-os/zam";
|
|
9730
10611
|
var CHANNELS = [
|
|
9731
10612
|
"developer",
|
|
@@ -9746,7 +10627,7 @@ function currentVersion() {
|
|
|
9746
10627
|
for (const up of ["..", "../..", "../../.."]) {
|
|
9747
10628
|
try {
|
|
9748
10629
|
const pkg2 = JSON.parse(
|
|
9749
|
-
|
|
10630
|
+
readFileSync13(join20(here, up, "package.json"), "utf-8")
|
|
9750
10631
|
);
|
|
9751
10632
|
if (pkg2.version) return pkg2.version;
|
|
9752
10633
|
} catch {
|
|
@@ -9792,7 +10673,7 @@ function render2(decision) {
|
|
|
9792
10673
|
);
|
|
9793
10674
|
}
|
|
9794
10675
|
}
|
|
9795
|
-
var checkCmd = new
|
|
10676
|
+
var checkCmd = new Command21("check").description("Check whether a newer ZAM has been released").option(
|
|
9796
10677
|
"--latest <version>",
|
|
9797
10678
|
"Compare against this version instead of fetching"
|
|
9798
10679
|
).option("--channel <channel>", "Override the detected install channel").option("--json", "Output as JSON").action(
|
|
@@ -9823,11 +10704,11 @@ var checkCmd = new Command20("check").description("Check whether a newer ZAM has
|
|
|
9823
10704
|
}
|
|
9824
10705
|
}
|
|
9825
10706
|
);
|
|
9826
|
-
var updateCommand = new
|
|
10707
|
+
var updateCommand = new Command21("update").description("Check for ZAM updates").addCommand(checkCmd);
|
|
9827
10708
|
|
|
9828
10709
|
// src/cli/commands/whoami.ts
|
|
9829
|
-
import { Command as
|
|
9830
|
-
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) => {
|
|
9831
10712
|
await withDb(async (db) => {
|
|
9832
10713
|
if (opts.set) {
|
|
9833
10714
|
await setSetting(db, "user.id", opts.set);
|
|
@@ -9864,10 +10745,10 @@ var whoamiCommand = new Command21("whoami").description("Show or set the default
|
|
|
9864
10745
|
|
|
9865
10746
|
// src/cli/commands/workspace.ts
|
|
9866
10747
|
import { execSync as execSync6 } from "child_process";
|
|
9867
|
-
import { existsSync as existsSync20, writeFileSync as
|
|
9868
|
-
import { join as
|
|
10748
|
+
import { existsSync as existsSync20, writeFileSync as writeFileSync11 } from "fs";
|
|
10749
|
+
import { join as join21 } from "path";
|
|
9869
10750
|
import { confirm as confirm3, input as input7 } from "@inquirer/prompts";
|
|
9870
|
-
import { Command as
|
|
10751
|
+
import { Command as Command23 } from "commander";
|
|
9871
10752
|
function runGit(cwd, args) {
|
|
9872
10753
|
try {
|
|
9873
10754
|
return execSync6(`git ${args}`, {
|
|
@@ -9879,7 +10760,7 @@ function runGit(cwd, args) {
|
|
|
9879
10760
|
throw new Error(`Git command failed: ${err.message}`);
|
|
9880
10761
|
}
|
|
9881
10762
|
}
|
|
9882
|
-
var workspaceCommand = new
|
|
10763
|
+
var workspaceCommand = new Command23("workspace").description(
|
|
9883
10764
|
"Manage your ZAM learning workspace"
|
|
9884
10765
|
);
|
|
9885
10766
|
workspaceCommand.command("publish").description("Publish your local workspace sandbox to GitHub").action(async () => {
|
|
@@ -9912,15 +10793,15 @@ Active workspace: \x1B[36m${workspaceDir}\x1B[0m`);
|
|
|
9912
10793
|
);
|
|
9913
10794
|
process.exit(1);
|
|
9914
10795
|
}
|
|
9915
|
-
const gitignorePath =
|
|
10796
|
+
const gitignorePath = join21(workspaceDir, ".gitignore");
|
|
9916
10797
|
if (!existsSync20(gitignorePath)) {
|
|
9917
|
-
|
|
10798
|
+
writeFileSync11(
|
|
9918
10799
|
gitignorePath,
|
|
9919
10800
|
"node_modules/\n.agent/\n.agents/\n.claude/\n.gemini/\n.goose/\n*.log\n",
|
|
9920
10801
|
"utf8"
|
|
9921
10802
|
);
|
|
9922
10803
|
}
|
|
9923
|
-
const hasGitRepo = existsSync20(
|
|
10804
|
+
const hasGitRepo = existsSync20(join21(workspaceDir, ".git"));
|
|
9924
10805
|
if (!hasGitRepo) {
|
|
9925
10806
|
console.log("Initializing local Git repository...");
|
|
9926
10807
|
runGit(workspaceDir, "init -b main");
|
|
@@ -10016,9 +10897,9 @@ Active workspace: \x1B[36m${workspaceDir}\x1B[0m`);
|
|
|
10016
10897
|
// src/cli/index.ts
|
|
10017
10898
|
var __dirname = dirname10(fileURLToPath5(import.meta.url));
|
|
10018
10899
|
var pkg = JSON.parse(
|
|
10019
|
-
|
|
10900
|
+
readFileSync14(join22(__dirname, "..", "..", "package.json"), "utf-8")
|
|
10020
10901
|
);
|
|
10021
|
-
var program = new
|
|
10902
|
+
var program = new Command24();
|
|
10022
10903
|
program.name("zam").description(
|
|
10023
10904
|
"The Symbiotic Learning Kernel: Elevating Human Intelligence through AI Collaboration."
|
|
10024
10905
|
).version(pkg.version);
|
|
@@ -10034,6 +10915,7 @@ program.addCommand(uiCommand);
|
|
|
10034
10915
|
program.addCommand(bridgeCommand);
|
|
10035
10916
|
program.addCommand(skillCommand);
|
|
10036
10917
|
program.addCommand(monitorCommand);
|
|
10918
|
+
program.addCommand(observerCommand);
|
|
10037
10919
|
program.addCommand(settingsCommand);
|
|
10038
10920
|
program.addCommand(whoamiCommand);
|
|
10039
10921
|
program.addCommand(connectorCommand);
|