zam-core 0.3.13 → 0.4.1
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/README.de.md +19 -0
- package/README.md +19 -0
- package/dist/cli/index.js +1538 -469
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +161 -1
- package/dist/index.js +560 -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
|
|
|
@@ -2316,184 +2765,44 @@ async function cascadeBlock(db, userId, tokenSlug) {
|
|
|
2316
2765
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2317
2766
|
await db.prepare(
|
|
2318
2767
|
"UPDATE cards SET blocked = 0, due_at = ? WHERE token_id = ? AND user_id = ?"
|
|
2319
|
-
).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 });
|
|
2768
|
+
).run(now, prereq.requires_id, userId);
|
|
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",
|
|
@@ -4538,6 +4847,37 @@ function decideUpdate(input8) {
|
|
|
4538
4847
|
};
|
|
4539
4848
|
}
|
|
4540
4849
|
}
|
|
4850
|
+
function planUpdate(decision) {
|
|
4851
|
+
if (!decision.updateAvailable) return [];
|
|
4852
|
+
switch (decision.channel) {
|
|
4853
|
+
case "developer":
|
|
4854
|
+
return [
|
|
4855
|
+
{ kind: "git-pull", label: "Pull the latest source" },
|
|
4856
|
+
{ kind: "npm-install", label: "Install dependencies" },
|
|
4857
|
+
{ kind: "npm-build", label: "Rebuild the CLI" },
|
|
4858
|
+
{
|
|
4859
|
+
kind: "distribute-skills",
|
|
4860
|
+
label: "Refresh skill files (zam setup --force)"
|
|
4861
|
+
}
|
|
4862
|
+
];
|
|
4863
|
+
case "winget":
|
|
4864
|
+
case "homebrew":
|
|
4865
|
+
return [
|
|
4866
|
+
{
|
|
4867
|
+
kind: "run-command",
|
|
4868
|
+
label: `Update via ${decision.channel}`,
|
|
4869
|
+
command: decision.command
|
|
4870
|
+
}
|
|
4871
|
+
];
|
|
4872
|
+
case "direct":
|
|
4873
|
+
return [
|
|
4874
|
+
{
|
|
4875
|
+
kind: "self-update",
|
|
4876
|
+
label: "Apply the signed update via the desktop app"
|
|
4877
|
+
}
|
|
4878
|
+
];
|
|
4879
|
+
}
|
|
4880
|
+
}
|
|
4541
4881
|
|
|
4542
4882
|
// src/cli/commands/agent.ts
|
|
4543
4883
|
var C = {
|
|
@@ -4550,7 +4890,7 @@ var C = {
|
|
|
4550
4890
|
};
|
|
4551
4891
|
var SUPPORTED_AGENTS = ["opencode"];
|
|
4552
4892
|
function agentsMdPresent(cwd = process.cwd()) {
|
|
4553
|
-
return existsSync11(
|
|
4893
|
+
return existsSync11(join11(cwd, "AGENTS.md"));
|
|
4554
4894
|
}
|
|
4555
4895
|
function printStatus() {
|
|
4556
4896
|
const installed = hasCommand("opencode");
|
|
@@ -4591,9 +4931,11 @@ var statusCmd = new Command("status").description("Show whether the agent is ins
|
|
|
4591
4931
|
var agentCommand = new Command("agent").description("Provision and inspect the agent that drives ZAM sessions").addCommand(installCmd).addCommand(statusCmd).action(printStatus);
|
|
4592
4932
|
|
|
4593
4933
|
// src/cli/commands/bridge.ts
|
|
4594
|
-
import {
|
|
4595
|
-
import {
|
|
4596
|
-
import {
|
|
4934
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
4935
|
+
import { randomBytes } from "crypto";
|
|
4936
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync10, rmSync as rmSync2 } from "fs";
|
|
4937
|
+
import { homedir as homedir8, tmpdir } from "os";
|
|
4938
|
+
import { join as join12 } from "path";
|
|
4597
4939
|
import { Command as Command2 } from "commander";
|
|
4598
4940
|
|
|
4599
4941
|
// src/cli/llm/client.ts
|
|
@@ -4956,8 +5298,8 @@ async function startLocalRunner(url, model, locale) {
|
|
|
4956
5298
|
process.stdout.write("\n");
|
|
4957
5299
|
console.log(`\x1B[33m${t(locale, "wait_warning")}\x1B[0m`);
|
|
4958
5300
|
console.log(`\x1B[2m${t(locale, "wait_info")}\x1B[0m`);
|
|
4959
|
-
const { confirm:
|
|
4960
|
-
const keepWaiting = await
|
|
5301
|
+
const { confirm: confirm5 } = await import("@inquirer/prompts");
|
|
5302
|
+
const keepWaiting = await confirm5({
|
|
4961
5303
|
message: t(locale, "keep_waiting_llm"),
|
|
4962
5304
|
default: true
|
|
4963
5305
|
}).catch(() => false);
|
|
@@ -5101,8 +5443,8 @@ async function fetchWithInteractiveTimeout(url, options = {}) {
|
|
|
5101
5443
|
}
|
|
5102
5444
|
console.log(`
|
|
5103
5445
|
\x1B[33m${t(locale, "local_ai_working")}\x1B[0m`);
|
|
5104
|
-
const { confirm:
|
|
5105
|
-
const keepWaiting = await
|
|
5446
|
+
const { confirm: confirm5 } = await import("@inquirer/prompts");
|
|
5447
|
+
const keepWaiting = await confirm5({
|
|
5106
5448
|
message: t(locale, "keep_waiting"),
|
|
5107
5449
|
default: true
|
|
5108
5450
|
}).catch(() => false);
|
|
@@ -5730,13 +6072,15 @@ bridgeCommand.command("start-session").description("Start a ZAM learning session
|
|
|
5730
6072
|
task: opts.task,
|
|
5731
6073
|
execution_context: context
|
|
5732
6074
|
});
|
|
6075
|
+
const observerPolicyHint = context === "ui" && !await isObserverPolicyConfigured(db) ? OBSERVER_POLICY_UNSET_HINT : void 0;
|
|
5733
6076
|
jsonOut2({
|
|
5734
6077
|
id: session.id,
|
|
5735
6078
|
userId: session.user_id,
|
|
5736
6079
|
task: session.task,
|
|
5737
6080
|
executionContext: session.execution_context,
|
|
5738
6081
|
startedAt: session.started_at,
|
|
5739
|
-
completedAt: session.completed_at
|
|
6082
|
+
completedAt: session.completed_at,
|
|
6083
|
+
...observerPolicyHint ? { observerPolicyHint } : {}
|
|
5740
6084
|
});
|
|
5741
6085
|
});
|
|
5742
6086
|
});
|
|
@@ -5859,7 +6203,8 @@ bridgeCommand.command("add-token").description("Create a token + card from JSON
|
|
|
5859
6203
|
bloom_level: data?.bloom_level ?? 1,
|
|
5860
6204
|
context: data?.context,
|
|
5861
6205
|
symbiosis_mode: data?.symbiosis_mode,
|
|
5862
|
-
source_link: data?.source_link ?? null
|
|
6206
|
+
source_link: data?.source_link ?? null,
|
|
6207
|
+
question: data?.question ?? null
|
|
5863
6208
|
});
|
|
5864
6209
|
const card = await ensureCard(db, token.id, userId);
|
|
5865
6210
|
jsonOut2({
|
|
@@ -5894,7 +6239,7 @@ bridgeCommand.command("discover-skills").description(
|
|
|
5894
6239
|
"20"
|
|
5895
6240
|
).action(async (opts) => {
|
|
5896
6241
|
try {
|
|
5897
|
-
const monitorDir =
|
|
6242
|
+
const monitorDir = join12(homedir8(), ".zam", "monitor");
|
|
5898
6243
|
let files;
|
|
5899
6244
|
try {
|
|
5900
6245
|
files = readdirSync2(monitorDir).filter((f) => f.endsWith(".jsonl"));
|
|
@@ -5907,7 +6252,7 @@ bridgeCommand.command("discover-skills").description(
|
|
|
5907
6252
|
return;
|
|
5908
6253
|
}
|
|
5909
6254
|
const limit = Number(opts.limit);
|
|
5910
|
-
const sorted = files.map((f) => ({ name: f, path:
|
|
6255
|
+
const sorted = files.map((f) => ({ name: f, path: join12(monitorDir, f) })).sort((a, b) => b.name.localeCompare(a.name)).slice(0, limit);
|
|
5911
6256
|
const sessionCommands = /* @__PURE__ */ new Map();
|
|
5912
6257
|
for (const file of sorted) {
|
|
5913
6258
|
const sessionId = file.name.replace(".jsonl", "");
|
|
@@ -5917,104 +6262,566 @@ bridgeCommand.command("discover-skills").description(
|
|
|
5917
6262
|
sessionCommands.set(sessionId, commands);
|
|
5918
6263
|
}
|
|
5919
6264
|
}
|
|
5920
|
-
if (sessionCommands.size === 0) {
|
|
5921
|
-
jsonOut2({ proposals: [], message: "No command data in monitor logs." });
|
|
5922
|
-
return;
|
|
5923
|
-
}
|
|
5924
|
-
let existingSkillSlugs = [];
|
|
5925
|
-
let db;
|
|
5926
|
-
try {
|
|
5927
|
-
db = await openDatabase();
|
|
5928
|
-
existingSkillSlugs = (await listAgentSkills(db)).map((s) => s.slug);
|
|
5929
|
-
} catch {
|
|
5930
|
-
} finally {
|
|
5931
|
-
await db?.close();
|
|
5932
|
-
}
|
|
5933
|
-
const proposals = discoverSkills(sessionCommands, {
|
|
5934
|
-
minSessions: Number(opts.minSessions),
|
|
5935
|
-
existingSkillSlugs
|
|
5936
|
-
});
|
|
5937
|
-
jsonOut2({
|
|
5938
|
-
sessionsAnalyzed: sessionCommands.size,
|
|
5939
|
-
proposals
|
|
5940
|
-
});
|
|
5941
|
-
} catch (err) {
|
|
5942
|
-
jsonError(err.message);
|
|
6265
|
+
if (sessionCommands.size === 0) {
|
|
6266
|
+
jsonOut2({ proposals: [], message: "No command data in monitor logs." });
|
|
6267
|
+
return;
|
|
6268
|
+
}
|
|
6269
|
+
let existingSkillSlugs = [];
|
|
6270
|
+
let db;
|
|
6271
|
+
try {
|
|
6272
|
+
db = await openDatabase();
|
|
6273
|
+
existingSkillSlugs = (await listAgentSkills(db)).map((s) => s.slug);
|
|
6274
|
+
} catch {
|
|
6275
|
+
} finally {
|
|
6276
|
+
await db?.close();
|
|
6277
|
+
}
|
|
6278
|
+
const proposals = discoverSkills(sessionCommands, {
|
|
6279
|
+
minSessions: Number(opts.minSessions),
|
|
6280
|
+
existingSkillSlugs
|
|
6281
|
+
});
|
|
6282
|
+
jsonOut2({
|
|
6283
|
+
sessionsAnalyzed: sessionCommands.size,
|
|
6284
|
+
proposals
|
|
6285
|
+
});
|
|
6286
|
+
} catch (err) {
|
|
6287
|
+
jsonError(err.message);
|
|
6288
|
+
}
|
|
6289
|
+
});
|
|
6290
|
+
bridgeCommand.command("observe-ui-watch").description(
|
|
6291
|
+
"Poll live UI observer watch reports for a ZAM learning session (JSON)"
|
|
6292
|
+
).requiredOption(
|
|
6293
|
+
"--session <id>",
|
|
6294
|
+
"ZAM session ID (also the observer log key)"
|
|
6295
|
+
).option("--after <n>", "Only return observations after this sequence").option("--limit <n>", "Maximum observations to return", "100").action(async (opts) => {
|
|
6296
|
+
await withDb2(async (db) => {
|
|
6297
|
+
const session = await db.prepare("SELECT id, execution_context FROM sessions WHERE id = ?").get(opts.session);
|
|
6298
|
+
if (!session) {
|
|
6299
|
+
jsonError(`Session not found: ${opts.session}`);
|
|
6300
|
+
}
|
|
6301
|
+
const after = opts.after === void 0 ? void 0 : parseNonNegativeIntegerOption("after", opts.after);
|
|
6302
|
+
const limit = parseNonNegativeIntegerOption("limit", opts.limit);
|
|
6303
|
+
const observations = readUiObservationLog(opts.session).filter((report) => after === void 0 || report.sequence > after).slice(0, limit);
|
|
6304
|
+
const last = observations[observations.length - 1];
|
|
6305
|
+
jsonOut2({
|
|
6306
|
+
sessionId: opts.session,
|
|
6307
|
+
executionContext: session.execution_context,
|
|
6308
|
+
observationSource: "ui",
|
|
6309
|
+
logExists: uiObservationLogExists(opts.session),
|
|
6310
|
+
after: after ?? null,
|
|
6311
|
+
count: observations.length,
|
|
6312
|
+
nextSequence: last?.sequence ?? after ?? null,
|
|
6313
|
+
observations
|
|
6314
|
+
});
|
|
6315
|
+
});
|
|
6316
|
+
});
|
|
6317
|
+
bridgeCommand.command("get-observations").description("Read UI observer reports for a session (JSON)").requiredOption("--session <id>", "Observer session ID").option("--after <n>", "Only return observations after this sequence").option("--limit <n>", "Maximum observations to return", "100").action((opts) => {
|
|
6318
|
+
try {
|
|
6319
|
+
const after = opts.after === void 0 ? void 0 : parseNonNegativeIntegerOption("after", opts.after);
|
|
6320
|
+
const limit = parseNonNegativeIntegerOption("limit", opts.limit);
|
|
6321
|
+
const observations = readUiObservationLog(opts.session).filter((report) => after === void 0 || report.sequence > after).slice(0, limit);
|
|
6322
|
+
const last = observations[observations.length - 1];
|
|
6323
|
+
jsonOut2({
|
|
6324
|
+
sessionId: opts.session,
|
|
6325
|
+
after: after ?? null,
|
|
6326
|
+
count: observations.length,
|
|
6327
|
+
nextSequence: last?.sequence ?? after ?? null,
|
|
6328
|
+
observations
|
|
6329
|
+
});
|
|
6330
|
+
} catch (err) {
|
|
6331
|
+
jsonError(err.message);
|
|
6332
|
+
}
|
|
6333
|
+
});
|
|
6334
|
+
bridgeCommand.command("observe-ui-snapshot").description(
|
|
6335
|
+
"Analyze a captured UI snapshot with the configured vision LLM (JSON)"
|
|
6336
|
+
).requiredOption("--session <id>", "Observer session ID").requiredOption("--sequence <n>", "Monotonic observation sequence number").requiredOption("--image <path>", "PNG snapshot path").requiredOption("--observed-from <iso>", "Observation window start time").requiredOption("--observed-to <iso>", "Observation window end time").requiredOption("--process-name <name>", "Observed application process name").option("--process-id <n>", "Observed application process ID").option("--window-title <title>", "Observed window title").option("--evidence-ref <ref>", "Evidence reference to put in the report").option("--model <model>", "Override configured LLM model for this request").option("--max-tokens <n>", "Model response token budget").option("--timeout <ms>", "Hard request timeout in milliseconds").option("--redacted", "Mark the snapshot evidence as redacted").option("--write-log", "Append the generated report to the session JSONL").action(async (opts) => {
|
|
6337
|
+
const sequence = parseNonNegativeIntegerOption("sequence", opts.sequence);
|
|
6338
|
+
const processId = opts.processId === void 0 ? void 0 : parseNonNegativeIntegerOption("process-id", opts.processId);
|
|
6339
|
+
const maxTokens = opts.maxTokens === void 0 ? void 0 : parseNonNegativeIntegerOption("max-tokens", opts.maxTokens);
|
|
6340
|
+
const hardTimeoutMs = opts.timeout === void 0 ? void 0 : parseNonNegativeIntegerOption("timeout", opts.timeout);
|
|
6341
|
+
await withDb2(async (db) => {
|
|
6342
|
+
const report = await observeUiSnapshotViaLLM(db, {
|
|
6343
|
+
sessionId: opts.session,
|
|
6344
|
+
sequence,
|
|
6345
|
+
observedFrom: opts.observedFrom,
|
|
6346
|
+
observedTo: opts.observedTo,
|
|
6347
|
+
imagePath: opts.image,
|
|
6348
|
+
application: {
|
|
6349
|
+
processName: opts.processName,
|
|
6350
|
+
processId,
|
|
6351
|
+
windowTitle: opts.windowTitle
|
|
6352
|
+
},
|
|
6353
|
+
evidenceRef: opts.evidenceRef,
|
|
6354
|
+
redacted: opts.redacted === true,
|
|
6355
|
+
model: opts.model,
|
|
6356
|
+
maxTokens,
|
|
6357
|
+
hardTimeoutMs
|
|
6358
|
+
});
|
|
6359
|
+
if (opts.writeLog === true) {
|
|
6360
|
+
appendUiObservationReport(report);
|
|
6361
|
+
}
|
|
6362
|
+
jsonOut2(report);
|
|
6363
|
+
});
|
|
6364
|
+
});
|
|
6365
|
+
function resolveWindowsPowerShell() {
|
|
6366
|
+
try {
|
|
6367
|
+
execFileSync2("where.exe", ["pwsh.exe"], { stdio: "ignore" });
|
|
6368
|
+
return "pwsh";
|
|
6369
|
+
} catch {
|
|
6370
|
+
return "powershell";
|
|
6371
|
+
}
|
|
6372
|
+
}
|
|
6373
|
+
function captureScreenshot(outputPath, hwnd, processName) {
|
|
6374
|
+
const platform = process.platform;
|
|
6375
|
+
if (hwnd && !/^(0x)?[0-9a-fA-F]+$/.test(hwnd)) {
|
|
6376
|
+
throw new Error(`Invalid HWND format: ${hwnd}`);
|
|
6377
|
+
}
|
|
6378
|
+
if (processName && !/^[a-zA-Z0-9\-_.]+$/.test(processName)) {
|
|
6379
|
+
throw new Error(`Invalid process name format: ${processName}`);
|
|
6380
|
+
}
|
|
6381
|
+
if (platform === "win32") {
|
|
6382
|
+
const stdout = execFileSync2(
|
|
6383
|
+
resolveWindowsPowerShell(),
|
|
6384
|
+
[
|
|
6385
|
+
"-NoProfile",
|
|
6386
|
+
"-Command",
|
|
6387
|
+
`
|
|
6388
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
6389
|
+
Add-Type -AssemblyName System.Drawing
|
|
6390
|
+
|
|
6391
|
+
$code = @'
|
|
6392
|
+
using System;
|
|
6393
|
+
using System.Runtime.InteropServices;
|
|
6394
|
+
|
|
6395
|
+
public class Win32 {
|
|
6396
|
+
public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
|
|
6397
|
+
|
|
6398
|
+
[DllImport("user32.dll")]
|
|
6399
|
+
public static extern bool SetProcessDPIAware();
|
|
6400
|
+
|
|
6401
|
+
[DllImport("user32.dll")]
|
|
6402
|
+
public static extern bool SetProcessDpiAwarenessContext(IntPtr dpiContext);
|
|
6403
|
+
|
|
6404
|
+
[DllImport("user32.dll")]
|
|
6405
|
+
public static extern bool GetWindowRect(IntPtr hWnd, out RECT rect);
|
|
6406
|
+
|
|
6407
|
+
[DllImport("user32.dll")]
|
|
6408
|
+
public static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
|
|
6409
|
+
|
|
6410
|
+
[DllImport("user32.dll")]
|
|
6411
|
+
public static extern bool EnumChildWindows(IntPtr hWnd, EnumWindowsProc lpEnumFunc, IntPtr lParam);
|
|
6412
|
+
|
|
6413
|
+
[DllImport("user32.dll")]
|
|
6414
|
+
public static extern bool IsWindowVisible(IntPtr hWnd);
|
|
6415
|
+
|
|
6416
|
+
[DllImport("user32.dll", SetLastError=true)]
|
|
6417
|
+
public static extern int GetWindowText(IntPtr hWnd, System.Text.StringBuilder lpString, int nMaxCount);
|
|
6418
|
+
|
|
6419
|
+
[DllImport("user32.dll", SetLastError=true)]
|
|
6420
|
+
public static extern int GetWindowTextLength(IntPtr hWnd);
|
|
6421
|
+
|
|
6422
|
+
[DllImport("user32.dll")]
|
|
6423
|
+
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
|
|
6424
|
+
|
|
6425
|
+
[DllImport("user32.dll")]
|
|
6426
|
+
public static extern bool SetForegroundWindow(IntPtr hWnd);
|
|
6427
|
+
|
|
6428
|
+
[DllImport("user32.dll")]
|
|
6429
|
+
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
|
|
6430
|
+
|
|
6431
|
+
[DllImport("user32.dll")]
|
|
6432
|
+
public static extern bool IsIconic(IntPtr hWnd);
|
|
6433
|
+
|
|
6434
|
+
[DllImport("user32.dll")]
|
|
6435
|
+
public static extern bool PrintWindow(IntPtr hWnd, IntPtr hdcBlt, uint nFlags);
|
|
6436
|
+
|
|
6437
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
6438
|
+
public struct RECT {
|
|
6439
|
+
public int Left;
|
|
6440
|
+
public int Top;
|
|
6441
|
+
public int Right;
|
|
6442
|
+
public int Bottom;
|
|
6443
|
+
}
|
|
6444
|
+
}
|
|
6445
|
+
'@
|
|
6446
|
+
Add-Type -TypeDefinition $code
|
|
6447
|
+
|
|
6448
|
+
try {
|
|
6449
|
+
# DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = -4. Without this, Windows
|
|
6450
|
+
# can return logical window bounds while PrintWindow renders physical
|
|
6451
|
+
# pixels, causing high-DPI captures to crop the right/bottom edge.
|
|
6452
|
+
[Win32]::SetProcessDpiAwarenessContext([IntPtr](-4)) | Out-Null
|
|
6453
|
+
} catch {
|
|
6454
|
+
try { [Win32]::SetProcessDPIAware() | Out-Null } catch {}
|
|
6455
|
+
}
|
|
6456
|
+
|
|
6457
|
+
function Get-WindowTitle([IntPtr]$hWnd) {
|
|
6458
|
+
$length = [Win32]::GetWindowTextLength($hWnd)
|
|
6459
|
+
$capacity = [Math]::Max(1, $length + 1)
|
|
6460
|
+
$builder = New-Object System.Text.StringBuilder $capacity
|
|
6461
|
+
[Win32]::GetWindowText($hWnd, $builder, $builder.Capacity) | Out-Null
|
|
6462
|
+
$builder.ToString()
|
|
6463
|
+
}
|
|
6464
|
+
|
|
6465
|
+
function Get-WindowProcess([IntPtr]$hWnd) {
|
|
6466
|
+
[uint32]$processId = 0
|
|
6467
|
+
[Win32]::GetWindowThreadProcessId($hWnd, [ref]$processId) | Out-Null
|
|
6468
|
+
if ($processId -eq 0) { return $null }
|
|
6469
|
+
Get-Process -Id $processId -ErrorAction SilentlyContinue
|
|
6470
|
+
}
|
|
6471
|
+
|
|
6472
|
+
function Get-VisibleTopLevelWindows {
|
|
6473
|
+
$script:windowCandidates = New-Object System.Collections.ArrayList
|
|
6474
|
+
$callback = [Win32+EnumWindowsProc]{
|
|
6475
|
+
param([IntPtr]$candidateHwnd, [IntPtr]$lParam)
|
|
6476
|
+
if ([Win32]::IsWindowVisible($candidateHwnd)) {
|
|
6477
|
+
$candidateRect = New-Object Win32+RECT
|
|
6478
|
+
if ([Win32]::GetWindowRect($candidateHwnd, [ref]$candidateRect)) {
|
|
6479
|
+
$candidateWidth = $candidateRect.Right - $candidateRect.Left
|
|
6480
|
+
$candidateHeight = $candidateRect.Bottom - $candidateRect.Top
|
|
6481
|
+
if ($candidateWidth -gt 0 -and $candidateHeight -gt 0) {
|
|
6482
|
+
[void]$script:windowCandidates.Add($candidateHwnd)
|
|
6483
|
+
}
|
|
6484
|
+
}
|
|
6485
|
+
}
|
|
6486
|
+
return $true
|
|
6487
|
+
}
|
|
6488
|
+
[Win32]::EnumWindows($callback, [IntPtr]::Zero) | Out-Null
|
|
6489
|
+
$windows = $script:windowCandidates
|
|
6490
|
+
Remove-Variable -Name windowCandidates -Scope Script -ErrorAction SilentlyContinue
|
|
6491
|
+
$windows
|
|
6492
|
+
}
|
|
6493
|
+
|
|
6494
|
+
function Find-TopLevelWindowByProcessName([string]$name) {
|
|
6495
|
+
foreach ($candidateHwnd in Get-VisibleTopLevelWindows) {
|
|
6496
|
+
$candidateProcess = Get-WindowProcess $candidateHwnd
|
|
6497
|
+
if ($candidateProcess -and $candidateProcess.ProcessName -ieq $name) {
|
|
6498
|
+
return [pscustomobject]@{
|
|
6499
|
+
Hwnd = $candidateHwnd
|
|
6500
|
+
MatchedBy = "process-top-level-window"
|
|
6501
|
+
}
|
|
6502
|
+
}
|
|
6503
|
+
|
|
6504
|
+
$script:desiredChildProcessName = $name
|
|
6505
|
+
$script:foundChildProcessWindow = $false
|
|
6506
|
+
$childCallback = [Win32+EnumWindowsProc]{
|
|
6507
|
+
param([IntPtr]$childHwnd, [IntPtr]$lParam)
|
|
6508
|
+
$childProcess = Get-WindowProcess $childHwnd
|
|
6509
|
+
if ($childProcess -and $childProcess.ProcessName -ieq $script:desiredChildProcessName) {
|
|
6510
|
+
$script:foundChildProcessWindow = $true
|
|
6511
|
+
return $false
|
|
6512
|
+
}
|
|
6513
|
+
return $true
|
|
6514
|
+
}
|
|
6515
|
+
[Win32]::EnumChildWindows($candidateHwnd, $childCallback, [IntPtr]::Zero) | Out-Null
|
|
6516
|
+
$foundChild = $script:foundChildProcessWindow
|
|
6517
|
+
Remove-Variable -Name desiredChildProcessName -Scope Script -ErrorAction SilentlyContinue
|
|
6518
|
+
Remove-Variable -Name foundChildProcessWindow -Scope Script -ErrorAction SilentlyContinue
|
|
6519
|
+
|
|
6520
|
+
if ($foundChild) {
|
|
6521
|
+
return [pscustomobject]@{
|
|
6522
|
+
Hwnd = $candidateHwnd
|
|
6523
|
+
MatchedBy = "process-child-window"
|
|
6524
|
+
}
|
|
6525
|
+
}
|
|
6526
|
+
}
|
|
6527
|
+
|
|
6528
|
+
return $null
|
|
6529
|
+
}
|
|
6530
|
+
|
|
6531
|
+
function New-CaptureTarget([IntPtr]$hWnd, [string]$matchedBy) {
|
|
6532
|
+
$target = [ordered]@{
|
|
6533
|
+
requestedHwnd = if ($targetHwnd -ne '') { $targetHwnd } else { $null }
|
|
6534
|
+
requestedProcessName = if ($processName -ne '') { $processName } else { $null }
|
|
6535
|
+
matchedBy = $matchedBy
|
|
6536
|
+
hwnd = $null
|
|
6537
|
+
processId = $null
|
|
6538
|
+
processName = $null
|
|
6539
|
+
windowTitle = $null
|
|
6540
|
+
bounds = $null
|
|
6541
|
+
}
|
|
6542
|
+
|
|
6543
|
+
if ($hWnd -ne [IntPtr]::Zero) {
|
|
6544
|
+
$target.hwnd = $hWnd.ToInt64()
|
|
6545
|
+
$target.windowTitle = Get-WindowTitle $hWnd
|
|
6546
|
+
|
|
6547
|
+
$windowProcess = Get-WindowProcess $hWnd
|
|
6548
|
+
if ($windowProcess) {
|
|
6549
|
+
$target.processId = $windowProcess.Id
|
|
6550
|
+
$target.processName = $windowProcess.ProcessName
|
|
6551
|
+
}
|
|
6552
|
+
|
|
6553
|
+
$targetRect = New-Object Win32+RECT
|
|
6554
|
+
if ([Win32]::GetWindowRect($hWnd, [ref]$targetRect)) {
|
|
6555
|
+
$target.bounds = [ordered]@{
|
|
6556
|
+
left = $targetRect.Left
|
|
6557
|
+
top = $targetRect.Top
|
|
6558
|
+
right = $targetRect.Right
|
|
6559
|
+
bottom = $targetRect.Bottom
|
|
6560
|
+
width = $targetRect.Right - $targetRect.Left
|
|
6561
|
+
height = $targetRect.Bottom - $targetRect.Top
|
|
6562
|
+
}
|
|
6563
|
+
}
|
|
6564
|
+
}
|
|
6565
|
+
|
|
6566
|
+
$target
|
|
6567
|
+
}
|
|
6568
|
+
|
|
6569
|
+
function Write-CaptureResult([string]$method, [IntPtr]$hWnd, [string]$matchedBy) {
|
|
6570
|
+
$result = [ordered]@{
|
|
6571
|
+
method = $method
|
|
6572
|
+
target = New-CaptureTarget $hWnd $matchedBy
|
|
6573
|
+
}
|
|
6574
|
+
Write-Output ("CAPTURE_RESULT:" + ($result | ConvertTo-Json -Compress -Depth 6))
|
|
6575
|
+
}
|
|
6576
|
+
|
|
6577
|
+
$hwndVal = [IntPtr]::Zero
|
|
6578
|
+
$matchedBy = "fullscreen-fallback"
|
|
6579
|
+
$targetHwnd = '${hwnd || ""}'
|
|
6580
|
+
$processName = '${processName || ""}'
|
|
6581
|
+
|
|
6582
|
+
if ($targetHwnd -ne '') {
|
|
6583
|
+
if ($targetHwnd.StartsWith("0x")) {
|
|
6584
|
+
$hwndVal = [IntPtr][Convert]::ToInt64($targetHwnd, 16)
|
|
6585
|
+
} else {
|
|
6586
|
+
$hwndVal = [IntPtr][Convert]::ToInt64($targetHwnd, 10)
|
|
6587
|
+
}
|
|
6588
|
+
$matchedBy = "hwnd"
|
|
6589
|
+
} elseif ($processName -ne '') {
|
|
6590
|
+
$proc = Get-Process -Name $processName -ErrorAction SilentlyContinue | Where-Object {$_.MainWindowHandle -ne 0} | Select-Object -First 1
|
|
6591
|
+
if ($proc) {
|
|
6592
|
+
$hwndVal = $proc.MainWindowHandle
|
|
6593
|
+
$matchedBy = "process-main-window"
|
|
6594
|
+
} else {
|
|
6595
|
+
$proc = Get-Process -Name $processName -ErrorAction SilentlyContinue | Select-Object -First 1
|
|
6596
|
+
if ($proc) {
|
|
6597
|
+
$hwndVal = $proc.MainWindowHandle
|
|
6598
|
+
$matchedBy = "process-zero-main-window"
|
|
6599
|
+
}
|
|
6600
|
+
}
|
|
6601
|
+
|
|
6602
|
+
if ($hwndVal -eq [IntPtr]::Zero) {
|
|
6603
|
+
$windowMatch = Find-TopLevelWindowByProcessName $processName
|
|
6604
|
+
if ($windowMatch) {
|
|
6605
|
+
$hwndVal = $windowMatch.Hwnd
|
|
6606
|
+
$matchedBy = $windowMatch.MatchedBy
|
|
6607
|
+
}
|
|
6608
|
+
}
|
|
6609
|
+
}
|
|
6610
|
+
|
|
6611
|
+
if ($hwndVal -ne [IntPtr]::Zero) {
|
|
6612
|
+
if ([Win32]::IsIconic($hwndVal)) {
|
|
6613
|
+
[Win32]::ShowWindow($hwndVal, 9) | Out-Null # SW_RESTORE = 9
|
|
6614
|
+
Start-Sleep -Milliseconds 250
|
|
6615
|
+
}
|
|
6616
|
+
|
|
6617
|
+
$rect = New-Object Win32+RECT
|
|
6618
|
+
if ([Win32]::GetWindowRect($hwndVal, [ref]$rect)) {
|
|
6619
|
+
$width = $rect.Right - $rect.Left
|
|
6620
|
+
$height = $rect.Bottom - $rect.Top
|
|
6621
|
+
if ($width -gt 0 -and $height -gt 0) {
|
|
6622
|
+
$bitmap = New-Object System.Drawing.Bitmap($width, $height)
|
|
6623
|
+
$graphics = [System.Drawing.Graphics]::FromImage($bitmap)
|
|
6624
|
+
# PrintWindow renders the target window directly, regardless of
|
|
6625
|
+
# z-order, so an occluded or background window is still captured
|
|
6626
|
+
# correctly. SetForegroundWindow from a background process is
|
|
6627
|
+
# blocked by Windows, so CopyFromScreen would grab whatever sits
|
|
6628
|
+
# on top. PW_RENDERFULLCONTENT (0x2) handles modern/UWP windows.
|
|
6629
|
+
$hdc = $graphics.GetHdc()
|
|
6630
|
+
$printed = [Win32]::PrintWindow($hwndVal, $hdc, 2)
|
|
6631
|
+
$graphics.ReleaseHdc($hdc)
|
|
6632
|
+
$method = "printwindow"
|
|
6633
|
+
|
|
6634
|
+
# Black-frame guard: PrintWindow can return a near-black frame on
|
|
6635
|
+
# some hardware-accelerated / DirectComposition surfaces. Sample a
|
|
6636
|
+
# sparse grid; if it is essentially black, fall back to a foreground
|
|
6637
|
+
# CopyFromScreen grab so the capture self-heals on those drivers.
|
|
6638
|
+
$needFallback = -not $printed
|
|
6639
|
+
if (-not $needFallback) {
|
|
6640
|
+
$sum = 0.0
|
|
6641
|
+
$cnt = 0
|
|
6642
|
+
$stepX = [Math]::Max(1, [int]($width / 12))
|
|
6643
|
+
$stepY = [Math]::Max(1, [int]($height / 12))
|
|
6644
|
+
for ($sy = 0; $sy -lt $height; $sy += $stepY) {
|
|
6645
|
+
for ($sx = 0; $sx -lt $width; $sx += $stepX) {
|
|
6646
|
+
$px = $bitmap.GetPixel($sx, $sy)
|
|
6647
|
+
$sum += ($px.R + $px.G + $px.B) / 3.0
|
|
6648
|
+
$cnt++
|
|
6649
|
+
}
|
|
6650
|
+
}
|
|
6651
|
+
if ($cnt -gt 0 -and ($sum / $cnt) -lt 6) { $needFallback = $true }
|
|
6652
|
+
}
|
|
6653
|
+
|
|
6654
|
+
if ($needFallback) {
|
|
6655
|
+
[Win32]::SetForegroundWindow($hwndVal) | Out-Null
|
|
6656
|
+
Start-Sleep -Milliseconds 250
|
|
6657
|
+
$graphics.CopyFromScreen($rect.Left, $rect.Top, 0, 0, $bitmap.Size)
|
|
6658
|
+
$method = "copyfromscreen"
|
|
6659
|
+
}
|
|
6660
|
+
$bitmap.Save('${outputPath.replace(/\\/g, "\\\\")}', [System.Drawing.Imaging.ImageFormat]::Png)
|
|
6661
|
+
$graphics.Dispose()
|
|
6662
|
+
$bitmap.Dispose()
|
|
6663
|
+
Write-CaptureResult $method $hwndVal $matchedBy
|
|
6664
|
+
exit 0
|
|
6665
|
+
}
|
|
6666
|
+
}
|
|
6667
|
+
}
|
|
6668
|
+
|
|
6669
|
+
# Fallback: full primary screen
|
|
6670
|
+
$screen = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds
|
|
6671
|
+
$bitmap = New-Object System.Drawing.Bitmap($screen.Width, $screen.Height)
|
|
6672
|
+
$graphics = [System.Drawing.Graphics]::FromImage($bitmap)
|
|
6673
|
+
$graphics.CopyFromScreen($screen.Location, [System.Drawing.Point]::Empty, $screen.Size)
|
|
6674
|
+
$bitmap.Save('${outputPath.replace(/\\/g, "\\\\")}', [System.Drawing.Imaging.ImageFormat]::Png)
|
|
6675
|
+
$graphics.Dispose()
|
|
6676
|
+
$bitmap.Dispose()
|
|
6677
|
+
Write-CaptureResult "fullscreen" $hwndVal $matchedBy
|
|
6678
|
+
`.trim()
|
|
6679
|
+
],
|
|
6680
|
+
{ stdio: "pipe", encoding: "utf8" }
|
|
6681
|
+
);
|
|
6682
|
+
const resultMatch = /CAPTURE_RESULT:(\{.*\})/.exec(stdout ?? "");
|
|
6683
|
+
if (resultMatch) {
|
|
6684
|
+
const parsed = JSON.parse(resultMatch[1]);
|
|
6685
|
+
return parsed;
|
|
6686
|
+
}
|
|
6687
|
+
const methodMatch = /CAPTURE_METHOD:(\w+)/.exec(stdout ?? "");
|
|
6688
|
+
return {
|
|
6689
|
+
method: methodMatch ? methodMatch[1] : "unknown",
|
|
6690
|
+
target: null
|
|
6691
|
+
};
|
|
6692
|
+
} else if (platform === "darwin") {
|
|
6693
|
+
if (hwnd) {
|
|
6694
|
+
const parsedHwnd = hwnd.startsWith("0x") ? parseInt(hwnd, 16) : parseInt(hwnd, 10);
|
|
6695
|
+
execFileSync2("screencapture", ["-l", String(parsedHwnd), outputPath], {
|
|
6696
|
+
stdio: "pipe"
|
|
6697
|
+
});
|
|
6698
|
+
return { method: "screencapture-window", target: null };
|
|
6699
|
+
} else if (processName) {
|
|
6700
|
+
try {
|
|
6701
|
+
const windowId = execFileSync2(
|
|
6702
|
+
"osascript",
|
|
6703
|
+
[
|
|
6704
|
+
"-e",
|
|
6705
|
+
`tell application "System Events" to get id of window 1 of process "${processName}"`
|
|
6706
|
+
],
|
|
6707
|
+
{ encoding: "utf8", stdio: ["pipe", "pipe", "ignore"] }
|
|
6708
|
+
).trim();
|
|
6709
|
+
if (windowId && /^\d+$/.test(windowId)) {
|
|
6710
|
+
execFileSync2("screencapture", ["-l", windowId, outputPath], {
|
|
6711
|
+
stdio: "pipe"
|
|
6712
|
+
});
|
|
6713
|
+
return { method: "screencapture-window", target: null };
|
|
6714
|
+
}
|
|
6715
|
+
} catch {
|
|
6716
|
+
}
|
|
6717
|
+
execFileSync2("screencapture", ["-x", outputPath], { stdio: "pipe" });
|
|
6718
|
+
return { method: "screencapture-full", target: null };
|
|
6719
|
+
} else {
|
|
6720
|
+
execFileSync2("screencapture", ["-x", outputPath], { stdio: "pipe" });
|
|
6721
|
+
return { method: "screencapture-full", target: null };
|
|
6722
|
+
}
|
|
6723
|
+
} else {
|
|
6724
|
+
throw new Error(
|
|
6725
|
+
`Screen capture not supported on platform: ${platform}. Use zam-observer or provide --image.`
|
|
6726
|
+
);
|
|
5943
6727
|
}
|
|
5944
|
-
}
|
|
5945
|
-
bridgeCommand.command("
|
|
5946
|
-
"Poll live UI observer watch reports for a ZAM learning session (JSON)"
|
|
5947
|
-
).requiredOption(
|
|
5948
|
-
"--session <id>",
|
|
5949
|
-
"ZAM session ID (also the observer log key)"
|
|
5950
|
-
).option("--after <n>", "Only return observations after this sequence").option("--limit <n>", "Maximum observations to return", "100").action(async (opts) => {
|
|
6728
|
+
}
|
|
6729
|
+
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) => {
|
|
5951
6730
|
await withDb2(async (db) => {
|
|
5952
|
-
const
|
|
5953
|
-
|
|
5954
|
-
|
|
6731
|
+
const policy = await resolveObserverPolicy(db);
|
|
6732
|
+
const permission = {
|
|
6733
|
+
scope: policy.scope,
|
|
6734
|
+
consent: policy.consent,
|
|
6735
|
+
retention: policy.retention
|
|
6736
|
+
};
|
|
6737
|
+
const isProvided = Boolean(opts.image);
|
|
6738
|
+
if (!isProvided) {
|
|
6739
|
+
const pre = decidePreCapture(policy, {
|
|
6740
|
+
hasExplicitTarget: Boolean(opts.hwnd || opts.processName),
|
|
6741
|
+
requestedProcessName: opts.processName ?? null
|
|
6742
|
+
});
|
|
6743
|
+
if (!pre.allowed) {
|
|
6744
|
+
jsonOut2({
|
|
6745
|
+
sessionId: opts.session ?? null,
|
|
6746
|
+
granted: false,
|
|
6747
|
+
denied: true,
|
|
6748
|
+
denialReason: pre.denialReason,
|
|
6749
|
+
reason: pre.reason,
|
|
6750
|
+
capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6751
|
+
platform: process.platform,
|
|
6752
|
+
permission: { ...permission, granted: false }
|
|
6753
|
+
});
|
|
6754
|
+
return;
|
|
6755
|
+
}
|
|
5955
6756
|
}
|
|
5956
|
-
const
|
|
5957
|
-
const
|
|
5958
|
-
|
|
5959
|
-
|
|
6757
|
+
const outputPath = opts.image ?? opts.output ?? join12(tmpdir(), `zam-capture-${randomBytes(4).toString("hex")}.png`);
|
|
6758
|
+
const captureResult = isProvided ? { method: "provided", target: null } : captureScreenshot(outputPath, opts.hwnd, opts.processName);
|
|
6759
|
+
if (!isProvided) {
|
|
6760
|
+
const post = decidePostCapture(policy, {
|
|
6761
|
+
method: captureResult.method,
|
|
6762
|
+
processName: captureResult.target?.processName ?? null,
|
|
6763
|
+
windowTitle: captureResult.target?.windowTitle ?? null
|
|
6764
|
+
});
|
|
6765
|
+
if (!post.allowed) {
|
|
6766
|
+
if (!opts.output) {
|
|
6767
|
+
try {
|
|
6768
|
+
rmSync2(outputPath, { force: true });
|
|
6769
|
+
} catch {
|
|
6770
|
+
}
|
|
6771
|
+
}
|
|
6772
|
+
jsonOut2({
|
|
6773
|
+
sessionId: opts.session ?? null,
|
|
6774
|
+
granted: false,
|
|
6775
|
+
denied: true,
|
|
6776
|
+
denialReason: post.denialReason,
|
|
6777
|
+
reason: post.reason,
|
|
6778
|
+
capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6779
|
+
platform: process.platform,
|
|
6780
|
+
permission: { ...permission, granted: false }
|
|
6781
|
+
});
|
|
6782
|
+
return;
|
|
6783
|
+
}
|
|
6784
|
+
}
|
|
6785
|
+
const imageBytes = readFileSync10(outputPath);
|
|
6786
|
+
const base64 = imageBytes.toString("base64");
|
|
5960
6787
|
jsonOut2({
|
|
5961
|
-
sessionId: opts.session,
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
6788
|
+
sessionId: opts.session ?? null,
|
|
6789
|
+
granted: true,
|
|
6790
|
+
imagePath: outputPath,
|
|
6791
|
+
base64,
|
|
6792
|
+
mimeType: "image/png",
|
|
6793
|
+
captureMethod: captureResult.method,
|
|
6794
|
+
captureTarget: captureResult.target,
|
|
6795
|
+
capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6796
|
+
platform: process.platform,
|
|
6797
|
+
permission: { ...permission, granted: true }
|
|
5969
6798
|
});
|
|
5970
6799
|
});
|
|
5971
6800
|
});
|
|
5972
|
-
bridgeCommand.command("get-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
const
|
|
5977
|
-
const last = observations[observations.length - 1];
|
|
6801
|
+
bridgeCommand.command("get-observer-policy").description(
|
|
6802
|
+
"Report the resolved observer policy so an agent can check before capturing (JSON)"
|
|
6803
|
+
).action(async () => {
|
|
6804
|
+
await withDb2(async (db) => {
|
|
6805
|
+
const policy = await resolveObserverPolicy(db);
|
|
5978
6806
|
jsonOut2({
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
6807
|
+
scope: policy.scope,
|
|
6808
|
+
consent: policy.consent,
|
|
6809
|
+
retention: policy.retention,
|
|
6810
|
+
allowlist: policy.allowlist,
|
|
6811
|
+
denylist: policy.denylist,
|
|
6812
|
+
redactWindowTitles: policy.redactWindowTitles,
|
|
6813
|
+
audioOptIn: policy.audioOptIn,
|
|
6814
|
+
builtInSensitiveAlwaysRefused: true,
|
|
6815
|
+
builtInSensitiveMatchers: [...BUILT_IN_SENSITIVE_MATCHERS]
|
|
5984
6816
|
});
|
|
5985
|
-
}
|
|
5986
|
-
jsonError(err.message);
|
|
5987
|
-
}
|
|
6817
|
+
});
|
|
5988
6818
|
});
|
|
5989
|
-
bridgeCommand.command("
|
|
5990
|
-
"
|
|
5991
|
-
).
|
|
5992
|
-
const sequence = parseNonNegativeIntegerOption("sequence", opts.sequence);
|
|
5993
|
-
const processId = opts.processId === void 0 ? void 0 : parseNonNegativeIntegerOption("process-id", opts.processId);
|
|
5994
|
-
const maxTokens = opts.maxTokens === void 0 ? void 0 : parseNonNegativeIntegerOption("max-tokens", opts.maxTokens);
|
|
5995
|
-
const hardTimeoutMs = opts.timeout === void 0 ? void 0 : parseNonNegativeIntegerOption("timeout", opts.timeout);
|
|
6819
|
+
bridgeCommand.command("sync-observer-policy").description(
|
|
6820
|
+
"Write the resolved observer policy to the native sidecar file (JSON)"
|
|
6821
|
+
).action(async () => {
|
|
5996
6822
|
await withDb2(async (db) => {
|
|
5997
|
-
const
|
|
5998
|
-
|
|
5999
|
-
sequence,
|
|
6000
|
-
observedFrom: opts.observedFrom,
|
|
6001
|
-
observedTo: opts.observedTo,
|
|
6002
|
-
imagePath: opts.image,
|
|
6003
|
-
application: {
|
|
6004
|
-
processName: opts.processName,
|
|
6005
|
-
processId,
|
|
6006
|
-
windowTitle: opts.windowTitle
|
|
6007
|
-
},
|
|
6008
|
-
evidenceRef: opts.evidenceRef,
|
|
6009
|
-
redacted: opts.redacted === true,
|
|
6010
|
-
model: opts.model,
|
|
6011
|
-
maxTokens,
|
|
6012
|
-
hardTimeoutMs
|
|
6013
|
-
});
|
|
6014
|
-
if (opts.writeLog === true) {
|
|
6015
|
-
appendUiObservationReport(report);
|
|
6016
|
-
}
|
|
6017
|
-
jsonOut2(report);
|
|
6823
|
+
const { path, policy } = await syncObserverSidecarPolicy(db);
|
|
6824
|
+
jsonOut2({ synced: true, path, policy });
|
|
6018
6825
|
});
|
|
6019
6826
|
});
|
|
6020
6827
|
bridgeCommand.command("check-llm").description("Check if LLM is enabled and online (JSON)").action(async () => {
|
|
@@ -6698,26 +7505,26 @@ async function setupTurso(urlArg, tokenArg, mode) {
|
|
|
6698
7505
|
|
|
6699
7506
|
// src/cli/commands/git-sync.ts
|
|
6700
7507
|
import { execSync as execSync4 } from "child_process";
|
|
6701
|
-
import { chmodSync as chmodSync2, existsSync as existsSync13, writeFileSync as
|
|
6702
|
-
import { join as
|
|
7508
|
+
import { chmodSync as chmodSync2, existsSync as existsSync13, writeFileSync as writeFileSync6 } from "fs";
|
|
7509
|
+
import { join as join13 } from "path";
|
|
6703
7510
|
import { Command as Command5 } from "commander";
|
|
6704
7511
|
function installHook2() {
|
|
6705
|
-
const gitDir =
|
|
7512
|
+
const gitDir = join13(process.cwd(), ".git");
|
|
6706
7513
|
if (!existsSync13(gitDir)) {
|
|
6707
7514
|
console.error(
|
|
6708
7515
|
"Error: Current directory is not the root of a Git repository."
|
|
6709
7516
|
);
|
|
6710
7517
|
process.exit(1);
|
|
6711
7518
|
}
|
|
6712
|
-
const hooksDir =
|
|
6713
|
-
const hookPath =
|
|
7519
|
+
const hooksDir = join13(gitDir, "hooks");
|
|
7520
|
+
const hookPath = join13(hooksDir, "post-commit");
|
|
6714
7521
|
const hookContent = `#!/bin/sh
|
|
6715
7522
|
# ZAM Spaced Repetition Auto-Stale Hook
|
|
6716
7523
|
# Triggered automatically on git commits to decay modified concept cards.
|
|
6717
7524
|
zam git-sync --commit HEAD --quiet
|
|
6718
7525
|
`;
|
|
6719
7526
|
try {
|
|
6720
|
-
|
|
7527
|
+
writeFileSync6(hookPath, hookContent, { encoding: "utf-8", flag: "w" });
|
|
6721
7528
|
try {
|
|
6722
7529
|
chmodSync2(hookPath, "755");
|
|
6723
7530
|
} catch (_e) {
|
|
@@ -6819,7 +7626,7 @@ ZAM Auto-Stale Complete: Scanned ${changedFiles.length} file(s).`
|
|
|
6819
7626
|
});
|
|
6820
7627
|
|
|
6821
7628
|
// src/cli/commands/goal.ts
|
|
6822
|
-
import { existsSync as existsSync14, mkdirSync as
|
|
7629
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync8 } from "fs";
|
|
6823
7630
|
import { resolve as resolve3 } from "path";
|
|
6824
7631
|
import { input as input2 } from "@inquirer/prompts";
|
|
6825
7632
|
import { Command as Command6 } from "commander";
|
|
@@ -6945,7 +7752,7 @@ ${"\u2500".repeat(50)}`);
|
|
|
6945
7752
|
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
7753
|
const goalsDir = await resolveGoalsDir();
|
|
6947
7754
|
if (!existsSync14(goalsDir)) {
|
|
6948
|
-
|
|
7755
|
+
mkdirSync8(goalsDir, { recursive: true });
|
|
6949
7756
|
}
|
|
6950
7757
|
let slug = opts.slug;
|
|
6951
7758
|
let title = opts.title;
|
|
@@ -7004,9 +7811,9 @@ goalCommand.command("status <slug> <status>").description("Update a goal's statu
|
|
|
7004
7811
|
});
|
|
7005
7812
|
|
|
7006
7813
|
// src/cli/commands/init.ts
|
|
7007
|
-
import { existsSync as existsSync15, mkdirSync as
|
|
7814
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync9, writeFileSync as writeFileSync7 } from "fs";
|
|
7008
7815
|
import { homedir as homedir9 } from "os";
|
|
7009
|
-
import { join as
|
|
7816
|
+
import { join as join14 } from "path";
|
|
7010
7817
|
import { confirm, input as input3 } from "@inquirer/prompts";
|
|
7011
7818
|
import { Command as Command7 } from "commander";
|
|
7012
7819
|
var HOME2 = homedir9();
|
|
@@ -7014,12 +7821,12 @@ function printLine(char = "\u2550", len = 60, color = "\x1B[36m") {
|
|
|
7014
7821
|
console.log(`${color}${char.repeat(len)}\x1B[0m`);
|
|
7015
7822
|
}
|
|
7016
7823
|
function bootstrapSandboxWorkspace(workspaceDir) {
|
|
7017
|
-
|
|
7018
|
-
|
|
7019
|
-
|
|
7020
|
-
const worldviewFile =
|
|
7824
|
+
mkdirSync9(join14(workspaceDir, "beliefs"), { recursive: true });
|
|
7825
|
+
mkdirSync9(join14(workspaceDir, "goals"), { recursive: true });
|
|
7826
|
+
mkdirSync9(join14(workspaceDir, "skills"), { recursive: true });
|
|
7827
|
+
const worldviewFile = join14(workspaceDir, "beliefs", "worldview.md");
|
|
7021
7828
|
if (!existsSync15(worldviewFile)) {
|
|
7022
|
-
|
|
7829
|
+
writeFileSync7(
|
|
7023
7830
|
worldviewFile,
|
|
7024
7831
|
`# Personal Worldview
|
|
7025
7832
|
|
|
@@ -7031,9 +7838,9 @@ Here, I declare the core concepts and principles I want to master.
|
|
|
7031
7838
|
"utf8"
|
|
7032
7839
|
);
|
|
7033
7840
|
}
|
|
7034
|
-
const goalsFile =
|
|
7841
|
+
const goalsFile = join14(workspaceDir, "goals", "goals.md");
|
|
7035
7842
|
if (!existsSync15(goalsFile)) {
|
|
7036
|
-
|
|
7843
|
+
writeFileSync7(
|
|
7037
7844
|
goalsFile,
|
|
7038
7845
|
`# Personal Goals
|
|
7039
7846
|
|
|
@@ -7055,7 +7862,7 @@ var initCommand = new Command7("init").description("Launch the guided interactiv
|
|
|
7055
7862
|
);
|
|
7056
7863
|
printLine();
|
|
7057
7864
|
console.log("\n\x1B[1m[1/5] Setting up Local Workspace Sandbox\x1B[0m");
|
|
7058
|
-
const defaultWorkspace =
|
|
7865
|
+
const defaultWorkspace = join14(HOME2, "Documents", "zam");
|
|
7059
7866
|
const workspacePath = await input3({
|
|
7060
7867
|
message: "Choose your ZAM workspace directory:",
|
|
7061
7868
|
default: defaultWorkspace
|
|
@@ -7752,10 +8559,10 @@ ${"\u2550".repeat(50)}`);
|
|
|
7752
8559
|
});
|
|
7753
8560
|
|
|
7754
8561
|
// 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
|
|
8562
|
+
import { execFileSync as execFileSync3, execSync as execSync5 } from "child_process";
|
|
8563
|
+
import { unlinkSync, writeFileSync as writeFileSync8 } from "fs";
|
|
8564
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
8565
|
+
import { basename as basename3, join as join15 } from "path";
|
|
7759
8566
|
import { Command as Command9 } from "commander";
|
|
7760
8567
|
function isPowerShellShell(shell) {
|
|
7761
8568
|
return shell === "pwsh" || shell === "powershell";
|
|
@@ -7906,11 +8713,23 @@ monitorCommand.command("status").description("Show monitoring status for a sessi
|
|
|
7906
8713
|
console.log(` To: ${result.timeSpan.end}`);
|
|
7907
8714
|
}
|
|
7908
8715
|
});
|
|
8716
|
+
function selectWindowsExecutable(results, pathext = process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD") {
|
|
8717
|
+
if (results.length === 0) return null;
|
|
8718
|
+
const extensions = pathext.split(";").map((ext) => ext.trim().toLowerCase()).filter(Boolean);
|
|
8719
|
+
const runnable = results.find(
|
|
8720
|
+
(result) => extensions.some((ext) => result.toLowerCase().endsWith(ext))
|
|
8721
|
+
);
|
|
8722
|
+
return runnable ?? results[0];
|
|
8723
|
+
}
|
|
7909
8724
|
function findExecutable(command) {
|
|
7910
8725
|
try {
|
|
7911
8726
|
const lookup = process.platform === "win32" ? `where.exe ${command}` : `command -v ${command}`;
|
|
7912
|
-
const
|
|
7913
|
-
|
|
8727
|
+
const results = execSync5(lookup, { encoding: "utf-8" }).split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
8728
|
+
if (results.length === 0) return null;
|
|
8729
|
+
if (process.platform === "win32") {
|
|
8730
|
+
return selectWindowsExecutable(results);
|
|
8731
|
+
}
|
|
8732
|
+
return results[0];
|
|
7914
8733
|
} catch {
|
|
7915
8734
|
return null;
|
|
7916
8735
|
}
|
|
@@ -7920,8 +8739,8 @@ function resolveZamInvocation(shell) {
|
|
|
7920
8739
|
if (installed) {
|
|
7921
8740
|
return isPowerShellShell(shell) ? `& ${psSingleQuoted2(installed)}` : installed;
|
|
7922
8741
|
}
|
|
7923
|
-
const projectRoot =
|
|
7924
|
-
const cliSource =
|
|
8742
|
+
const projectRoot = join15(import.meta.dirname, "..", "..", "..");
|
|
8743
|
+
const cliSource = join15(projectRoot, "src/cli/index.ts");
|
|
7925
8744
|
if (isPowerShellShell(shell)) {
|
|
7926
8745
|
return `& npx --prefix ${psSingleQuoted2(projectRoot)} tsx ${psSingleQuoted2(cliSource)}`;
|
|
7927
8746
|
}
|
|
@@ -8011,9 +8830,9 @@ end tell` : `tell application "Terminal"
|
|
|
8011
8830
|
activate
|
|
8012
8831
|
do script "${escaped}"
|
|
8013
8832
|
end tell`;
|
|
8014
|
-
const tmpFile =
|
|
8833
|
+
const tmpFile = join15(tmpdir2(), `zam-monitor-${sessionId}.scpt`);
|
|
8015
8834
|
try {
|
|
8016
|
-
|
|
8835
|
+
writeFileSync8(tmpFile, appleScript);
|
|
8017
8836
|
execSync5(`osascript ${JSON.stringify(tmpFile)}`, { stdio: "ignore" });
|
|
8018
8837
|
console.log(
|
|
8019
8838
|
`Opened ${useIterm ? "iTerm2" : "Terminal.app"} window with monitoring for session ${sessionId}`
|
|
@@ -8040,9 +8859,10 @@ function openWindowsPowerShell(shellSetup, sessionId, dir, requestedShell) {
|
|
|
8040
8859
|
`-FilePath ${psSingleQuoted2(executable)}`,
|
|
8041
8860
|
`-ArgumentList @('-NoExit','-NoProfile','-Command',${psSingleQuoted2(shellSetup)})`
|
|
8042
8861
|
].join(" ");
|
|
8862
|
+
const launcher = findExecutable("pwsh.exe") ? "pwsh.exe" : "powershell.exe";
|
|
8043
8863
|
try {
|
|
8044
|
-
|
|
8045
|
-
|
|
8864
|
+
execFileSync3(
|
|
8865
|
+
launcher,
|
|
8046
8866
|
["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", startCommand],
|
|
8047
8867
|
{
|
|
8048
8868
|
stdio: "ignore"
|
|
@@ -8061,10 +8881,73 @@ Run this manually in a new PowerShell terminal:
|
|
|
8061
8881
|
}
|
|
8062
8882
|
}
|
|
8063
8883
|
|
|
8884
|
+
// src/cli/commands/observer.ts
|
|
8885
|
+
import { Command as Command10 } from "commander";
|
|
8886
|
+
var observerCommand = new Command10("observer").description(
|
|
8887
|
+
"Configure what the UI observer may capture (Layer 2 policy)"
|
|
8888
|
+
);
|
|
8889
|
+
function applyObserverListChange(current, entry, op) {
|
|
8890
|
+
const normalized = entry.trim().toLowerCase();
|
|
8891
|
+
const list = parseObserverList(current);
|
|
8892
|
+
const next = op === "add" ? [.../* @__PURE__ */ new Set([...list, normalized])] : list.filter((item) => item !== normalized);
|
|
8893
|
+
return next.join(",");
|
|
8894
|
+
}
|
|
8895
|
+
observerCommand.command("status").description("Show the active observer policy").option("--json", "Output as JSON").action(async (opts) => {
|
|
8896
|
+
await withDb(async (db) => {
|
|
8897
|
+
const policy = await resolveObserverPolicy(db);
|
|
8898
|
+
if (opts.json) {
|
|
8899
|
+
console.log(JSON.stringify(policy, null, 2));
|
|
8900
|
+
return;
|
|
8901
|
+
}
|
|
8902
|
+
console.log("Observer policy:\n");
|
|
8903
|
+
console.log(` Scope: ${policy.scope}`);
|
|
8904
|
+
console.log(` Consent: ${policy.consent}`);
|
|
8905
|
+
console.log(` Retention: ${policy.retention}`);
|
|
8906
|
+
console.log(
|
|
8907
|
+
` Allowlist: ${policy.allowlist.length ? policy.allowlist.join(", ") : "(any targeted window)"}`
|
|
8908
|
+
);
|
|
8909
|
+
console.log(
|
|
8910
|
+
` Denylist: ${policy.denylist.length ? policy.denylist.join(", ") : "(none)"}`
|
|
8911
|
+
);
|
|
8912
|
+
console.log(` Redact titles: ${policy.redactWindowTitles}`);
|
|
8913
|
+
console.log(
|
|
8914
|
+
"\nBuilt-in sensitive surfaces (password managers, auth/UAC dialogs, banking) are always refused and cannot be allowlisted."
|
|
8915
|
+
);
|
|
8916
|
+
});
|
|
8917
|
+
});
|
|
8918
|
+
observerCommand.command("grant <process>").description(
|
|
8919
|
+
"Allow the observer to capture a process (adds it to observer.allowlist)"
|
|
8920
|
+
).action(async (processName) => {
|
|
8921
|
+
await withDb(async (db) => {
|
|
8922
|
+
const next = applyObserverListChange(
|
|
8923
|
+
await getSetting(db, "observer.allowlist"),
|
|
8924
|
+
processName,
|
|
8925
|
+
"add"
|
|
8926
|
+
);
|
|
8927
|
+
await setSetting(db, "observer.allowlist", next);
|
|
8928
|
+
await syncObserverSidecarPolicy(db);
|
|
8929
|
+
console.log(`Granted: ${processName.trim().toLowerCase()}`);
|
|
8930
|
+
console.log(`observer.allowlist = ${next || "(empty)"}`);
|
|
8931
|
+
});
|
|
8932
|
+
});
|
|
8933
|
+
observerCommand.command("revoke <process>").description("Remove a process from observer.allowlist").action(async (processName) => {
|
|
8934
|
+
await withDb(async (db) => {
|
|
8935
|
+
const next = applyObserverListChange(
|
|
8936
|
+
await getSetting(db, "observer.allowlist"),
|
|
8937
|
+
processName,
|
|
8938
|
+
"remove"
|
|
8939
|
+
);
|
|
8940
|
+
await setSetting(db, "observer.allowlist", next);
|
|
8941
|
+
await syncObserverSidecarPolicy(db);
|
|
8942
|
+
console.log(`Revoked: ${processName.trim().toLowerCase()}`);
|
|
8943
|
+
console.log(`observer.allowlist = ${next || "(empty)"}`);
|
|
8944
|
+
});
|
|
8945
|
+
});
|
|
8946
|
+
|
|
8064
8947
|
// src/cli/commands/profile.ts
|
|
8065
8948
|
import { homedir as homedir10 } from "os";
|
|
8066
|
-
import { dirname as dirname5, join as
|
|
8067
|
-
import { Command as
|
|
8949
|
+
import { dirname as dirname5, join as join16, resolve as resolve4 } from "path";
|
|
8950
|
+
import { Command as Command11 } from "commander";
|
|
8068
8951
|
var C2 = {
|
|
8069
8952
|
reset: "\x1B[0m",
|
|
8070
8953
|
bold: "\x1B[1m",
|
|
@@ -8073,7 +8956,7 @@ var C2 = {
|
|
|
8073
8956
|
green: "\x1B[32m"
|
|
8074
8957
|
};
|
|
8075
8958
|
function defaultPersonalDir() {
|
|
8076
|
-
return
|
|
8959
|
+
return join16(homedir10(), "Documents", "zam");
|
|
8077
8960
|
}
|
|
8078
8961
|
function render(profile) {
|
|
8079
8962
|
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 +8967,7 @@ function render(profile) {
|
|
|
8084
8967
|
console.log(` data dir: ${C2.cyan}${profile.dataDir}${C2.reset}`);
|
|
8085
8968
|
console.log(` database: ${C2.cyan}${profile.dbPath}${C2.reset}`);
|
|
8086
8969
|
}
|
|
8087
|
-
var profileCommand = new
|
|
8970
|
+
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
8971
|
if (opts.mode && opts.mode !== "developer" && opts.mode !== "default") {
|
|
8089
8972
|
console.error(`Invalid --mode: ${opts.mode}. Use developer or default.`);
|
|
8090
8973
|
process.exit(1);
|
|
@@ -8120,8 +9003,8 @@ var profileCommand = new Command10("profile").description("Show or change this m
|
|
|
8120
9003
|
});
|
|
8121
9004
|
|
|
8122
9005
|
// src/cli/commands/review.ts
|
|
8123
|
-
import { Command as
|
|
8124
|
-
var reviewCommand = new
|
|
9006
|
+
import { Command as Command12 } from "commander";
|
|
9007
|
+
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
9008
|
let db;
|
|
8126
9009
|
try {
|
|
8127
9010
|
db = await openDatabase();
|
|
@@ -8234,10 +9117,10 @@ ${"\u2550".repeat(50)}`);
|
|
|
8234
9117
|
});
|
|
8235
9118
|
|
|
8236
9119
|
// src/cli/commands/session.ts
|
|
8237
|
-
import { readFileSync as
|
|
9120
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
8238
9121
|
import { input as input6, select as select2 } from "@inquirer/prompts";
|
|
8239
|
-
import { Command as
|
|
8240
|
-
var sessionCommand = new
|
|
9122
|
+
import { Command as Command13 } from "commander";
|
|
9123
|
+
var sessionCommand = new Command13("session").description(
|
|
8241
9124
|
"Manage learning sessions"
|
|
8242
9125
|
);
|
|
8243
9126
|
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 +9172,18 @@ sessionCommand.command("start").description("Start a new learning session (revie
|
|
|
8289
9172
|
task,
|
|
8290
9173
|
execution_context: opts.context
|
|
8291
9174
|
});
|
|
9175
|
+
const observerHint = opts.context === "ui" && !await isObserverPolicyConfigured(db) ? OBSERVER_POLICY_UNSET_HINT : null;
|
|
8292
9176
|
await db.close();
|
|
8293
9177
|
if (opts.quiet) {
|
|
8294
9178
|
console.log(session.id);
|
|
8295
9179
|
} else if (opts.json) {
|
|
8296
|
-
console.log(
|
|
9180
|
+
console.log(
|
|
9181
|
+
JSON.stringify(
|
|
9182
|
+
observerHint ? { ...session, observerPolicyHint: observerHint } : session,
|
|
9183
|
+
null,
|
|
9184
|
+
2
|
|
9185
|
+
)
|
|
9186
|
+
);
|
|
8297
9187
|
} else {
|
|
8298
9188
|
console.log(`
|
|
8299
9189
|
Session started: ${session.id}`);
|
|
@@ -8301,6 +9191,10 @@ Session started: ${session.id}`);
|
|
|
8301
9191
|
console.log(` Task: ${session.task}`);
|
|
8302
9192
|
console.log(` Context: ${session.execution_context}`);
|
|
8303
9193
|
console.log(` Started: ${session.started_at}`);
|
|
9194
|
+
if (observerHint) {
|
|
9195
|
+
console.log(`
|
|
9196
|
+
${observerHint}`);
|
|
9197
|
+
}
|
|
8304
9198
|
}
|
|
8305
9199
|
} catch (err) {
|
|
8306
9200
|
await db?.close();
|
|
@@ -8415,7 +9309,7 @@ function loadPatternFile(path) {
|
|
|
8415
9309
|
if (!path) return [];
|
|
8416
9310
|
let parsed;
|
|
8417
9311
|
try {
|
|
8418
|
-
parsed = JSON.parse(
|
|
9312
|
+
parsed = JSON.parse(readFileSync11(path, "utf-8"));
|
|
8419
9313
|
} catch (err) {
|
|
8420
9314
|
throw new Error(
|
|
8421
9315
|
`Cannot read synthesis patterns from ${path}: ${err.message}`
|
|
@@ -8606,8 +9500,8 @@ sessionCommand.command("end").description("End a session and show summary").requ
|
|
|
8606
9500
|
|
|
8607
9501
|
// src/cli/commands/settings.ts
|
|
8608
9502
|
import { existsSync as existsSync16 } from "fs";
|
|
8609
|
-
import { Command as
|
|
8610
|
-
var settingsCommand = new
|
|
9503
|
+
import { Command as Command14 } from "commander";
|
|
9504
|
+
var settingsCommand = new Command14("settings").description(
|
|
8611
9505
|
"Manage user settings"
|
|
8612
9506
|
);
|
|
8613
9507
|
var BOOLEAN_SETTING_KEYS = /* @__PURE__ */ new Set(["llm.enabled", "llm.vision.enabled"]);
|
|
@@ -8661,6 +9555,9 @@ settingsCommand.command("set <key> <value>").description("Set a setting").option
|
|
|
8661
9555
|
await withDb(async (db) => {
|
|
8662
9556
|
const parsedVal = normalizeSettingValue(key, value);
|
|
8663
9557
|
await setSetting(db, key, parsedVal);
|
|
9558
|
+
if (key.startsWith("observer.")) {
|
|
9559
|
+
await syncObserverSidecarPolicy(db);
|
|
9560
|
+
}
|
|
8664
9561
|
if (!opts.quiet) {
|
|
8665
9562
|
console.log(`Set ${key} = ${parsedVal}`);
|
|
8666
9563
|
}
|
|
@@ -8669,6 +9566,9 @@ settingsCommand.command("set <key> <value>").description("Set a setting").option
|
|
|
8669
9566
|
settingsCommand.command("delete <key>").description("Delete a setting").option("--quiet", "Suppress output").action(async (key, opts) => {
|
|
8670
9567
|
await withDb(async (db) => {
|
|
8671
9568
|
const deleted = await deleteSetting(db, key);
|
|
9569
|
+
if (key.startsWith("observer.")) {
|
|
9570
|
+
await syncObserverSidecarPolicy(db);
|
|
9571
|
+
}
|
|
8672
9572
|
if (!opts.quiet) {
|
|
8673
9573
|
if (deleted) {
|
|
8674
9574
|
console.log(`Deleted: ${key}`);
|
|
@@ -8776,32 +9676,32 @@ settingsCommand.command("repos").description("Show or set Personal, Team, and Or
|
|
|
8776
9676
|
});
|
|
8777
9677
|
|
|
8778
9678
|
// 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
|
|
9679
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync17, mkdirSync as mkdirSync10, writeFileSync as writeFileSync9 } from "fs";
|
|
9680
|
+
import { basename as basename4, dirname as dirname6, join as join17 } from "path";
|
|
8781
9681
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
8782
|
-
import { Command as
|
|
9682
|
+
import { Command as Command15 } from "commander";
|
|
8783
9683
|
var packageRoot = [
|
|
8784
9684
|
fileURLToPath2(new URL("../..", import.meta.url)),
|
|
8785
9685
|
fileURLToPath2(new URL("../../..", import.meta.url))
|
|
8786
|
-
].find((candidate) => existsSync17(
|
|
9686
|
+
].find((candidate) => existsSync17(join17(candidate, "package.json"))) ?? fileURLToPath2(new URL("../..", import.meta.url));
|
|
8787
9687
|
var SKILL_PAIRS = [
|
|
8788
9688
|
{
|
|
8789
|
-
from:
|
|
8790
|
-
to:
|
|
9689
|
+
from: join17(packageRoot, ".claude", "skills", "zam", "SKILL.md"),
|
|
9690
|
+
to: join17(".claude", "skills", "zam", "SKILL.md")
|
|
8791
9691
|
},
|
|
8792
9692
|
{
|
|
8793
|
-
from:
|
|
8794
|
-
to:
|
|
9693
|
+
from: join17(packageRoot, ".agent", "skills", "zam", "SKILL.md"),
|
|
9694
|
+
to: join17(".agent", "skills", "zam", "SKILL.md")
|
|
8795
9695
|
},
|
|
8796
9696
|
{
|
|
8797
|
-
from:
|
|
8798
|
-
to:
|
|
9697
|
+
from: join17(packageRoot, ".agents", "skills", "zam", "SKILL.md"),
|
|
9698
|
+
to: join17(".agents", "skills", "zam", "SKILL.md")
|
|
8799
9699
|
}
|
|
8800
9700
|
];
|
|
8801
9701
|
function copySkills(force, cwd = process.cwd()) {
|
|
8802
9702
|
let anyAction = false;
|
|
8803
9703
|
for (const { from, to } of SKILL_PAIRS) {
|
|
8804
|
-
const dest =
|
|
9704
|
+
const dest = join17(cwd, to);
|
|
8805
9705
|
if (!existsSync17(from)) {
|
|
8806
9706
|
console.warn(` warn source not found, skipping: ${from}`);
|
|
8807
9707
|
continue;
|
|
@@ -8810,7 +9710,7 @@ function copySkills(force, cwd = process.cwd()) {
|
|
|
8810
9710
|
console.log(` skip ${to} (already present \xE2\u20AC\u201D use --force to update)`);
|
|
8811
9711
|
continue;
|
|
8812
9712
|
}
|
|
8813
|
-
|
|
9713
|
+
mkdirSync10(dirname6(dest), { recursive: true });
|
|
8814
9714
|
copyFileSync2(from, dest);
|
|
8815
9715
|
console.log(` copy ${to}`);
|
|
8816
9716
|
anyAction = true;
|
|
@@ -8821,13 +9721,25 @@ function copySkills(force, cwd = process.cwd()) {
|
|
|
8821
9721
|
);
|
|
8822
9722
|
}
|
|
8823
9723
|
}
|
|
9724
|
+
function formatDatabaseInitTarget(target) {
|
|
9725
|
+
switch (target.kind) {
|
|
9726
|
+
case "local":
|
|
9727
|
+
return `ZAM database at ${target.location} (local SQLite)`;
|
|
9728
|
+
case "turso-remote":
|
|
9729
|
+
return `ZAM database via Turso remote at ${target.location}`;
|
|
9730
|
+
case "turso-native":
|
|
9731
|
+
return `ZAM database via Turso native driver at ${target.location}`;
|
|
9732
|
+
case "turso-replica":
|
|
9733
|
+
return `ZAM database replica at ${target.location} syncing from ${target.syncUrl}`;
|
|
9734
|
+
}
|
|
9735
|
+
}
|
|
8824
9736
|
async function initDatabase(skipInit) {
|
|
8825
9737
|
if (skipInit) return;
|
|
8826
9738
|
try {
|
|
8827
|
-
const
|
|
9739
|
+
const target = getDatabaseTargetInfo();
|
|
8828
9740
|
const db = await openDatabaseWithSync({ initialize: true });
|
|
8829
9741
|
await db.close();
|
|
8830
|
-
console.log(` init
|
|
9742
|
+
console.log(` init ${formatDatabaseInitTarget(target)}`);
|
|
8831
9743
|
} catch (err) {
|
|
8832
9744
|
const msg = err.message;
|
|
8833
9745
|
if (!msg.includes("already")) {
|
|
@@ -8839,13 +9751,13 @@ async function initDatabase(skipInit) {
|
|
|
8839
9751
|
}
|
|
8840
9752
|
function writeClaudeMd(skipClaudeMd, cwd = process.cwd()) {
|
|
8841
9753
|
if (skipClaudeMd) return;
|
|
8842
|
-
const dest =
|
|
9754
|
+
const dest = join17(cwd, "CLAUDE.md");
|
|
8843
9755
|
if (existsSync17(dest)) {
|
|
8844
9756
|
console.log(` skip CLAUDE.md (already present)`);
|
|
8845
9757
|
return;
|
|
8846
9758
|
}
|
|
8847
9759
|
const name = basename4(cwd);
|
|
8848
|
-
|
|
9760
|
+
writeFileSync9(
|
|
8849
9761
|
dest,
|
|
8850
9762
|
`# ZAM Personal Kernel \xE2\u20AC\u201D ${name}
|
|
8851
9763
|
|
|
@@ -8873,13 +9785,13 @@ Use \`zam connector setup turso\` to store cloud credentials in
|
|
|
8873
9785
|
}
|
|
8874
9786
|
function writeAgentsMd(skipAgentsMd, cwd = process.cwd()) {
|
|
8875
9787
|
if (skipAgentsMd) return;
|
|
8876
|
-
const dest =
|
|
9788
|
+
const dest = join17(cwd, "AGENTS.md");
|
|
8877
9789
|
if (existsSync17(dest)) {
|
|
8878
9790
|
console.log(` skip AGENTS.md (already present)`);
|
|
8879
9791
|
return;
|
|
8880
9792
|
}
|
|
8881
9793
|
const name = basename4(cwd);
|
|
8882
|
-
|
|
9794
|
+
writeFileSync9(
|
|
8883
9795
|
dest,
|
|
8884
9796
|
`# ZAM Personal Kernel - ${name}
|
|
8885
9797
|
|
|
@@ -8912,7 +9824,7 @@ Codex discovers repository skills under \`.agents/skills/\`. Run
|
|
|
8912
9824
|
);
|
|
8913
9825
|
console.log(` write AGENTS.md`);
|
|
8914
9826
|
}
|
|
8915
|
-
var setupCommand = new
|
|
9827
|
+
var setupCommand = new Command15("setup").description(
|
|
8916
9828
|
"Distribute ZAM skill files into this personal instance and initialize the database"
|
|
8917
9829
|
).option(
|
|
8918
9830
|
"--force",
|
|
@@ -8933,8 +9845,8 @@ var setupCommand = new Command14("setup").description(
|
|
|
8933
9845
|
);
|
|
8934
9846
|
|
|
8935
9847
|
// src/cli/commands/skill.ts
|
|
8936
|
-
import { Command as
|
|
8937
|
-
var skillCommand = new
|
|
9848
|
+
import { Command as Command16 } from "commander";
|
|
9849
|
+
var skillCommand = new Command16("skill").description(
|
|
8938
9850
|
"Manage agent skill entries (task recipes)"
|
|
8939
9851
|
);
|
|
8940
9852
|
skillCommand.command("list").description("List all agent skills").option("--json", "Output as JSON").action(async (opts) => {
|
|
@@ -9019,10 +9931,10 @@ skillCommand.command("add").description("Register a new agent skill").requiredOp
|
|
|
9019
9931
|
});
|
|
9020
9932
|
|
|
9021
9933
|
// src/cli/commands/snapshot.ts
|
|
9022
|
-
import { existsSync as existsSync18, mkdirSync as
|
|
9934
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync11, readFileSync as readFileSync12, writeFileSync as writeFileSync10 } from "fs";
|
|
9023
9935
|
import { homedir as homedir11 } from "os";
|
|
9024
|
-
import { dirname as dirname7, join as
|
|
9025
|
-
import { Command as
|
|
9936
|
+
import { dirname as dirname7, join as join18 } from "path";
|
|
9937
|
+
import { Command as Command17 } from "commander";
|
|
9026
9938
|
function defaultOutName() {
|
|
9027
9939
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d+Z$/, "").replace(/:/g, "-");
|
|
9028
9940
|
return `zam-snapshot-${stamp}.sql`;
|
|
@@ -9036,12 +9948,12 @@ function summarize(tables) {
|
|
|
9036
9948
|
}
|
|
9037
9949
|
return { total, nonEmpty };
|
|
9038
9950
|
}
|
|
9039
|
-
var exportCmd = new
|
|
9951
|
+
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
9952
|
let db;
|
|
9041
9953
|
try {
|
|
9042
9954
|
db = await openDatabaseWithSync({ initialize: true });
|
|
9043
9955
|
const snapshot = await exportSnapshot(db);
|
|
9044
|
-
const personalDir = await getSetting(db, "personal.workspace_dir") ||
|
|
9956
|
+
const personalDir = await getSetting(db, "personal.workspace_dir") || join18(homedir11(), "Documents", "zam");
|
|
9045
9957
|
await db.close();
|
|
9046
9958
|
db = void 0;
|
|
9047
9959
|
const manifest = verifySnapshot(snapshot);
|
|
@@ -9050,12 +9962,12 @@ var exportCmd = new Command16("export").description("Write a portable SQL-text s
|
|
|
9050
9962
|
process.stdout.write(snapshot);
|
|
9051
9963
|
return;
|
|
9052
9964
|
}
|
|
9053
|
-
const out = opts.out ??
|
|
9965
|
+
const out = opts.out ?? join18(personalDir, "snapshots", defaultOutName());
|
|
9054
9966
|
const dir = dirname7(out);
|
|
9055
9967
|
if (dir && dir !== "." && !existsSync18(dir)) {
|
|
9056
|
-
|
|
9968
|
+
mkdirSync11(dir, { recursive: true });
|
|
9057
9969
|
}
|
|
9058
|
-
|
|
9970
|
+
writeFileSync10(out, snapshot, "utf-8");
|
|
9059
9971
|
console.log(`Snapshot written: ${out}`);
|
|
9060
9972
|
console.log(
|
|
9061
9973
|
` ${total} row(s)${nonEmpty.length ? ` \u2014 ${nonEmpty.join(", ")}` : ""}`
|
|
@@ -9066,14 +9978,14 @@ var exportCmd = new Command16("export").description("Write a portable SQL-text s
|
|
|
9066
9978
|
process.exit(1);
|
|
9067
9979
|
}
|
|
9068
9980
|
});
|
|
9069
|
-
var importCmd = new
|
|
9981
|
+
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
9982
|
let db;
|
|
9071
9983
|
try {
|
|
9072
9984
|
if (!existsSync18(file)) {
|
|
9073
9985
|
console.error(`Error: Snapshot file not found: ${file}`);
|
|
9074
9986
|
process.exit(1);
|
|
9075
9987
|
}
|
|
9076
|
-
const snapshot =
|
|
9988
|
+
const snapshot = readFileSync12(file, "utf-8");
|
|
9077
9989
|
db = await openDatabaseWithSync({ initialize: true });
|
|
9078
9990
|
const result = await importSnapshot(db, snapshot, { force: opts.force });
|
|
9079
9991
|
await db.close();
|
|
@@ -9089,13 +10001,13 @@ var importCmd = new Command16("import").description("Restore a snapshot into the
|
|
|
9089
10001
|
process.exit(1);
|
|
9090
10002
|
}
|
|
9091
10003
|
});
|
|
9092
|
-
var verifyCmd = new
|
|
10004
|
+
var verifyCmd = new Command17("verify").description("Check a snapshot's manifest and checksum without importing").argument("<file>", "Snapshot file to verify").action((file) => {
|
|
9093
10005
|
try {
|
|
9094
10006
|
if (!existsSync18(file)) {
|
|
9095
10007
|
console.error(`Error: Snapshot file not found: ${file}`);
|
|
9096
10008
|
process.exit(1);
|
|
9097
10009
|
}
|
|
9098
|
-
const manifest = verifySnapshot(
|
|
10010
|
+
const manifest = verifySnapshot(readFileSync12(file, "utf-8"));
|
|
9099
10011
|
const { total, nonEmpty } = summarize(manifest.tables);
|
|
9100
10012
|
console.log(`Valid snapshot (format v${manifest.version})`);
|
|
9101
10013
|
console.log(` created: ${manifest.createdAt}`);
|
|
@@ -9107,11 +10019,11 @@ var verifyCmd = new Command16("verify").description("Check a snapshot's manifest
|
|
|
9107
10019
|
process.exit(1);
|
|
9108
10020
|
}
|
|
9109
10021
|
});
|
|
9110
|
-
var snapshotCommand = new
|
|
10022
|
+
var snapshotCommand = new Command17("snapshot").description("Export, import, or verify a portable database snapshot").addCommand(exportCmd).addCommand(importCmd).addCommand(verifyCmd);
|
|
9111
10023
|
|
|
9112
10024
|
// src/cli/commands/stats.ts
|
|
9113
|
-
import { Command as
|
|
9114
|
-
var statsCommand = new
|
|
10025
|
+
import { Command as Command18 } from "commander";
|
|
10026
|
+
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
10027
|
await withDb(async (db) => {
|
|
9116
10028
|
const userId = await resolveUser(opts, db);
|
|
9117
10029
|
const stats = await getUserStats(db, userId);
|
|
@@ -9147,8 +10059,8 @@ var statsCommand = new Command17("stats").description("Show learning dashboard f
|
|
|
9147
10059
|
});
|
|
9148
10060
|
|
|
9149
10061
|
// src/cli/commands/token.ts
|
|
9150
|
-
import { Command as
|
|
9151
|
-
var tokenCommand = new
|
|
10062
|
+
import { Command as Command19 } from "commander";
|
|
10063
|
+
var tokenCommand = new Command19("token").description(
|
|
9152
10064
|
"Manage knowledge tokens"
|
|
9153
10065
|
);
|
|
9154
10066
|
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 +10349,9 @@ tokenCommand.command("status").description("Show full status of a token for a us
|
|
|
9437
10349
|
import { spawn as spawn2, spawnSync } from "child_process";
|
|
9438
10350
|
import { existsSync as existsSync19 } from "fs";
|
|
9439
10351
|
import { homedir as homedir12 } from "os";
|
|
9440
|
-
import { dirname as dirname8, join as
|
|
10352
|
+
import { dirname as dirname8, join as join19 } from "path";
|
|
9441
10353
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
9442
|
-
import { Command as
|
|
10354
|
+
import { Command as Command20 } from "commander";
|
|
9443
10355
|
var C3 = {
|
|
9444
10356
|
reset: "\x1B[0m",
|
|
9445
10357
|
red: "\x1B[31m",
|
|
@@ -9453,8 +10365,8 @@ function findDesktopDir() {
|
|
|
9453
10365
|
for (const start of starts) {
|
|
9454
10366
|
let dir = start;
|
|
9455
10367
|
for (let i = 0; i < 10; i++) {
|
|
9456
|
-
if (existsSync19(
|
|
9457
|
-
return
|
|
10368
|
+
if (existsSync19(join19(dir, "desktop", "src-tauri", "tauri.conf.json"))) {
|
|
10369
|
+
return join19(dir, "desktop");
|
|
9458
10370
|
}
|
|
9459
10371
|
const parent = dirname8(dir);
|
|
9460
10372
|
if (parent === dir) break;
|
|
@@ -9464,18 +10376,18 @@ function findDesktopDir() {
|
|
|
9464
10376
|
return null;
|
|
9465
10377
|
}
|
|
9466
10378
|
function findBuiltApp(desktopDir) {
|
|
9467
|
-
const releaseDir =
|
|
10379
|
+
const releaseDir = join19(desktopDir, "src-tauri", "target", "release");
|
|
9468
10380
|
if (process.platform === "win32") {
|
|
9469
10381
|
for (const name of ["ZAM.exe", "zam.exe", "zam-desktop.exe"]) {
|
|
9470
|
-
const p =
|
|
10382
|
+
const p = join19(releaseDir, name);
|
|
9471
10383
|
if (existsSync19(p)) return p;
|
|
9472
10384
|
}
|
|
9473
10385
|
} else if (process.platform === "darwin") {
|
|
9474
|
-
const app =
|
|
10386
|
+
const app = join19(releaseDir, "bundle", "macos", "ZAM.app");
|
|
9475
10387
|
if (existsSync19(app)) return app;
|
|
9476
10388
|
} else {
|
|
9477
10389
|
for (const name of ["zam", "ZAM", "zam-desktop"]) {
|
|
9478
|
-
const p =
|
|
10390
|
+
const p = join19(releaseDir, name);
|
|
9479
10391
|
if (existsSync19(p)) return p;
|
|
9480
10392
|
}
|
|
9481
10393
|
}
|
|
@@ -9483,10 +10395,10 @@ function findBuiltApp(desktopDir) {
|
|
|
9483
10395
|
}
|
|
9484
10396
|
function findInstalledApp() {
|
|
9485
10397
|
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",
|
|
10398
|
+
process.env.LOCALAPPDATA && join19(process.env.LOCALAPPDATA, "Programs", "ZAM", "ZAM.exe"),
|
|
10399
|
+
process.env.ProgramFiles && join19(process.env.ProgramFiles, "ZAM", "ZAM.exe"),
|
|
10400
|
+
process.env["ProgramFiles(x86)"] && join19(process.env["ProgramFiles(x86)"], "ZAM", "ZAM.exe")
|
|
10401
|
+
] : process.platform === "darwin" ? ["/Applications/ZAM.app", join19(homedir12(), "Applications", "ZAM.app")] : ["/opt/ZAM/zam", "/usr/bin/zam-desktop"];
|
|
9490
10402
|
return candidates.find((candidate) => candidate && existsSync19(candidate)) || null;
|
|
9491
10403
|
}
|
|
9492
10404
|
function runNpm(args, opts) {
|
|
@@ -9498,7 +10410,7 @@ function runNpm(args, opts) {
|
|
|
9498
10410
|
return res.status ?? 1;
|
|
9499
10411
|
}
|
|
9500
10412
|
function ensureDesktopDeps(desktopDir) {
|
|
9501
|
-
if (existsSync19(
|
|
10413
|
+
if (existsSync19(join19(desktopDir, "node_modules"))) return true;
|
|
9502
10414
|
console.log(
|
|
9503
10415
|
`${C3.cyan}Installing desktop dependencies (one-time)...${C3.reset}`
|
|
9504
10416
|
);
|
|
@@ -9524,7 +10436,7 @@ function requireRust() {
|
|
|
9524
10436
|
function hasMsvcBuildTools() {
|
|
9525
10437
|
if (process.platform !== "win32") return true;
|
|
9526
10438
|
const pf86 = process.env["ProgramFiles(x86)"] || "C:\\Program Files (x86)";
|
|
9527
|
-
const vswhere =
|
|
10439
|
+
const vswhere = join19(
|
|
9528
10440
|
pf86,
|
|
9529
10441
|
"Microsoft Visual Studio",
|
|
9530
10442
|
"Installer",
|
|
@@ -9562,7 +10474,7 @@ function requireMsvcOnWindows() {
|
|
|
9562
10474
|
return false;
|
|
9563
10475
|
}
|
|
9564
10476
|
function warnIfCliMissing(repoRoot) {
|
|
9565
|
-
if (!existsSync19(
|
|
10477
|
+
if (!existsSync19(join19(repoRoot, "dist", "cli", "index.js"))) {
|
|
9566
10478
|
console.warn(
|
|
9567
10479
|
`${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
10480
|
);
|
|
@@ -9619,7 +10531,7 @@ function createShortcuts(appPath, repoRoot) {
|
|
|
9619
10531
|
console.error(`${C3.red}\u2717 Could not create shortcuts.${C3.reset}`);
|
|
9620
10532
|
}
|
|
9621
10533
|
}
|
|
9622
|
-
var uiCommand = new
|
|
10534
|
+
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
10535
|
"--build",
|
|
9624
10536
|
"Build the native installer for your OS (needs Rust, one-time)"
|
|
9625
10537
|
).option("--shortcut", "Create Desktop + Start-menu shortcuts to the GUI").action((opts) => {
|
|
@@ -9646,7 +10558,7 @@ var uiCommand = new Command19("ui").description("Launch the ZAM Desktop GUI (Act
|
|
|
9646
10558
|
);
|
|
9647
10559
|
const code = runNpm(["run", "tauri", "build"], { cwd: desktopDir });
|
|
9648
10560
|
if (code === 0) {
|
|
9649
|
-
const bundle =
|
|
10561
|
+
const bundle = join19(
|
|
9650
10562
|
desktopDir,
|
|
9651
10563
|
"src-tauri",
|
|
9652
10564
|
"target",
|
|
@@ -9722,10 +10634,12 @@ ${C3.dim}After that, 'zam ui' launches it directly.${C3.reset}`
|
|
|
9722
10634
|
});
|
|
9723
10635
|
|
|
9724
10636
|
// src/cli/commands/update.ts
|
|
9725
|
-
import {
|
|
9726
|
-
import {
|
|
10637
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
10638
|
+
import { existsSync as existsSync20, readFileSync as readFileSync13, realpathSync } from "fs";
|
|
10639
|
+
import { dirname as dirname9, join as join20 } from "path";
|
|
9727
10640
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
9728
|
-
import {
|
|
10641
|
+
import { confirm as confirm3 } from "@inquirer/prompts";
|
|
10642
|
+
import { Command as Command21 } from "commander";
|
|
9729
10643
|
var GITHUB_REPO = "zam-os/zam";
|
|
9730
10644
|
var CHANNELS = [
|
|
9731
10645
|
"developer",
|
|
@@ -9739,6 +10653,7 @@ var C4 = {
|
|
|
9739
10653
|
cyan: "\x1B[36m",
|
|
9740
10654
|
dim: "\x1B[2m",
|
|
9741
10655
|
green: "\x1B[32m",
|
|
10656
|
+
red: "\x1B[31m",
|
|
9742
10657
|
yellow: "\x1B[33m"
|
|
9743
10658
|
};
|
|
9744
10659
|
function currentVersion() {
|
|
@@ -9746,7 +10661,7 @@ function currentVersion() {
|
|
|
9746
10661
|
for (const up of ["..", "../..", "../../.."]) {
|
|
9747
10662
|
try {
|
|
9748
10663
|
const pkg2 = JSON.parse(
|
|
9749
|
-
|
|
10664
|
+
readFileSync13(join20(here, up, "package.json"), "utf-8")
|
|
9750
10665
|
);
|
|
9751
10666
|
if (pkg2.version) return pkg2.version;
|
|
9752
10667
|
} catch {
|
|
@@ -9754,6 +10669,16 @@ function currentVersion() {
|
|
|
9754
10669
|
}
|
|
9755
10670
|
return "0.0.0";
|
|
9756
10671
|
}
|
|
10672
|
+
function versionAt(dir) {
|
|
10673
|
+
try {
|
|
10674
|
+
const pkg2 = JSON.parse(
|
|
10675
|
+
readFileSync13(join20(dir, "package.json"), "utf-8")
|
|
10676
|
+
);
|
|
10677
|
+
return pkg2.version ?? "unknown";
|
|
10678
|
+
} catch {
|
|
10679
|
+
return "unknown";
|
|
10680
|
+
}
|
|
10681
|
+
}
|
|
9757
10682
|
async function fetchLatestVersion(repo) {
|
|
9758
10683
|
const res = await fetch(
|
|
9759
10684
|
`https://api.github.com/repos/${repo}/releases/latest`,
|
|
@@ -9792,7 +10717,7 @@ function render2(decision) {
|
|
|
9792
10717
|
);
|
|
9793
10718
|
}
|
|
9794
10719
|
}
|
|
9795
|
-
var checkCmd = new
|
|
10720
|
+
var checkCmd = new Command21("check").description("Check whether a newer ZAM has been released").option(
|
|
9796
10721
|
"--latest <version>",
|
|
9797
10722
|
"Compare against this version instead of fetching"
|
|
9798
10723
|
).option("--channel <channel>", "Override the detected install channel").option("--json", "Output as JSON").action(
|
|
@@ -9823,11 +10748,154 @@ var checkCmd = new Command20("check").description("Check whether a newer ZAM has
|
|
|
9823
10748
|
}
|
|
9824
10749
|
}
|
|
9825
10750
|
);
|
|
9826
|
-
|
|
10751
|
+
function findSourceRepo() {
|
|
10752
|
+
let dir = realpathSync(dirname9(fileURLToPath4(import.meta.url)));
|
|
10753
|
+
let parent = dirname9(dir);
|
|
10754
|
+
while (parent !== dir) {
|
|
10755
|
+
if (existsSync20(join20(dir, ".git"))) return dir;
|
|
10756
|
+
dir = parent;
|
|
10757
|
+
parent = dirname9(dir);
|
|
10758
|
+
}
|
|
10759
|
+
return existsSync20(join20(dir, ".git")) ? dir : null;
|
|
10760
|
+
}
|
|
10761
|
+
function runGit(cwd, args, capture) {
|
|
10762
|
+
const res = spawnSync2("git", args, {
|
|
10763
|
+
cwd,
|
|
10764
|
+
stdio: capture ? ["ignore", "pipe", "pipe"] : "inherit",
|
|
10765
|
+
encoding: "utf8"
|
|
10766
|
+
});
|
|
10767
|
+
return { ok: res.status === 0, out: (res.stdout ?? "").trim() };
|
|
10768
|
+
}
|
|
10769
|
+
function runNpm2(args, cwd) {
|
|
10770
|
+
const res = spawnSync2("npm", args, {
|
|
10771
|
+
cwd,
|
|
10772
|
+
stdio: "inherit",
|
|
10773
|
+
shell: process.platform === "win32"
|
|
10774
|
+
});
|
|
10775
|
+
return res.status ?? 1;
|
|
10776
|
+
}
|
|
10777
|
+
function runShell(command) {
|
|
10778
|
+
const res = spawnSync2(command, { stdio: "inherit", shell: true });
|
|
10779
|
+
return res.status === 0;
|
|
10780
|
+
}
|
|
10781
|
+
function applyDeveloperUpdate(force) {
|
|
10782
|
+
if (!hasCommand("git")) {
|
|
10783
|
+
console.error(`${C4.red}\u2717${C4.reset} git was not found on PATH.`);
|
|
10784
|
+
process.exit(1);
|
|
10785
|
+
}
|
|
10786
|
+
const src = findSourceRepo();
|
|
10787
|
+
if (!src) {
|
|
10788
|
+
console.error(
|
|
10789
|
+
`${C4.red}\u2717${C4.reset} Could not locate the ZAM source checkout to update.`
|
|
10790
|
+
);
|
|
10791
|
+
process.exit(1);
|
|
10792
|
+
}
|
|
10793
|
+
const status = runGit(src, ["status", "--porcelain"], true);
|
|
10794
|
+
if (status.ok && status.out && !force) {
|
|
10795
|
+
console.error(
|
|
10796
|
+
`${C4.red}\u2717${C4.reset} The source checkout has uncommitted changes:
|
|
10797
|
+
${status.out}
|
|
10798
|
+
Commit or stash them, or re-run with ${C4.cyan}--force${C4.reset}.`
|
|
10799
|
+
);
|
|
10800
|
+
process.exit(1);
|
|
10801
|
+
}
|
|
10802
|
+
console.log(
|
|
10803
|
+
`${C4.dim}\u2192 git pull --ff-only${C4.reset} ${C4.dim}(${src})${C4.reset}`
|
|
10804
|
+
);
|
|
10805
|
+
if (!runGit(src, ["pull", "--ff-only"], false).ok) {
|
|
10806
|
+
console.error(
|
|
10807
|
+
`${C4.red}\u2717${C4.reset} git pull failed \u2014 the branch may have diverged or you are offline. Resolve it manually, then retry.`
|
|
10808
|
+
);
|
|
10809
|
+
process.exit(1);
|
|
10810
|
+
}
|
|
10811
|
+
console.log(`${C4.dim}\u2192 npm install${C4.reset}`);
|
|
10812
|
+
if (runNpm2(["install"], src) !== 0) {
|
|
10813
|
+
console.error(`${C4.red}\u2717${C4.reset} npm install failed.`);
|
|
10814
|
+
process.exit(1);
|
|
10815
|
+
}
|
|
10816
|
+
console.log(`${C4.dim}\u2192 npm run build${C4.reset}`);
|
|
10817
|
+
if (runNpm2(["run", "build"], src) !== 0) {
|
|
10818
|
+
console.error(`${C4.red}\u2717${C4.reset} Build failed.`);
|
|
10819
|
+
process.exit(1);
|
|
10820
|
+
}
|
|
10821
|
+
console.log(`${C4.dim}\u2192 zam setup --force${C4.reset}`);
|
|
10822
|
+
const setup = spawnSync2(
|
|
10823
|
+
process.execPath,
|
|
10824
|
+
[join20(src, "dist", "cli", "index.js"), "setup", "--force"],
|
|
10825
|
+
{ cwd: process.cwd(), stdio: "inherit" }
|
|
10826
|
+
);
|
|
10827
|
+
if (setup.status !== 0) {
|
|
10828
|
+
console.warn(
|
|
10829
|
+
`${C4.yellow}\u26A0${C4.reset} Skill refresh reported a problem \u2014 run '${C4.cyan}zam setup --force${C4.reset}' here manually.`
|
|
10830
|
+
);
|
|
10831
|
+
}
|
|
10832
|
+
console.log(
|
|
10833
|
+
`
|
|
10834
|
+
${C4.green}\u2713${C4.reset} Updated to ${C4.cyan}${versionAt(src)}${C4.reset}. Restart your agent client (e.g. Claude Code) to load the refreshed ${C4.cyan}/zam${C4.reset} skill.`
|
|
10835
|
+
);
|
|
10836
|
+
}
|
|
10837
|
+
async function applyUpdate(opts) {
|
|
10838
|
+
try {
|
|
10839
|
+
const current = currentVersion();
|
|
10840
|
+
const latest = await fetchLatestVersion(GITHUB_REPO);
|
|
10841
|
+
const channel = getInstallChannel();
|
|
10842
|
+
const decision = decideUpdate({
|
|
10843
|
+
currentVersion: current,
|
|
10844
|
+
latestVersion: latest,
|
|
10845
|
+
channel
|
|
10846
|
+
});
|
|
10847
|
+
if (!decision.updateAvailable) {
|
|
10848
|
+
console.log(
|
|
10849
|
+
`${C4.green}\u2713${C4.reset} ZAM is up to date (${C4.cyan}${current}${C4.reset}).`
|
|
10850
|
+
);
|
|
10851
|
+
return;
|
|
10852
|
+
}
|
|
10853
|
+
console.log(
|
|
10854
|
+
`${C4.yellow}\u2191${C4.reset} ${C4.bold}Update available${C4.reset}: ${C4.dim}${current}${C4.reset} \u2192 ${C4.cyan}${latest}${C4.reset} ${C4.dim}(${channel} install)${C4.reset}`
|
|
10855
|
+
);
|
|
10856
|
+
for (const step of planUpdate(decision)) {
|
|
10857
|
+
console.log(` ${C4.dim}\u2022${C4.reset} ${step.label}`);
|
|
10858
|
+
}
|
|
10859
|
+
if (channel === "direct") {
|
|
10860
|
+
console.log(
|
|
10861
|
+
`
|
|
10862
|
+
${C4.dim}This install updates through the desktop app's signed updater \u2014 open ZAM Desktop to install ${latest}.${C4.reset}`
|
|
10863
|
+
);
|
|
10864
|
+
return;
|
|
10865
|
+
}
|
|
10866
|
+
if (!opts.yes) {
|
|
10867
|
+
const ok = await confirm3({
|
|
10868
|
+
message: "Apply this update?",
|
|
10869
|
+
default: true
|
|
10870
|
+
});
|
|
10871
|
+
if (!ok) {
|
|
10872
|
+
console.log("Aborted.");
|
|
10873
|
+
return;
|
|
10874
|
+
}
|
|
10875
|
+
}
|
|
10876
|
+
console.log();
|
|
10877
|
+
if (channel === "winget" || channel === "homebrew") {
|
|
10878
|
+
if (!decision.command || !runShell(decision.command)) process.exit(1);
|
|
10879
|
+
return;
|
|
10880
|
+
}
|
|
10881
|
+
applyDeveloperUpdate(opts.force ?? false);
|
|
10882
|
+
} catch (err) {
|
|
10883
|
+
console.error("Error:", err.message);
|
|
10884
|
+
process.exit(1);
|
|
10885
|
+
}
|
|
10886
|
+
}
|
|
10887
|
+
var updateCommand = new Command21("update").description(
|
|
10888
|
+
"Update ZAM to the latest release (use `update check` to only check)"
|
|
10889
|
+
).option("-y, --yes", "Apply without confirmation").option(
|
|
10890
|
+
"--force",
|
|
10891
|
+
"Update even if the source checkout has uncommitted changes"
|
|
10892
|
+
).action(async (opts) => {
|
|
10893
|
+
await applyUpdate(opts);
|
|
10894
|
+
}).addCommand(checkCmd);
|
|
9827
10895
|
|
|
9828
10896
|
// src/cli/commands/whoami.ts
|
|
9829
|
-
import { Command as
|
|
9830
|
-
var whoamiCommand = new
|
|
10897
|
+
import { Command as Command22 } from "commander";
|
|
10898
|
+
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
10899
|
await withDb(async (db) => {
|
|
9832
10900
|
if (opts.set) {
|
|
9833
10901
|
await setSetting(db, "user.id", opts.set);
|
|
@@ -9864,11 +10932,11 @@ var whoamiCommand = new Command21("whoami").description("Show or set the default
|
|
|
9864
10932
|
|
|
9865
10933
|
// src/cli/commands/workspace.ts
|
|
9866
10934
|
import { execSync as execSync6 } from "child_process";
|
|
9867
|
-
import { existsSync as
|
|
9868
|
-
import { join as
|
|
9869
|
-
import { confirm as
|
|
9870
|
-
import { Command as
|
|
9871
|
-
function
|
|
10935
|
+
import { existsSync as existsSync21, writeFileSync as writeFileSync11 } from "fs";
|
|
10936
|
+
import { join as join21 } from "path";
|
|
10937
|
+
import { confirm as confirm4, input as input7 } from "@inquirer/prompts";
|
|
10938
|
+
import { Command as Command23 } from "commander";
|
|
10939
|
+
function runGit2(cwd, args) {
|
|
9872
10940
|
try {
|
|
9873
10941
|
return execSync6(`git ${args}`, {
|
|
9874
10942
|
cwd,
|
|
@@ -9879,7 +10947,7 @@ function runGit(cwd, args) {
|
|
|
9879
10947
|
throw new Error(`Git command failed: ${err.message}`);
|
|
9880
10948
|
}
|
|
9881
10949
|
}
|
|
9882
|
-
var workspaceCommand = new
|
|
10950
|
+
var workspaceCommand = new Command23("workspace").description(
|
|
9883
10951
|
"Manage your ZAM learning workspace"
|
|
9884
10952
|
);
|
|
9885
10953
|
workspaceCommand.command("publish").description("Publish your local workspace sandbox to GitHub").action(async () => {
|
|
@@ -9898,7 +10966,7 @@ workspaceCommand.command("publish").description("Publish your local workspace sa
|
|
|
9898
10966
|
);
|
|
9899
10967
|
process.exit(1);
|
|
9900
10968
|
}
|
|
9901
|
-
if (!
|
|
10969
|
+
if (!existsSync21(workspaceDir)) {
|
|
9902
10970
|
console.error(
|
|
9903
10971
|
`\x1B[31m\u2717 Workspace directory does not exist: ${workspaceDir}\x1B[0m`
|
|
9904
10972
|
);
|
|
@@ -9912,20 +10980,20 @@ Active workspace: \x1B[36m${workspaceDir}\x1B[0m`);
|
|
|
9912
10980
|
);
|
|
9913
10981
|
process.exit(1);
|
|
9914
10982
|
}
|
|
9915
|
-
const gitignorePath =
|
|
9916
|
-
if (!
|
|
9917
|
-
|
|
10983
|
+
const gitignorePath = join21(workspaceDir, ".gitignore");
|
|
10984
|
+
if (!existsSync21(gitignorePath)) {
|
|
10985
|
+
writeFileSync11(
|
|
9918
10986
|
gitignorePath,
|
|
9919
10987
|
"node_modules/\n.agent/\n.agents/\n.claude/\n.gemini/\n.goose/\n*.log\n",
|
|
9920
10988
|
"utf8"
|
|
9921
10989
|
);
|
|
9922
10990
|
}
|
|
9923
|
-
const hasGitRepo =
|
|
10991
|
+
const hasGitRepo = existsSync21(join21(workspaceDir, ".git"));
|
|
9924
10992
|
if (!hasGitRepo) {
|
|
9925
10993
|
console.log("Initializing local Git repository...");
|
|
9926
|
-
|
|
9927
|
-
|
|
9928
|
-
|
|
10994
|
+
runGit2(workspaceDir, "init -b main");
|
|
10995
|
+
runGit2(workspaceDir, "add .");
|
|
10996
|
+
runGit2(
|
|
9929
10997
|
workspaceDir,
|
|
9930
10998
|
'commit -m "chore: initial workspace sandbox bootstrap"'
|
|
9931
10999
|
);
|
|
@@ -9937,14 +11005,14 @@ Active workspace: \x1B[36m${workspaceDir}\x1B[0m`);
|
|
|
9937
11005
|
message: "Choose a name for your GitHub repository:",
|
|
9938
11006
|
default: "zam-personal"
|
|
9939
11007
|
});
|
|
9940
|
-
const isPrivate = await
|
|
11008
|
+
const isPrivate = await confirm4({
|
|
9941
11009
|
message: "Should the repository be private?",
|
|
9942
11010
|
default: true
|
|
9943
11011
|
});
|
|
9944
11012
|
const repoVisibility = isPrivate ? "--private" : "--public";
|
|
9945
11013
|
if (hasCommand("gh")) {
|
|
9946
11014
|
console.log("GitHub CLI detected! Automating repository creation...");
|
|
9947
|
-
const proceedGh = await
|
|
11015
|
+
const proceedGh = await confirm4({
|
|
9948
11016
|
message: "Would you like ZAM to create the repository using the GitHub CLI?",
|
|
9949
11017
|
default: true
|
|
9950
11018
|
});
|
|
@@ -9989,16 +11057,16 @@ Active workspace: \x1B[36m${workspaceDir}\x1B[0m`);
|
|
|
9989
11057
|
console.log("Linking remote repository and pushing...");
|
|
9990
11058
|
let hasOrigin = false;
|
|
9991
11059
|
try {
|
|
9992
|
-
|
|
11060
|
+
runGit2(workspaceDir, "remote get-url origin");
|
|
9993
11061
|
hasOrigin = true;
|
|
9994
11062
|
} catch {
|
|
9995
11063
|
}
|
|
9996
11064
|
if (hasOrigin) {
|
|
9997
|
-
|
|
11065
|
+
runGit2(workspaceDir, `remote set-url origin ${githubUrl}`);
|
|
9998
11066
|
} else {
|
|
9999
|
-
|
|
11067
|
+
runGit2(workspaceDir, `remote add origin ${githubUrl}`);
|
|
10000
11068
|
}
|
|
10001
|
-
|
|
11069
|
+
runGit2(workspaceDir, "push -u origin main");
|
|
10002
11070
|
console.log(
|
|
10003
11071
|
"\x1B[32m\u2713 Successfully linked and pushed to GitHub!\x1B[0m"
|
|
10004
11072
|
);
|
|
@@ -10016,9 +11084,9 @@ Active workspace: \x1B[36m${workspaceDir}\x1B[0m`);
|
|
|
10016
11084
|
// src/cli/index.ts
|
|
10017
11085
|
var __dirname = dirname10(fileURLToPath5(import.meta.url));
|
|
10018
11086
|
var pkg = JSON.parse(
|
|
10019
|
-
|
|
11087
|
+
readFileSync14(join22(__dirname, "..", "..", "package.json"), "utf-8")
|
|
10020
11088
|
);
|
|
10021
|
-
var program = new
|
|
11089
|
+
var program = new Command24();
|
|
10022
11090
|
program.name("zam").description(
|
|
10023
11091
|
"The Symbiotic Learning Kernel: Elevating Human Intelligence through AI Collaboration."
|
|
10024
11092
|
).version(pkg.version);
|
|
@@ -10034,6 +11102,7 @@ program.addCommand(uiCommand);
|
|
|
10034
11102
|
program.addCommand(bridgeCommand);
|
|
10035
11103
|
program.addCommand(skillCommand);
|
|
10036
11104
|
program.addCommand(monitorCommand);
|
|
11105
|
+
program.addCommand(observerCommand);
|
|
10037
11106
|
program.addCommand(settingsCommand);
|
|
10038
11107
|
program.addCommand(whoamiCommand);
|
|
10039
11108
|
program.addCommand(connectorCommand);
|