@agentprojectcontext/apx 1.68.0 → 1.70.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/core/agent/tools/handlers/_obsidian.js +28 -0
- package/src/core/agent/tools/handlers/obsidian-list-notes.js +28 -0
- package/src/core/agent/tools/handlers/obsidian-read-note.js +31 -0
- package/src/core/agent/tools/handlers/obsidian-search-notes.js +30 -0
- package/src/core/agent/tools/handlers/obsidian-write-note.js +38 -0
- package/src/core/agent/tools/names.js +10 -0
- package/src/core/agent/tools/registry.js +8 -0
- package/src/core/integrations/catalog.js +6 -3
- package/src/core/integrations/index.js +2 -0
- package/src/core/integrations/mcp-sync.js +71 -0
- package/src/core/integrations/obsidian-memory.js +108 -0
- package/src/core/integrations/plugins/obsidian.js +299 -0
- package/src/core/mcp/runner.js +54 -20
- package/src/core/memory/broker.js +4 -1
- package/src/core/memory/index.js +29 -2
- package/src/core/memory/indexer.js +88 -1
- package/src/core/memory/store.js +23 -3
- package/src/host/daemon/api/agents.js +37 -1
- package/src/host/daemon/api/integrations.js +27 -2
- package/src/host/daemon/api/mcps.js +2 -2
- package/src/host/daemon/index.js +7 -1
- package/src/interfaces/cli/commands/obsidian.js +79 -0
- package/src/interfaces/cli/index.js +48 -1
- package/src/interfaces/web/dist/assets/index-CDz9OwCP.css +1 -0
- package/src/interfaces/web/dist/assets/index-YFsZFhM6.js +798 -0
- package/src/interfaces/web/dist/assets/index-YFsZFhM6.js.map +1 -0
- package/src/interfaces/web/dist/index.html +2 -2
- package/src/interfaces/web/src/components/integrations/BrandLogos.tsx +29 -0
- package/src/interfaces/web/src/components/integrations/ComingSoonPlugin.tsx +5 -4
- package/src/interfaces/web/src/components/integrations/FolderInput.tsx +137 -0
- package/src/interfaces/web/src/components/integrations/PluginConnect.tsx +129 -10
- package/src/interfaces/web/src/i18n/en.ts +25 -0
- package/src/interfaces/web/src/i18n/es.ts +25 -0
- package/src/interfaces/web/src/lib/api/agents.ts +2 -1
- package/src/interfaces/web/src/lib/api/integrations.ts +10 -1
- package/src/interfaces/web/src/screens/project/AgentBrainGraph.tsx +169 -47
- package/src/interfaces/web/src/screens/project/AgentDetailScreen.tsx +108 -34
- package/src/interfaces/web/src/screens/project/AgentsTab.tsx +72 -16
- package/src/interfaces/web/src/screens/project/Overview.tsx +90 -1
- package/src/interfaces/web/src/types/daemon.ts +10 -0
- package/src/interfaces/web/dist/assets/index-D4BmWoDM.css +0 -1
- package/src/interfaces/web/dist/assets/index-vwd6yQVw.js +0 -803
- package/src/interfaces/web/dist/assets/index-vwd6yQVw.js.map +0 -1
|
@@ -25,6 +25,40 @@ import {
|
|
|
25
25
|
import { agentToResponse } from "./shared.js";
|
|
26
26
|
import { normalizeVaultPatch } from "#core/apc/agents-vault.js";
|
|
27
27
|
import { PERMISSION_MODES } from "#core/constants/permissions.js";
|
|
28
|
+
import { listConversations } from "#core/stores/conversations.js";
|
|
29
|
+
import { listTasks } from "#core/stores/tasks.js";
|
|
30
|
+
import { listRoutines } from "#core/stores/routines.js";
|
|
31
|
+
import { readProjectMessages } from "#core/stores/messages.js";
|
|
32
|
+
|
|
33
|
+
// Attach a per-agent activity summary ({ threads, records, tasks, heartbeats })
|
|
34
|
+
// to a list of agent responses. Reads each store once and tallies by agent, so
|
|
35
|
+
// the whole list costs O(stores) rather than O(agents × stores). Gated behind
|
|
36
|
+
// `?stats=1` on the list endpoint since it touches the message ledger.
|
|
37
|
+
function attachAgentStats(p, agents) {
|
|
38
|
+
const store = p.storagePath || p.path;
|
|
39
|
+
const tally = (rows, key) => {
|
|
40
|
+
const m = Object.create(null);
|
|
41
|
+
for (const r of rows) {
|
|
42
|
+
const a = typeof key === "function" ? key(r) : r?.[key];
|
|
43
|
+
if (a) m[a] = (m[a] || 0) + 1;
|
|
44
|
+
}
|
|
45
|
+
return m;
|
|
46
|
+
};
|
|
47
|
+
let tasksByAgent = {}, hbByAgent = {}, recByAgent = {};
|
|
48
|
+
try { tasksByAgent = tally(listTasks(store, { state: "all" }), "agent"); } catch { /* no task store */ }
|
|
49
|
+
try { hbByAgent = tally(listRoutines(store), (r) => r?.spec?.agent); } catch { /* no routines */ }
|
|
50
|
+
try { recByAgent = tally(readProjectMessages(store, { limit: 1000 }), "agent_slug"); } catch { /* no ledger */ }
|
|
51
|
+
for (const a of agents) {
|
|
52
|
+
let threads = 0;
|
|
53
|
+
try { threads = listConversations(store, a.slug).length; } catch { /* none */ }
|
|
54
|
+
a.stats = {
|
|
55
|
+
threads,
|
|
56
|
+
records: recByAgent[a.slug] || 0,
|
|
57
|
+
tasks: tasksByAgent[a.slug] || 0,
|
|
58
|
+
heartbeats: hbByAgent[a.slug] || 0,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
28
62
|
|
|
29
63
|
// Autonomy mirrors the super-agent permission modes (total/automatico/permiso).
|
|
30
64
|
// An invalid value is dropped rather than persisted so a typo can't silently
|
|
@@ -123,7 +157,9 @@ export function register(app, { projects, project }) {
|
|
|
123
157
|
app.get("/projects/:pid/agents", (req, res) => {
|
|
124
158
|
const p = project(req, res);
|
|
125
159
|
if (!p) return;
|
|
126
|
-
|
|
160
|
+
const agents = readAgents(p.path).map(agentToResponse);
|
|
161
|
+
if (req.query.stats === "1") attachAgentStats(p, agents);
|
|
162
|
+
res.json(agents);
|
|
127
163
|
});
|
|
128
164
|
|
|
129
165
|
app.get("/projects/:pid/agents/:slug", (req, res) => {
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
defaultIntegrationsStorage,
|
|
24
24
|
listCatalog,
|
|
25
25
|
getPluginService,
|
|
26
|
+
reconcilePluginMcp,
|
|
26
27
|
} from "#core/integrations/index.js";
|
|
27
28
|
|
|
28
29
|
function normalizeScope(raw) {
|
|
@@ -43,7 +44,23 @@ function storagePathForScope(scope, p, projects) {
|
|
|
43
44
|
return p.storagePath || null;
|
|
44
45
|
}
|
|
45
46
|
|
|
46
|
-
export function register(app, { projects, project }) {
|
|
47
|
+
export function register(app, { projects, project, registries }) {
|
|
48
|
+
// Keep a plugin's optional auto-registered MCP server (svc.mcpServer hook) in
|
|
49
|
+
// lockstep with its stored state. Best-effort: a failure here must not break
|
|
50
|
+
// the configure/validate/deactivate/delete response. `storagePath` is the one
|
|
51
|
+
// the handler wrote to (so global-scope records resolve from the default
|
|
52
|
+
// store), `scope` is the integration scope ("project" | "global").
|
|
53
|
+
function reconcileMcp(svc, storagePath, scope, p) {
|
|
54
|
+
if (typeof svc?.mcpServer !== "function") return;
|
|
55
|
+
try {
|
|
56
|
+
const record = storagePath ? new IntegrationStore(storagePath).get(svc.slug) : null;
|
|
57
|
+
const desired = svc.mcpServer(record);
|
|
58
|
+
reconcilePluginMcp({ desired, integrationScope: scope, project: p, projects, registries });
|
|
59
|
+
} catch {
|
|
60
|
+
/* best-effort MCP reconcile — ignore */
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
47
64
|
// List stored integrations in the chosen scope (secrets redacted).
|
|
48
65
|
app.get("/projects/:pid/integrations", (req, res) => {
|
|
49
66
|
const p = project(req, res);
|
|
@@ -107,6 +124,7 @@ export function register(app, { projects, project }) {
|
|
|
107
124
|
try {
|
|
108
125
|
const { patch } = svc.configure(store.get(req.params.slug), req.body || {});
|
|
109
126
|
const record = store.upsert(req.params.slug, patch);
|
|
127
|
+
reconcileMcp(svc, storagePath, scope, p);
|
|
110
128
|
res.status(201).json(redactRecord(record));
|
|
111
129
|
} catch (e) {
|
|
112
130
|
res.status(400).json({ error: e.message });
|
|
@@ -129,6 +147,7 @@ export function register(app, { projects, project }) {
|
|
|
129
147
|
try {
|
|
130
148
|
const { patch, result } = await svc.validate(record);
|
|
131
149
|
store.upsert(req.params.slug, patch);
|
|
150
|
+
reconcileMcp(svc, storagePath, scope, p);
|
|
132
151
|
if (result && result.ok === false) return res.status(400).json(result);
|
|
133
152
|
res.json(result);
|
|
134
153
|
} catch (e) {
|
|
@@ -150,6 +169,7 @@ export function register(app, { projects, project }) {
|
|
|
150
169
|
if (!store.get(req.params.slug)) return res.status(404).json({ error: "integration not configured" });
|
|
151
170
|
const { patch } = svc.deactivate(store.get(req.params.slug));
|
|
152
171
|
const record = store.upsert(req.params.slug, patch);
|
|
172
|
+
reconcileMcp(svc, storagePath, scope, p);
|
|
153
173
|
res.json(svc.status(record));
|
|
154
174
|
});
|
|
155
175
|
|
|
@@ -170,7 +190,10 @@ export function register(app, { projects, project }) {
|
|
|
170
190
|
const record = new IntegrationStore(storagePath).get(req.params.slug);
|
|
171
191
|
if (!record) return res.status(404).json({ error: "integration not configured" });
|
|
172
192
|
try {
|
|
173
|
-
|
|
193
|
+
// Actions get a 2nd ctx arg (existing plugins ignore it). Obsidian's
|
|
194
|
+
// sync_memory uses it to reach every project's memory.md.
|
|
195
|
+
const actionCtx = { storagePath, scope, project: p, projects, registries };
|
|
196
|
+
res.json(await fn.call(svc.actions, record, actionCtx));
|
|
174
197
|
} catch (e) {
|
|
175
198
|
res.status(400).json({ error: e.message });
|
|
176
199
|
}
|
|
@@ -186,6 +209,8 @@ export function register(app, { projects, project }) {
|
|
|
186
209
|
if (!storagePath) return res.status(400).json({ error: "project has no storage path" });
|
|
187
210
|
const removed = new IntegrationStore(storagePath).remove(req.params.slug);
|
|
188
211
|
if (!removed) return res.status(404).end();
|
|
212
|
+
// Record is gone → svc.mcpServer(null) yields def:null → drop any auto MCP.
|
|
213
|
+
reconcileMcp(getPluginService(req.params.slug), storagePath, scope, p);
|
|
189
214
|
res.status(204).end();
|
|
190
215
|
});
|
|
191
216
|
}
|
|
@@ -96,7 +96,7 @@ export function register(app, { projects, registries, project }) {
|
|
|
96
96
|
} catch (e) {
|
|
97
97
|
return res.status(400).json({ error: e.message });
|
|
98
98
|
}
|
|
99
|
-
registries.
|
|
99
|
+
registries.evictName(name);
|
|
100
100
|
projects.rebuild(p.id);
|
|
101
101
|
const entry = registries.for(p).getByName(name);
|
|
102
102
|
res.status(201).json(entry);
|
|
@@ -150,7 +150,7 @@ export function register(app, { projects, registries, project }) {
|
|
|
150
150
|
} catch (e) {
|
|
151
151
|
return res.status(400).json({ error: e.message });
|
|
152
152
|
}
|
|
153
|
-
registries.
|
|
153
|
+
registries.evictName(req.params.name);
|
|
154
154
|
projects.rebuild(p.id);
|
|
155
155
|
res.status(204).end();
|
|
156
156
|
});
|
package/src/host/daemon/index.js
CHANGED
|
@@ -147,6 +147,12 @@ class RegistryCache {
|
|
|
147
147
|
for(projectEntry) {
|
|
148
148
|
return this.ensure(projectEntry);
|
|
149
149
|
}
|
|
150
|
+
// Evict one MCP by name from every cached registry so its live process
|
|
151
|
+
// respawns with fresh config on next use. Used after add/remove instead of
|
|
152
|
+
// shutting down (and cold-restarting) every MCP across every project.
|
|
153
|
+
evictName(name) {
|
|
154
|
+
for (const r of this.byProjectId.values()) r.evict(name);
|
|
155
|
+
}
|
|
150
156
|
shutdown() {
|
|
151
157
|
for (const r of this.byProjectId.values()) r.shutdown();
|
|
152
158
|
this.byProjectId.clear();
|
|
@@ -238,7 +244,7 @@ async function main() {
|
|
|
238
244
|
// Cross-channel memory: ensure ~/.apx/memory.md exists, open the vector
|
|
239
245
|
// store, and start the incremental RAG indexer. Best-effort — never blocks
|
|
240
246
|
// boot and never throws into the daemon.
|
|
241
|
-
initMemory({ config: cfg, log }).catch((e) => log(`memory: init failed: ${e?.message || e}`));
|
|
247
|
+
initMemory({ config: cfg, log, projects }).catch((e) => log(`memory: init failed: ${e?.message || e}`));
|
|
242
248
|
// Skill Inspector: if enabled, refresh its vector index in the background so
|
|
243
249
|
// any SKILL.md added/edited while the daemon was down is picked up without a
|
|
244
250
|
// manual `apx skills index`. Best-effort; never blocks boot.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// `apx obsidian` — configure and drive the Obsidian integration from the CLI.
|
|
2
|
+
// Thin client over the generic integrations daemon API (the same routes the web
|
|
3
|
+
// panel uses), so scoping stays consistent: `--global` (or `--scope global`)
|
|
4
|
+
// targets the default space; otherwise the current/`--project` project. A vault
|
|
5
|
+
// is a local directory of Markdown notes; connecting it lets APX agents read,
|
|
6
|
+
// search and write notes, optionally auto-registers an Obsidian MCP, and lets
|
|
7
|
+
// you mirror APX memory into the vault.
|
|
8
|
+
import { http } from "../http.js";
|
|
9
|
+
import { resolveProjectId } from "./project.js";
|
|
10
|
+
|
|
11
|
+
const SLUG = "obsidian";
|
|
12
|
+
|
|
13
|
+
function resolveScope(flags = {}) {
|
|
14
|
+
if (flags.global) return "global";
|
|
15
|
+
const s = flags.scope ? String(flags.scope).toLowerCase() : "project";
|
|
16
|
+
if (s === "default") return "global";
|
|
17
|
+
if (s !== "project" && s !== "global") {
|
|
18
|
+
throw new Error(`unknown --scope "${flags.scope}" (use project|global)`);
|
|
19
|
+
}
|
|
20
|
+
return s;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function scopeQuery(scope) {
|
|
24
|
+
return `?scope=${encodeURIComponent(scope)}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function cmdObsidianSet(args) {
|
|
28
|
+
const vaultPath = args._[0];
|
|
29
|
+
if (!vaultPath) throw new Error("apx obsidian set: missing <vault-path>");
|
|
30
|
+
const scope = resolveScope(args.flags);
|
|
31
|
+
const pid = await resolveProjectId(args?.flags?.project);
|
|
32
|
+
const body = { vault_path: vaultPath };
|
|
33
|
+
if (args.flags.mcp) body.auto_mcp = true;
|
|
34
|
+
if (args.flags.memory || args.flags["memory-sync"]) body.memory_sync = true;
|
|
35
|
+
|
|
36
|
+
const q = scopeQuery(scope);
|
|
37
|
+
await http.post(`/projects/${pid}/integrations/${SLUG}/configure${q}`, body);
|
|
38
|
+
try {
|
|
39
|
+
const r = await http.post(`/projects/${pid}/integrations/${SLUG}/validate${q}`, {});
|
|
40
|
+
const badge = r.is_vault ? "Obsidian vault" : "folder (no .obsidian)";
|
|
41
|
+
console.log(`✓ Obsidian connected (${scope}) — ${r.vault_name} · ${r.note_count} notes · ${badge}`);
|
|
42
|
+
console.log(` ${r.vault_path}`);
|
|
43
|
+
if (body.auto_mcp) console.log(" auto-MCP: on — an 'obsidian' MCP server was registered for this scope");
|
|
44
|
+
if (body.memory_sync) console.log(" memory-sync: on — run `apx obsidian sync` to mirror APX memory into the vault");
|
|
45
|
+
} catch (e) {
|
|
46
|
+
throw new Error(`Vault path saved but validation failed: ${e.message}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function cmdObsidianStatus(args) {
|
|
51
|
+
const scope = resolveScope(args.flags);
|
|
52
|
+
const pid = await resolveProjectId(args?.flags?.project);
|
|
53
|
+
const s = await http.get(`/projects/${pid}/integrations/${SLUG}${scopeQuery(scope)}`);
|
|
54
|
+
if (!s || s.status === "disconnected") {
|
|
55
|
+
console.log(`(Obsidian not configured in scope "${scope}")`);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
console.log(`Obsidian — ${s.status}${s.is_enabled ? " (enabled)" : ""}`);
|
|
59
|
+
if (s.vault_path) console.log(` vault: ${s.vault_path}`);
|
|
60
|
+
if (s.vault_name) console.log(` name: ${s.vault_name}`);
|
|
61
|
+
if (s.note_count != null) console.log(` notes: ${s.note_count}`);
|
|
62
|
+
console.log(` auto-MCP: ${s.auto_mcp ? "on" : "off"}`);
|
|
63
|
+
console.log(` memory-sync: ${s.memory_sync ? "on" : "off"}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function cmdObsidianSync(args) {
|
|
67
|
+
const scope = resolveScope(args.flags);
|
|
68
|
+
const pid = await resolveProjectId(args?.flags?.project);
|
|
69
|
+
const r = await http.post(`/projects/${pid}/integrations/${SLUG}/action/sync_memory${scopeQuery(scope)}`, {});
|
|
70
|
+
console.log(`✓ Synced ${r.count} memory file(s) → vault folder "${r.folder}" (${r.changed} changed)`);
|
|
71
|
+
for (const n of r.notes || []) console.log(` ${n.changed ? "↑" : "="} ${n.note}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function cmdObsidianRemove(args) {
|
|
75
|
+
const scope = resolveScope(args.flags);
|
|
76
|
+
const pid = await resolveProjectId(args?.flags?.project);
|
|
77
|
+
await http.delete(`/projects/${pid}/integrations/${SLUG}${scopeQuery(scope)}`);
|
|
78
|
+
console.log(`Removed Obsidian integration (scope: ${scope})`);
|
|
79
|
+
}
|
|
@@ -101,6 +101,12 @@ import {
|
|
|
101
101
|
cmdPermission,
|
|
102
102
|
} from "./commands/config.js";
|
|
103
103
|
import { cmdPluginsList, cmdPluginStatus } from "./commands/plugins.js";
|
|
104
|
+
import {
|
|
105
|
+
cmdObsidianSet,
|
|
106
|
+
cmdObsidianStatus,
|
|
107
|
+
cmdObsidianSync,
|
|
108
|
+
cmdObsidianRemove,
|
|
109
|
+
} from "./commands/obsidian.js";
|
|
104
110
|
import { cmdDesktopStart, cmdDesktopStop, cmdDesktopRestart, cmdDesktopStatus, cmdDesktopInstall, cmdDesktopUninstall, desktopRunning } from "./commands/desktop.js";
|
|
105
111
|
import { cmdVoiceSay, cmdVoiceListen, cmdVoiceProviders } from "./commands/voice.js";
|
|
106
112
|
import { cmdSkillsAdd, cmdSkillsList, cmdSkillsStatus, cmdSkillsSync, cmdSkillsIndex, cmdSkillsInspect, cmdSkillsInspector } from "./commands/skills.js";
|
|
@@ -715,6 +721,30 @@ const HELP_TOPICS = new Map(Object.entries({
|
|
|
715
721
|
"apx sessions list --engine codex --dir /Volumes/work/iacrmar",
|
|
716
722
|
],
|
|
717
723
|
}),
|
|
724
|
+
obsidian: topic({
|
|
725
|
+
title: "apx obsidian",
|
|
726
|
+
summary: "Connect an Obsidian vault so agents can read, search and write notes; mirror APX memory into it.",
|
|
727
|
+
usage: ["apx obsidian <subcommand> [--global | --project <name|id|path>]"],
|
|
728
|
+
commands: [
|
|
729
|
+
["set <vault-path>", "Connect a vault (validates the path). Add --mcp / --memory to enable extras."],
|
|
730
|
+
["status", "Show the configured vault, note count and toggles for the scope."],
|
|
731
|
+
["sync", "Mirror APX memory (global + projects) into the vault, without duplicates."],
|
|
732
|
+
["remove | rm", "Disconnect the vault (also removes any auto-registered MCP)."],
|
|
733
|
+
],
|
|
734
|
+
options: [
|
|
735
|
+
["--global", "Target the default space (shared by all projects) instead of the current project."],
|
|
736
|
+
["--scope <project|global>", "Explicit scope (alias of --global)."],
|
|
737
|
+
["--mcp", "On `set`: auto-register an 'obsidian' MCP server pointing at the vault."],
|
|
738
|
+
["--memory", "On `set`: enable memory sync (then run `apx obsidian sync`)."],
|
|
739
|
+
["--project <name|id|path>", "Pin command to a specific project."],
|
|
740
|
+
],
|
|
741
|
+
examples: [
|
|
742
|
+
"apx obsidian set ~/Obsidian/Work --project appsi",
|
|
743
|
+
"apx obsidian set ~/Obsidian/Personal --global --mcp --memory",
|
|
744
|
+
"apx obsidian sync --global",
|
|
745
|
+
"apx obsidian status",
|
|
746
|
+
],
|
|
747
|
+
}),
|
|
718
748
|
mcp: topic({
|
|
719
749
|
title: "apx mcp",
|
|
720
750
|
summary: "Manage and call MCP servers merged from APC and supported IDE configs.",
|
|
@@ -2135,6 +2165,12 @@ function buildHelp(version) {
|
|
|
2135
2165
|
hCmd("apx mcp logs <name>", 36, "spawn/init log + stderr tail"),
|
|
2136
2166
|
hCmd("apx mcp check", 36, "audit multi-source merge"),
|
|
2137
2167
|
|
|
2168
|
+
hSec("Obsidian"),
|
|
2169
|
+
hCmd("apx obsidian set <path>", 36, "connect a vault --global --project P --mcp --memory"),
|
|
2170
|
+
hCmd("apx obsidian status", 36, "show configured vault + toggles"),
|
|
2171
|
+
hCmd("apx obsidian sync", 36, "mirror APX memory into the vault (no duplicates)"),
|
|
2172
|
+
hCmd("apx obsidian remove", 36, "disconnect the vault"),
|
|
2173
|
+
|
|
2138
2174
|
hSec("Daemon Service"),
|
|
2139
2175
|
hCmd("apx daemon start", 36, ""),
|
|
2140
2176
|
hCmd("apx daemon reload", 36, "reload ~/.apx/config.json without restart"),
|
|
@@ -2313,7 +2349,7 @@ function findHelpTopic(argv) {
|
|
|
2313
2349
|
// Flags that never take a value. Without this the parser would greedily
|
|
2314
2350
|
// swallow the following positional (e.g. `apx exec --code "hi"` would set
|
|
2315
2351
|
// flags.code = "hi" and drop the prompt). Boolean flags always resolve to true.
|
|
2316
|
-
const BOOLEAN_FLAGS = new Set(["code", "verbose"]);
|
|
2352
|
+
const BOOLEAN_FLAGS = new Set(["code", "verbose", "global", "mcp", "memory"]);
|
|
2317
2353
|
|
|
2318
2354
|
function parseArgs(argv) {
|
|
2319
2355
|
const args = { _: [], flags: {} };
|
|
@@ -2501,6 +2537,17 @@ async function dispatch(cmd, rest) {
|
|
|
2501
2537
|
break;
|
|
2502
2538
|
}
|
|
2503
2539
|
|
|
2540
|
+
case "obsidian": {
|
|
2541
|
+
const sub = rest[0];
|
|
2542
|
+
const a = parseArgs(rest.slice(1));
|
|
2543
|
+
if (!sub || sub === "status" || sub === "show") await cmdObsidianStatus(a);
|
|
2544
|
+
else if (sub === "set" || sub === "connect" || sub === "add") await cmdObsidianSet(a);
|
|
2545
|
+
else if (sub === "sync") await cmdObsidianSync(a);
|
|
2546
|
+
else if (sub === "remove" || sub === "rm" || sub === "disconnect") await cmdObsidianRemove(a);
|
|
2547
|
+
else die(`unknown obsidian subcommand: ${sub}\nUsage: apx obsidian <set|status|sync|remove> [--global|--project <p>]`);
|
|
2548
|
+
break;
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2504
2551
|
case "daemon": {
|
|
2505
2552
|
const sub = rest[0];
|
|
2506
2553
|
const a = parseArgs(rest.slice(1));
|