zam-core 0.4.3 → 0.4.4
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/.agent/skills/zam/SKILL.md +9 -6
- package/.agents/skills/zam/SKILL.md +9 -6
- package/.claude/skills/zam/SKILL.md +9 -6
- package/dist/cli/index.js +1543 -647
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +17 -1
- package/dist/index.js +40 -10
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/cli/index.js
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli/index.ts
|
|
4
4
|
import { readFileSync as readFileSync14 } from "fs";
|
|
5
|
-
import { dirname as dirname10, join as
|
|
5
|
+
import { dirname as dirname10, join as join24 } from "path";
|
|
6
6
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
7
|
-
import { Command as
|
|
7
|
+
import { Command as Command25 } from "commander";
|
|
8
8
|
|
|
9
9
|
// src/cli/commands/agent.ts
|
|
10
|
-
import { existsSync as
|
|
11
|
-
import { join as
|
|
10
|
+
import { existsSync as existsSync13 } from "fs";
|
|
11
|
+
import { join as join13 } from "path";
|
|
12
12
|
import { Command } from "commander";
|
|
13
13
|
|
|
14
14
|
// src/kernel/analytics/stats.ts
|
|
@@ -203,6 +203,25 @@ function clearADOCredentials(path) {
|
|
|
203
203
|
delete creds.ado;
|
|
204
204
|
saveCredentials(creds, path);
|
|
205
205
|
}
|
|
206
|
+
function getProviderApiKey(name, path) {
|
|
207
|
+
const key = loadCredentials(path).llmProviders?.[name]?.apiKey;
|
|
208
|
+
return key && key.length > 0 ? key : null;
|
|
209
|
+
}
|
|
210
|
+
function setProviderApiKey(name, apiKey, path) {
|
|
211
|
+
const creds = loadCredentials(path);
|
|
212
|
+
creds.llmProviders = { ...creds.llmProviders, [name]: { apiKey } };
|
|
213
|
+
saveCredentials(creds, path);
|
|
214
|
+
}
|
|
215
|
+
function clearProviderApiKey(name, path) {
|
|
216
|
+
const creds = loadCredentials(path);
|
|
217
|
+
if (creds.llmProviders && name in creds.llmProviders) {
|
|
218
|
+
delete creds.llmProviders[name];
|
|
219
|
+
saveCredentials(creds, path);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
function listProviderApiKeyRefs(path) {
|
|
223
|
+
return Object.keys(loadCredentials(path).llmProviders ?? {});
|
|
224
|
+
}
|
|
206
225
|
|
|
207
226
|
// src/kernel/connectors/azure-devops.ts
|
|
208
227
|
function loadADOConfig() {
|
|
@@ -4707,13 +4726,11 @@ function runCommand(cmd) {
|
|
|
4707
4726
|
return "";
|
|
4708
4727
|
}
|
|
4709
4728
|
}
|
|
4710
|
-
function
|
|
4729
|
+
function detectWindowsNPU() {
|
|
4711
4730
|
if (process.platform !== "win32") return false;
|
|
4712
|
-
const cmd = `powershell -NoProfile -Command "Get-CimInstance Win32_PnPEntity | Where-Object { $_.Name -like '*AMD IPU*' -or $_.Name -like '*AMD NPU*' -or $_.Name -like '*NPU
|
|
4731
|
+
const cmd = `powershell -NoProfile -Command "Get-CimInstance Win32_PnPEntity | Where-Object { $_.PNPClass -eq 'ComputeAccelerator' -or $_.Name -like '*AMD IPU*' -or $_.Name -like '*AMD NPU*' -or $_.Name -like '*Qualcomm*NPU*' -or $_.Name -like '*Hexagon*NPU*' -or $_.Name -like '*Intel*AI Boost*' -or $_.Name -like '*NPU Compute*' -or $_.Name -like '*Ryzen AI*' } | Select-Object -First 1 -ExpandProperty Name"`;
|
|
4713
4732
|
const output = runCommand(cmd);
|
|
4714
|
-
return Boolean(
|
|
4715
|
-
output && (output.toLowerCase().includes("amd") || output.toLowerCase().includes("ipu") || output.toLowerCase().includes("npu") || output.toLowerCase().includes("ryzen"))
|
|
4716
|
-
);
|
|
4733
|
+
return Boolean(output && output.length > 0);
|
|
4717
4734
|
}
|
|
4718
4735
|
function getSystemProfile() {
|
|
4719
4736
|
const platform = process.platform;
|
|
@@ -4725,13 +4742,21 @@ function getSystemProfile() {
|
|
|
4725
4742
|
let arch = "unknown";
|
|
4726
4743
|
if (archStr === "x64") arch = "x64";
|
|
4727
4744
|
else if (archStr === "arm64") arch = "arm64";
|
|
4728
|
-
const
|
|
4745
|
+
const hasNpu = os === "windows" && detectWindowsNPU();
|
|
4729
4746
|
const hasAppleSilicon = os === "macos" && arch === "arm64";
|
|
4730
4747
|
let recommendedRunner = "generic";
|
|
4731
4748
|
let recommendedModel = "qwen3.5:4b";
|
|
4732
|
-
if (
|
|
4733
|
-
|
|
4734
|
-
|
|
4749
|
+
if (hasNpu) {
|
|
4750
|
+
const isQualcomm = runCommand(
|
|
4751
|
+
`powershell -NoProfile -Command "Get-CimInstance Win32_PnPEntity | Where-Object { $_.Name -like '*Qualcomm*' } | Select-Object -First 1"`
|
|
4752
|
+
).length > 0;
|
|
4753
|
+
if (isQualcomm) {
|
|
4754
|
+
recommendedRunner = "generic";
|
|
4755
|
+
recommendedModel = "qwen3.5-4b";
|
|
4756
|
+
} else {
|
|
4757
|
+
recommendedRunner = "fastflowlm";
|
|
4758
|
+
recommendedModel = "qwen3.5:4b";
|
|
4759
|
+
}
|
|
4735
4760
|
} else if (hasAppleSilicon) {
|
|
4736
4761
|
recommendedRunner = "ollama";
|
|
4737
4762
|
recommendedModel = "llama3.2:3b";
|
|
@@ -4742,7 +4767,8 @@ function getSystemProfile() {
|
|
|
4742
4767
|
return {
|
|
4743
4768
|
os,
|
|
4744
4769
|
arch,
|
|
4745
|
-
hasRyzenNPU,
|
|
4770
|
+
hasRyzenNPU: hasNpu && !archStr.includes("arm64"),
|
|
4771
|
+
// backwards compatibility flag
|
|
4746
4772
|
hasAppleSilicon,
|
|
4747
4773
|
recommendedRunner,
|
|
4748
4774
|
recommendedModel
|
|
@@ -4879,6 +4905,289 @@ function planUpdate(decision) {
|
|
|
4879
4905
|
}
|
|
4880
4906
|
}
|
|
4881
4907
|
|
|
4908
|
+
// src/cli/agent-harness.ts
|
|
4909
|
+
import { spawn } from "child_process";
|
|
4910
|
+
import { existsSync as existsSync12 } from "fs";
|
|
4911
|
+
import { homedir as homedir8 } from "os";
|
|
4912
|
+
import { join as join12 } from "path";
|
|
4913
|
+
|
|
4914
|
+
// src/cli/terminal-open.ts
|
|
4915
|
+
import { execFileSync as execFileSync2, execSync as execSync4 } from "child_process";
|
|
4916
|
+
import {
|
|
4917
|
+
accessSync,
|
|
4918
|
+
constants,
|
|
4919
|
+
existsSync as existsSync11,
|
|
4920
|
+
unlinkSync,
|
|
4921
|
+
writeFileSync as writeFileSync6
|
|
4922
|
+
} from "fs";
|
|
4923
|
+
import { tmpdir } from "os";
|
|
4924
|
+
import { basename as basename2, delimiter, extname, isAbsolute, join as join11 } from "path";
|
|
4925
|
+
function isPowerShellShell(shell) {
|
|
4926
|
+
return shell === "pwsh" || shell === "powershell";
|
|
4927
|
+
}
|
|
4928
|
+
function psSingleQuoted2(value) {
|
|
4929
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
4930
|
+
}
|
|
4931
|
+
function detectShell() {
|
|
4932
|
+
if (process.platform === "win32")
|
|
4933
|
+
return findExecutable("pwsh.exe") ? "pwsh" : "powershell";
|
|
4934
|
+
const shell = process.env.SHELL ?? "";
|
|
4935
|
+
return basename2(shell) === "bash" ? "bash" : "zsh";
|
|
4936
|
+
}
|
|
4937
|
+
function normalizeShell(shell) {
|
|
4938
|
+
if (!shell) return detectShell();
|
|
4939
|
+
const normalized = shell.toLowerCase();
|
|
4940
|
+
if (normalized === "zsh" || normalized === "bash" || normalized === "pwsh" || normalized === "powershell") {
|
|
4941
|
+
return normalized;
|
|
4942
|
+
}
|
|
4943
|
+
throw new Error(
|
|
4944
|
+
`Unsupported shell: ${shell}. Expected zsh, bash, pwsh, or powershell.`
|
|
4945
|
+
);
|
|
4946
|
+
}
|
|
4947
|
+
function selectWindowsExecutable(results, pathext = process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD") {
|
|
4948
|
+
if (results.length === 0) return null;
|
|
4949
|
+
const extensions = pathext.split(";").map((ext) => ext.trim().toLowerCase()).filter(Boolean);
|
|
4950
|
+
const runnable = results.find(
|
|
4951
|
+
(result) => extensions.some((ext) => result.toLowerCase().endsWith(ext))
|
|
4952
|
+
);
|
|
4953
|
+
return runnable ?? results[0];
|
|
4954
|
+
}
|
|
4955
|
+
function stripSurroundingQuotes(command) {
|
|
4956
|
+
const trimmed = command.trim();
|
|
4957
|
+
if (trimmed.length < 2) return trimmed;
|
|
4958
|
+
const first = trimmed[0];
|
|
4959
|
+
const last = trimmed[trimmed.length - 1];
|
|
4960
|
+
if (first === '"' && last === '"' || first === "'" && last === "'") {
|
|
4961
|
+
return trimmed.slice(1, -1);
|
|
4962
|
+
}
|
|
4963
|
+
return trimmed;
|
|
4964
|
+
}
|
|
4965
|
+
function executableExists(path) {
|
|
4966
|
+
if (!existsSync11(path)) return false;
|
|
4967
|
+
if (process.platform === "win32") return true;
|
|
4968
|
+
try {
|
|
4969
|
+
accessSync(path, constants.X_OK);
|
|
4970
|
+
return true;
|
|
4971
|
+
} catch {
|
|
4972
|
+
return false;
|
|
4973
|
+
}
|
|
4974
|
+
}
|
|
4975
|
+
function windowsExecutableNames(command) {
|
|
4976
|
+
if (process.platform !== "win32") return [command];
|
|
4977
|
+
if (extname(command)) return [command];
|
|
4978
|
+
const extensions = (process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").map((ext) => ext.trim()).filter(Boolean);
|
|
4979
|
+
return extensions.map((ext) => `${command}${ext}`);
|
|
4980
|
+
}
|
|
4981
|
+
function hasDirectoryPart(command) {
|
|
4982
|
+
return isAbsolute(command) || command.includes("/") || command.includes("\\");
|
|
4983
|
+
}
|
|
4984
|
+
function findExecutable(command) {
|
|
4985
|
+
const normalized = stripSurroundingQuotes(command);
|
|
4986
|
+
if (!normalized) return null;
|
|
4987
|
+
const matches = [];
|
|
4988
|
+
if (hasDirectoryPart(normalized)) {
|
|
4989
|
+
for (const candidate of windowsExecutableNames(normalized)) {
|
|
4990
|
+
if (executableExists(candidate)) matches.push(candidate);
|
|
4991
|
+
}
|
|
4992
|
+
return process.platform === "win32" ? selectWindowsExecutable(matches) : matches[0] ?? null;
|
|
4993
|
+
}
|
|
4994
|
+
const pathEntries = (process.env.PATH ?? "").split(delimiter).map((entry) => entry.trim()).filter(Boolean);
|
|
4995
|
+
for (const entry of pathEntries) {
|
|
4996
|
+
for (const name of windowsExecutableNames(normalized)) {
|
|
4997
|
+
const candidate = join11(entry, name);
|
|
4998
|
+
if (executableExists(candidate)) matches.push(candidate);
|
|
4999
|
+
}
|
|
5000
|
+
}
|
|
5001
|
+
return process.platform === "win32" ? selectWindowsExecutable(matches) : matches[0] ?? null;
|
|
5002
|
+
}
|
|
5003
|
+
function resolveZamInvocation(shell) {
|
|
5004
|
+
const installed = findExecutable("zam");
|
|
5005
|
+
if (installed) {
|
|
5006
|
+
return isPowerShellShell(shell) ? `& ${psSingleQuoted2(installed)}` : installed;
|
|
5007
|
+
}
|
|
5008
|
+
const projectRoot = join11(import.meta.dirname, "..", "..");
|
|
5009
|
+
const cliSource = join11(projectRoot, "src/cli/index.ts");
|
|
5010
|
+
if (isPowerShellShell(shell)) {
|
|
5011
|
+
return `& npx --prefix ${psSingleQuoted2(projectRoot)} tsx ${psSingleQuoted2(cliSource)}`;
|
|
5012
|
+
}
|
|
5013
|
+
return `npx --prefix ${JSON.stringify(projectRoot)} tsx ${JSON.stringify(cliSource)}`;
|
|
5014
|
+
}
|
|
5015
|
+
function buildShellSetupCommand(dir, shell, command) {
|
|
5016
|
+
if (isPowerShellShell(shell)) {
|
|
5017
|
+
return [`Set-Location -LiteralPath ${psSingleQuoted2(dir)}`, command].join(
|
|
5018
|
+
"; "
|
|
5019
|
+
);
|
|
5020
|
+
}
|
|
5021
|
+
return `cd ${JSON.stringify(dir)} && ${command}`;
|
|
5022
|
+
}
|
|
5023
|
+
function isItermRunning() {
|
|
5024
|
+
try {
|
|
5025
|
+
const result = execSync4(
|
|
5026
|
+
`osascript -e 'tell application "System Events" to (name of processes) contains "iTerm2"' 2>/dev/null`,
|
|
5027
|
+
{ encoding: "utf-8" }
|
|
5028
|
+
).trim();
|
|
5029
|
+
return result === "true";
|
|
5030
|
+
} catch {
|
|
5031
|
+
return false;
|
|
5032
|
+
}
|
|
5033
|
+
}
|
|
5034
|
+
function openMacTerminal(shellSetup, label, dir) {
|
|
5035
|
+
const useIterm = isItermRunning();
|
|
5036
|
+
const escaped = shellSetup.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
5037
|
+
const appleScript = useIterm ? `tell application "iTerm2"
|
|
5038
|
+
activate
|
|
5039
|
+
set newWindow to (create window with default profile)
|
|
5040
|
+
tell current session of newWindow
|
|
5041
|
+
write text "${escaped}"
|
|
5042
|
+
end tell
|
|
5043
|
+
end tell` : `tell application "Terminal"
|
|
5044
|
+
activate
|
|
5045
|
+
do script "${escaped}"
|
|
5046
|
+
end tell`;
|
|
5047
|
+
const tmpFile = join11(tmpdir(), `zam-terminal-${label}.scpt`);
|
|
5048
|
+
try {
|
|
5049
|
+
writeFileSync6(tmpFile, appleScript);
|
|
5050
|
+
execSync4(`osascript ${JSON.stringify(tmpFile)}`, { stdio: "ignore" });
|
|
5051
|
+
console.log(
|
|
5052
|
+
`Opened ${useIterm ? "iTerm2" : "Terminal.app"} window (${label})`
|
|
5053
|
+
);
|
|
5054
|
+
console.log(` Directory: ${dir}`);
|
|
5055
|
+
} catch (err) {
|
|
5056
|
+
console.error(`Failed to open terminal: ${err.message}`);
|
|
5057
|
+
console.log(`
|
|
5058
|
+
Run this manually in a new terminal:
|
|
5059
|
+
`);
|
|
5060
|
+
console.log(` ${shellSetup}`);
|
|
5061
|
+
} finally {
|
|
5062
|
+
try {
|
|
5063
|
+
unlinkSync(tmpFile);
|
|
5064
|
+
} catch {
|
|
5065
|
+
}
|
|
5066
|
+
}
|
|
5067
|
+
}
|
|
5068
|
+
function openWindowsPowerShell(shellSetup, label, dir, requestedShell) {
|
|
5069
|
+
const requestedExecutable = requestedShell === "powershell" ? "powershell.exe" : "pwsh.exe";
|
|
5070
|
+
const executable = findExecutable(requestedExecutable) ? requestedExecutable : "powershell.exe";
|
|
5071
|
+
const startCommand = [
|
|
5072
|
+
"Start-Process",
|
|
5073
|
+
`-FilePath ${psSingleQuoted2(executable)}`,
|
|
5074
|
+
`-ArgumentList @('-NoExit','-NoProfile','-Command',${psSingleQuoted2(shellSetup)})`
|
|
5075
|
+
].join(" ");
|
|
5076
|
+
const launcher = findExecutable("pwsh.exe") ? "pwsh.exe" : "powershell.exe";
|
|
5077
|
+
try {
|
|
5078
|
+
execFileSync2(
|
|
5079
|
+
launcher,
|
|
5080
|
+
["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", startCommand],
|
|
5081
|
+
{
|
|
5082
|
+
stdio: "ignore"
|
|
5083
|
+
}
|
|
5084
|
+
);
|
|
5085
|
+
console.log(
|
|
5086
|
+
`Opened ${executable === "pwsh.exe" ? "PowerShell" : "Windows PowerShell"} window (${label})`
|
|
5087
|
+
);
|
|
5088
|
+
console.log(` Directory: ${dir}`);
|
|
5089
|
+
} catch (err) {
|
|
5090
|
+
console.error(`Failed to open PowerShell: ${err.message}`);
|
|
5091
|
+
console.log(`
|
|
5092
|
+
Run this manually in a new PowerShell terminal:
|
|
5093
|
+
`);
|
|
5094
|
+
console.log(` ${shellSetup}`);
|
|
5095
|
+
}
|
|
5096
|
+
}
|
|
5097
|
+
function openTerminalWindow(opts) {
|
|
5098
|
+
const { shellSetup, label, dir, shell } = opts;
|
|
5099
|
+
if (process.platform === "darwin" && !isPowerShellShell(shell)) {
|
|
5100
|
+
openMacTerminal(shellSetup, label, dir);
|
|
5101
|
+
return;
|
|
5102
|
+
}
|
|
5103
|
+
if (process.platform === "win32" && isPowerShellShell(shell)) {
|
|
5104
|
+
openWindowsPowerShell(shellSetup, label, dir, shell);
|
|
5105
|
+
return;
|
|
5106
|
+
}
|
|
5107
|
+
console.log(`Run this in a new terminal:
|
|
5108
|
+
`);
|
|
5109
|
+
console.log(` ${shellSetup}
|
|
5110
|
+
`);
|
|
5111
|
+
console.log(
|
|
5112
|
+
`(Automatic terminal opening is only supported on macOS Terminal/iTerm2 and Windows PowerShell for now.)`
|
|
5113
|
+
);
|
|
5114
|
+
}
|
|
5115
|
+
|
|
5116
|
+
// src/cli/agent-harness.ts
|
|
5117
|
+
var AGENT_HARNESSES = [
|
|
5118
|
+
{ id: "claude-code", label: "Claude Code", kind: "cli", command: "claude" },
|
|
5119
|
+
{ id: "codex", label: "Codex", kind: "cli", command: "codex" },
|
|
5120
|
+
{ id: "opencode", label: "opencode", kind: "cli", command: "opencode" },
|
|
5121
|
+
{
|
|
5122
|
+
id: "cursor",
|
|
5123
|
+
label: "Cursor",
|
|
5124
|
+
kind: "app",
|
|
5125
|
+
command: "cursor",
|
|
5126
|
+
candidatePaths: {
|
|
5127
|
+
win32: [
|
|
5128
|
+
join12(homedir8(), "AppData", "Local", "Programs", "cursor", "Cursor.exe")
|
|
5129
|
+
],
|
|
5130
|
+
darwin: ["/Applications/Cursor.app/Contents/MacOS/Cursor"]
|
|
5131
|
+
}
|
|
5132
|
+
},
|
|
5133
|
+
{ id: "copilot", label: "GitHub Copilot", kind: "app", command: "copilot" },
|
|
5134
|
+
{
|
|
5135
|
+
id: "antigravity",
|
|
5136
|
+
label: "Antigravity",
|
|
5137
|
+
kind: "app",
|
|
5138
|
+
command: "antigravity"
|
|
5139
|
+
}
|
|
5140
|
+
];
|
|
5141
|
+
function getHarness(id) {
|
|
5142
|
+
return AGENT_HARNESSES.find((h) => h.id === id);
|
|
5143
|
+
}
|
|
5144
|
+
function resolveHarnessExecutable(harness, overrideCommand, deps = {}) {
|
|
5145
|
+
const find = deps.find ?? findExecutable;
|
|
5146
|
+
const exists = deps.exists ?? existsSync12;
|
|
5147
|
+
const platform = deps.platform ?? process.platform;
|
|
5148
|
+
const found = find(overrideCommand || harness.command);
|
|
5149
|
+
if (found) return found;
|
|
5150
|
+
if (harness.kind === "app") {
|
|
5151
|
+
for (const candidate of harness.candidatePaths?.[platform] ?? []) {
|
|
5152
|
+
if (exists(candidate)) return candidate;
|
|
5153
|
+
}
|
|
5154
|
+
}
|
|
5155
|
+
return null;
|
|
5156
|
+
}
|
|
5157
|
+
function planHarnessLaunch(harness, opts) {
|
|
5158
|
+
if (harness.kind === "cli") {
|
|
5159
|
+
const invocation = isPowerShellShell(opts.shell) ? `& ${psSingleQuoted2(opts.executable)}` : JSON.stringify(opts.executable);
|
|
5160
|
+
return {
|
|
5161
|
+
kind: "cli",
|
|
5162
|
+
shell: opts.shell,
|
|
5163
|
+
shellSetup: buildShellSetupCommand(
|
|
5164
|
+
opts.workspace,
|
|
5165
|
+
opts.shell,
|
|
5166
|
+
invocation
|
|
5167
|
+
)
|
|
5168
|
+
};
|
|
5169
|
+
}
|
|
5170
|
+
return { kind: "app", executable: opts.executable, args: [opts.workspace] };
|
|
5171
|
+
}
|
|
5172
|
+
function launchHarness(harness, opts) {
|
|
5173
|
+
const plan = planHarnessLaunch(harness, opts);
|
|
5174
|
+
if (plan.kind === "cli") {
|
|
5175
|
+
openTerminalWindow({
|
|
5176
|
+
shellSetup: plan.shellSetup,
|
|
5177
|
+
label: `agent-${harness.id}`,
|
|
5178
|
+
dir: opts.workspace,
|
|
5179
|
+
shell: plan.shell
|
|
5180
|
+
});
|
|
5181
|
+
return;
|
|
5182
|
+
}
|
|
5183
|
+
spawn(plan.executable, plan.args, {
|
|
5184
|
+
detached: true,
|
|
5185
|
+
stdio: "ignore",
|
|
5186
|
+
windowsHide: true
|
|
5187
|
+
}).unref();
|
|
5188
|
+
console.log(`Launched ${harness.label} in ${opts.workspace}`);
|
|
5189
|
+
}
|
|
5190
|
+
|
|
4882
5191
|
// src/cli/commands/agent.ts
|
|
4883
5192
|
var C = {
|
|
4884
5193
|
reset: "\x1B[0m",
|
|
@@ -4890,7 +5199,7 @@ var C = {
|
|
|
4890
5199
|
};
|
|
4891
5200
|
var SUPPORTED_AGENTS = ["opencode"];
|
|
4892
5201
|
function agentsMdPresent(cwd = process.cwd()) {
|
|
4893
|
-
return
|
|
5202
|
+
return existsSync13(join13(cwd, "AGENTS.md"));
|
|
4894
5203
|
}
|
|
4895
5204
|
function printStatus() {
|
|
4896
5205
|
const installed = hasCommand("opencode");
|
|
@@ -4928,19 +5237,87 @@ var installCmd = new Command("install").description("Download and install the de
|
|
|
4928
5237
|
console.log(` Start it with: ${C.cyan}opencode${C.reset}`);
|
|
4929
5238
|
});
|
|
4930
5239
|
var statusCmd = new Command("status").description("Show whether the agent is installed and wired to ZAM").action(printStatus);
|
|
4931
|
-
var
|
|
5240
|
+
var listCmd = new Command("list").description("List known agent harnesses and whether each is detected").action(() => {
|
|
5241
|
+
console.log(`${C.bold}Agent harnesses${C.reset}`);
|
|
5242
|
+
for (const h of AGENT_HARNESSES) {
|
|
5243
|
+
const status = resolveHarnessExecutable(h) ? `${C.green}detected${C.reset}` : `${C.yellow}not found${C.reset}`;
|
|
5244
|
+
console.log(
|
|
5245
|
+
` ${h.id.padEnd(13)} ${status} ${C.dim}${h.label} (${h.kind})${C.reset}`
|
|
5246
|
+
);
|
|
5247
|
+
}
|
|
5248
|
+
console.log(`
|
|
5249
|
+
${C.dim}Open one: zam agent open --id <id>${C.reset}`);
|
|
5250
|
+
console.log(
|
|
5251
|
+
` ${C.dim}Point ZAM at a custom path: zam settings set agent.<id>.command <path>${C.reset}`
|
|
5252
|
+
);
|
|
5253
|
+
});
|
|
5254
|
+
var openCmd = new Command("open").description("Open an agent harness in the workspace, ready to drive ZAM").option(
|
|
5255
|
+
"--id <id>",
|
|
5256
|
+
"Harness id (default: agent.default setting, else first detected)"
|
|
5257
|
+
).option("--dir <path>", "Workspace directory (defaults to cwd)").option(
|
|
5258
|
+
"--shell <type>",
|
|
5259
|
+
"Shell type: zsh | bash | pwsh | powershell (auto-detected)"
|
|
5260
|
+
).action(async (opts) => {
|
|
5261
|
+
let shell;
|
|
5262
|
+
try {
|
|
5263
|
+
shell = normalizeShell(opts.shell);
|
|
5264
|
+
} catch (err) {
|
|
5265
|
+
console.error(`Error: ${err.message}`);
|
|
5266
|
+
process.exit(1);
|
|
5267
|
+
}
|
|
5268
|
+
let id = opts.id;
|
|
5269
|
+
let override;
|
|
5270
|
+
let db;
|
|
5271
|
+
try {
|
|
5272
|
+
db = await openDatabase();
|
|
5273
|
+
if (!id) {
|
|
5274
|
+
id = await getSetting(db, "agent.default") || void 0;
|
|
5275
|
+
}
|
|
5276
|
+
if (!id) {
|
|
5277
|
+
id = AGENT_HARNESSES.find((h) => resolveHarnessExecutable(h))?.id;
|
|
5278
|
+
}
|
|
5279
|
+
if (id) {
|
|
5280
|
+
override = await getSetting(db, `agent.${id}.command`) || void 0;
|
|
5281
|
+
}
|
|
5282
|
+
} finally {
|
|
5283
|
+
await db?.close();
|
|
5284
|
+
}
|
|
5285
|
+
if (!id) {
|
|
5286
|
+
console.error(
|
|
5287
|
+
"No agent harness configured or detected. Install one (e.g. zam agent install), then set a default: zam settings set agent.default <id>."
|
|
5288
|
+
);
|
|
5289
|
+
process.exit(1);
|
|
5290
|
+
}
|
|
5291
|
+
const harness = getHarness(id);
|
|
5292
|
+
if (!harness) {
|
|
5293
|
+
console.error(
|
|
5294
|
+
`Unknown harness: ${id}. Known: ${AGENT_HARNESSES.map((h) => h.id).join(", ")}.`
|
|
5295
|
+
);
|
|
5296
|
+
process.exit(1);
|
|
5297
|
+
}
|
|
5298
|
+
const executable = resolveHarnessExecutable(harness, override);
|
|
5299
|
+
if (!executable) {
|
|
5300
|
+
console.error(
|
|
5301
|
+
`${harness.label} was not detected. Install it, or point ZAM at it: zam settings set agent.${harness.id}.command <path>.`
|
|
5302
|
+
);
|
|
5303
|
+
process.exit(1);
|
|
5304
|
+
}
|
|
5305
|
+
const workspace = opts.dir ?? process.cwd();
|
|
5306
|
+
launchHarness(harness, { executable, workspace, shell });
|
|
5307
|
+
});
|
|
5308
|
+
var agentCommand = new Command("agent").description("Provision and inspect the agent that drives ZAM sessions").addCommand(installCmd).addCommand(statusCmd).addCommand(openCmd).addCommand(listCmd).action(printStatus);
|
|
4932
5309
|
|
|
4933
5310
|
// src/cli/commands/bridge.ts
|
|
4934
|
-
import { execFileSync as
|
|
5311
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
4935
5312
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
4936
5313
|
import { readdirSync as readdirSync2, readFileSync as readFileSync10, rmSync as rmSync2 } from "fs";
|
|
4937
|
-
import { homedir as
|
|
4938
|
-
import { join as
|
|
4939
|
-
import { Command as
|
|
5314
|
+
import { homedir as homedir10, tmpdir as tmpdir3 } from "os";
|
|
5315
|
+
import { join as join16, resolve as resolve3 } from "path";
|
|
5316
|
+
import { Command as Command3 } from "commander";
|
|
4940
5317
|
|
|
4941
5318
|
// src/cli/llm/client.ts
|
|
4942
|
-
import { spawn } from "child_process";
|
|
4943
|
-
import { existsSync as
|
|
5319
|
+
import { spawn as spawn2 } from "child_process";
|
|
5320
|
+
import { existsSync as existsSync14 } from "fs";
|
|
4944
5321
|
var DEFAULT_LLM_URL = "http://localhost:8000/v1";
|
|
4945
5322
|
var DEFAULT_LLM_MODEL = "qwen3.5:4b";
|
|
4946
5323
|
var DEFAULT_LLM_API_KEY = "sk-none";
|
|
@@ -4953,19 +5330,108 @@ async function getLlmConfig(db) {
|
|
|
4953
5330
|
locale: await getSetting(db, "system.locale") || "en"
|
|
4954
5331
|
};
|
|
4955
5332
|
}
|
|
4956
|
-
|
|
4957
|
-
const
|
|
4958
|
-
|
|
4959
|
-
|
|
5333
|
+
function getCloudModelRecommendation(url) {
|
|
5334
|
+
const lowercase = url.toLowerCase();
|
|
5335
|
+
if (lowercase.includes("openrouter.ai")) {
|
|
5336
|
+
return "openrouter/free";
|
|
5337
|
+
}
|
|
5338
|
+
if (lowercase.includes("openai.com") || lowercase.includes("api.openai")) {
|
|
5339
|
+
return "gpt-5-mini";
|
|
5340
|
+
}
|
|
5341
|
+
if (lowercase.includes("googleapis.com") || lowercase.includes("google")) {
|
|
5342
|
+
return "gemini-3.5-flash";
|
|
5343
|
+
}
|
|
5344
|
+
if (lowercase.includes("deepseek.com")) {
|
|
5345
|
+
return "deepseek-v4-flash";
|
|
5346
|
+
}
|
|
5347
|
+
if (lowercase.includes("mimo")) {
|
|
5348
|
+
return "mimo-v2.5";
|
|
5349
|
+
}
|
|
5350
|
+
return null;
|
|
5351
|
+
}
|
|
5352
|
+
function inferApiFlavor(url) {
|
|
5353
|
+
try {
|
|
5354
|
+
return new URL(url).hostname.toLowerCase().endsWith("anthropic.com") ? "anthropic-messages" : "chat-completions";
|
|
5355
|
+
} catch {
|
|
5356
|
+
return "chat-completions";
|
|
5357
|
+
}
|
|
5358
|
+
}
|
|
5359
|
+
async function readJsonSetting(db, key) {
|
|
5360
|
+
const raw = await getSetting(db, key);
|
|
5361
|
+
if (!raw) return null;
|
|
5362
|
+
try {
|
|
5363
|
+
return JSON.parse(raw);
|
|
5364
|
+
} catch {
|
|
5365
|
+
return null;
|
|
5366
|
+
}
|
|
5367
|
+
}
|
|
5368
|
+
function resolveProviderApiKey(rec) {
|
|
5369
|
+
if (rec.apiKey) return rec.apiKey;
|
|
5370
|
+
if (rec.apiKeyRef) {
|
|
5371
|
+
const key = getProviderApiKey(rec.apiKeyRef);
|
|
5372
|
+
if (key) return key;
|
|
5373
|
+
}
|
|
5374
|
+
return DEFAULT_LLM_API_KEY;
|
|
5375
|
+
}
|
|
5376
|
+
async function getProviderForRole(db, role) {
|
|
5377
|
+
const enabled = role === "vision" ? await getSetting(db, "llm.vision.enabled") === "true" : await getSetting(db, "llm.enabled") === "true";
|
|
5378
|
+
const base = await getLegacyRoleConfig(db, role, enabled);
|
|
5379
|
+
const providers = await readJsonSetting(db, "llm.providers");
|
|
5380
|
+
const roles = await readJsonSetting(db, "llm.roles");
|
|
5381
|
+
const binding = roles?.[role];
|
|
5382
|
+
if (providers && binding?.primary && providers[binding.primary]) {
|
|
5383
|
+
const primary = materializeProvider(providers[binding.primary], base, role);
|
|
5384
|
+
const fallback = binding.fallback && providers[binding.fallback] ? materializeProvider(providers[binding.fallback], base, role) : void 0;
|
|
5385
|
+
return { ...primary, fallback };
|
|
5386
|
+
}
|
|
5387
|
+
return base;
|
|
5388
|
+
}
|
|
5389
|
+
function materializeProvider(rec, base, role) {
|
|
5390
|
+
const url = rec.url || base.url;
|
|
4960
5391
|
return {
|
|
4961
|
-
enabled:
|
|
4962
|
-
url
|
|
4963
|
-
model:
|
|
4964
|
-
apiKey:
|
|
5392
|
+
enabled: base.enabled,
|
|
5393
|
+
url,
|
|
5394
|
+
model: rec.model || base.model,
|
|
5395
|
+
apiKey: resolveProviderApiKey(rec),
|
|
5396
|
+
apiFlavor: rec.apiFlavor || inferApiFlavor(url),
|
|
4965
5397
|
locale: base.locale,
|
|
4966
|
-
maxFrames:
|
|
5398
|
+
...role === "vision" ? { maxFrames: base.maxFrames } : {}
|
|
5399
|
+
};
|
|
5400
|
+
}
|
|
5401
|
+
async function getLegacyRoleConfig(db, role, enabled) {
|
|
5402
|
+
const base = await getLlmConfig(db);
|
|
5403
|
+
if (role === "vision") {
|
|
5404
|
+
const maxFramesStr = await getSetting(db, "llm.vision.max_frames");
|
|
5405
|
+
const parsed = maxFramesStr ? parseInt(maxFramesStr, 10) : 100;
|
|
5406
|
+
const url = await getSetting(db, "llm.vision.url") || base.url;
|
|
5407
|
+
let model = await getSetting(db, "llm.vision.model");
|
|
5408
|
+
if (!model) model = getCloudModelRecommendation(url) || base.model;
|
|
5409
|
+
return {
|
|
5410
|
+
enabled,
|
|
5411
|
+
url,
|
|
5412
|
+
model,
|
|
5413
|
+
apiKey: await getSetting(db, "llm.vision.api_key") || base.apiKey,
|
|
5414
|
+
apiFlavor: inferApiFlavor(url),
|
|
5415
|
+
locale: base.locale,
|
|
5416
|
+
maxFrames: Number.isNaN(parsed) ? 100 : parsed
|
|
5417
|
+
};
|
|
5418
|
+
}
|
|
5419
|
+
return {
|
|
5420
|
+
enabled,
|
|
5421
|
+
url: base.url,
|
|
5422
|
+
model: base.model,
|
|
5423
|
+
apiKey: base.apiKey,
|
|
5424
|
+
apiFlavor: inferApiFlavor(base.url),
|
|
5425
|
+
locale: base.locale
|
|
4967
5426
|
};
|
|
4968
5427
|
}
|
|
5428
|
+
function assertChatCompletions(cfg) {
|
|
5429
|
+
if (cfg.apiFlavor !== "chat-completions") {
|
|
5430
|
+
throw new Error(
|
|
5431
|
+
`This role is configured for a "${cfg.apiFlavor}" provider, which is not supported here yet. Use a chat-completions provider for the recall role.`
|
|
5432
|
+
);
|
|
5433
|
+
}
|
|
5434
|
+
}
|
|
4969
5435
|
var LANGUAGE_NAMES = {
|
|
4970
5436
|
en: "English",
|
|
4971
5437
|
de: "German",
|
|
@@ -5006,10 +5472,11 @@ async function readChatContent(res, label) {
|
|
|
5006
5472
|
return content.trim();
|
|
5007
5473
|
}
|
|
5008
5474
|
async function generateQuestionViaLLM(db, input8) {
|
|
5009
|
-
const cfg = await
|
|
5475
|
+
const cfg = await getProviderForRole(db, "recall");
|
|
5010
5476
|
if (!cfg.enabled) {
|
|
5011
5477
|
throw new Error("LLM integration is disabled in settings (llm.enabled)");
|
|
5012
5478
|
}
|
|
5479
|
+
assertChatCompletions(cfg);
|
|
5013
5480
|
const bloom = input8.bloomLevel >= 1 && input8.bloomLevel <= 5 ? input8.bloomLevel : 1;
|
|
5014
5481
|
const verb = BLOOM_VERBS2[bloom];
|
|
5015
5482
|
const langName = LANGUAGE_NAMES[cfg.locale] || "English";
|
|
@@ -5049,10 +5516,11 @@ Active-Recall Question:`;
|
|
|
5049
5516
|
return readChatContent(res, "LLM request");
|
|
5050
5517
|
}
|
|
5051
5518
|
async function evaluateAnswerViaLLM(db, input8) {
|
|
5052
|
-
const cfg = await
|
|
5519
|
+
const cfg = await getProviderForRole(db, "recall");
|
|
5053
5520
|
if (!cfg.enabled) {
|
|
5054
5521
|
throw new Error("LLM integration is disabled in settings (llm.enabled)");
|
|
5055
5522
|
}
|
|
5523
|
+
assertChatCompletions(cfg);
|
|
5056
5524
|
const langName = LANGUAGE_NAMES[cfg.locale] || "English";
|
|
5057
5525
|
const ratingPrefix = LOCALIZED_RATING_PREFIX[cfg.locale] || "Suggested rating";
|
|
5058
5526
|
const systemPrompt = `You are ZAM, an extremely warm, encouraging, and patient skills trainer.
|
|
@@ -5102,10 +5570,11 @@ Evaluation:`;
|
|
|
5102
5570
|
return readChatContent(res, "LLM evaluation");
|
|
5103
5571
|
}
|
|
5104
5572
|
async function translateQuestionViaLLM(db, question) {
|
|
5105
|
-
const cfg = await
|
|
5573
|
+
const cfg = await getProviderForRole(db, "recall");
|
|
5106
5574
|
if (!cfg.enabled) {
|
|
5107
5575
|
throw new Error("LLM integration is disabled in settings");
|
|
5108
5576
|
}
|
|
5577
|
+
assertChatCompletions(cfg);
|
|
5109
5578
|
const targetLang = LANGUAGE_NAMES[cfg.locale] || "English";
|
|
5110
5579
|
const systemPrompt = `You are a highly precise translator. Translate the given active-recall question into clear, natural ${targetLang}.
|
|
5111
5580
|
Output ONLY the raw translation. Do not include any headers, preamble, quotes, or conversational filler.`;
|
|
@@ -5159,34 +5628,82 @@ async function getAvailableModels(url, apiKey = DEFAULT_LLM_API_KEY) {
|
|
|
5159
5628
|
return [];
|
|
5160
5629
|
}
|
|
5161
5630
|
}
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
(candidate) => candidate.toLowerCase() === model.toLowerCase()
|
|
5175
|
-
);
|
|
5176
|
-
}
|
|
5631
|
+
function isLocalEndpoint(url) {
|
|
5632
|
+
return url.includes("localhost") || url.includes("127.0.0.1") || url.includes("[::1]") || url.includes("::1");
|
|
5633
|
+
}
|
|
5634
|
+
async function checkProviderEndpoint(endpoint) {
|
|
5635
|
+
const online = await isLlmOnline(endpoint.url);
|
|
5636
|
+
if (!online) {
|
|
5637
|
+
return {
|
|
5638
|
+
endpoint,
|
|
5639
|
+
online: false,
|
|
5640
|
+
availableModels: [],
|
|
5641
|
+
modelAvailable: false
|
|
5642
|
+
};
|
|
5177
5643
|
}
|
|
5644
|
+
const availableModels = await getAvailableModels(
|
|
5645
|
+
endpoint.url,
|
|
5646
|
+
endpoint.apiKey
|
|
5647
|
+
);
|
|
5648
|
+
const modelAvailable = availableModels.length === 0 || availableModels.some(
|
|
5649
|
+
(candidate) => candidate.toLowerCase() === endpoint.model.toLowerCase()
|
|
5650
|
+
);
|
|
5651
|
+
return { endpoint, online, availableModels, modelAvailable };
|
|
5652
|
+
}
|
|
5653
|
+
function isEndpointUsable(readiness) {
|
|
5654
|
+
return readiness.online && readiness.modelAvailable;
|
|
5655
|
+
}
|
|
5656
|
+
function providerChain(primary) {
|
|
5657
|
+
return [primary, ...primary.fallback ? [primary.fallback] : []];
|
|
5658
|
+
}
|
|
5659
|
+
async function checkProviderChain(primary) {
|
|
5660
|
+
let first;
|
|
5661
|
+
for (const endpoint of providerChain(primary)) {
|
|
5662
|
+
const readiness = await checkProviderEndpoint(endpoint);
|
|
5663
|
+
first ??= readiness;
|
|
5664
|
+
if (isEndpointUsable(readiness)) {
|
|
5665
|
+
return { primary: first, firstUsable: readiness };
|
|
5666
|
+
}
|
|
5667
|
+
}
|
|
5668
|
+
return { primary: first };
|
|
5669
|
+
}
|
|
5670
|
+
async function isVisionProviderModelExplicit(db, active) {
|
|
5671
|
+
if (await getSetting(db, "llm.vision.model")) return true;
|
|
5672
|
+
const providers = await readJsonSetting(db, "llm.providers");
|
|
5673
|
+
const roles = await readJsonSetting(db, "llm.roles");
|
|
5674
|
+
const binding = roles?.vision;
|
|
5675
|
+
if (!providers || !binding) return false;
|
|
5676
|
+
return [binding.primary, binding.fallback].some((id) => {
|
|
5677
|
+
if (!id) return false;
|
|
5678
|
+
const rec = providers[id];
|
|
5679
|
+
return rec?.model === active.model && (rec.url === void 0 || rec.url === active.url);
|
|
5680
|
+
});
|
|
5681
|
+
}
|
|
5682
|
+
async function checkVisionReadiness(db) {
|
|
5683
|
+
const cfg = await getProviderForRole(db, "vision");
|
|
5684
|
+
const chain = cfg.enabled ? await checkProviderChain(cfg) : void 0;
|
|
5685
|
+
const selected = chain?.firstUsable ?? chain?.primary;
|
|
5686
|
+
const active = selected?.endpoint ?? cfg;
|
|
5687
|
+
const online = selected?.online ?? false;
|
|
5688
|
+
const availableModels = selected?.availableModels ?? [];
|
|
5689
|
+
const modelAvailable = selected?.modelAvailable ?? false;
|
|
5690
|
+
const visionModelExplicit = await isVisionProviderModelExplicit(db, active);
|
|
5178
5691
|
let warning;
|
|
5179
|
-
if (enabled && online && modelAvailable && !visionModelExplicit) {
|
|
5180
|
-
|
|
5692
|
+
if (cfg.enabled && online && modelAvailable && !visionModelExplicit) {
|
|
5693
|
+
const cloudRec = getCloudModelRecommendation(active.url);
|
|
5694
|
+
if (cloudRec && active.model === cloudRec) {
|
|
5695
|
+
} else {
|
|
5696
|
+
warning = `No explicit vision model configured (llm.vision.model). Falling back to base model "${active.model}", which may not support image input. Set a multimodal model: zam settings set llm.vision.model <model>`;
|
|
5697
|
+
}
|
|
5181
5698
|
}
|
|
5182
5699
|
return {
|
|
5183
|
-
enabled,
|
|
5700
|
+
enabled: cfg.enabled,
|
|
5184
5701
|
online,
|
|
5185
|
-
url,
|
|
5186
|
-
model,
|
|
5702
|
+
url: active.url,
|
|
5703
|
+
model: active.model,
|
|
5187
5704
|
modelAvailable,
|
|
5188
5705
|
availableModels,
|
|
5189
|
-
usable: enabled && online && modelAvailable,
|
|
5706
|
+
usable: cfg.enabled && online && modelAvailable,
|
|
5190
5707
|
visionModelExplicit,
|
|
5191
5708
|
warning
|
|
5192
5709
|
};
|
|
@@ -5211,16 +5728,16 @@ function spawnLocalRunner(url, model) {
|
|
|
5211
5728
|
const { runner, port } = detectRunner(url, model);
|
|
5212
5729
|
try {
|
|
5213
5730
|
if (runner === "fastflowlm") {
|
|
5214
|
-
const flmExe =
|
|
5731
|
+
const flmExe = existsSync14("C:\\Program Files\\flm\\flm.exe") ? "C:\\Program Files\\flm\\flm.exe" : "flm";
|
|
5215
5732
|
if (!hasCommand("flm") && flmExe === "flm") return;
|
|
5216
|
-
|
|
5733
|
+
spawn2(flmExe, ["serve", model, "--port", port], {
|
|
5217
5734
|
detached: true,
|
|
5218
5735
|
stdio: "ignore",
|
|
5219
5736
|
windowsHide: true
|
|
5220
5737
|
}).unref();
|
|
5221
5738
|
} else if (runner === "ollama" || runner === "generic") {
|
|
5222
5739
|
if (!hasCommand("ollama")) return;
|
|
5223
|
-
|
|
5740
|
+
spawn2("ollama", ["serve"], {
|
|
5224
5741
|
detached: true,
|
|
5225
5742
|
stdio: "ignore",
|
|
5226
5743
|
windowsHide: true
|
|
@@ -5232,7 +5749,7 @@ function spawnLocalRunner(url, model) {
|
|
|
5232
5749
|
async function startLocalRunner(url, model, locale) {
|
|
5233
5750
|
const { runner, port } = detectRunner(url, model);
|
|
5234
5751
|
if (runner === "fastflowlm") {
|
|
5235
|
-
const flmExe =
|
|
5752
|
+
const flmExe = existsSync14("C:\\Program Files\\flm\\flm.exe") ? "C:\\Program Files\\flm\\flm.exe" : "flm";
|
|
5236
5753
|
if (!hasCommand("flm") && flmExe === "flm") {
|
|
5237
5754
|
console.warn(
|
|
5238
5755
|
"\x1B[31m\u2717 FastFlowLM is configured but could not be found on the system.\x1B[0m"
|
|
@@ -5245,7 +5762,7 @@ async function startLocalRunner(url, model, locale) {
|
|
|
5245
5762
|
`\x1B[36mStarting FastFlowLM serve process: ${flmExe} ${args.join(" ")}\x1B[0m`
|
|
5246
5763
|
);
|
|
5247
5764
|
try {
|
|
5248
|
-
|
|
5765
|
+
spawn2(flmExe, args, {
|
|
5249
5766
|
detached: true,
|
|
5250
5767
|
stdio: "ignore",
|
|
5251
5768
|
windowsHide: true
|
|
@@ -5266,7 +5783,7 @@ async function startLocalRunner(url, model, locale) {
|
|
|
5266
5783
|
}
|
|
5267
5784
|
console.log("\x1B[36mStarting Ollama serve process: ollama serve\x1B[0m");
|
|
5268
5785
|
try {
|
|
5269
|
-
|
|
5786
|
+
spawn2("ollama", ["serve"], {
|
|
5270
5787
|
detached: true,
|
|
5271
5788
|
stdio: "ignore",
|
|
5272
5789
|
windowsHide: true
|
|
@@ -5289,7 +5806,7 @@ async function startLocalRunner(url, model, locale) {
|
|
|
5289
5806
|
let attempts = 0;
|
|
5290
5807
|
const dotsPerLine = 30;
|
|
5291
5808
|
while (true) {
|
|
5292
|
-
await new Promise((
|
|
5809
|
+
await new Promise((resolve6) => setTimeout(resolve6, 500));
|
|
5293
5810
|
if (await isLlmOnline(url)) {
|
|
5294
5811
|
if (attempts > 0) process.stdout.write("\n");
|
|
5295
5812
|
return true;
|
|
@@ -5314,12 +5831,18 @@ async function startLocalRunner(url, model, locale) {
|
|
|
5314
5831
|
}
|
|
5315
5832
|
}
|
|
5316
5833
|
async function ensureLocalLlmRunning(db) {
|
|
5317
|
-
const cfg = await
|
|
5834
|
+
const cfg = await getProviderForRole(db, "recall");
|
|
5318
5835
|
if (!cfg.enabled) {
|
|
5319
5836
|
return { usable: false, reason: "disabled" };
|
|
5320
5837
|
}
|
|
5838
|
+
if (cfg.apiFlavor !== "chat-completions") {
|
|
5839
|
+
console.warn(
|
|
5840
|
+
`\x1B[31m\u2717 Recall is configured for "${cfg.apiFlavor}", but active recall currently supports chat-completions providers only.\x1B[0m`
|
|
5841
|
+
);
|
|
5842
|
+
return { usable: false, reason: "unsupported-provider" };
|
|
5843
|
+
}
|
|
5321
5844
|
const { url, model, apiKey, locale } = cfg;
|
|
5322
|
-
const isLocal =
|
|
5845
|
+
const isLocal = isLocalEndpoint(url);
|
|
5323
5846
|
console.log(`Checking if local LLM server is online at ${url}...`);
|
|
5324
5847
|
let online = await isLlmOnline(url);
|
|
5325
5848
|
if (!online && isLocal) {
|
|
@@ -5353,8 +5876,9 @@ async function ensureLocalLlmRunning(db) {
|
|
|
5353
5876
|
}
|
|
5354
5877
|
async function ensureLlmReadyHeadless(db, opts = {}) {
|
|
5355
5878
|
const timeoutMs = opts.timeoutMs ?? 25e3;
|
|
5356
|
-
const
|
|
5357
|
-
|
|
5879
|
+
const cfg = await getProviderForRole(db, "recall");
|
|
5880
|
+
const { url, model, apiKey } = cfg;
|
|
5881
|
+
if (!cfg.enabled) {
|
|
5358
5882
|
return {
|
|
5359
5883
|
usable: false,
|
|
5360
5884
|
reason: "disabled",
|
|
@@ -5363,7 +5887,16 @@ async function ensureLlmReadyHeadless(db, opts = {}) {
|
|
|
5363
5887
|
availableModels: []
|
|
5364
5888
|
};
|
|
5365
5889
|
}
|
|
5366
|
-
|
|
5890
|
+
if (cfg.apiFlavor !== "chat-completions") {
|
|
5891
|
+
return {
|
|
5892
|
+
usable: false,
|
|
5893
|
+
reason: "unsupported-provider",
|
|
5894
|
+
online: false,
|
|
5895
|
+
model,
|
|
5896
|
+
availableModels: []
|
|
5897
|
+
};
|
|
5898
|
+
}
|
|
5899
|
+
const isLocal = isLocalEndpoint(url);
|
|
5367
5900
|
let online = await isLlmOnline(url);
|
|
5368
5901
|
if (!online && isLocal) {
|
|
5369
5902
|
spawnLocalRunner(url, model);
|
|
@@ -5428,8 +5961,8 @@ async function fetchWithInteractiveTimeout(url, options = {}) {
|
|
|
5428
5961
|
const dotsPerLine = 30;
|
|
5429
5962
|
while (true) {
|
|
5430
5963
|
let timeoutId;
|
|
5431
|
-
const timeoutPromise = new Promise((
|
|
5432
|
-
timeoutId = setTimeout(() =>
|
|
5964
|
+
const timeoutPromise = new Promise((resolve6) => {
|
|
5965
|
+
timeoutId = setTimeout(() => resolve6("timeout"), timeoutMs);
|
|
5433
5966
|
});
|
|
5434
5967
|
const dotsInterval = setInterval(() => {
|
|
5435
5968
|
process.stdout.write(".");
|
|
@@ -5502,8 +6035,8 @@ async function ensureHighQualityQuestion(db, token) {
|
|
|
5502
6035
|
// src/cli/llm/vision.ts
|
|
5503
6036
|
import { randomBytes } from "crypto";
|
|
5504
6037
|
import { readFileSync as readFileSync9 } from "fs";
|
|
5505
|
-
import { tmpdir } from "os";
|
|
5506
|
-
import { basename as
|
|
6038
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
6039
|
+
import { basename as basename3, join as join14 } from "path";
|
|
5507
6040
|
var LANGUAGE_NAMES2 = {
|
|
5508
6041
|
en: "English",
|
|
5509
6042
|
de: "German",
|
|
@@ -5530,7 +6063,7 @@ var ACTION_TYPES2 = /* @__PURE__ */ new Set([
|
|
|
5530
6063
|
"window-change"
|
|
5531
6064
|
]);
|
|
5532
6065
|
async function observeUiSnapshotViaLLM(db, input8) {
|
|
5533
|
-
const cfg = await
|
|
6066
|
+
const cfg = await getProviderForRole(db, "vision");
|
|
5534
6067
|
if (!cfg.enabled) {
|
|
5535
6068
|
throw new Error(
|
|
5536
6069
|
"Vision observation is disabled in settings (llm.vision.enabled)"
|
|
@@ -5539,21 +6072,21 @@ async function observeUiSnapshotViaLLM(db, input8) {
|
|
|
5539
6072
|
const imageUrls = [];
|
|
5540
6073
|
const isVideo = /\.(mp4|mov|m4v|avi|mkv|webm)$/i.test(input8.imagePath);
|
|
5541
6074
|
if (isVideo) {
|
|
5542
|
-
const { mkdirSync:
|
|
5543
|
-
const { execSync:
|
|
5544
|
-
const tempDir =
|
|
5545
|
-
|
|
6075
|
+
const { mkdirSync: mkdirSync13, readdirSync: readdirSync3, rmSync: rmSync3 } = await import("fs");
|
|
6076
|
+
const { execSync: execSync6 } = await import("child_process");
|
|
6077
|
+
const tempDir = join14(
|
|
6078
|
+
tmpdir2(),
|
|
5546
6079
|
`zam-frames-${randomBytes(4).toString("hex")}`
|
|
5547
6080
|
);
|
|
5548
|
-
|
|
6081
|
+
mkdirSync13(tempDir, { recursive: true });
|
|
5549
6082
|
try {
|
|
5550
|
-
|
|
6083
|
+
execSync6(
|
|
5551
6084
|
`ffmpeg -i "${input8.imagePath}" -vf "fps=1/3,scale=1280:-1" -vsync vfr "${tempDir}/frame_%03d.png"`,
|
|
5552
6085
|
{ stdio: "ignore" }
|
|
5553
6086
|
);
|
|
5554
6087
|
let files = readdirSync3(tempDir).filter((f) => f.endsWith(".png")).sort();
|
|
5555
6088
|
if (files.length === 0) {
|
|
5556
|
-
|
|
6089
|
+
execSync6(
|
|
5557
6090
|
`ffmpeg -i "${input8.imagePath}" -vframes 1 "${tempDir}/frame_001.png"`,
|
|
5558
6091
|
{ stdio: "ignore" }
|
|
5559
6092
|
);
|
|
@@ -5574,7 +6107,7 @@ async function observeUiSnapshotViaLLM(db, input8) {
|
|
|
5574
6107
|
}
|
|
5575
6108
|
}
|
|
5576
6109
|
for (const file of sampledFiles) {
|
|
5577
|
-
const bytes = readFileSync9(
|
|
6110
|
+
const bytes = readFileSync9(join14(tempDir, file));
|
|
5578
6111
|
imageUrls.push(`data:image/png;base64,${bytes.toString("base64")}`);
|
|
5579
6112
|
}
|
|
5580
6113
|
} finally {
|
|
@@ -5592,42 +6125,92 @@ async function observeUiSnapshotViaLLM(db, input8) {
|
|
|
5592
6125
|
if (imageUrls.length === 0) {
|
|
5593
6126
|
throw new Error("No image data available for vision analysis");
|
|
5594
6127
|
}
|
|
5595
|
-
const
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
"Vision model returned output that could not be parsed as JSON."
|
|
5611
|
-
);
|
|
6128
|
+
const endpoints = [
|
|
6129
|
+
{
|
|
6130
|
+
url: cfg.url,
|
|
6131
|
+
apiKey: cfg.apiKey || DEFAULT_LLM_API_KEY,
|
|
6132
|
+
model: input8.model ?? cfg.model,
|
|
6133
|
+
apiFlavor: cfg.apiFlavor
|
|
6134
|
+
}
|
|
6135
|
+
];
|
|
6136
|
+
if (cfg.fallback) {
|
|
6137
|
+
endpoints.push({
|
|
6138
|
+
url: cfg.fallback.url,
|
|
6139
|
+
apiKey: cfg.fallback.apiKey || DEFAULT_LLM_API_KEY,
|
|
6140
|
+
model: cfg.fallback.model,
|
|
6141
|
+
apiFlavor: cfg.fallback.apiFlavor
|
|
6142
|
+
});
|
|
5612
6143
|
}
|
|
5613
|
-
|
|
5614
|
-
|
|
6144
|
+
let lastRequestError;
|
|
6145
|
+
let sawUnparseableDraft = false;
|
|
6146
|
+
let sawInvalidDraft = false;
|
|
6147
|
+
for (const endpoint of endpoints) {
|
|
6148
|
+
let content;
|
|
6149
|
+
try {
|
|
6150
|
+
content = await requestVisionDraft({
|
|
6151
|
+
...endpoint,
|
|
6152
|
+
locale: cfg.locale,
|
|
6153
|
+
imageUrls,
|
|
6154
|
+
input: input8
|
|
6155
|
+
});
|
|
6156
|
+
} catch (err) {
|
|
6157
|
+
lastRequestError = err;
|
|
6158
|
+
continue;
|
|
6159
|
+
}
|
|
6160
|
+
let draft;
|
|
6161
|
+
try {
|
|
6162
|
+
draft = extractDraft(content);
|
|
6163
|
+
} catch {
|
|
6164
|
+
sawUnparseableDraft = true;
|
|
6165
|
+
continue;
|
|
6166
|
+
}
|
|
6167
|
+
const report = buildReport(input8, draft);
|
|
6168
|
+
if (isUiObservationReport(report)) {
|
|
6169
|
+
return report;
|
|
6170
|
+
}
|
|
6171
|
+
sawInvalidDraft = true;
|
|
6172
|
+
}
|
|
6173
|
+
if (lastRequestError && !sawUnparseableDraft && !sawInvalidDraft) {
|
|
6174
|
+
throw lastRequestError;
|
|
6175
|
+
}
|
|
6176
|
+
if (sawInvalidDraft) {
|
|
5615
6177
|
return uncertainReport(
|
|
5616
6178
|
input8,
|
|
5617
6179
|
"Vision model returned an invalid UI report draft."
|
|
5618
6180
|
);
|
|
5619
6181
|
}
|
|
5620
|
-
return
|
|
6182
|
+
return uncertainReport(
|
|
6183
|
+
input8,
|
|
6184
|
+
"Vision model returned output that could not be parsed as JSON."
|
|
6185
|
+
);
|
|
5621
6186
|
}
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
|
|
6187
|
+
var VISION_SYSTEM_PROMPT = "You are ZAM's UI observer. Return only strict JSON. Do not include markdown, prose, or fields outside the requested schema.";
|
|
6188
|
+
function visionSchema(language) {
|
|
6189
|
+
return `{
|
|
5625
6190
|
"kind": "progress | step-completed | error | help-seeking | uncertain",
|
|
5626
6191
|
"summary": "short factual UI summary in ${language}",
|
|
5627
6192
|
"actions": [{"type": "click | shortcut | typing | scroll | window-change", "target": "optional UI target", "result": "optional visible result"}],
|
|
5628
6193
|
"candidateTokens": [{"slug": "optional-kebab-case-skill-token", "confidence": 0.0, "rationale": "why this token may matter"}],
|
|
5629
6194
|
"confidence": 0.0
|
|
5630
6195
|
}`;
|
|
6196
|
+
}
|
|
6197
|
+
function visionUserText(args, language) {
|
|
6198
|
+
const intro = args.imageUrls.length > 1 ? "Observe this sequence of Windows/macOS application snapshots showing a task performed over time." : "Observe this Windows/macOS application snapshot for a learning session.";
|
|
6199
|
+
return `${intro}
|
|
6200
|
+
Application process: ${args.input.application.processName}
|
|
6201
|
+
Window title: ${args.input.application.windowTitle ?? "(unknown)"}
|
|
6202
|
+
|
|
6203
|
+
Return this JSON draft only:
|
|
6204
|
+
${visionSchema(language)}`;
|
|
6205
|
+
}
|
|
6206
|
+
async function requestVisionDraft(args) {
|
|
6207
|
+
if (args.apiFlavor === "anthropic-messages") {
|
|
6208
|
+
return requestAnthropicVisionDraft(args);
|
|
6209
|
+
}
|
|
6210
|
+
return requestChatCompletionsVisionDraft(args);
|
|
6211
|
+
}
|
|
6212
|
+
async function requestChatCompletionsVisionDraft(args) {
|
|
6213
|
+
const language = LANGUAGE_NAMES2[args.locale] ?? "English";
|
|
5631
6214
|
const res = await fetchWithInteractiveTimeout(
|
|
5632
6215
|
`${args.url}/chat/completions`,
|
|
5633
6216
|
{
|
|
@@ -5639,27 +6222,11 @@ async function requestVisionDraft(args) {
|
|
|
5639
6222
|
body: JSON.stringify({
|
|
5640
6223
|
model: args.model,
|
|
5641
6224
|
messages: [
|
|
5642
|
-
{
|
|
5643
|
-
role: "system",
|
|
5644
|
-
content: "You are ZAM's UI observer. Return only strict JSON. Do not include markdown, prose, or fields outside the requested schema."
|
|
5645
|
-
},
|
|
6225
|
+
{ role: "system", content: VISION_SYSTEM_PROMPT },
|
|
5646
6226
|
{
|
|
5647
6227
|
role: "user",
|
|
5648
6228
|
content: [
|
|
5649
|
-
{
|
|
5650
|
-
type: "text",
|
|
5651
|
-
text: args.imageUrls.length > 1 ? `Observe this sequence of Windows/macOS application snapshots showing a task performed over time.
|
|
5652
|
-
Application process: ${args.input.application.processName}
|
|
5653
|
-
Window title: ${args.input.application.windowTitle ?? "(unknown)"}
|
|
5654
|
-
|
|
5655
|
-
Return this JSON draft only:
|
|
5656
|
-
${schema}` : `Observe this Windows/macOS application snapshot for a learning session.
|
|
5657
|
-
Application process: ${args.input.application.processName}
|
|
5658
|
-
Window title: ${args.input.application.windowTitle ?? "(unknown)"}
|
|
5659
|
-
|
|
5660
|
-
Return this JSON draft only:
|
|
5661
|
-
${schema}`
|
|
5662
|
-
},
|
|
6229
|
+
{ type: "text", text: visionUserText(args, language) },
|
|
5663
6230
|
...args.imageUrls.map((url) => ({
|
|
5664
6231
|
type: "image_url",
|
|
5665
6232
|
image_url: { url }
|
|
@@ -5702,6 +6269,64 @@ ${schema}`
|
|
|
5702
6269
|
}
|
|
5703
6270
|
return content.trim();
|
|
5704
6271
|
}
|
|
6272
|
+
function dataUrlToAnthropicImage(dataUrl) {
|
|
6273
|
+
const match = dataUrl.match(/^data:(image\/[a-zA-Z]+);base64,(.+)$/);
|
|
6274
|
+
if (!match) {
|
|
6275
|
+
throw new Error("Unsupported image data for Anthropic vision request");
|
|
6276
|
+
}
|
|
6277
|
+
return {
|
|
6278
|
+
type: "image",
|
|
6279
|
+
source: { type: "base64", media_type: match[1], data: match[2] }
|
|
6280
|
+
};
|
|
6281
|
+
}
|
|
6282
|
+
async function requestAnthropicVisionDraft(args) {
|
|
6283
|
+
const language = LANGUAGE_NAMES2[args.locale] ?? "English";
|
|
6284
|
+
const base = args.url.replace(/\/+$/, "").replace(/\/v1$/, "");
|
|
6285
|
+
const res = await fetchWithInteractiveTimeout(`${base}/v1/messages`, {
|
|
6286
|
+
method: "POST",
|
|
6287
|
+
headers: {
|
|
6288
|
+
"Content-Type": "application/json",
|
|
6289
|
+
"x-api-key": args.apiKey,
|
|
6290
|
+
"anthropic-version": "2023-06-01"
|
|
6291
|
+
},
|
|
6292
|
+
body: JSON.stringify({
|
|
6293
|
+
model: args.model,
|
|
6294
|
+
max_tokens: args.input.maxTokens ?? 450,
|
|
6295
|
+
system: VISION_SYSTEM_PROMPT,
|
|
6296
|
+
messages: [
|
|
6297
|
+
{
|
|
6298
|
+
role: "user",
|
|
6299
|
+
content: [
|
|
6300
|
+
{ type: "text", text: visionUserText(args, language) },
|
|
6301
|
+
...args.imageUrls.map(dataUrlToAnthropicImage)
|
|
6302
|
+
]
|
|
6303
|
+
}
|
|
6304
|
+
]
|
|
6305
|
+
}),
|
|
6306
|
+
locale: args.locale,
|
|
6307
|
+
hardTimeoutMs: args.input.hardTimeoutMs ?? 18e4
|
|
6308
|
+
});
|
|
6309
|
+
if (!res.ok) {
|
|
6310
|
+
const errorText = await res.text().catch(() => "");
|
|
6311
|
+
throw new Error(
|
|
6312
|
+
`Vision LLM request failed: ${res.statusText} (${res.status}) - ${errorText}`
|
|
6313
|
+
);
|
|
6314
|
+
}
|
|
6315
|
+
const data = await res.json();
|
|
6316
|
+
if (data.error !== void 0) {
|
|
6317
|
+
throw new Error(`Vision model failed: ${formatModelError(data.error)}`);
|
|
6318
|
+
}
|
|
6319
|
+
if (data.stop_reason === "refusal") {
|
|
6320
|
+
throw new Error("Vision model refused the request (safety classifier).");
|
|
6321
|
+
}
|
|
6322
|
+
const text = data.content?.find(
|
|
6323
|
+
(b) => b.type === "text" && typeof b.text === "string"
|
|
6324
|
+
)?.text;
|
|
6325
|
+
if (!text) {
|
|
6326
|
+
throw new Error("Empty response from vision model");
|
|
6327
|
+
}
|
|
6328
|
+
return text.trim();
|
|
6329
|
+
}
|
|
5705
6330
|
function extractDraft(content) {
|
|
5706
6331
|
const fenced = content.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
5707
6332
|
const candidate = (fenced?.[1] ?? content).trim();
|
|
@@ -5730,7 +6355,7 @@ function buildReport(input8, draft) {
|
|
|
5730
6355
|
evidence: [
|
|
5731
6356
|
{
|
|
5732
6357
|
type: "keyframe",
|
|
5733
|
-
ref: input8.evidenceRef ??
|
|
6358
|
+
ref: input8.evidenceRef ?? basename3(input8.imagePath),
|
|
5734
6359
|
redacted: input8.redacted ?? false
|
|
5735
6360
|
}
|
|
5736
6361
|
],
|
|
@@ -5752,7 +6377,7 @@ function uncertainReport(input8, summary) {
|
|
|
5752
6377
|
evidence: [
|
|
5753
6378
|
{
|
|
5754
6379
|
type: "keyframe",
|
|
5755
|
-
ref: input8.evidenceRef ??
|
|
6380
|
+
ref: input8.evidenceRef ?? basename3(input8.imagePath),
|
|
5756
6381
|
redacted: input8.redacted ?? false
|
|
5757
6382
|
}
|
|
5758
6383
|
],
|
|
@@ -5862,6 +6487,206 @@ function jsonOut(data) {
|
|
|
5862
6487
|
console.log(JSON.stringify(data, null, 2));
|
|
5863
6488
|
}
|
|
5864
6489
|
|
|
6490
|
+
// src/cli/commands/workspace.ts
|
|
6491
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
6492
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync8, writeFileSync as writeFileSync7 } from "fs";
|
|
6493
|
+
import { homedir as homedir9 } from "os";
|
|
6494
|
+
import { join as join15 } from "path";
|
|
6495
|
+
import { confirm, input } from "@inquirer/prompts";
|
|
6496
|
+
import { Command as Command2 } from "commander";
|
|
6497
|
+
function runGit(cwd, args) {
|
|
6498
|
+
try {
|
|
6499
|
+
return execFileSync3("git", args, {
|
|
6500
|
+
cwd,
|
|
6501
|
+
stdio: "pipe",
|
|
6502
|
+
encoding: "utf8"
|
|
6503
|
+
}).trim();
|
|
6504
|
+
} catch (err) {
|
|
6505
|
+
throw new Error(`Git command failed: ${err.message}`);
|
|
6506
|
+
}
|
|
6507
|
+
}
|
|
6508
|
+
function ghRepoCreateArgs(repoName, repoVisibility) {
|
|
6509
|
+
return ["repo", "create", repoName, repoVisibility, "--source=.", "--push"];
|
|
6510
|
+
}
|
|
6511
|
+
function gitRemoteArgs(githubUrl, hasOrigin) {
|
|
6512
|
+
return hasOrigin ? ["remote", "set-url", "origin", githubUrl] : ["remote", "add", "origin", githubUrl];
|
|
6513
|
+
}
|
|
6514
|
+
var workspaceCommand = new Command2("workspace").description(
|
|
6515
|
+
"Manage your ZAM learning workspace"
|
|
6516
|
+
);
|
|
6517
|
+
workspaceCommand.command("publish").description("Publish your local workspace sandbox to GitHub").action(async () => {
|
|
6518
|
+
let db;
|
|
6519
|
+
let workspaceDir = "";
|
|
6520
|
+
try {
|
|
6521
|
+
db = await openDatabase();
|
|
6522
|
+
workspaceDir = await getSetting(db, "personal.workspace_dir") || "";
|
|
6523
|
+
await db.close();
|
|
6524
|
+
} catch {
|
|
6525
|
+
await db?.close();
|
|
6526
|
+
}
|
|
6527
|
+
if (!workspaceDir) {
|
|
6528
|
+
console.error(
|
|
6529
|
+
"\x1B[31m\u2717 No active workspace configured. Please run `zam init` first.\x1B[0m"
|
|
6530
|
+
);
|
|
6531
|
+
process.exit(1);
|
|
6532
|
+
}
|
|
6533
|
+
if (!existsSync15(workspaceDir)) {
|
|
6534
|
+
console.error(
|
|
6535
|
+
`\x1B[31m\u2717 Workspace directory does not exist: ${workspaceDir}\x1B[0m`
|
|
6536
|
+
);
|
|
6537
|
+
process.exit(1);
|
|
6538
|
+
}
|
|
6539
|
+
console.log(`
|
|
6540
|
+
Active workspace: \x1B[36m${workspaceDir}\x1B[0m`);
|
|
6541
|
+
if (!hasCommand("git")) {
|
|
6542
|
+
console.error(
|
|
6543
|
+
"\x1B[31m\u2717 Git command was not found on this system. Please install Git first.\x1B[0m"
|
|
6544
|
+
);
|
|
6545
|
+
process.exit(1);
|
|
6546
|
+
}
|
|
6547
|
+
const gitignorePath = join15(workspaceDir, ".gitignore");
|
|
6548
|
+
if (!existsSync15(gitignorePath)) {
|
|
6549
|
+
writeFileSync7(
|
|
6550
|
+
gitignorePath,
|
|
6551
|
+
"node_modules/\n.agent/\n.agents/\n.claude/\n.gemini/\n.goose/\n*.log\n",
|
|
6552
|
+
"utf8"
|
|
6553
|
+
);
|
|
6554
|
+
}
|
|
6555
|
+
const hasGitRepo = existsSync15(join15(workspaceDir, ".git"));
|
|
6556
|
+
if (!hasGitRepo) {
|
|
6557
|
+
console.log("Initializing local Git repository...");
|
|
6558
|
+
runGit(workspaceDir, ["init", "-b", "main"]);
|
|
6559
|
+
runGit(workspaceDir, ["add", "."]);
|
|
6560
|
+
runGit(workspaceDir, [
|
|
6561
|
+
"commit",
|
|
6562
|
+
"-m",
|
|
6563
|
+
"chore: initial workspace sandbox bootstrap"
|
|
6564
|
+
]);
|
|
6565
|
+
console.log("\x1B[32m\u2713 Local Git repository initialized.\x1B[0m");
|
|
6566
|
+
} else {
|
|
6567
|
+
console.log("Git repository is already initialized.");
|
|
6568
|
+
}
|
|
6569
|
+
const repoName = await input({
|
|
6570
|
+
message: "Choose a name for your GitHub repository:",
|
|
6571
|
+
default: "zam-personal"
|
|
6572
|
+
});
|
|
6573
|
+
const isPrivate = await confirm({
|
|
6574
|
+
message: "Should the repository be private?",
|
|
6575
|
+
default: true
|
|
6576
|
+
});
|
|
6577
|
+
const repoVisibility = isPrivate ? "--private" : "--public";
|
|
6578
|
+
if (hasCommand("gh")) {
|
|
6579
|
+
console.log("GitHub CLI detected! Automating repository creation...");
|
|
6580
|
+
const proceedGh = await confirm({
|
|
6581
|
+
message: "Would you like ZAM to create the repository using the GitHub CLI?",
|
|
6582
|
+
default: true
|
|
6583
|
+
});
|
|
6584
|
+
if (proceedGh) {
|
|
6585
|
+
try {
|
|
6586
|
+
console.log(`Creating GitHub repository ${repoName}...`);
|
|
6587
|
+
execFileSync3("gh", ghRepoCreateArgs(repoName, repoVisibility), {
|
|
6588
|
+
cwd: workspaceDir,
|
|
6589
|
+
stdio: "inherit"
|
|
6590
|
+
});
|
|
6591
|
+
console.log(
|
|
6592
|
+
"\n\x1B[32m\u2713 Successfully published workspace to GitHub!\x1B[0m"
|
|
6593
|
+
);
|
|
6594
|
+
process.exit(0);
|
|
6595
|
+
} catch (err) {
|
|
6596
|
+
console.warn(
|
|
6597
|
+
`\x1B[33m\u26A0 GitHub CLI creation failed: ${err.message}\x1B[0m`
|
|
6598
|
+
);
|
|
6599
|
+
}
|
|
6600
|
+
}
|
|
6601
|
+
}
|
|
6602
|
+
console.log(
|
|
6603
|
+
"\n\x1B[1mPlease create the repository manually on GitHub:\x1B[0m"
|
|
6604
|
+
);
|
|
6605
|
+
console.log(" 1. Go to https://github.com/new");
|
|
6606
|
+
console.log(` 2. Name it exactly: \x1B[36m${repoName}\x1B[0m`);
|
|
6607
|
+
console.log(
|
|
6608
|
+
` 3. Choose \x1B[36m${isPrivate ? "Private" : "Public"}\x1B[0m`
|
|
6609
|
+
);
|
|
6610
|
+
console.log(
|
|
6611
|
+
" 4. Do NOT initialize it with README, .gitignore, or license"
|
|
6612
|
+
);
|
|
6613
|
+
console.log(" 5. Click 'Create repository'\n");
|
|
6614
|
+
const githubUrl = await input({
|
|
6615
|
+
message: "Paste the repository Git URL (e.g. git@github.com:user/repo.git):"
|
|
6616
|
+
});
|
|
6617
|
+
if (githubUrl) {
|
|
6618
|
+
try {
|
|
6619
|
+
console.log("Linking remote repository and pushing...");
|
|
6620
|
+
let hasOrigin = false;
|
|
6621
|
+
try {
|
|
6622
|
+
runGit(workspaceDir, ["remote", "get-url", "origin"]);
|
|
6623
|
+
hasOrigin = true;
|
|
6624
|
+
} catch {
|
|
6625
|
+
}
|
|
6626
|
+
runGit(workspaceDir, gitRemoteArgs(githubUrl, hasOrigin));
|
|
6627
|
+
runGit(workspaceDir, ["push", "-u", "origin", "main"]);
|
|
6628
|
+
console.log(
|
|
6629
|
+
"\x1B[32m\u2713 Successfully linked and pushed to GitHub!\x1B[0m"
|
|
6630
|
+
);
|
|
6631
|
+
} catch (err) {
|
|
6632
|
+
console.error(
|
|
6633
|
+
`\x1B[31m\u2717 Push failed: ${err.message}\x1B[0m`
|
|
6634
|
+
);
|
|
6635
|
+
console.log(
|
|
6636
|
+
"You can push manually later using: git push -u origin main"
|
|
6637
|
+
);
|
|
6638
|
+
}
|
|
6639
|
+
}
|
|
6640
|
+
});
|
|
6641
|
+
async function backupDatabaseTo(db, targetDir) {
|
|
6642
|
+
const backupDir = join15(targetDir, "zam-backups");
|
|
6643
|
+
mkdirSync8(backupDir, { recursive: true });
|
|
6644
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
6645
|
+
const dest = join15(backupDir, `zam-${stamp}.db`);
|
|
6646
|
+
await db.exec(`VACUUM INTO '${dest.replace(/'/g, "''")}'`);
|
|
6647
|
+
return dest;
|
|
6648
|
+
}
|
|
6649
|
+
workspaceCommand.command("data-dir").description("Print the ZAM data directory (database, credentials, config)").option("--json", "Output as JSON").action((opts) => {
|
|
6650
|
+
const dir = join15(homedir9(), ".zam");
|
|
6651
|
+
console.log(opts.json ? JSON.stringify({ dataDir: dir }) : dir);
|
|
6652
|
+
});
|
|
6653
|
+
workspaceCommand.command("backup").description("Back up the local ZAM database into your workspace").option(
|
|
6654
|
+
"--dir <path>",
|
|
6655
|
+
"Target directory (default: workspace dir, else ~/Documents/zam)"
|
|
6656
|
+
).option("--json", "Output as JSON").action(async (opts) => {
|
|
6657
|
+
const target = getDatabaseTargetInfo();
|
|
6658
|
+
if (target.kind !== "local") {
|
|
6659
|
+
const reason = `The database is ${target.kind} (${target.location}); file backup applies only to a local database \u2014 your Turso remote is already the cloud backup.`;
|
|
6660
|
+
if (opts.json) {
|
|
6661
|
+
console.log(JSON.stringify({ ok: false, reason }));
|
|
6662
|
+
return;
|
|
6663
|
+
}
|
|
6664
|
+
console.error(`\x1B[33m\u26A0 ${reason}\x1B[0m`);
|
|
6665
|
+
process.exit(1);
|
|
6666
|
+
}
|
|
6667
|
+
let db;
|
|
6668
|
+
try {
|
|
6669
|
+
db = await openDatabase();
|
|
6670
|
+
const workspaceDir = opts.dir || await getSetting(db, "personal.workspace_dir") || join15(homedir9(), "Documents", "zam");
|
|
6671
|
+
const dest = await backupDatabaseTo(db, workspaceDir);
|
|
6672
|
+
if (opts.json) {
|
|
6673
|
+
console.log(JSON.stringify({ ok: true, path: dest }));
|
|
6674
|
+
} else {
|
|
6675
|
+
console.log(`\x1B[32m\u2713 Database backed up to ${dest}\x1B[0m`);
|
|
6676
|
+
}
|
|
6677
|
+
} catch (err) {
|
|
6678
|
+
const reason = err.message;
|
|
6679
|
+
if (opts.json) {
|
|
6680
|
+
console.log(JSON.stringify({ ok: false, reason }));
|
|
6681
|
+
} else {
|
|
6682
|
+
console.error(`\x1B[31m\u2717 Backup failed: ${reason}\x1B[0m`);
|
|
6683
|
+
}
|
|
6684
|
+
process.exit(1);
|
|
6685
|
+
} finally {
|
|
6686
|
+
await db?.close();
|
|
6687
|
+
}
|
|
6688
|
+
});
|
|
6689
|
+
|
|
5865
6690
|
// src/cli/commands/bridge.ts
|
|
5866
6691
|
var isServeMode = false;
|
|
5867
6692
|
function jsonOut2(data) {
|
|
@@ -5918,7 +6743,7 @@ function parseTokenUpdates(opts) {
|
|
|
5918
6743
|
}
|
|
5919
6744
|
return updates;
|
|
5920
6745
|
}
|
|
5921
|
-
var bridgeCommand = new
|
|
6746
|
+
var bridgeCommand = new Command3("bridge").description(
|
|
5922
6747
|
"Machine-readable JSON protocol for AI integration"
|
|
5923
6748
|
);
|
|
5924
6749
|
bridgeCommand.command("check-due").description("Check due cards for a user (JSON)").option("--user <id>", "User ID (default: whoami)").option("--domain <domain>", "Filter by knowledge domain").action(async (opts) => {
|
|
@@ -5946,6 +6771,45 @@ bridgeCommand.command("check-due").description("Check due cards for a user (JSON
|
|
|
5946
6771
|
});
|
|
5947
6772
|
});
|
|
5948
6773
|
});
|
|
6774
|
+
bridgeCommand.command("backup-db").description("Back up the local database into the workspace (JSON)").option(
|
|
6775
|
+
"--dir <path>",
|
|
6776
|
+
"Target directory (default: workspace dir, else ~/Documents/zam)"
|
|
6777
|
+
).action(async (opts) => {
|
|
6778
|
+
const target = getDatabaseTargetInfo();
|
|
6779
|
+
if (target.kind !== "local") {
|
|
6780
|
+
jsonOut2({
|
|
6781
|
+
ok: false,
|
|
6782
|
+
reason: "remote",
|
|
6783
|
+
target: target.kind,
|
|
6784
|
+
location: target.location
|
|
6785
|
+
});
|
|
6786
|
+
return;
|
|
6787
|
+
}
|
|
6788
|
+
await withDb2(async (db) => {
|
|
6789
|
+
const workspaceDir = opts.dir || await getSetting(db, "personal.workspace_dir") || join16(homedir10(), "Documents", "zam");
|
|
6790
|
+
const path = await backupDatabaseTo(db, workspaceDir);
|
|
6791
|
+
jsonOut2({ ok: true, path });
|
|
6792
|
+
});
|
|
6793
|
+
});
|
|
6794
|
+
bridgeCommand.command("workspace-info").description("Report the workspace dir, its default, and the data dir (JSON)").action(async () => {
|
|
6795
|
+
await withDb2(async (db) => {
|
|
6796
|
+
const workspaceDir = await getSetting(db, "personal.workspace_dir") || null;
|
|
6797
|
+
jsonOut2({
|
|
6798
|
+
workspaceDir,
|
|
6799
|
+
defaultWorkspaceDir: join16(homedir10(), "Documents", "zam"),
|
|
6800
|
+
dataDir: join16(homedir10(), ".zam")
|
|
6801
|
+
});
|
|
6802
|
+
});
|
|
6803
|
+
});
|
|
6804
|
+
bridgeCommand.command("set-workspace-dir").description("Set the personal workspace directory (JSON)").requiredOption("--dir <path>", "Path to the workspace directory").action(async (opts) => {
|
|
6805
|
+
const raw = String(opts.dir ?? "").trim();
|
|
6806
|
+
if (!raw) jsonError("A non-empty --dir is required");
|
|
6807
|
+
const dir = resolve3(raw);
|
|
6808
|
+
await withDb2(async (db) => {
|
|
6809
|
+
await setSetting(db, "personal.workspace_dir", dir);
|
|
6810
|
+
jsonOut2({ ok: true, workspaceDir: dir });
|
|
6811
|
+
});
|
|
6812
|
+
});
|
|
5949
6813
|
bridgeCommand.command("get-review").description("Get next review card with prompt (JSON)").option("--user <id>", "User ID (default: whoami)").option("--no-resolve", "Skip resolving the token's source_link into context").action(async (opts) => {
|
|
5950
6814
|
await withDb2(async (db) => {
|
|
5951
6815
|
const userId = await resolveUser(opts, db, { json: true });
|
|
@@ -6304,7 +7168,7 @@ bridgeCommand.command("discover-skills").description(
|
|
|
6304
7168
|
"20"
|
|
6305
7169
|
).action(async (opts) => {
|
|
6306
7170
|
try {
|
|
6307
|
-
const monitorDir =
|
|
7171
|
+
const monitorDir = join16(homedir10(), ".zam", "monitor");
|
|
6308
7172
|
let files;
|
|
6309
7173
|
try {
|
|
6310
7174
|
files = readdirSync2(monitorDir).filter((f) => f.endsWith(".jsonl"));
|
|
@@ -6317,7 +7181,7 @@ bridgeCommand.command("discover-skills").description(
|
|
|
6317
7181
|
return;
|
|
6318
7182
|
}
|
|
6319
7183
|
const limit = Number(opts.limit);
|
|
6320
|
-
const sorted = files.map((f) => ({ name: f, path:
|
|
7184
|
+
const sorted = files.map((f) => ({ name: f, path: join16(monitorDir, f) })).sort((a, b) => b.name.localeCompare(a.name)).slice(0, limit);
|
|
6321
7185
|
const sessionCommands = /* @__PURE__ */ new Map();
|
|
6322
7186
|
for (const file of sorted) {
|
|
6323
7187
|
const sessionId = file.name.replace(".jsonl", "");
|
|
@@ -6429,7 +7293,7 @@ bridgeCommand.command("observe-ui-snapshot").description(
|
|
|
6429
7293
|
});
|
|
6430
7294
|
function resolveWindowsPowerShell() {
|
|
6431
7295
|
try {
|
|
6432
|
-
|
|
7296
|
+
execFileSync4("where.exe", ["pwsh.exe"], { stdio: "ignore" });
|
|
6433
7297
|
return "pwsh";
|
|
6434
7298
|
} catch {
|
|
6435
7299
|
return "powershell";
|
|
@@ -6444,7 +7308,7 @@ function captureScreenshot(outputPath, hwnd, processName) {
|
|
|
6444
7308
|
throw new Error(`Invalid process name format: ${processName}`);
|
|
6445
7309
|
}
|
|
6446
7310
|
if (platform === "win32") {
|
|
6447
|
-
const stdout =
|
|
7311
|
+
const stdout = execFileSync4(
|
|
6448
7312
|
resolveWindowsPowerShell(),
|
|
6449
7313
|
[
|
|
6450
7314
|
"-NoProfile",
|
|
@@ -6757,13 +7621,13 @@ Write-CaptureResult "fullscreen" $hwndVal $matchedBy
|
|
|
6757
7621
|
} else if (platform === "darwin") {
|
|
6758
7622
|
if (hwnd) {
|
|
6759
7623
|
const parsedHwnd = hwnd.startsWith("0x") ? parseInt(hwnd, 16) : parseInt(hwnd, 10);
|
|
6760
|
-
|
|
7624
|
+
execFileSync4("screencapture", ["-l", String(parsedHwnd), outputPath], {
|
|
6761
7625
|
stdio: "pipe"
|
|
6762
7626
|
});
|
|
6763
7627
|
return { method: "screencapture-window", target: null };
|
|
6764
7628
|
} else if (processName) {
|
|
6765
7629
|
try {
|
|
6766
|
-
const windowId =
|
|
7630
|
+
const windowId = execFileSync4(
|
|
6767
7631
|
"osascript",
|
|
6768
7632
|
[
|
|
6769
7633
|
"-e",
|
|
@@ -6772,17 +7636,17 @@ Write-CaptureResult "fullscreen" $hwndVal $matchedBy
|
|
|
6772
7636
|
{ encoding: "utf8", stdio: ["pipe", "pipe", "ignore"] }
|
|
6773
7637
|
).trim();
|
|
6774
7638
|
if (windowId && /^\d+$/.test(windowId)) {
|
|
6775
|
-
|
|
7639
|
+
execFileSync4("screencapture", ["-l", windowId, outputPath], {
|
|
6776
7640
|
stdio: "pipe"
|
|
6777
7641
|
});
|
|
6778
7642
|
return { method: "screencapture-window", target: null };
|
|
6779
7643
|
}
|
|
6780
7644
|
} catch {
|
|
6781
7645
|
}
|
|
6782
|
-
|
|
7646
|
+
execFileSync4("screencapture", ["-x", outputPath], { stdio: "pipe" });
|
|
6783
7647
|
return { method: "screencapture-full", target: null };
|
|
6784
7648
|
} else {
|
|
6785
|
-
|
|
7649
|
+
execFileSync4("screencapture", ["-x", outputPath], { stdio: "pipe" });
|
|
6786
7650
|
return { method: "screencapture-full", target: null };
|
|
6787
7651
|
}
|
|
6788
7652
|
} else {
|
|
@@ -6819,7 +7683,7 @@ bridgeCommand.command("capture-ui").description("Capture a screenshot for agent-
|
|
|
6819
7683
|
return;
|
|
6820
7684
|
}
|
|
6821
7685
|
}
|
|
6822
|
-
const outputPath = opts.image ?? opts.output ??
|
|
7686
|
+
const outputPath = opts.image ?? opts.output ?? join16(tmpdir3(), `zam-capture-${randomBytes2(4).toString("hex")}.png`);
|
|
6823
7687
|
const captureResult = isProvided ? { method: "provided", target: null } : captureScreenshot(outputPath, opts.hwnd, opts.processName);
|
|
6824
7688
|
if (!isProvided) {
|
|
6825
7689
|
const post = decidePostCapture(policy, {
|
|
@@ -6863,21 +7727,22 @@ bridgeCommand.command("capture-ui").description("Capture a screenshot for agent-
|
|
|
6863
7727
|
});
|
|
6864
7728
|
});
|
|
6865
7729
|
});
|
|
6866
|
-
bridgeCommand.command("start-recording").description("Start screen recording in the background (
|
|
7730
|
+
bridgeCommand.command("start-recording").description("Start screen recording in the background (JSON)").requiredOption("--session <id>", "ZAM session ID").option("--output <path>", "Video output path").action(async (opts) => {
|
|
6867
7731
|
const platform = process.platform;
|
|
6868
|
-
if (platform !== "darwin") {
|
|
7732
|
+
if (platform !== "darwin" && platform !== "win32") {
|
|
6869
7733
|
jsonOut2({
|
|
6870
7734
|
sessionId: opts.session,
|
|
6871
7735
|
started: false,
|
|
6872
|
-
error: "Screen recording is only supported on macOS (darwin)"
|
|
7736
|
+
error: "Screen recording is only supported on macOS (darwin) and Windows (win32)"
|
|
6873
7737
|
});
|
|
6874
7738
|
return;
|
|
6875
7739
|
}
|
|
6876
7740
|
const sessionId = opts.session;
|
|
6877
|
-
const statePath =
|
|
6878
|
-
const
|
|
6879
|
-
const
|
|
6880
|
-
|
|
7741
|
+
const statePath = join16(tmpdir3(), `zam-recording-${sessionId}.json`);
|
|
7742
|
+
const defaultExt = platform === "win32" ? ".mkv" : ".mov";
|
|
7743
|
+
const outputPath = opts.output ?? join16(tmpdir3(), `zam-recording-${sessionId}${defaultExt}`);
|
|
7744
|
+
const { existsSync: existsSync24, writeFileSync: writeFileSync12, openSync, closeSync } = await import("fs");
|
|
7745
|
+
if (existsSync24(statePath)) {
|
|
6881
7746
|
jsonOut2({
|
|
6882
7747
|
sessionId,
|
|
6883
7748
|
started: false,
|
|
@@ -6885,7 +7750,7 @@ bridgeCommand.command("start-recording").description("Start screen recording in
|
|
|
6885
7750
|
});
|
|
6886
7751
|
return;
|
|
6887
7752
|
}
|
|
6888
|
-
const logPath =
|
|
7753
|
+
const logPath = join16(tmpdir3(), `zam-recording-${sessionId}.log`);
|
|
6889
7754
|
let logFd;
|
|
6890
7755
|
try {
|
|
6891
7756
|
logFd = openSync(logPath, "w");
|
|
@@ -6897,26 +7762,34 @@ bridgeCommand.command("start-recording").description("Start screen recording in
|
|
|
6897
7762
|
});
|
|
6898
7763
|
return;
|
|
6899
7764
|
}
|
|
6900
|
-
const { spawn:
|
|
6901
|
-
const
|
|
6902
|
-
"
|
|
6903
|
-
|
|
6904
|
-
|
|
6905
|
-
|
|
6906
|
-
|
|
6907
|
-
|
|
6908
|
-
|
|
6909
|
-
|
|
6910
|
-
|
|
6911
|
-
|
|
6912
|
-
|
|
6913
|
-
|
|
6914
|
-
|
|
6915
|
-
|
|
6916
|
-
|
|
6917
|
-
|
|
6918
|
-
|
|
6919
|
-
|
|
7765
|
+
const { spawn: spawn4 } = await import("child_process");
|
|
7766
|
+
const ffmpegArgs = platform === "darwin" ? [
|
|
7767
|
+
"-y",
|
|
7768
|
+
"-f",
|
|
7769
|
+
"avfoundation",
|
|
7770
|
+
"-r",
|
|
7771
|
+
"5",
|
|
7772
|
+
"-i",
|
|
7773
|
+
"0",
|
|
7774
|
+
"-pix_fmt",
|
|
7775
|
+
"yuv420p",
|
|
7776
|
+
outputPath
|
|
7777
|
+
] : [
|
|
7778
|
+
"-y",
|
|
7779
|
+
"-f",
|
|
7780
|
+
"gdigrab",
|
|
7781
|
+
"-framerate",
|
|
7782
|
+
"5",
|
|
7783
|
+
"-i",
|
|
7784
|
+
"desktop",
|
|
7785
|
+
"-pix_fmt",
|
|
7786
|
+
"yuv420p",
|
|
7787
|
+
outputPath
|
|
7788
|
+
];
|
|
7789
|
+
const child = spawn4("ffmpeg", ffmpegArgs, {
|
|
7790
|
+
detached: true,
|
|
7791
|
+
stdio: ["pipe", logFd, logFd]
|
|
7792
|
+
});
|
|
6920
7793
|
try {
|
|
6921
7794
|
closeSync(logFd);
|
|
6922
7795
|
} catch {
|
|
@@ -6947,21 +7820,21 @@ bridgeCommand.command("start-recording").description("Start screen recording in
|
|
|
6947
7820
|
}
|
|
6948
7821
|
});
|
|
6949
7822
|
bridgeCommand.command("stop-recording").description(
|
|
6950
|
-
"Stop active screen recording and apply idle-frame compression (
|
|
7823
|
+
"Stop active screen recording and apply idle-frame compression (JSON)"
|
|
6951
7824
|
).requiredOption("--session <id>", "ZAM session ID").action(async (opts) => {
|
|
6952
7825
|
const platform = process.platform;
|
|
6953
|
-
if (platform !== "darwin") {
|
|
7826
|
+
if (platform !== "darwin" && platform !== "win32") {
|
|
6954
7827
|
jsonOut2({
|
|
6955
7828
|
sessionId: opts.session,
|
|
6956
7829
|
stopped: false,
|
|
6957
|
-
error: "Screen recording is only supported on macOS (darwin)"
|
|
7830
|
+
error: "Screen recording is only supported on macOS (darwin) and Windows (win32)"
|
|
6958
7831
|
});
|
|
6959
7832
|
return;
|
|
6960
7833
|
}
|
|
6961
7834
|
const sessionId = opts.session;
|
|
6962
|
-
const statePath =
|
|
6963
|
-
const { existsSync:
|
|
6964
|
-
if (!
|
|
7835
|
+
const statePath = join16(tmpdir3(), `zam-recording-${sessionId}.json`);
|
|
7836
|
+
const { existsSync: existsSync24, readFileSync: readFileSync15, rmSync: rmSync3 } = await import("fs");
|
|
7837
|
+
if (!existsSync24(statePath)) {
|
|
6965
7838
|
jsonOut2({
|
|
6966
7839
|
sessionId,
|
|
6967
7840
|
stopped: false,
|
|
@@ -6985,7 +7858,7 @@ bridgeCommand.command("stop-recording").description(
|
|
|
6985
7858
|
};
|
|
6986
7859
|
let attempts = 0;
|
|
6987
7860
|
while (isProcessRunning(pid) && attempts < 20) {
|
|
6988
|
-
await new Promise((
|
|
7861
|
+
await new Promise((resolve6) => setTimeout(resolve6, 250));
|
|
6989
7862
|
attempts++;
|
|
6990
7863
|
}
|
|
6991
7864
|
if (isProcessRunning(pid)) {
|
|
@@ -6998,7 +7871,7 @@ bridgeCommand.command("stop-recording").description(
|
|
|
6998
7871
|
rmSync3(statePath, { force: true });
|
|
6999
7872
|
} catch {
|
|
7000
7873
|
}
|
|
7001
|
-
if (!
|
|
7874
|
+
if (!existsSync24(outputPath)) {
|
|
7002
7875
|
jsonOut2({
|
|
7003
7876
|
sessionId,
|
|
7004
7877
|
stopped: false,
|
|
@@ -7007,9 +7880,9 @@ bridgeCommand.command("stop-recording").description(
|
|
|
7007
7880
|
return;
|
|
7008
7881
|
}
|
|
7009
7882
|
const decimatedPath = outputPath.replace(/\.[^.]+$/, "-decimated.mp4");
|
|
7010
|
-
const { execSync:
|
|
7883
|
+
const { execSync: execSync6 } = await import("child_process");
|
|
7011
7884
|
try {
|
|
7012
|
-
|
|
7885
|
+
execSync6(
|
|
7013
7886
|
`ffmpeg -y -i "${outputPath}" -vf "mpdecimate,setpts=N/FRAME_RATE/TB" -an -pix_fmt yuv420p "${decimatedPath}"`,
|
|
7014
7887
|
{ stdio: "ignore" }
|
|
7015
7888
|
);
|
|
@@ -7062,11 +7935,13 @@ bridgeCommand.command("sync-observer-policy").description(
|
|
|
7062
7935
|
});
|
|
7063
7936
|
bridgeCommand.command("check-llm").description("Check if LLM is enabled and online (JSON)").action(async () => {
|
|
7064
7937
|
await withDb2(async (db) => {
|
|
7065
|
-
const
|
|
7938
|
+
const provider = await getProviderForRole(db, "recall");
|
|
7939
|
+
const { enabled, url, model, apiKey } = provider;
|
|
7940
|
+
const unsupportedProvider = provider.apiFlavor !== "chat-completions";
|
|
7066
7941
|
let online = false;
|
|
7067
7942
|
let availableModels = [];
|
|
7068
7943
|
let modelAvailable = false;
|
|
7069
|
-
if (enabled) {
|
|
7944
|
+
if (enabled && !unsupportedProvider) {
|
|
7070
7945
|
online = await isLlmOnline(url);
|
|
7071
7946
|
if (online) {
|
|
7072
7947
|
availableModels = await getAvailableModels(url, apiKey);
|
|
@@ -7081,7 +7956,9 @@ bridgeCommand.command("check-llm").description("Check if LLM is enabled and onli
|
|
|
7081
7956
|
url,
|
|
7082
7957
|
model,
|
|
7083
7958
|
modelAvailable,
|
|
7084
|
-
availableModels
|
|
7959
|
+
availableModels,
|
|
7960
|
+
apiFlavor: provider.apiFlavor,
|
|
7961
|
+
unsupportedProvider
|
|
7085
7962
|
});
|
|
7086
7963
|
});
|
|
7087
7964
|
});
|
|
@@ -7397,8 +8274,8 @@ bridgeCommand.command("serve").description("Start the persistent JSON-RPC stdin/
|
|
|
7397
8274
|
});
|
|
7398
8275
|
|
|
7399
8276
|
// src/cli/commands/card.ts
|
|
7400
|
-
import { Command as
|
|
7401
|
-
var cardCommand = new
|
|
8277
|
+
import { Command as Command4 } from "commander";
|
|
8278
|
+
var cardCommand = new Command4("card").description(
|
|
7402
8279
|
"Manage spaced-repetition cards"
|
|
7403
8280
|
);
|
|
7404
8281
|
cardCommand.command("due").description("Show due tokens for a user").option("--user <id>", "User ID (default: whoami)").option("--domain <domain>", "Filter by knowledge domain").option("--json", "Output as JSON").option("--summary", "Show only counts per domain (no slugs or concepts)").action(async (opts) => {
|
|
@@ -7596,9 +8473,9 @@ cardCommand.command("delete").description("Delete one user's card for a token").
|
|
|
7596
8473
|
});
|
|
7597
8474
|
|
|
7598
8475
|
// src/cli/commands/connector.ts
|
|
7599
|
-
import { input, password } from "@inquirer/prompts";
|
|
7600
|
-
import { Command as
|
|
7601
|
-
var connectorCommand = new
|
|
8476
|
+
import { input as input2, password } from "@inquirer/prompts";
|
|
8477
|
+
import { Command as Command5 } from "commander";
|
|
8478
|
+
var connectorCommand = new Command5("connector").description(
|
|
7602
8479
|
"Manage external service connectors"
|
|
7603
8480
|
);
|
|
7604
8481
|
connectorCommand.command("setup").description("Configure a connector").argument("<type>", "Connector type (ado, turso)").option("--url <url>", "Turso database URL (non-interactive)").option("--token <token>", "Turso auth token (non-interactive)").option(
|
|
@@ -7617,10 +8494,10 @@ connectorCommand.command("setup").description("Configure a connector").argument(
|
|
|
7617
8494
|
process.exit(1);
|
|
7618
8495
|
}
|
|
7619
8496
|
try {
|
|
7620
|
-
const orgUrl = await
|
|
8497
|
+
const orgUrl = await input2({
|
|
7621
8498
|
message: "Organization URL (e.g. https://dev.azure.com/myorg):"
|
|
7622
8499
|
});
|
|
7623
|
-
const project = await
|
|
8500
|
+
const project = await input2({
|
|
7624
8501
|
message: "Project name:"
|
|
7625
8502
|
});
|
|
7626
8503
|
const pat = await password({
|
|
@@ -7711,7 +8588,7 @@ connectorCommand.command("sync").description("Verify the Turso cloud database co
|
|
|
7711
8588
|
async function setupTurso(urlArg, tokenArg, mode) {
|
|
7712
8589
|
let db;
|
|
7713
8590
|
try {
|
|
7714
|
-
const url = urlArg ?? await
|
|
8591
|
+
const url = urlArg ?? await input2({
|
|
7715
8592
|
message: "Turso database URL (e.g. libsql://my-db-user.turso.io):"
|
|
7716
8593
|
});
|
|
7717
8594
|
const token = tokenArg ?? await password({
|
|
@@ -7740,27 +8617,27 @@ async function setupTurso(urlArg, tokenArg, mode) {
|
|
|
7740
8617
|
}
|
|
7741
8618
|
|
|
7742
8619
|
// src/cli/commands/git-sync.ts
|
|
7743
|
-
import { execSync as
|
|
7744
|
-
import { chmodSync as chmodSync2, existsSync as
|
|
7745
|
-
import { join as
|
|
7746
|
-
import { Command as
|
|
8620
|
+
import { execSync as execSync5 } from "child_process";
|
|
8621
|
+
import { chmodSync as chmodSync2, existsSync as existsSync16, writeFileSync as writeFileSync8 } from "fs";
|
|
8622
|
+
import { join as join17 } from "path";
|
|
8623
|
+
import { Command as Command6 } from "commander";
|
|
7747
8624
|
function installHook2() {
|
|
7748
|
-
const gitDir =
|
|
7749
|
-
if (!
|
|
8625
|
+
const gitDir = join17(process.cwd(), ".git");
|
|
8626
|
+
if (!existsSync16(gitDir)) {
|
|
7750
8627
|
console.error(
|
|
7751
8628
|
"Error: Current directory is not the root of a Git repository."
|
|
7752
8629
|
);
|
|
7753
8630
|
process.exit(1);
|
|
7754
8631
|
}
|
|
7755
|
-
const hooksDir =
|
|
7756
|
-
const hookPath =
|
|
8632
|
+
const hooksDir = join17(gitDir, "hooks");
|
|
8633
|
+
const hookPath = join17(hooksDir, "post-commit");
|
|
7757
8634
|
const hookContent = `#!/bin/sh
|
|
7758
8635
|
# ZAM Spaced Repetition Auto-Stale Hook
|
|
7759
8636
|
# Triggered automatically on git commits to decay modified concept cards.
|
|
7760
8637
|
zam git-sync --commit HEAD --quiet
|
|
7761
8638
|
`;
|
|
7762
8639
|
try {
|
|
7763
|
-
|
|
8640
|
+
writeFileSync8(hookPath, hookContent, { encoding: "utf-8", flag: "w" });
|
|
7764
8641
|
try {
|
|
7765
8642
|
chmodSync2(hookPath, "755");
|
|
7766
8643
|
} catch (_e) {
|
|
@@ -7773,7 +8650,7 @@ zam git-sync --commit HEAD --quiet
|
|
|
7773
8650
|
process.exit(1);
|
|
7774
8651
|
}
|
|
7775
8652
|
}
|
|
7776
|
-
var gitSyncCommand = new
|
|
8653
|
+
var gitSyncCommand = new Command6("git-sync").description("Sync learning cards with recent Git file modifications").option("--commit <hash>", "Git commit hash to check", "HEAD").option("--user <id>", "User ID (default: whoami)").option("--install", "Install git post-commit hook in current repo").option("--quiet", "Suppress verbose output").action(async (opts) => {
|
|
7777
8654
|
if (opts.install) {
|
|
7778
8655
|
installHook2();
|
|
7779
8656
|
return;
|
|
@@ -7782,7 +8659,7 @@ var gitSyncCommand = new Command5("git-sync").description("Sync learning cards w
|
|
|
7782
8659
|
const userId = await resolveUser(opts, db);
|
|
7783
8660
|
let changedFiles = [];
|
|
7784
8661
|
try {
|
|
7785
|
-
const output =
|
|
8662
|
+
const output = execSync5(
|
|
7786
8663
|
`git diff-tree --no-commit-id --name-only -r ${opts.commit}`,
|
|
7787
8664
|
{
|
|
7788
8665
|
encoding: "utf-8",
|
|
@@ -7862,10 +8739,10 @@ ZAM Auto-Stale Complete: Scanned ${changedFiles.length} file(s).`
|
|
|
7862
8739
|
});
|
|
7863
8740
|
|
|
7864
8741
|
// src/cli/commands/goal.ts
|
|
7865
|
-
import { existsSync as
|
|
7866
|
-
import { resolve as
|
|
7867
|
-
import { input as
|
|
7868
|
-
import { Command as
|
|
8742
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync9 } from "fs";
|
|
8743
|
+
import { resolve as resolve4 } from "path";
|
|
8744
|
+
import { input as input3 } from "@inquirer/prompts";
|
|
8745
|
+
import { Command as Command7 } from "commander";
|
|
7869
8746
|
async function resolveGoalsDir() {
|
|
7870
8747
|
let goalsDir;
|
|
7871
8748
|
let db;
|
|
@@ -7876,9 +8753,9 @@ async function resolveGoalsDir() {
|
|
|
7876
8753
|
} finally {
|
|
7877
8754
|
await db?.close();
|
|
7878
8755
|
}
|
|
7879
|
-
return goalsDir ?
|
|
8756
|
+
return goalsDir ? resolve4(goalsDir) : resolve4("goals");
|
|
7880
8757
|
}
|
|
7881
|
-
var goalCommand = new
|
|
8758
|
+
var goalCommand = new Command7("goal").description(
|
|
7882
8759
|
"Manage learning goals (markdown files)"
|
|
7883
8760
|
);
|
|
7884
8761
|
goalCommand.command("list").description("List all goals").option(
|
|
@@ -7886,7 +8763,7 @@ goalCommand.command("list").description("List all goals").option(
|
|
|
7886
8763
|
"Filter by status (active, completed, paused, abandoned)"
|
|
7887
8764
|
).option("--tree", "Show goals as a tree with parent/child relationships").option("--json", "Output as JSON").action(async (opts) => {
|
|
7888
8765
|
const goalsDir = await resolveGoalsDir();
|
|
7889
|
-
if (!
|
|
8766
|
+
if (!existsSync17(goalsDir)) {
|
|
7890
8767
|
console.error(`Goals directory not found: ${goalsDir}`);
|
|
7891
8768
|
console.error(
|
|
7892
8769
|
"Set it with: zam settings set personal.goals_dir /path/to/goals"
|
|
@@ -7987,8 +8864,8 @@ ${"\u2500".repeat(50)}`);
|
|
|
7987
8864
|
});
|
|
7988
8865
|
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) => {
|
|
7989
8866
|
const goalsDir = await resolveGoalsDir();
|
|
7990
|
-
if (!
|
|
7991
|
-
|
|
8867
|
+
if (!existsSync17(goalsDir)) {
|
|
8868
|
+
mkdirSync9(goalsDir, { recursive: true });
|
|
7992
8869
|
}
|
|
7993
8870
|
let slug = opts.slug;
|
|
7994
8871
|
let title = opts.title;
|
|
@@ -7997,11 +8874,11 @@ goalCommand.command("create").description("Create a new goal").option("--slug <s
|
|
|
7997
8874
|
if (!slug || !title) {
|
|
7998
8875
|
try {
|
|
7999
8876
|
if (!title) {
|
|
8000
|
-
title = await
|
|
8877
|
+
title = await input3({ message: "Goal title:" });
|
|
8001
8878
|
}
|
|
8002
8879
|
if (!slug) {
|
|
8003
8880
|
const suggested = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
8004
|
-
slug = await
|
|
8881
|
+
slug = await input3({
|
|
8005
8882
|
message: "Goal slug (filename):",
|
|
8006
8883
|
default: suggested
|
|
8007
8884
|
});
|
|
@@ -8047,22 +8924,22 @@ goalCommand.command("status <slug> <status>").description("Update a goal's statu
|
|
|
8047
8924
|
});
|
|
8048
8925
|
|
|
8049
8926
|
// src/cli/commands/init.ts
|
|
8050
|
-
import { existsSync as
|
|
8051
|
-
import { homedir as
|
|
8052
|
-
import { join as
|
|
8053
|
-
import { confirm, input as
|
|
8054
|
-
import { Command as
|
|
8055
|
-
var HOME2 =
|
|
8927
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync10, writeFileSync as writeFileSync9 } from "fs";
|
|
8928
|
+
import { homedir as homedir11 } from "os";
|
|
8929
|
+
import { join as join18 } from "path";
|
|
8930
|
+
import { confirm as confirm2, input as input4 } from "@inquirer/prompts";
|
|
8931
|
+
import { Command as Command8 } from "commander";
|
|
8932
|
+
var HOME2 = homedir11();
|
|
8056
8933
|
function printLine(char = "\u2550", len = 60, color = "\x1B[36m") {
|
|
8057
8934
|
console.log(`${color}${char.repeat(len)}\x1B[0m`);
|
|
8058
8935
|
}
|
|
8059
8936
|
function bootstrapSandboxWorkspace(workspaceDir) {
|
|
8060
|
-
|
|
8061
|
-
|
|
8062
|
-
|
|
8063
|
-
const worldviewFile =
|
|
8064
|
-
if (!
|
|
8065
|
-
|
|
8937
|
+
mkdirSync10(join18(workspaceDir, "beliefs"), { recursive: true });
|
|
8938
|
+
mkdirSync10(join18(workspaceDir, "goals"), { recursive: true });
|
|
8939
|
+
mkdirSync10(join18(workspaceDir, "skills"), { recursive: true });
|
|
8940
|
+
const worldviewFile = join18(workspaceDir, "beliefs", "worldview.md");
|
|
8941
|
+
if (!existsSync18(worldviewFile)) {
|
|
8942
|
+
writeFileSync9(
|
|
8066
8943
|
worldviewFile,
|
|
8067
8944
|
`# Personal Worldview
|
|
8068
8945
|
|
|
@@ -8074,9 +8951,9 @@ Here, I declare the core concepts and principles I want to master.
|
|
|
8074
8951
|
"utf8"
|
|
8075
8952
|
);
|
|
8076
8953
|
}
|
|
8077
|
-
const goalsFile =
|
|
8078
|
-
if (!
|
|
8079
|
-
|
|
8954
|
+
const goalsFile = join18(workspaceDir, "goals", "goals.md");
|
|
8955
|
+
if (!existsSync18(goalsFile)) {
|
|
8956
|
+
writeFileSync9(
|
|
8080
8957
|
goalsFile,
|
|
8081
8958
|
`# Personal Goals
|
|
8082
8959
|
|
|
@@ -8088,7 +8965,7 @@ Here, I declare the core concepts and principles I want to master.
|
|
|
8088
8965
|
);
|
|
8089
8966
|
}
|
|
8090
8967
|
}
|
|
8091
|
-
var initCommand = new
|
|
8968
|
+
var initCommand = new Command8("init").description("Launch the guided interactive onboarding wizard").action(async () => {
|
|
8092
8969
|
printLine();
|
|
8093
8970
|
console.log(
|
|
8094
8971
|
"\x1B[1m\x1B[32m ZAM \u2014 The Symbiotic Learning Agent Onboarding\x1B[0m"
|
|
@@ -8098,8 +8975,8 @@ var initCommand = new Command7("init").description("Launch the guided interactiv
|
|
|
8098
8975
|
);
|
|
8099
8976
|
printLine();
|
|
8100
8977
|
console.log("\n\x1B[1m[1/5] Setting up Local Workspace Sandbox\x1B[0m");
|
|
8101
|
-
const defaultWorkspace =
|
|
8102
|
-
const workspacePath = await
|
|
8978
|
+
const defaultWorkspace = join18(HOME2, "Documents", "zam");
|
|
8979
|
+
const workspacePath = await input4({
|
|
8103
8980
|
message: "Choose your ZAM workspace directory:",
|
|
8104
8981
|
default: defaultWorkspace
|
|
8105
8982
|
});
|
|
@@ -8135,7 +9012,7 @@ var initCommand = new Command7("init").description("Launch the guided interactiv
|
|
|
8135
9012
|
\x1B[1mRecommendation:\x1B[0m ZAM suggests installing \x1B[32m${runnerLabel}\x1B[0m with \x1B[36m${profile.recommendedModel}\x1B[0m.`
|
|
8136
9013
|
);
|
|
8137
9014
|
console.log("\n\x1B[1m[3/5] Setting up Local LLM Runner\x1B[0m");
|
|
8138
|
-
const proceedInstall = await
|
|
9015
|
+
const proceedInstall = await confirm2({
|
|
8139
9016
|
message: `Would you like ZAM to install and configure ${runnerLabel} automatically?`,
|
|
8140
9017
|
default: true
|
|
8141
9018
|
});
|
|
@@ -8203,7 +9080,7 @@ var initCommand = new Command7("init").description("Launch the guided interactiv
|
|
|
8203
9080
|
console.log(
|
|
8204
9081
|
"\n\x1B[1m[5/5] Wiring Developer Agents & Terminal Helpers\x1B[0m"
|
|
8205
9082
|
);
|
|
8206
|
-
const proceedHooks = await
|
|
9083
|
+
const proceedHooks = await confirm2({
|
|
8207
9084
|
message: "Distribute ZAM skills and install optional monitored-session shell helpers?",
|
|
8208
9085
|
default: true
|
|
8209
9086
|
});
|
|
@@ -8254,8 +9131,8 @@ var initCommand = new Command7("init").description("Launch the guided interactiv
|
|
|
8254
9131
|
});
|
|
8255
9132
|
|
|
8256
9133
|
// src/cli/commands/learn.ts
|
|
8257
|
-
import { input as
|
|
8258
|
-
import { Command as
|
|
9134
|
+
import { input as input6 } from "@inquirer/prompts";
|
|
9135
|
+
import { Command as Command9 } from "commander";
|
|
8259
9136
|
|
|
8260
9137
|
// src/cli/learn-format.ts
|
|
8261
9138
|
var BLOOM_VERBS3 = {
|
|
@@ -8304,7 +9181,7 @@ function formatReveal(input8) {
|
|
|
8304
9181
|
}
|
|
8305
9182
|
|
|
8306
9183
|
// src/cli/review-actions.ts
|
|
8307
|
-
import { confirm as
|
|
9184
|
+
import { confirm as confirm3, input as input5, select } from "@inquirer/prompts";
|
|
8308
9185
|
async function runInteractiveReviewAction(inputData) {
|
|
8309
9186
|
let currentItem = { ...inputData.item };
|
|
8310
9187
|
while (true) {
|
|
@@ -8419,7 +9296,7 @@ async function runInteractiveReviewAction(inputData) {
|
|
|
8419
9296
|
continue;
|
|
8420
9297
|
}
|
|
8421
9298
|
if (choice === "deprecate-token") {
|
|
8422
|
-
const approved = await
|
|
9299
|
+
const approved = await confirm3({
|
|
8423
9300
|
message: `Deprecate ${currentItem.slug} so it stops appearing in review queues?`,
|
|
8424
9301
|
default: false
|
|
8425
9302
|
});
|
|
@@ -8450,7 +9327,7 @@ async function runInteractiveReviewAction(inputData) {
|
|
|
8450
9327
|
console.log(` Session steps: ${impact.session_steps}`);
|
|
8451
9328
|
console.log(` Sessions touched: ${impact.sessions_touched}`);
|
|
8452
9329
|
console.log(` Agent skills updated: ${impact.agent_skills}`);
|
|
8453
|
-
const approved = await
|
|
9330
|
+
const approved = await confirm3({
|
|
8454
9331
|
message: "Permanently delete this token and its dependent learning data?",
|
|
8455
9332
|
default: false
|
|
8456
9333
|
});
|
|
@@ -8475,7 +9352,7 @@ async function runInteractiveReviewAction(inputData) {
|
|
|
8475
9352
|
);
|
|
8476
9353
|
console.log(`Delete your card for ${currentItem.slug}?`);
|
|
8477
9354
|
console.log(` Review logs removed: ${impact.review_logs}`);
|
|
8478
|
-
const approved = await
|
|
9355
|
+
const approved = await confirm3({
|
|
8479
9356
|
message: "Delete only your card for this token?",
|
|
8480
9357
|
default: false
|
|
8481
9358
|
});
|
|
@@ -8513,21 +9390,21 @@ async function promptTokenEdit(token) {
|
|
|
8513
9390
|
}
|
|
8514
9391
|
switch (field) {
|
|
8515
9392
|
case "concept": {
|
|
8516
|
-
const concept = await
|
|
9393
|
+
const concept = await input5({
|
|
8517
9394
|
message: "Concept:",
|
|
8518
9395
|
default: token.concept
|
|
8519
9396
|
});
|
|
8520
9397
|
return concept === token.concept ? null : { concept };
|
|
8521
9398
|
}
|
|
8522
9399
|
case "domain": {
|
|
8523
|
-
const domain = await
|
|
9400
|
+
const domain = await input5({
|
|
8524
9401
|
message: "Domain (blank allowed):",
|
|
8525
9402
|
default: token.domain
|
|
8526
9403
|
});
|
|
8527
9404
|
return domain === token.domain ? null : { domain };
|
|
8528
9405
|
}
|
|
8529
9406
|
case "context": {
|
|
8530
|
-
const context = await
|
|
9407
|
+
const context = await input5({
|
|
8531
9408
|
message: "Context (blank allowed):",
|
|
8532
9409
|
default: token.context
|
|
8533
9410
|
});
|
|
@@ -8568,14 +9445,14 @@ async function promptTokenEdit(token) {
|
|
|
8568
9445
|
return mode === token.symbiosis_mode ? null : { symbiosis_mode: mode };
|
|
8569
9446
|
}
|
|
8570
9447
|
case "source_link": {
|
|
8571
|
-
const link = await
|
|
9448
|
+
const link = await input5({
|
|
8572
9449
|
message: "Source link (blank allowed):",
|
|
8573
9450
|
default: token.source_link ?? ""
|
|
8574
9451
|
});
|
|
8575
9452
|
return link === token.source_link ? null : { source_link: link || null };
|
|
8576
9453
|
}
|
|
8577
9454
|
case "question": {
|
|
8578
|
-
const question = await
|
|
9455
|
+
const question = await input5({
|
|
8579
9456
|
message: "Recall question (blank allowed):",
|
|
8580
9457
|
default: token.question ?? ""
|
|
8581
9458
|
});
|
|
@@ -8593,7 +9470,7 @@ var STOP_WORDS = /* @__PURE__ */ new Set(["q", ":q", "quit", "stop"]);
|
|
|
8593
9470
|
function isExitPrompt(err) {
|
|
8594
9471
|
return err instanceof Error && err.name === "ExitPromptError";
|
|
8595
9472
|
}
|
|
8596
|
-
var learnCommand = new
|
|
9473
|
+
var learnCommand = new Command9("learn").description(
|
|
8597
9474
|
"Run a spoiler-free, in-process learning session (recall \u2192 reveal \u2192 self-rate)"
|
|
8598
9475
|
).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 the revealed answer").action(async (opts) => {
|
|
8599
9476
|
let db;
|
|
@@ -8673,7 +9550,7 @@ ${"\u2500".repeat(50)}`);
|
|
|
8673
9550
|
${prompt.question}`);
|
|
8674
9551
|
let answer;
|
|
8675
9552
|
try {
|
|
8676
|
-
answer = await
|
|
9553
|
+
answer = await input6({
|
|
8677
9554
|
message: t(locale, "prompt_answer")
|
|
8678
9555
|
});
|
|
8679
9556
|
} catch (err) {
|
|
@@ -8793,36 +9670,52 @@ ${"\u2550".repeat(50)}`);
|
|
|
8793
9670
|
process.exit(1);
|
|
8794
9671
|
}
|
|
8795
9672
|
});
|
|
8796
|
-
|
|
8797
|
-
|
|
8798
|
-
|
|
8799
|
-
|
|
8800
|
-
import { tmpdir as tmpdir3 } from "os";
|
|
8801
|
-
import { basename as basename3, join as join16 } from "path";
|
|
8802
|
-
import { Command as Command9 } from "commander";
|
|
8803
|
-
function isPowerShellShell(shell) {
|
|
8804
|
-
return shell === "pwsh" || shell === "powershell";
|
|
9673
|
+
function buildLearnCommand(shell, userId) {
|
|
9674
|
+
const zamInvocation = resolveZamInvocation(shell);
|
|
9675
|
+
const learnArgs = userId ? ` learn --user ${userId}` : " learn";
|
|
9676
|
+
return `${zamInvocation}${learnArgs}`;
|
|
8805
9677
|
}
|
|
8806
|
-
|
|
8807
|
-
|
|
8808
|
-
|
|
8809
|
-
|
|
8810
|
-
|
|
9678
|
+
learnCommand.command("open").description("Open a new terminal window running zam learn (Active Recall)").option("--user <id>", "User ID (default: whoami)").option("--dir <path>", "Working directory (defaults to cwd)").option(
|
|
9679
|
+
"--shell <type>",
|
|
9680
|
+
"Shell type: zsh | bash | pwsh | powershell (auto-detected)"
|
|
9681
|
+
).action(async (opts) => {
|
|
9682
|
+
let shell;
|
|
9683
|
+
try {
|
|
9684
|
+
shell = normalizeShell(opts.shell);
|
|
9685
|
+
} catch (err) {
|
|
9686
|
+
console.error(`Error: ${err.message}`);
|
|
9687
|
+
process.exit(1);
|
|
8811
9688
|
}
|
|
8812
|
-
|
|
8813
|
-
|
|
8814
|
-
|
|
8815
|
-
|
|
8816
|
-
|
|
8817
|
-
|
|
8818
|
-
}
|
|
8819
|
-
|
|
8820
|
-
|
|
8821
|
-
|
|
8822
|
-
|
|
8823
|
-
|
|
8824
|
-
|
|
8825
|
-
|
|
9689
|
+
let db;
|
|
9690
|
+
let userId = opts.user;
|
|
9691
|
+
try {
|
|
9692
|
+
db = await openDatabase();
|
|
9693
|
+
if (!userId) {
|
|
9694
|
+
userId = await resolveUser(opts, db);
|
|
9695
|
+
}
|
|
9696
|
+
if (!await getSetting(db, "review_method")) {
|
|
9697
|
+
await setSetting(db, "review_method", "console");
|
|
9698
|
+
}
|
|
9699
|
+
} catch (err) {
|
|
9700
|
+
console.error(`Error: ${err.message}`);
|
|
9701
|
+
process.exit(1);
|
|
9702
|
+
} finally {
|
|
9703
|
+
await db?.close();
|
|
9704
|
+
}
|
|
9705
|
+
const dir = opts.dir ?? process.cwd();
|
|
9706
|
+
const learnCommandLine = buildLearnCommand(shell, userId);
|
|
9707
|
+
const shellSetup = buildShellSetupCommand(dir, shell, learnCommandLine);
|
|
9708
|
+
openTerminalWindow({
|
|
9709
|
+
shellSetup,
|
|
9710
|
+
label: "learn",
|
|
9711
|
+
dir,
|
|
9712
|
+
shell
|
|
9713
|
+
});
|
|
9714
|
+
});
|
|
9715
|
+
|
|
9716
|
+
// src/cli/commands/monitor.ts
|
|
9717
|
+
import { Command as Command10 } from "commander";
|
|
9718
|
+
var monitorCommand = new Command10("monitor").description(
|
|
8826
9719
|
"Shell observation for real-time task monitoring"
|
|
8827
9720
|
);
|
|
8828
9721
|
monitorCommand.command("start").description("Output shell hook code to install monitoring").requiredOption("--session <id>", "Session ID to monitor").option(
|
|
@@ -8949,39 +9842,6 @@ monitorCommand.command("status").description("Show monitoring status for a sessi
|
|
|
8949
9842
|
console.log(` To: ${result.timeSpan.end}`);
|
|
8950
9843
|
}
|
|
8951
9844
|
});
|
|
8952
|
-
function selectWindowsExecutable(results, pathext = process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD") {
|
|
8953
|
-
if (results.length === 0) return null;
|
|
8954
|
-
const extensions = pathext.split(";").map((ext) => ext.trim().toLowerCase()).filter(Boolean);
|
|
8955
|
-
const runnable = results.find(
|
|
8956
|
-
(result) => extensions.some((ext) => result.toLowerCase().endsWith(ext))
|
|
8957
|
-
);
|
|
8958
|
-
return runnable ?? results[0];
|
|
8959
|
-
}
|
|
8960
|
-
function findExecutable(command) {
|
|
8961
|
-
try {
|
|
8962
|
-
const lookup = process.platform === "win32" ? `where.exe ${command}` : `command -v ${command}`;
|
|
8963
|
-
const results = execSync5(lookup, { encoding: "utf-8" }).split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
8964
|
-
if (results.length === 0) return null;
|
|
8965
|
-
if (process.platform === "win32") {
|
|
8966
|
-
return selectWindowsExecutable(results);
|
|
8967
|
-
}
|
|
8968
|
-
return results[0];
|
|
8969
|
-
} catch {
|
|
8970
|
-
return null;
|
|
8971
|
-
}
|
|
8972
|
-
}
|
|
8973
|
-
function resolveZamInvocation(shell) {
|
|
8974
|
-
const installed = findExecutable("zam");
|
|
8975
|
-
if (installed) {
|
|
8976
|
-
return isPowerShellShell(shell) ? `& ${psSingleQuoted2(installed)}` : installed;
|
|
8977
|
-
}
|
|
8978
|
-
const projectRoot = join16(import.meta.dirname, "..", "..", "..");
|
|
8979
|
-
const cliSource = join16(projectRoot, "src/cli/index.ts");
|
|
8980
|
-
if (isPowerShellShell(shell)) {
|
|
8981
|
-
return `& npx --prefix ${psSingleQuoted2(projectRoot)} tsx ${psSingleQuoted2(cliSource)}`;
|
|
8982
|
-
}
|
|
8983
|
-
return `npx --prefix ${JSON.stringify(projectRoot)} tsx ${JSON.stringify(cliSource)}`;
|
|
8984
|
-
}
|
|
8985
9845
|
function buildMonitorSetupCommand(dir, sessionId, shell) {
|
|
8986
9846
|
const zamInvocation = resolveZamInvocation(shell);
|
|
8987
9847
|
if (isPowerShellShell(shell)) {
|
|
@@ -8994,17 +9854,6 @@ function buildMonitorSetupCommand(dir, sessionId, shell) {
|
|
|
8994
9854
|
}
|
|
8995
9855
|
return `cd ${JSON.stringify(dir)} && eval "$(${zamInvocation} monitor start --session ${sessionId} --shell ${shell})"`;
|
|
8996
9856
|
}
|
|
8997
|
-
function isItermRunning() {
|
|
8998
|
-
try {
|
|
8999
|
-
const result = execSync5(
|
|
9000
|
-
`osascript -e 'tell application "System Events" to (name of processes) contains "iTerm2"' 2>/dev/null`,
|
|
9001
|
-
{ encoding: "utf-8" }
|
|
9002
|
-
).trim();
|
|
9003
|
-
return result === "true";
|
|
9004
|
-
} catch {
|
|
9005
|
-
return false;
|
|
9006
|
-
}
|
|
9007
|
-
}
|
|
9008
9857
|
monitorCommand.command("open").description("Open a new monitored terminal window for a session").requiredOption("--session <id>", "Session ID to monitor").option("--dir <path>", "Working directory (defaults to cwd)").option(
|
|
9009
9858
|
"--shell <type>",
|
|
9010
9859
|
"Shell type: zsh | bash | pwsh | powershell (auto-detected)"
|
|
@@ -9037,89 +9886,19 @@ monitorCommand.command("open").description("Open a new monitored terminal window
|
|
|
9037
9886
|
} finally {
|
|
9038
9887
|
await db?.close();
|
|
9039
9888
|
}
|
|
9040
|
-
const dir = opts.dir ?? process.cwd();
|
|
9041
|
-
const shellSetup = buildMonitorSetupCommand(dir, opts.session, shell);
|
|
9042
|
-
|
|
9043
|
-
|
|
9044
|
-
|
|
9045
|
-
|
|
9046
|
-
|
|
9047
|
-
|
|
9048
|
-
|
|
9049
|
-
console.log(` ${shellSetup}
|
|
9050
|
-
`);
|
|
9051
|
-
console.log(
|
|
9052
|
-
`(Automatic terminal opening is only supported on macOS Terminal/iTerm2 and Windows PowerShell for now.)`
|
|
9053
|
-
);
|
|
9054
|
-
}
|
|
9055
|
-
});
|
|
9056
|
-
function openMacTerminal(shellSetup, sessionId, dir) {
|
|
9057
|
-
const useIterm = isItermRunning();
|
|
9058
|
-
const escaped = shellSetup.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
9059
|
-
const appleScript = useIterm ? `tell application "iTerm2"
|
|
9060
|
-
activate
|
|
9061
|
-
set newWindow to (create window with default profile)
|
|
9062
|
-
tell current session of newWindow
|
|
9063
|
-
write text "${escaped}"
|
|
9064
|
-
end tell
|
|
9065
|
-
end tell` : `tell application "Terminal"
|
|
9066
|
-
activate
|
|
9067
|
-
do script "${escaped}"
|
|
9068
|
-
end tell`;
|
|
9069
|
-
const tmpFile = join16(tmpdir3(), `zam-monitor-${sessionId}.scpt`);
|
|
9070
|
-
try {
|
|
9071
|
-
writeFileSync8(tmpFile, appleScript);
|
|
9072
|
-
execSync5(`osascript ${JSON.stringify(tmpFile)}`, { stdio: "ignore" });
|
|
9073
|
-
console.log(
|
|
9074
|
-
`Opened ${useIterm ? "iTerm2" : "Terminal.app"} window with monitoring for session ${sessionId}`
|
|
9075
|
-
);
|
|
9076
|
-
console.log(` Directory: ${dir}`);
|
|
9077
|
-
} catch (err) {
|
|
9078
|
-
console.error(`Failed to open terminal: ${err.message}`);
|
|
9079
|
-
console.log(`
|
|
9080
|
-
Run this manually in a new terminal:
|
|
9081
|
-
`);
|
|
9082
|
-
console.log(` ${shellSetup}`);
|
|
9083
|
-
} finally {
|
|
9084
|
-
try {
|
|
9085
|
-
unlinkSync(tmpFile);
|
|
9086
|
-
} catch {
|
|
9087
|
-
}
|
|
9088
|
-
}
|
|
9089
|
-
}
|
|
9090
|
-
function openWindowsPowerShell(shellSetup, sessionId, dir, requestedShell) {
|
|
9091
|
-
const requestedExecutable = requestedShell === "powershell" ? "powershell.exe" : "pwsh.exe";
|
|
9092
|
-
const executable = findExecutable(requestedExecutable) ? requestedExecutable : "powershell.exe";
|
|
9093
|
-
const startCommand = [
|
|
9094
|
-
"Start-Process",
|
|
9095
|
-
`-FilePath ${psSingleQuoted2(executable)}`,
|
|
9096
|
-
`-ArgumentList @('-NoExit','-NoProfile','-Command',${psSingleQuoted2(shellSetup)})`
|
|
9097
|
-
].join(" ");
|
|
9098
|
-
const launcher = findExecutable("pwsh.exe") ? "pwsh.exe" : "powershell.exe";
|
|
9099
|
-
try {
|
|
9100
|
-
execFileSync3(
|
|
9101
|
-
launcher,
|
|
9102
|
-
["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", startCommand],
|
|
9103
|
-
{
|
|
9104
|
-
stdio: "ignore"
|
|
9105
|
-
}
|
|
9106
|
-
);
|
|
9107
|
-
console.log(
|
|
9108
|
-
`Opened ${executable === "pwsh.exe" ? "PowerShell" : "Windows PowerShell"} window with monitoring for session ${sessionId}`
|
|
9109
|
-
);
|
|
9110
|
-
console.log(` Directory: ${dir}`);
|
|
9111
|
-
} catch (err) {
|
|
9112
|
-
console.error(`Failed to open PowerShell: ${err.message}`);
|
|
9113
|
-
console.log(`
|
|
9114
|
-
Run this manually in a new PowerShell terminal:
|
|
9115
|
-
`);
|
|
9116
|
-
console.log(` ${shellSetup}`);
|
|
9117
|
-
}
|
|
9118
|
-
}
|
|
9889
|
+
const dir = opts.dir ?? process.cwd();
|
|
9890
|
+
const shellSetup = buildMonitorSetupCommand(dir, opts.session, shell);
|
|
9891
|
+
openTerminalWindow({
|
|
9892
|
+
shellSetup,
|
|
9893
|
+
label: `monitor-${opts.session}`,
|
|
9894
|
+
dir,
|
|
9895
|
+
shell
|
|
9896
|
+
});
|
|
9897
|
+
});
|
|
9119
9898
|
|
|
9120
9899
|
// src/cli/commands/observer.ts
|
|
9121
|
-
import { Command as
|
|
9122
|
-
var observerCommand = new
|
|
9900
|
+
import { Command as Command11 } from "commander";
|
|
9901
|
+
var observerCommand = new Command11("observer").description(
|
|
9123
9902
|
"Configure what the UI observer may capture (Layer 2 policy)"
|
|
9124
9903
|
);
|
|
9125
9904
|
function applyObserverListChange(current, entry, op) {
|
|
@@ -9181,9 +9960,9 @@ observerCommand.command("revoke <process>").description("Remove a process from o
|
|
|
9181
9960
|
});
|
|
9182
9961
|
|
|
9183
9962
|
// src/cli/commands/profile.ts
|
|
9184
|
-
import { homedir as
|
|
9185
|
-
import { dirname as dirname5, join as
|
|
9186
|
-
import { Command as
|
|
9963
|
+
import { homedir as homedir12 } from "os";
|
|
9964
|
+
import { dirname as dirname5, join as join19, resolve as resolve5 } from "path";
|
|
9965
|
+
import { Command as Command12 } from "commander";
|
|
9187
9966
|
var C2 = {
|
|
9188
9967
|
reset: "\x1B[0m",
|
|
9189
9968
|
bold: "\x1B[1m",
|
|
@@ -9192,7 +9971,7 @@ var C2 = {
|
|
|
9192
9971
|
green: "\x1B[32m"
|
|
9193
9972
|
};
|
|
9194
9973
|
function defaultPersonalDir() {
|
|
9195
|
-
return
|
|
9974
|
+
return join19(homedir12(), "Documents", "zam");
|
|
9196
9975
|
}
|
|
9197
9976
|
function render(profile) {
|
|
9198
9977
|
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}`;
|
|
@@ -9203,7 +9982,7 @@ function render(profile) {
|
|
|
9203
9982
|
console.log(` data dir: ${C2.cyan}${profile.dataDir}${C2.reset}`);
|
|
9204
9983
|
console.log(` database: ${C2.cyan}${profile.dbPath}${C2.reset}`);
|
|
9205
9984
|
}
|
|
9206
|
-
var profileCommand = new
|
|
9985
|
+
var profileCommand = new Command12("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) => {
|
|
9207
9986
|
if (opts.mode && opts.mode !== "developer" && opts.mode !== "default") {
|
|
9208
9987
|
console.error(`Invalid --mode: ${opts.mode}. Use developer or default.`);
|
|
9209
9988
|
process.exit(1);
|
|
@@ -9213,7 +9992,7 @@ var profileCommand = new Command11("profile").description("Show or change this m
|
|
|
9213
9992
|
if (opts.mode) setInstallMode(opts.mode);
|
|
9214
9993
|
db = await openDatabaseWithSync({ initialize: true });
|
|
9215
9994
|
if (opts.dir) {
|
|
9216
|
-
await setSetting(db, "personal.workspace_dir",
|
|
9995
|
+
await setSetting(db, "personal.workspace_dir", resolve5(opts.dir));
|
|
9217
9996
|
}
|
|
9218
9997
|
const personalDir = await getSetting(db, "personal.workspace_dir") || defaultPersonalDir();
|
|
9219
9998
|
await db.close();
|
|
@@ -9238,9 +10017,276 @@ var profileCommand = new Command11("profile").description("Show or change this m
|
|
|
9238
10017
|
}
|
|
9239
10018
|
});
|
|
9240
10019
|
|
|
10020
|
+
// src/cli/commands/provider.ts
|
|
10021
|
+
import { password as password2 } from "@inquirer/prompts";
|
|
10022
|
+
import { Command as Command13 } from "commander";
|
|
10023
|
+
var VALID_API_FLAVORS = [
|
|
10024
|
+
"chat-completions",
|
|
10025
|
+
"anthropic-messages"
|
|
10026
|
+
];
|
|
10027
|
+
var VALID_ROLES = ["vision", "recall", "text"];
|
|
10028
|
+
function upsertProviderRecord(providers, name, patch) {
|
|
10029
|
+
const merged = { ...providers[name] ?? {} };
|
|
10030
|
+
if (patch.url !== void 0) merged.url = patch.url;
|
|
10031
|
+
if (patch.model !== void 0) merged.model = patch.model;
|
|
10032
|
+
if (patch.apiFlavor !== void 0) merged.apiFlavor = patch.apiFlavor;
|
|
10033
|
+
if (patch.apiKeyRef !== void 0) merged.apiKeyRef = patch.apiKeyRef;
|
|
10034
|
+
return { ...providers, [name]: merged };
|
|
10035
|
+
}
|
|
10036
|
+
function removeProviderRecord(providers, name) {
|
|
10037
|
+
if (!(name in providers)) return { providers, removed: false };
|
|
10038
|
+
const next = { ...providers };
|
|
10039
|
+
delete next[name];
|
|
10040
|
+
return { providers: next, removed: true };
|
|
10041
|
+
}
|
|
10042
|
+
function rolesReferencing(roles, name) {
|
|
10043
|
+
return VALID_ROLES.filter((role) => {
|
|
10044
|
+
const binding = roles[role];
|
|
10045
|
+
return binding?.primary === name || binding?.fallback === name;
|
|
10046
|
+
});
|
|
10047
|
+
}
|
|
10048
|
+
function bindRoleProviders(roles, role, primary, fallback) {
|
|
10049
|
+
const binding = { primary };
|
|
10050
|
+
if (fallback) binding.fallback = fallback;
|
|
10051
|
+
return { ...roles, [role]: binding };
|
|
10052
|
+
}
|
|
10053
|
+
function maskSecret(key) {
|
|
10054
|
+
return key.length <= 4 ? "\u2022\u2022\u2022\u2022" : `\u2026${key.slice(-4)}`;
|
|
10055
|
+
}
|
|
10056
|
+
function buildProviderListing(providers, hasKey) {
|
|
10057
|
+
return Object.entries(providers).map(([name, rec]) => {
|
|
10058
|
+
const apiFlavor = rec.apiFlavor ?? (rec.url ? inferApiFlavor(rec.url) : "chat-completions");
|
|
10059
|
+
let keyState;
|
|
10060
|
+
if (!rec.apiKeyRef) keyState = "none";
|
|
10061
|
+
else keyState = hasKey(rec.apiKeyRef) ? "set" : "missing";
|
|
10062
|
+
return {
|
|
10063
|
+
name,
|
|
10064
|
+
url: rec.url,
|
|
10065
|
+
model: rec.model,
|
|
10066
|
+
apiFlavor,
|
|
10067
|
+
apiKeyRef: rec.apiKeyRef,
|
|
10068
|
+
keyState
|
|
10069
|
+
};
|
|
10070
|
+
});
|
|
10071
|
+
}
|
|
10072
|
+
function findOrphanKeyRefs(storedRefs, providers) {
|
|
10073
|
+
const used = new Set(
|
|
10074
|
+
Object.values(providers).map((rec) => rec.apiKeyRef).filter((ref) => !!ref)
|
|
10075
|
+
);
|
|
10076
|
+
return storedRefs.filter((ref) => !used.has(ref));
|
|
10077
|
+
}
|
|
10078
|
+
async function readJson(db, key, fallback) {
|
|
10079
|
+
const raw = await getSetting(db, key);
|
|
10080
|
+
if (!raw) return fallback;
|
|
10081
|
+
try {
|
|
10082
|
+
return JSON.parse(raw);
|
|
10083
|
+
} catch {
|
|
10084
|
+
return fallback;
|
|
10085
|
+
}
|
|
10086
|
+
}
|
|
10087
|
+
var readProviders = (db) => readJson(db, "llm.providers", {});
|
|
10088
|
+
var readRoles = (db) => readJson(db, "llm.roles", {});
|
|
10089
|
+
async function writeProviders(db, p) {
|
|
10090
|
+
await setSetting(db, "llm.providers", JSON.stringify(p));
|
|
10091
|
+
}
|
|
10092
|
+
async function writeRoles(db, r) {
|
|
10093
|
+
await setSetting(db, "llm.roles", JSON.stringify(r));
|
|
10094
|
+
}
|
|
10095
|
+
var providerCommand = new Command13("provider").description(
|
|
10096
|
+
"Configure role-based AI providers (url/model/flavor/key, per role)"
|
|
10097
|
+
);
|
|
10098
|
+
providerCommand.command("list").description("Show configured providers, role bindings, and key status").option("--json", "Output as JSON").action(async (opts) => {
|
|
10099
|
+
await withDb(async (db) => {
|
|
10100
|
+
const providers = await readProviders(db);
|
|
10101
|
+
const roles = await readRoles(db);
|
|
10102
|
+
const rows = buildProviderListing(
|
|
10103
|
+
providers,
|
|
10104
|
+
(ref) => getProviderApiKey(ref) !== null
|
|
10105
|
+
);
|
|
10106
|
+
const orphans = findOrphanKeyRefs(listProviderApiKeyRefs(), providers);
|
|
10107
|
+
if (opts.json) {
|
|
10108
|
+
console.log(
|
|
10109
|
+
JSON.stringify({ providers: rows, roles, orphans }, null, 2)
|
|
10110
|
+
);
|
|
10111
|
+
return;
|
|
10112
|
+
}
|
|
10113
|
+
console.log("Providers (llm.providers):\n");
|
|
10114
|
+
if (rows.length === 0) {
|
|
10115
|
+
console.log(
|
|
10116
|
+
" (none) \u2014 add one: zam provider add <name> --url <url> --model <model>"
|
|
10117
|
+
);
|
|
10118
|
+
} else {
|
|
10119
|
+
for (const row of rows) {
|
|
10120
|
+
const key = row.keyState === "set" ? "\x1B[32mkey \u2713\x1B[0m" : row.keyState === "missing" ? `\x1B[31mkey \u2717 (set: zam provider set-key ${row.apiKeyRef})\x1B[0m` : "\x1B[90mno key\x1B[0m";
|
|
10121
|
+
console.log(` \x1B[36m${row.name.padEnd(12)}\x1B[0m ${key}`);
|
|
10122
|
+
console.log(` url: ${row.url ?? "(inherits llm.url)"}`);
|
|
10123
|
+
console.log(` model: ${row.model ?? "(inherits llm.model)"}`);
|
|
10124
|
+
console.log(` flavor: ${row.apiFlavor}`);
|
|
10125
|
+
if (row.apiKeyRef) console.log(` key-ref: ${row.apiKeyRef}`);
|
|
10126
|
+
}
|
|
10127
|
+
}
|
|
10128
|
+
console.log("\nRoles (llm.roles):\n");
|
|
10129
|
+
for (const role of VALID_ROLES) {
|
|
10130
|
+
const binding = roles[role];
|
|
10131
|
+
if (!binding?.primary) {
|
|
10132
|
+
console.log(` ${role.padEnd(7)} \x1B[90m(unset)\x1B[0m`);
|
|
10133
|
+
} else {
|
|
10134
|
+
const fb = binding.fallback ? ` \u2192 fallback: ${binding.fallback}` : "";
|
|
10135
|
+
console.log(` ${role.padEnd(7)} primary: ${binding.primary}${fb}`);
|
|
10136
|
+
}
|
|
10137
|
+
}
|
|
10138
|
+
if (orphans.length > 0) {
|
|
10139
|
+
console.log(
|
|
10140
|
+
`
|
|
10141
|
+
\x1B[33mOrphan keys (stored but unreferenced):\x1B[0m ${orphans.join(", ")}`
|
|
10142
|
+
);
|
|
10143
|
+
}
|
|
10144
|
+
});
|
|
10145
|
+
});
|
|
10146
|
+
providerCommand.command("add <name>").description("Add or update a provider record in llm.providers").option("--url <url>", "Endpoint base URL (e.g. https://api.deepseek.com/v1)").option("--model <model>", "Model id (e.g. deepseek-v4-flash)").option(
|
|
10147
|
+
"--flavor <flavor>",
|
|
10148
|
+
`Wire protocol: ${VALID_API_FLAVORS.join(" | ")} (default: inferred from URL)`
|
|
10149
|
+
).option(
|
|
10150
|
+
"--key-ref <ref>",
|
|
10151
|
+
"Credential reference for the API key (default: <name> when --key is given)"
|
|
10152
|
+
).option(
|
|
10153
|
+
"--key <value>",
|
|
10154
|
+
"Store this API key now (prefer `set-key` to keep it out of shell history)"
|
|
10155
|
+
).action(async (name, opts) => {
|
|
10156
|
+
let apiFlavor;
|
|
10157
|
+
if (opts.flavor) {
|
|
10158
|
+
if (!VALID_API_FLAVORS.includes(opts.flavor)) {
|
|
10159
|
+
console.error(
|
|
10160
|
+
`Invalid --flavor: ${opts.flavor}. Use ${VALID_API_FLAVORS.join(" or ")}.`
|
|
10161
|
+
);
|
|
10162
|
+
process.exit(1);
|
|
10163
|
+
}
|
|
10164
|
+
apiFlavor = opts.flavor;
|
|
10165
|
+
}
|
|
10166
|
+
const apiKeyRef = opts.keyRef ?? (opts.key ? name : void 0);
|
|
10167
|
+
await withDb(async (db) => {
|
|
10168
|
+
const providers = await readProviders(db);
|
|
10169
|
+
const next = upsertProviderRecord(providers, name, {
|
|
10170
|
+
url: opts.url,
|
|
10171
|
+
model: opts.model,
|
|
10172
|
+
apiFlavor,
|
|
10173
|
+
apiKeyRef
|
|
10174
|
+
});
|
|
10175
|
+
await writeProviders(db, next);
|
|
10176
|
+
if (opts.key && apiKeyRef) setProviderApiKey(apiKeyRef, opts.key);
|
|
10177
|
+
const rec = next[name];
|
|
10178
|
+
console.log(`Provider "${name}" saved:`);
|
|
10179
|
+
console.log(` url: ${rec.url ?? "(inherits llm.url)"}`);
|
|
10180
|
+
console.log(` model: ${rec.model ?? "(inherits llm.model)"}`);
|
|
10181
|
+
console.log(
|
|
10182
|
+
` flavor: ${rec.apiFlavor ?? `${rec.url ? inferApiFlavor(rec.url) : "chat-completions"} (inferred)`}`
|
|
10183
|
+
);
|
|
10184
|
+
console.log(` key-ref: ${rec.apiKeyRef ?? "(none \u2014 uses default key)"}`);
|
|
10185
|
+
if (opts.key) {
|
|
10186
|
+
console.log(` key: stored (${maskSecret(opts.key)})`);
|
|
10187
|
+
} else if (rec.apiKeyRef && getProviderApiKey(rec.apiKeyRef) === null) {
|
|
10188
|
+
console.log(
|
|
10189
|
+
`
|
|
10190
|
+
\u26A0 No key stored for "${rec.apiKeyRef}". Run: zam provider set-key ${rec.apiKeyRef}`
|
|
10191
|
+
);
|
|
10192
|
+
}
|
|
10193
|
+
if (!rec.url) {
|
|
10194
|
+
console.log(
|
|
10195
|
+
"\n \u26A0 No --url set; this provider inherits the base llm.url."
|
|
10196
|
+
);
|
|
10197
|
+
}
|
|
10198
|
+
console.log(
|
|
10199
|
+
`
|
|
10200
|
+
Bind it to a role: zam provider use recall --primary ${name}`
|
|
10201
|
+
);
|
|
10202
|
+
});
|
|
10203
|
+
});
|
|
10204
|
+
providerCommand.command("remove <name>").description("Remove a provider record from llm.providers").action(async (name) => {
|
|
10205
|
+
await withDb(async (db) => {
|
|
10206
|
+
const providers = await readProviders(db);
|
|
10207
|
+
const { providers: next, removed } = removeProviderRecord(
|
|
10208
|
+
providers,
|
|
10209
|
+
name
|
|
10210
|
+
);
|
|
10211
|
+
if (!removed) {
|
|
10212
|
+
console.log(`No such provider: ${name}`);
|
|
10213
|
+
return;
|
|
10214
|
+
}
|
|
10215
|
+
await writeProviders(db, next);
|
|
10216
|
+
console.log(`Removed provider "${name}".`);
|
|
10217
|
+
const referencing = rolesReferencing(await readRoles(db), name);
|
|
10218
|
+
if (referencing.length > 0) {
|
|
10219
|
+
console.log(
|
|
10220
|
+
` \u26A0 Still referenced by role(s): ${referencing.join(", ")} \u2014 rebind with: zam provider use <role> --primary <name>`
|
|
10221
|
+
);
|
|
10222
|
+
}
|
|
10223
|
+
});
|
|
10224
|
+
});
|
|
10225
|
+
providerCommand.command("use <role>").description(`Bind providers to a role (${VALID_ROLES.join(" | ")})`).option("--primary <name>", "Primary provider").option("--fallback <name>", "Fallback provider (optional)").action(async (role, opts) => {
|
|
10226
|
+
if (!VALID_ROLES.includes(role)) {
|
|
10227
|
+
console.error(`Invalid role: ${role}. Use ${VALID_ROLES.join(", ")}.`);
|
|
10228
|
+
process.exit(1);
|
|
10229
|
+
}
|
|
10230
|
+
if (!opts.primary) {
|
|
10231
|
+
console.error("--primary is required.");
|
|
10232
|
+
process.exit(1);
|
|
10233
|
+
}
|
|
10234
|
+
await withDb(async (db) => {
|
|
10235
|
+
const providers = await readProviders(db);
|
|
10236
|
+
await writeRoles(
|
|
10237
|
+
db,
|
|
10238
|
+
bindRoleProviders(
|
|
10239
|
+
await readRoles(db),
|
|
10240
|
+
role,
|
|
10241
|
+
opts.primary,
|
|
10242
|
+
opts.fallback
|
|
10243
|
+
)
|
|
10244
|
+
);
|
|
10245
|
+
const fb = opts.fallback ? `, fallback: ${opts.fallback}` : "";
|
|
10246
|
+
console.log(`Role "${role}" \u2192 primary: ${opts.primary}${fb}`);
|
|
10247
|
+
for (const [label, ref] of [
|
|
10248
|
+
["primary", opts.primary],
|
|
10249
|
+
["fallback", opts.fallback]
|
|
10250
|
+
]) {
|
|
10251
|
+
if (ref && !(ref in providers)) {
|
|
10252
|
+
console.log(
|
|
10253
|
+
` \u26A0 ${label} "${ref}" is not a defined provider yet. Add it: zam provider add ${ref} --url <url> --model <model>`
|
|
10254
|
+
);
|
|
10255
|
+
}
|
|
10256
|
+
}
|
|
10257
|
+
});
|
|
10258
|
+
});
|
|
10259
|
+
providerCommand.command("set-key <ref>").description(
|
|
10260
|
+
"Store an API key for a provider reference (in credentials.json)"
|
|
10261
|
+
).option(
|
|
10262
|
+
"--key <value>",
|
|
10263
|
+
"The API key (omit to enter it interactively, hidden)"
|
|
10264
|
+
).action(async (ref, opts) => {
|
|
10265
|
+
try {
|
|
10266
|
+
const key = opts.key ?? await password2({ message: `API key for "${ref}":` });
|
|
10267
|
+
if (!key || key.trim().length === 0) {
|
|
10268
|
+
console.error("No key provided.");
|
|
10269
|
+
process.exit(1);
|
|
10270
|
+
}
|
|
10271
|
+
setProviderApiKey(ref, key.trim());
|
|
10272
|
+
console.log(`Stored API key for "${ref}" (${maskSecret(key.trim())}).`);
|
|
10273
|
+
} catch (err) {
|
|
10274
|
+
if (err.name === "ExitPromptError") {
|
|
10275
|
+
console.log("\nCancelled.");
|
|
10276
|
+
process.exit(0);
|
|
10277
|
+
}
|
|
10278
|
+
console.error("Error:", err.message);
|
|
10279
|
+
process.exit(1);
|
|
10280
|
+
}
|
|
10281
|
+
});
|
|
10282
|
+
providerCommand.command("clear-key <ref>").description("Remove a stored API key").action((ref) => {
|
|
10283
|
+
clearProviderApiKey(ref);
|
|
10284
|
+
console.log(`Cleared API key for "${ref}".`);
|
|
10285
|
+
});
|
|
10286
|
+
|
|
9241
10287
|
// src/cli/commands/review.ts
|
|
9242
|
-
import { Command as
|
|
9243
|
-
var reviewCommand = new
|
|
10288
|
+
import { Command as Command14 } from "commander";
|
|
10289
|
+
var reviewCommand = new Command14("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) => {
|
|
9244
10290
|
let db;
|
|
9245
10291
|
try {
|
|
9246
10292
|
db = await openDatabase();
|
|
@@ -9354,9 +10400,9 @@ ${"\u2550".repeat(50)}`);
|
|
|
9354
10400
|
|
|
9355
10401
|
// src/cli/commands/session.ts
|
|
9356
10402
|
import { readFileSync as readFileSync11 } from "fs";
|
|
9357
|
-
import { input as
|
|
9358
|
-
import { Command as
|
|
9359
|
-
var sessionCommand = new
|
|
10403
|
+
import { input as input7, select as select2 } from "@inquirer/prompts";
|
|
10404
|
+
import { Command as Command15 } from "commander";
|
|
10405
|
+
var sessionCommand = new Command15("session").description(
|
|
9360
10406
|
"Manage learning sessions"
|
|
9361
10407
|
);
|
|
9362
10408
|
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(
|
|
@@ -9533,7 +10579,7 @@ async function selectTask() {
|
|
|
9533
10579
|
console.log("No active work items found in Azure DevOps.");
|
|
9534
10580
|
}
|
|
9535
10581
|
}
|
|
9536
|
-
return
|
|
10582
|
+
return input7({ message: "Task description:" });
|
|
9537
10583
|
}
|
|
9538
10584
|
var RATING_LABELS = {
|
|
9539
10585
|
1: "Again",
|
|
@@ -9735,9 +10781,9 @@ sessionCommand.command("end").description("End a session and show summary").requ
|
|
|
9735
10781
|
});
|
|
9736
10782
|
|
|
9737
10783
|
// src/cli/commands/settings.ts
|
|
9738
|
-
import { existsSync as
|
|
9739
|
-
import { Command as
|
|
9740
|
-
var settingsCommand = new
|
|
10784
|
+
import { existsSync as existsSync19 } from "fs";
|
|
10785
|
+
import { Command as Command16 } from "commander";
|
|
10786
|
+
var settingsCommand = new Command16("settings").description(
|
|
9741
10787
|
"Manage user settings"
|
|
9742
10788
|
);
|
|
9743
10789
|
var BOOLEAN_SETTING_KEYS = /* @__PURE__ */ new Set(["llm.enabled", "llm.vision.enabled"]);
|
|
@@ -9900,7 +10946,7 @@ settingsCommand.command("repos").description("Show or set Personal, Team, and Or
|
|
|
9900
10946
|
console.log("\nValidation:");
|
|
9901
10947
|
for (const [name, path] of Object.entries(paths)) {
|
|
9902
10948
|
if (path) {
|
|
9903
|
-
const exists =
|
|
10949
|
+
const exists = existsSync19(path);
|
|
9904
10950
|
console.log(
|
|
9905
10951
|
` ${name.padEnd(9)}: ${exists ? "\x1B[32m\u2713 Valid folder\x1B[0m" : "\x1B[31m\u2717 Directory does not exist\x1B[0m"}`
|
|
9906
10952
|
);
|
|
@@ -9912,41 +10958,41 @@ settingsCommand.command("repos").description("Show or set Personal, Team, and Or
|
|
|
9912
10958
|
});
|
|
9913
10959
|
|
|
9914
10960
|
// src/cli/commands/setup.ts
|
|
9915
|
-
import { copyFileSync as copyFileSync2, existsSync as
|
|
9916
|
-
import { basename as basename4, dirname as dirname6, join as
|
|
10961
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync20, mkdirSync as mkdirSync11, writeFileSync as writeFileSync10 } from "fs";
|
|
10962
|
+
import { basename as basename4, dirname as dirname6, join as join20 } from "path";
|
|
9917
10963
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
9918
|
-
import { Command as
|
|
10964
|
+
import { Command as Command17 } from "commander";
|
|
9919
10965
|
var packageRoot = [
|
|
9920
10966
|
fileURLToPath2(new URL("../..", import.meta.url)),
|
|
9921
10967
|
fileURLToPath2(new URL("../../..", import.meta.url))
|
|
9922
|
-
].find((candidate) =>
|
|
10968
|
+
].find((candidate) => existsSync20(join20(candidate, "package.json"))) ?? fileURLToPath2(new URL("../..", import.meta.url));
|
|
9923
10969
|
var SKILL_PAIRS = [
|
|
9924
10970
|
{
|
|
9925
|
-
from:
|
|
9926
|
-
to:
|
|
10971
|
+
from: join20(packageRoot, ".claude", "skills", "zam", "SKILL.md"),
|
|
10972
|
+
to: join20(".claude", "skills", "zam", "SKILL.md")
|
|
9927
10973
|
},
|
|
9928
10974
|
{
|
|
9929
|
-
from:
|
|
9930
|
-
to:
|
|
10975
|
+
from: join20(packageRoot, ".agent", "skills", "zam", "SKILL.md"),
|
|
10976
|
+
to: join20(".agent", "skills", "zam", "SKILL.md")
|
|
9931
10977
|
},
|
|
9932
10978
|
{
|
|
9933
|
-
from:
|
|
9934
|
-
to:
|
|
10979
|
+
from: join20(packageRoot, ".agents", "skills", "zam", "SKILL.md"),
|
|
10980
|
+
to: join20(".agents", "skills", "zam", "SKILL.md")
|
|
9935
10981
|
}
|
|
9936
10982
|
];
|
|
9937
10983
|
function copySkills(force, cwd = process.cwd()) {
|
|
9938
10984
|
let anyAction = false;
|
|
9939
10985
|
for (const { from, to } of SKILL_PAIRS) {
|
|
9940
|
-
const dest =
|
|
9941
|
-
if (!
|
|
10986
|
+
const dest = join20(cwd, to);
|
|
10987
|
+
if (!existsSync20(from)) {
|
|
9942
10988
|
console.warn(` warn source not found, skipping: ${from}`);
|
|
9943
10989
|
continue;
|
|
9944
10990
|
}
|
|
9945
|
-
if (
|
|
10991
|
+
if (existsSync20(dest) && !force) {
|
|
9946
10992
|
console.log(` skip ${to} (already present \xE2\u20AC\u201D use --force to update)`);
|
|
9947
10993
|
continue;
|
|
9948
10994
|
}
|
|
9949
|
-
|
|
10995
|
+
mkdirSync11(dirname6(dest), { recursive: true });
|
|
9950
10996
|
copyFileSync2(from, dest);
|
|
9951
10997
|
console.log(` copy ${to}`);
|
|
9952
10998
|
anyAction = true;
|
|
@@ -9987,13 +11033,13 @@ async function initDatabase(skipInit) {
|
|
|
9987
11033
|
}
|
|
9988
11034
|
function writeClaudeMd(skipClaudeMd, cwd = process.cwd()) {
|
|
9989
11035
|
if (skipClaudeMd) return;
|
|
9990
|
-
const dest =
|
|
9991
|
-
if (
|
|
11036
|
+
const dest = join20(cwd, "CLAUDE.md");
|
|
11037
|
+
if (existsSync20(dest)) {
|
|
9992
11038
|
console.log(` skip CLAUDE.md (already present)`);
|
|
9993
11039
|
return;
|
|
9994
11040
|
}
|
|
9995
11041
|
const name = basename4(cwd);
|
|
9996
|
-
|
|
11042
|
+
writeFileSync10(
|
|
9997
11043
|
dest,
|
|
9998
11044
|
`# ZAM Personal Kernel \xE2\u20AC\u201D ${name}
|
|
9999
11045
|
|
|
@@ -10021,13 +11067,13 @@ Use \`zam connector setup turso\` to store cloud credentials in
|
|
|
10021
11067
|
}
|
|
10022
11068
|
function writeAgentsMd(skipAgentsMd, cwd = process.cwd()) {
|
|
10023
11069
|
if (skipAgentsMd) return;
|
|
10024
|
-
const dest =
|
|
10025
|
-
if (
|
|
11070
|
+
const dest = join20(cwd, "AGENTS.md");
|
|
11071
|
+
if (existsSync20(dest)) {
|
|
10026
11072
|
console.log(` skip AGENTS.md (already present)`);
|
|
10027
11073
|
return;
|
|
10028
11074
|
}
|
|
10029
11075
|
const name = basename4(cwd);
|
|
10030
|
-
|
|
11076
|
+
writeFileSync10(
|
|
10031
11077
|
dest,
|
|
10032
11078
|
`# ZAM Personal Kernel - ${name}
|
|
10033
11079
|
|
|
@@ -10060,7 +11106,7 @@ Codex discovers repository skills under \`.agents/skills/\`. Run
|
|
|
10060
11106
|
);
|
|
10061
11107
|
console.log(` write AGENTS.md`);
|
|
10062
11108
|
}
|
|
10063
|
-
var setupCommand = new
|
|
11109
|
+
var setupCommand = new Command17("setup").description(
|
|
10064
11110
|
"Distribute ZAM skill files into this personal instance and initialize the database"
|
|
10065
11111
|
).option(
|
|
10066
11112
|
"--force",
|
|
@@ -10081,8 +11127,8 @@ var setupCommand = new Command15("setup").description(
|
|
|
10081
11127
|
);
|
|
10082
11128
|
|
|
10083
11129
|
// src/cli/commands/skill.ts
|
|
10084
|
-
import { Command as
|
|
10085
|
-
var skillCommand = new
|
|
11130
|
+
import { Command as Command18 } from "commander";
|
|
11131
|
+
var skillCommand = new Command18("skill").description(
|
|
10086
11132
|
"Manage agent skill entries (task recipes)"
|
|
10087
11133
|
);
|
|
10088
11134
|
skillCommand.command("list").description("List all agent skills").option("--json", "Output as JSON").action(async (opts) => {
|
|
@@ -10167,10 +11213,10 @@ skillCommand.command("add").description("Register a new agent skill").requiredOp
|
|
|
10167
11213
|
});
|
|
10168
11214
|
|
|
10169
11215
|
// src/cli/commands/snapshot.ts
|
|
10170
|
-
import { existsSync as
|
|
10171
|
-
import { homedir as
|
|
10172
|
-
import { dirname as dirname7, join as
|
|
10173
|
-
import { Command as
|
|
11216
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync12, readFileSync as readFileSync12, writeFileSync as writeFileSync11 } from "fs";
|
|
11217
|
+
import { homedir as homedir13 } from "os";
|
|
11218
|
+
import { dirname as dirname7, join as join21 } from "path";
|
|
11219
|
+
import { Command as Command19 } from "commander";
|
|
10174
11220
|
function defaultOutName() {
|
|
10175
11221
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d+Z$/, "").replace(/:/g, "-");
|
|
10176
11222
|
return `zam-snapshot-${stamp}.sql`;
|
|
@@ -10184,12 +11230,12 @@ function summarize(tables) {
|
|
|
10184
11230
|
}
|
|
10185
11231
|
return { total, nonEmpty };
|
|
10186
11232
|
}
|
|
10187
|
-
var exportCmd = new
|
|
11233
|
+
var exportCmd = new Command19("export").description("Write a portable SQL-text snapshot of the database").option("--out <file>", "Output file (use - for stdout)").action(async (opts) => {
|
|
10188
11234
|
let db;
|
|
10189
11235
|
try {
|
|
10190
11236
|
db = await openDatabaseWithSync({ initialize: true });
|
|
10191
11237
|
const snapshot = await exportSnapshot(db);
|
|
10192
|
-
const personalDir = await getSetting(db, "personal.workspace_dir") ||
|
|
11238
|
+
const personalDir = await getSetting(db, "personal.workspace_dir") || join21(homedir13(), "Documents", "zam");
|
|
10193
11239
|
await db.close();
|
|
10194
11240
|
db = void 0;
|
|
10195
11241
|
const manifest = verifySnapshot(snapshot);
|
|
@@ -10198,12 +11244,12 @@ var exportCmd = new Command17("export").description("Write a portable SQL-text s
|
|
|
10198
11244
|
process.stdout.write(snapshot);
|
|
10199
11245
|
return;
|
|
10200
11246
|
}
|
|
10201
|
-
const out = opts.out ??
|
|
11247
|
+
const out = opts.out ?? join21(personalDir, "snapshots", defaultOutName());
|
|
10202
11248
|
const dir = dirname7(out);
|
|
10203
|
-
if (dir && dir !== "." && !
|
|
10204
|
-
|
|
11249
|
+
if (dir && dir !== "." && !existsSync21(dir)) {
|
|
11250
|
+
mkdirSync12(dir, { recursive: true });
|
|
10205
11251
|
}
|
|
10206
|
-
|
|
11252
|
+
writeFileSync11(out, snapshot, "utf-8");
|
|
10207
11253
|
console.log(`Snapshot written: ${out}`);
|
|
10208
11254
|
console.log(
|
|
10209
11255
|
` ${total} row(s)${nonEmpty.length ? ` \u2014 ${nonEmpty.join(", ")}` : ""}`
|
|
@@ -10214,10 +11260,10 @@ var exportCmd = new Command17("export").description("Write a portable SQL-text s
|
|
|
10214
11260
|
process.exit(1);
|
|
10215
11261
|
}
|
|
10216
11262
|
});
|
|
10217
|
-
var importCmd = new
|
|
11263
|
+
var importCmd = new Command19("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) => {
|
|
10218
11264
|
let db;
|
|
10219
11265
|
try {
|
|
10220
|
-
if (!
|
|
11266
|
+
if (!existsSync21(file)) {
|
|
10221
11267
|
console.error(`Error: Snapshot file not found: ${file}`);
|
|
10222
11268
|
process.exit(1);
|
|
10223
11269
|
}
|
|
@@ -10237,9 +11283,9 @@ var importCmd = new Command17("import").description("Restore a snapshot into the
|
|
|
10237
11283
|
process.exit(1);
|
|
10238
11284
|
}
|
|
10239
11285
|
});
|
|
10240
|
-
var verifyCmd = new
|
|
11286
|
+
var verifyCmd = new Command19("verify").description("Check a snapshot's manifest and checksum without importing").argument("<file>", "Snapshot file to verify").action((file) => {
|
|
10241
11287
|
try {
|
|
10242
|
-
if (!
|
|
11288
|
+
if (!existsSync21(file)) {
|
|
10243
11289
|
console.error(`Error: Snapshot file not found: ${file}`);
|
|
10244
11290
|
process.exit(1);
|
|
10245
11291
|
}
|
|
@@ -10255,11 +11301,11 @@ var verifyCmd = new Command17("verify").description("Check a snapshot's manifest
|
|
|
10255
11301
|
process.exit(1);
|
|
10256
11302
|
}
|
|
10257
11303
|
});
|
|
10258
|
-
var snapshotCommand = new
|
|
11304
|
+
var snapshotCommand = new Command19("snapshot").description("Export, import, or verify a portable database snapshot").addCommand(exportCmd).addCommand(importCmd).addCommand(verifyCmd);
|
|
10259
11305
|
|
|
10260
11306
|
// src/cli/commands/stats.ts
|
|
10261
|
-
import { Command as
|
|
10262
|
-
var statsCommand = new
|
|
11307
|
+
import { Command as Command20 } from "commander";
|
|
11308
|
+
var statsCommand = new Command20("stats").description("Show learning dashboard for a user").option("--user <id>", "User ID (default: whoami)").option("--json", "Output as JSON").action(async (opts) => {
|
|
10263
11309
|
await withDb(async (db) => {
|
|
10264
11310
|
const userId = await resolveUser(opts, db);
|
|
10265
11311
|
const stats = await getUserStats(db, userId);
|
|
@@ -10295,8 +11341,8 @@ var statsCommand = new Command18("stats").description("Show learning dashboard f
|
|
|
10295
11341
|
});
|
|
10296
11342
|
|
|
10297
11343
|
// src/cli/commands/token.ts
|
|
10298
|
-
import { Command as
|
|
10299
|
-
var tokenCommand = new
|
|
11344
|
+
import { Command as Command21 } from "commander";
|
|
11345
|
+
var tokenCommand = new Command21("token").description(
|
|
10300
11346
|
"Manage knowledge tokens"
|
|
10301
11347
|
);
|
|
10302
11348
|
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) => {
|
|
@@ -10582,12 +11628,12 @@ tokenCommand.command("status").description("Show full status of a token for a us
|
|
|
10582
11628
|
});
|
|
10583
11629
|
|
|
10584
11630
|
// src/cli/commands/ui.ts
|
|
10585
|
-
import { spawn as
|
|
10586
|
-
import { existsSync as
|
|
10587
|
-
import { homedir as
|
|
10588
|
-
import { dirname as dirname8, join as
|
|
11631
|
+
import { spawn as spawn3, spawnSync } from "child_process";
|
|
11632
|
+
import { existsSync as existsSync22 } from "fs";
|
|
11633
|
+
import { homedir as homedir14 } from "os";
|
|
11634
|
+
import { dirname as dirname8, join as join22 } from "path";
|
|
10589
11635
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
10590
|
-
import { Command as
|
|
11636
|
+
import { Command as Command22 } from "commander";
|
|
10591
11637
|
var C3 = {
|
|
10592
11638
|
reset: "\x1B[0m",
|
|
10593
11639
|
red: "\x1B[31m",
|
|
@@ -10601,8 +11647,8 @@ function findDesktopDir() {
|
|
|
10601
11647
|
for (const start of starts) {
|
|
10602
11648
|
let dir = start;
|
|
10603
11649
|
for (let i = 0; i < 10; i++) {
|
|
10604
|
-
if (
|
|
10605
|
-
return
|
|
11650
|
+
if (existsSync22(join22(dir, "desktop", "src-tauri", "tauri.conf.json"))) {
|
|
11651
|
+
return join22(dir, "desktop");
|
|
10606
11652
|
}
|
|
10607
11653
|
const parent = dirname8(dir);
|
|
10608
11654
|
if (parent === dir) break;
|
|
@@ -10612,30 +11658,30 @@ function findDesktopDir() {
|
|
|
10612
11658
|
return null;
|
|
10613
11659
|
}
|
|
10614
11660
|
function findBuiltApp(desktopDir) {
|
|
10615
|
-
const releaseDir =
|
|
11661
|
+
const releaseDir = join22(desktopDir, "src-tauri", "target", "release");
|
|
10616
11662
|
if (process.platform === "win32") {
|
|
10617
11663
|
for (const name of ["ZAM.exe", "zam.exe", "zam-desktop.exe"]) {
|
|
10618
|
-
const p =
|
|
10619
|
-
if (
|
|
11664
|
+
const p = join22(releaseDir, name);
|
|
11665
|
+
if (existsSync22(p)) return p;
|
|
10620
11666
|
}
|
|
10621
11667
|
} else if (process.platform === "darwin") {
|
|
10622
|
-
const app =
|
|
10623
|
-
if (
|
|
11668
|
+
const app = join22(releaseDir, "bundle", "macos", "ZAM.app");
|
|
11669
|
+
if (existsSync22(app)) return app;
|
|
10624
11670
|
} else {
|
|
10625
11671
|
for (const name of ["zam", "ZAM", "zam-desktop"]) {
|
|
10626
|
-
const p =
|
|
10627
|
-
if (
|
|
11672
|
+
const p = join22(releaseDir, name);
|
|
11673
|
+
if (existsSync22(p)) return p;
|
|
10628
11674
|
}
|
|
10629
11675
|
}
|
|
10630
11676
|
return null;
|
|
10631
11677
|
}
|
|
10632
11678
|
function findInstalledApp() {
|
|
10633
11679
|
const candidates = process.platform === "win32" ? [
|
|
10634
|
-
process.env.LOCALAPPDATA &&
|
|
10635
|
-
process.env.ProgramFiles &&
|
|
10636
|
-
process.env["ProgramFiles(x86)"] &&
|
|
10637
|
-
] : process.platform === "darwin" ? ["/Applications/ZAM.app",
|
|
10638
|
-
return candidates.find((candidate) => candidate &&
|
|
11680
|
+
process.env.LOCALAPPDATA && join22(process.env.LOCALAPPDATA, "Programs", "ZAM", "ZAM.exe"),
|
|
11681
|
+
process.env.ProgramFiles && join22(process.env.ProgramFiles, "ZAM", "ZAM.exe"),
|
|
11682
|
+
process.env["ProgramFiles(x86)"] && join22(process.env["ProgramFiles(x86)"], "ZAM", "ZAM.exe")
|
|
11683
|
+
] : process.platform === "darwin" ? ["/Applications/ZAM.app", join22(homedir14(), "Applications", "ZAM.app")] : ["/opt/ZAM/zam", "/usr/bin/zam-desktop"];
|
|
11684
|
+
return candidates.find((candidate) => candidate && existsSync22(candidate)) || null;
|
|
10639
11685
|
}
|
|
10640
11686
|
function runNpm(args, opts) {
|
|
10641
11687
|
const res = spawnSync("npm", args, {
|
|
@@ -10646,7 +11692,7 @@ function runNpm(args, opts) {
|
|
|
10646
11692
|
return res.status ?? 1;
|
|
10647
11693
|
}
|
|
10648
11694
|
function ensureDesktopDeps(desktopDir) {
|
|
10649
|
-
if (
|
|
11695
|
+
if (existsSync22(join22(desktopDir, "node_modules"))) return true;
|
|
10650
11696
|
console.log(
|
|
10651
11697
|
`${C3.cyan}Installing desktop dependencies (one-time)...${C3.reset}`
|
|
10652
11698
|
);
|
|
@@ -10672,13 +11718,13 @@ function requireRust() {
|
|
|
10672
11718
|
function hasMsvcBuildTools() {
|
|
10673
11719
|
if (process.platform !== "win32") return true;
|
|
10674
11720
|
const pf86 = process.env["ProgramFiles(x86)"] || "C:\\Program Files (x86)";
|
|
10675
|
-
const vswhere =
|
|
11721
|
+
const vswhere = join22(
|
|
10676
11722
|
pf86,
|
|
10677
11723
|
"Microsoft Visual Studio",
|
|
10678
11724
|
"Installer",
|
|
10679
11725
|
"vswhere.exe"
|
|
10680
11726
|
);
|
|
10681
|
-
if (!
|
|
11727
|
+
if (!existsSync22(vswhere)) return false;
|
|
10682
11728
|
const res = spawnSync(
|
|
10683
11729
|
vswhere,
|
|
10684
11730
|
[
|
|
@@ -10710,7 +11756,7 @@ function requireMsvcOnWindows() {
|
|
|
10710
11756
|
return false;
|
|
10711
11757
|
}
|
|
10712
11758
|
function warnIfCliMissing(repoRoot) {
|
|
10713
|
-
if (!
|
|
11759
|
+
if (!existsSync22(join22(repoRoot, "dist", "cli", "index.js"))) {
|
|
10714
11760
|
console.warn(
|
|
10715
11761
|
`${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}`
|
|
10716
11762
|
);
|
|
@@ -10719,13 +11765,13 @@ function warnIfCliMissing(repoRoot) {
|
|
|
10719
11765
|
function launchApp(appPath, workingDir) {
|
|
10720
11766
|
console.log(`${C3.green}\u2713 Launching ZAM Desktop...${C3.reset}`);
|
|
10721
11767
|
if (process.platform === "darwin" && appPath.endsWith(".app")) {
|
|
10722
|
-
|
|
11768
|
+
spawn3("open", [appPath], {
|
|
10723
11769
|
cwd: workingDir,
|
|
10724
11770
|
detached: true,
|
|
10725
11771
|
stdio: "ignore"
|
|
10726
11772
|
}).unref();
|
|
10727
11773
|
} else {
|
|
10728
|
-
|
|
11774
|
+
spawn3(appPath, [], {
|
|
10729
11775
|
cwd: workingDir,
|
|
10730
11776
|
detached: true,
|
|
10731
11777
|
stdio: "ignore",
|
|
@@ -10767,13 +11813,13 @@ function createShortcuts(appPath, repoRoot) {
|
|
|
10767
11813
|
console.error(`${C3.red}\u2717 Could not create shortcuts.${C3.reset}`);
|
|
10768
11814
|
}
|
|
10769
11815
|
}
|
|
10770
|
-
var uiCommand = new
|
|
11816
|
+
var uiCommand = new Command22("ui").description("Launch the ZAM Desktop GUI (Active-Recall Studio)").option("--dev", "Run in hot-reload development mode (needs Rust)").option(
|
|
10771
11817
|
"--build",
|
|
10772
11818
|
"Build the native installer for your OS (needs Rust, one-time)"
|
|
10773
11819
|
).option("--shortcut", "Create Desktop + Start-menu shortcuts to the GUI").action((opts) => {
|
|
10774
11820
|
const installedApp = findInstalledApp();
|
|
10775
11821
|
if (!opts.dev && !opts.build && !opts.shortcut && installedApp) {
|
|
10776
|
-
launchApp(installedApp,
|
|
11822
|
+
launchApp(installedApp, homedir14());
|
|
10777
11823
|
return;
|
|
10778
11824
|
}
|
|
10779
11825
|
const desktopDir = findDesktopDir();
|
|
@@ -10794,7 +11840,7 @@ var uiCommand = new Command20("ui").description("Launch the ZAM Desktop GUI (Act
|
|
|
10794
11840
|
);
|
|
10795
11841
|
const code = runNpm(["run", "tauri", "build"], { cwd: desktopDir });
|
|
10796
11842
|
if (code === 0) {
|
|
10797
|
-
const bundle =
|
|
11843
|
+
const bundle = join22(
|
|
10798
11844
|
desktopDir,
|
|
10799
11845
|
"src-tauri",
|
|
10800
11846
|
"target",
|
|
@@ -10871,11 +11917,11 @@ ${C3.dim}After that, 'zam ui' launches it directly.${C3.reset}`
|
|
|
10871
11917
|
|
|
10872
11918
|
// src/cli/commands/update.ts
|
|
10873
11919
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
10874
|
-
import { existsSync as
|
|
10875
|
-
import { dirname as dirname9, join as
|
|
11920
|
+
import { existsSync as existsSync23, readFileSync as readFileSync13, realpathSync } from "fs";
|
|
11921
|
+
import { dirname as dirname9, join as join23 } from "path";
|
|
10876
11922
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
10877
|
-
import { confirm as
|
|
10878
|
-
import { Command as
|
|
11923
|
+
import { confirm as confirm4 } from "@inquirer/prompts";
|
|
11924
|
+
import { Command as Command23 } from "commander";
|
|
10879
11925
|
var GITHUB_REPO = "zam-os/zam";
|
|
10880
11926
|
var CHANNELS = [
|
|
10881
11927
|
"developer",
|
|
@@ -10897,7 +11943,7 @@ function currentVersion() {
|
|
|
10897
11943
|
for (const up of ["..", "../..", "../../.."]) {
|
|
10898
11944
|
try {
|
|
10899
11945
|
const pkg2 = JSON.parse(
|
|
10900
|
-
readFileSync13(
|
|
11946
|
+
readFileSync13(join23(here, up, "package.json"), "utf-8")
|
|
10901
11947
|
);
|
|
10902
11948
|
if (pkg2.version) return pkg2.version;
|
|
10903
11949
|
} catch {
|
|
@@ -10908,7 +11954,7 @@ function currentVersion() {
|
|
|
10908
11954
|
function versionAt(dir) {
|
|
10909
11955
|
try {
|
|
10910
11956
|
const pkg2 = JSON.parse(
|
|
10911
|
-
readFileSync13(
|
|
11957
|
+
readFileSync13(join23(dir, "package.json"), "utf-8")
|
|
10912
11958
|
);
|
|
10913
11959
|
return pkg2.version ?? "unknown";
|
|
10914
11960
|
} catch {
|
|
@@ -10953,7 +11999,7 @@ function render2(decision) {
|
|
|
10953
11999
|
);
|
|
10954
12000
|
}
|
|
10955
12001
|
}
|
|
10956
|
-
var checkCmd = new
|
|
12002
|
+
var checkCmd = new Command23("check").description("Check whether a newer ZAM has been released").option(
|
|
10957
12003
|
"--latest <version>",
|
|
10958
12004
|
"Compare against this version instead of fetching"
|
|
10959
12005
|
).option("--channel <channel>", "Override the detected install channel").option("--json", "Output as JSON").action(
|
|
@@ -10988,13 +12034,13 @@ function findSourceRepo() {
|
|
|
10988
12034
|
let dir = realpathSync(dirname9(fileURLToPath4(import.meta.url)));
|
|
10989
12035
|
let parent = dirname9(dir);
|
|
10990
12036
|
while (parent !== dir) {
|
|
10991
|
-
if (
|
|
12037
|
+
if (existsSync23(join23(dir, ".git"))) return dir;
|
|
10992
12038
|
dir = parent;
|
|
10993
12039
|
parent = dirname9(dir);
|
|
10994
12040
|
}
|
|
10995
|
-
return
|
|
12041
|
+
return existsSync23(join23(dir, ".git")) ? dir : null;
|
|
10996
12042
|
}
|
|
10997
|
-
function
|
|
12043
|
+
function runGit2(cwd, args, capture) {
|
|
10998
12044
|
const res = spawnSync2("git", args, {
|
|
10999
12045
|
cwd,
|
|
11000
12046
|
stdio: capture ? ["ignore", "pipe", "pipe"] : "inherit",
|
|
@@ -11026,7 +12072,7 @@ function applyDeveloperUpdate(force) {
|
|
|
11026
12072
|
);
|
|
11027
12073
|
process.exit(1);
|
|
11028
12074
|
}
|
|
11029
|
-
const status =
|
|
12075
|
+
const status = runGit2(src, ["status", "--porcelain"], true);
|
|
11030
12076
|
if (status.ok && status.out && !force) {
|
|
11031
12077
|
console.error(
|
|
11032
12078
|
`${C4.red}\u2717${C4.reset} The source checkout has uncommitted changes:
|
|
@@ -11038,7 +12084,7 @@ Commit or stash them, or re-run with ${C4.cyan}--force${C4.reset}.`
|
|
|
11038
12084
|
console.log(
|
|
11039
12085
|
`${C4.dim}\u2192 git pull --ff-only${C4.reset} ${C4.dim}(${src})${C4.reset}`
|
|
11040
12086
|
);
|
|
11041
|
-
if (!
|
|
12087
|
+
if (!runGit2(src, ["pull", "--ff-only"], false).ok) {
|
|
11042
12088
|
console.error(
|
|
11043
12089
|
`${C4.red}\u2717${C4.reset} git pull failed \u2014 the branch may have diverged or you are offline. Resolve it manually, then retry.`
|
|
11044
12090
|
);
|
|
@@ -11057,7 +12103,7 @@ Commit or stash them, or re-run with ${C4.cyan}--force${C4.reset}.`
|
|
|
11057
12103
|
console.log(`${C4.dim}\u2192 zam setup --force${C4.reset}`);
|
|
11058
12104
|
const setup = spawnSync2(
|
|
11059
12105
|
process.execPath,
|
|
11060
|
-
[
|
|
12106
|
+
[join23(src, "dist", "cli", "index.js"), "setup", "--force"],
|
|
11061
12107
|
{ cwd: process.cwd(), stdio: "inherit" }
|
|
11062
12108
|
);
|
|
11063
12109
|
if (setup.status !== 0) {
|
|
@@ -11100,7 +12146,7 @@ ${C4.dim}This install updates through the desktop app's signed updater \u2014 op
|
|
|
11100
12146
|
return;
|
|
11101
12147
|
}
|
|
11102
12148
|
if (!opts.yes) {
|
|
11103
|
-
const ok = await
|
|
12149
|
+
const ok = await confirm4({
|
|
11104
12150
|
message: "Apply this update?",
|
|
11105
12151
|
default: true
|
|
11106
12152
|
});
|
|
@@ -11120,7 +12166,7 @@ ${C4.dim}This install updates through the desktop app's signed updater \u2014 op
|
|
|
11120
12166
|
process.exit(1);
|
|
11121
12167
|
}
|
|
11122
12168
|
}
|
|
11123
|
-
var updateCommand = new
|
|
12169
|
+
var updateCommand = new Command23("update").description(
|
|
11124
12170
|
"Update ZAM to the latest release (use `update check` to only check)"
|
|
11125
12171
|
).option("-y, --yes", "Apply without confirmation").option(
|
|
11126
12172
|
"--force",
|
|
@@ -11130,8 +12176,8 @@ var updateCommand = new Command21("update").description(
|
|
|
11130
12176
|
}).addCommand(checkCmd);
|
|
11131
12177
|
|
|
11132
12178
|
// src/cli/commands/whoami.ts
|
|
11133
|
-
import { Command as
|
|
11134
|
-
var whoamiCommand = new
|
|
12179
|
+
import { Command as Command24 } from "commander";
|
|
12180
|
+
var whoamiCommand = new Command24("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) => {
|
|
11135
12181
|
await withDb(async (db) => {
|
|
11136
12182
|
if (opts.set) {
|
|
11137
12183
|
await setSetting(db, "user.id", opts.set);
|
|
@@ -11166,163 +12212,12 @@ var whoamiCommand = new Command22("whoami").description("Show or set the default
|
|
|
11166
12212
|
});
|
|
11167
12213
|
});
|
|
11168
12214
|
|
|
11169
|
-
// src/cli/commands/workspace.ts
|
|
11170
|
-
import { execSync as execSync6 } from "child_process";
|
|
11171
|
-
import { existsSync as existsSync21, writeFileSync as writeFileSync11 } from "fs";
|
|
11172
|
-
import { join as join22 } from "path";
|
|
11173
|
-
import { confirm as confirm4, input as input7 } from "@inquirer/prompts";
|
|
11174
|
-
import { Command as Command23 } from "commander";
|
|
11175
|
-
function runGit2(cwd, args) {
|
|
11176
|
-
try {
|
|
11177
|
-
return execSync6(`git ${args}`, {
|
|
11178
|
-
cwd,
|
|
11179
|
-
stdio: "pipe",
|
|
11180
|
-
encoding: "utf8"
|
|
11181
|
-
}).trim();
|
|
11182
|
-
} catch (err) {
|
|
11183
|
-
throw new Error(`Git command failed: ${err.message}`);
|
|
11184
|
-
}
|
|
11185
|
-
}
|
|
11186
|
-
var workspaceCommand = new Command23("workspace").description(
|
|
11187
|
-
"Manage your ZAM learning workspace"
|
|
11188
|
-
);
|
|
11189
|
-
workspaceCommand.command("publish").description("Publish your local workspace sandbox to GitHub").action(async () => {
|
|
11190
|
-
let db;
|
|
11191
|
-
let workspaceDir = "";
|
|
11192
|
-
try {
|
|
11193
|
-
db = await openDatabase();
|
|
11194
|
-
workspaceDir = await getSetting(db, "personal.workspace_dir") || "";
|
|
11195
|
-
await db.close();
|
|
11196
|
-
} catch {
|
|
11197
|
-
await db?.close();
|
|
11198
|
-
}
|
|
11199
|
-
if (!workspaceDir) {
|
|
11200
|
-
console.error(
|
|
11201
|
-
"\x1B[31m\u2717 No active workspace configured. Please run `zam init` first.\x1B[0m"
|
|
11202
|
-
);
|
|
11203
|
-
process.exit(1);
|
|
11204
|
-
}
|
|
11205
|
-
if (!existsSync21(workspaceDir)) {
|
|
11206
|
-
console.error(
|
|
11207
|
-
`\x1B[31m\u2717 Workspace directory does not exist: ${workspaceDir}\x1B[0m`
|
|
11208
|
-
);
|
|
11209
|
-
process.exit(1);
|
|
11210
|
-
}
|
|
11211
|
-
console.log(`
|
|
11212
|
-
Active workspace: \x1B[36m${workspaceDir}\x1B[0m`);
|
|
11213
|
-
if (!hasCommand("git")) {
|
|
11214
|
-
console.error(
|
|
11215
|
-
"\x1B[31m\u2717 Git command was not found on this system. Please install Git first.\x1B[0m"
|
|
11216
|
-
);
|
|
11217
|
-
process.exit(1);
|
|
11218
|
-
}
|
|
11219
|
-
const gitignorePath = join22(workspaceDir, ".gitignore");
|
|
11220
|
-
if (!existsSync21(gitignorePath)) {
|
|
11221
|
-
writeFileSync11(
|
|
11222
|
-
gitignorePath,
|
|
11223
|
-
"node_modules/\n.agent/\n.agents/\n.claude/\n.gemini/\n.goose/\n*.log\n",
|
|
11224
|
-
"utf8"
|
|
11225
|
-
);
|
|
11226
|
-
}
|
|
11227
|
-
const hasGitRepo = existsSync21(join22(workspaceDir, ".git"));
|
|
11228
|
-
if (!hasGitRepo) {
|
|
11229
|
-
console.log("Initializing local Git repository...");
|
|
11230
|
-
runGit2(workspaceDir, "init -b main");
|
|
11231
|
-
runGit2(workspaceDir, "add .");
|
|
11232
|
-
runGit2(
|
|
11233
|
-
workspaceDir,
|
|
11234
|
-
'commit -m "chore: initial workspace sandbox bootstrap"'
|
|
11235
|
-
);
|
|
11236
|
-
console.log("\x1B[32m\u2713 Local Git repository initialized.\x1B[0m");
|
|
11237
|
-
} else {
|
|
11238
|
-
console.log("Git repository is already initialized.");
|
|
11239
|
-
}
|
|
11240
|
-
const repoName = await input7({
|
|
11241
|
-
message: "Choose a name for your GitHub repository:",
|
|
11242
|
-
default: "zam-personal"
|
|
11243
|
-
});
|
|
11244
|
-
const isPrivate = await confirm4({
|
|
11245
|
-
message: "Should the repository be private?",
|
|
11246
|
-
default: true
|
|
11247
|
-
});
|
|
11248
|
-
const repoVisibility = isPrivate ? "--private" : "--public";
|
|
11249
|
-
if (hasCommand("gh")) {
|
|
11250
|
-
console.log("GitHub CLI detected! Automating repository creation...");
|
|
11251
|
-
const proceedGh = await confirm4({
|
|
11252
|
-
message: "Would you like ZAM to create the repository using the GitHub CLI?",
|
|
11253
|
-
default: true
|
|
11254
|
-
});
|
|
11255
|
-
if (proceedGh) {
|
|
11256
|
-
try {
|
|
11257
|
-
console.log(`Creating GitHub repository ${repoName}...`);
|
|
11258
|
-
execSync6(
|
|
11259
|
-
`gh repo create ${repoName} ${repoVisibility} --source=. --push`,
|
|
11260
|
-
{
|
|
11261
|
-
cwd: workspaceDir,
|
|
11262
|
-
stdio: "inherit"
|
|
11263
|
-
}
|
|
11264
|
-
);
|
|
11265
|
-
console.log(
|
|
11266
|
-
"\n\x1B[32m\u2713 Successfully published workspace to GitHub!\x1B[0m"
|
|
11267
|
-
);
|
|
11268
|
-
process.exit(0);
|
|
11269
|
-
} catch (err) {
|
|
11270
|
-
console.warn(
|
|
11271
|
-
`\x1B[33m\u26A0 GitHub CLI creation failed: ${err.message}\x1B[0m`
|
|
11272
|
-
);
|
|
11273
|
-
}
|
|
11274
|
-
}
|
|
11275
|
-
}
|
|
11276
|
-
console.log(
|
|
11277
|
-
"\n\x1B[1mPlease create the repository manually on GitHub:\x1B[0m"
|
|
11278
|
-
);
|
|
11279
|
-
console.log(" 1. Go to https://github.com/new");
|
|
11280
|
-
console.log(` 2. Name it exactly: \x1B[36m${repoName}\x1B[0m`);
|
|
11281
|
-
console.log(
|
|
11282
|
-
` 3. Choose \x1B[36m${isPrivate ? "Private" : "Public"}\x1B[0m`
|
|
11283
|
-
);
|
|
11284
|
-
console.log(
|
|
11285
|
-
" 4. Do NOT initialize it with README, .gitignore, or license"
|
|
11286
|
-
);
|
|
11287
|
-
console.log(" 5. Click 'Create repository'\n");
|
|
11288
|
-
const githubUrl = await input7({
|
|
11289
|
-
message: "Paste the repository Git URL (e.g. git@github.com:user/repo.git):"
|
|
11290
|
-
});
|
|
11291
|
-
if (githubUrl) {
|
|
11292
|
-
try {
|
|
11293
|
-
console.log("Linking remote repository and pushing...");
|
|
11294
|
-
let hasOrigin = false;
|
|
11295
|
-
try {
|
|
11296
|
-
runGit2(workspaceDir, "remote get-url origin");
|
|
11297
|
-
hasOrigin = true;
|
|
11298
|
-
} catch {
|
|
11299
|
-
}
|
|
11300
|
-
if (hasOrigin) {
|
|
11301
|
-
runGit2(workspaceDir, `remote set-url origin ${githubUrl}`);
|
|
11302
|
-
} else {
|
|
11303
|
-
runGit2(workspaceDir, `remote add origin ${githubUrl}`);
|
|
11304
|
-
}
|
|
11305
|
-
runGit2(workspaceDir, "push -u origin main");
|
|
11306
|
-
console.log(
|
|
11307
|
-
"\x1B[32m\u2713 Successfully linked and pushed to GitHub!\x1B[0m"
|
|
11308
|
-
);
|
|
11309
|
-
} catch (err) {
|
|
11310
|
-
console.error(
|
|
11311
|
-
`\x1B[31m\u2717 Push failed: ${err.message}\x1B[0m`
|
|
11312
|
-
);
|
|
11313
|
-
console.log(
|
|
11314
|
-
"You can push manually later using: git push -u origin main"
|
|
11315
|
-
);
|
|
11316
|
-
}
|
|
11317
|
-
}
|
|
11318
|
-
});
|
|
11319
|
-
|
|
11320
12215
|
// src/cli/index.ts
|
|
11321
12216
|
var __dirname = dirname10(fileURLToPath5(import.meta.url));
|
|
11322
12217
|
var pkg = JSON.parse(
|
|
11323
|
-
readFileSync14(
|
|
12218
|
+
readFileSync14(join24(__dirname, "..", "..", "package.json"), "utf-8")
|
|
11324
12219
|
);
|
|
11325
|
-
var program = new
|
|
12220
|
+
var program = new Command25();
|
|
11326
12221
|
program.name("zam").description(
|
|
11327
12222
|
"The Symbiotic Learning Kernel: Elevating Human Intelligence through AI Collaboration."
|
|
11328
12223
|
).version(pkg.version);
|
|
@@ -11342,6 +12237,7 @@ program.addCommand(observerCommand);
|
|
|
11342
12237
|
program.addCommand(settingsCommand);
|
|
11343
12238
|
program.addCommand(whoamiCommand);
|
|
11344
12239
|
program.addCommand(connectorCommand);
|
|
12240
|
+
program.addCommand(providerCommand);
|
|
11345
12241
|
program.addCommand(snapshotCommand);
|
|
11346
12242
|
program.addCommand(profileCommand);
|
|
11347
12243
|
program.addCommand(updateCommand);
|