@opvs-ai/cli 0.22.0 → 0.24.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -0
- package/claude-plugin/.claude-plugin/marketplace.json +14 -0
- package/claude-plugin/.claude-plugin/plugin.json +9 -0
- package/claude-plugin/.mcp.json +8 -0
- package/claude-plugin/README.md +24 -0
- package/claude-plugin/hooks/hooks.json +50 -0
- package/claude-plugin/skills/remember/SKILL.md +31 -0
- package/claude-plugin/skills/remember/scripts/remember.mjs +121 -0
- package/dist/.build-stamp.json +5 -5
- package/dist/commands/code-graph.d.ts +17 -0
- package/dist/commands/code-graph.js +64 -0
- package/dist/commands/code-graph.js.map +1 -0
- package/dist/commands/code.d.ts +13 -3
- package/dist/commands/code.js +191 -3
- package/dist/commands/code.js.map +1 -1
- package/dist/commands/memory-capture.d.ts +76 -0
- package/dist/commands/memory-capture.js +562 -0
- package/dist/commands/memory-capture.js.map +1 -0
- package/dist/commands/memory-hook.d.ts +21 -0
- package/dist/commands/memory-hook.js +138 -0
- package/dist/commands/memory-hook.js.map +1 -0
- package/dist/commands/memory-install.d.ts +17 -0
- package/dist/commands/memory-install.js +349 -0
- package/dist/commands/memory-install.js.map +1 -0
- package/dist/commands/memory-lessons.d.ts +8 -0
- package/dist/commands/memory-lessons.js +135 -0
- package/dist/commands/memory-lessons.js.map +1 -0
- package/dist/commands/memory.d.ts +24 -0
- package/dist/commands/memory.js +239 -0
- package/dist/commands/memory.js.map +1 -0
- package/dist/commands/myMail.d.ts +3 -0
- package/dist/commands/myMail.js +119 -0
- package/dist/commands/myMail.js.map +1 -0
- package/dist/commands/orgChart.d.ts +3 -0
- package/dist/commands/orgChart.js +88 -0
- package/dist/commands/orgChart.js.map +1 -0
- package/dist/commands/tasks.js +45 -3
- package/dist/commands/tasks.js.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/redact.d.ts +16 -0
- package/dist/lib/redact.js +92 -0
- package/dist/lib/redact.js.map +1 -0
- package/package.json +3 -1
- package/runtime-plugins/cline/opvs-memory.ts +99 -0
- package/runtime-plugins/opencode/opvs-memory.ts +94 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* opvs memory lessons — pull and contribute a skill's shared lessons (AS-590).
|
|
3
|
+
*
|
|
4
|
+
* Skills that are installed on a brand can contribute their own anonymized
|
|
5
|
+
* lessons and pull the ones other brands left behind. The CLI is the terminal
|
|
6
|
+
* face of `/api/v1/skill-lessons/{vendor}/{name}`; the route validates the
|
|
7
|
+
* `<vendor>/<name>` reference server-side, but we validate it here too so a
|
|
8
|
+
* typo never hits the wire.
|
|
9
|
+
*
|
|
10
|
+
* The provenance field is one-way: `source_engagement_hash` is `sha256("cli:" +
|
|
11
|
+
* workspace_slug + ":" + body)`. It must NEVER carry a brand id (AS-445 sealed
|
|
12
|
+
* anything credential-shaped at curation, but the CLI never gets to find out the
|
|
13
|
+
* hard way).
|
|
14
|
+
*/
|
|
15
|
+
import { createHash } from "node:crypto";
|
|
16
|
+
import { readFileSync } from "node:fs";
|
|
17
|
+
import { createClient } from "@opvs-ai/core";
|
|
18
|
+
import { buildUrl, printJson } from "../lib/format.js";
|
|
19
|
+
const SEGMENT_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
20
|
+
const REF_RE = /^@?([a-z0-9][a-z0-9-]{0,63})\/([a-z0-9][a-z0-9-]{0,63})$/;
|
|
21
|
+
export function parseSkillRef(ref) {
|
|
22
|
+
const match = REF_RE.exec(ref);
|
|
23
|
+
if (!match)
|
|
24
|
+
return null;
|
|
25
|
+
if (!SEGMENT_RE.test(match[1]) || !SEGMENT_RE.test(match[2]))
|
|
26
|
+
return null;
|
|
27
|
+
return { vendor: match[1], name: match[2] };
|
|
28
|
+
}
|
|
29
|
+
/** sha256("cli:" + workspace_slug + ":" + body) — never a brand id (AS-590). */
|
|
30
|
+
export function lessonProvenanceHash(workspace, body) {
|
|
31
|
+
return createHash("sha256").update(`cli:${workspace ?? ""}:${body}`).digest("hex");
|
|
32
|
+
}
|
|
33
|
+
function readBody(body, bodyFile) {
|
|
34
|
+
if (body && bodyFile) {
|
|
35
|
+
throw new Error("provide exactly one of --body or --body-file");
|
|
36
|
+
}
|
|
37
|
+
if (bodyFile)
|
|
38
|
+
return readFileSync(bodyFile, "utf8");
|
|
39
|
+
if (!body)
|
|
40
|
+
throw new Error("--body or --body-file is required");
|
|
41
|
+
return body;
|
|
42
|
+
}
|
|
43
|
+
function fail(message) {
|
|
44
|
+
console.error(`Error: ${message}`);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
function workspaceOf(command) {
|
|
48
|
+
let current = command;
|
|
49
|
+
while (current) {
|
|
50
|
+
const opts = current.opts();
|
|
51
|
+
if (opts.workspace)
|
|
52
|
+
return opts.workspace;
|
|
53
|
+
current = current.parent;
|
|
54
|
+
}
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
export function registerLessonCommands(memory) {
|
|
58
|
+
const lessons = memory.command("lessons").description("Pull and contribute a skill's shared lessons");
|
|
59
|
+
lessons
|
|
60
|
+
.command("get <vendor>/<name>")
|
|
61
|
+
.description("Pull a skill's shared lessons")
|
|
62
|
+
.option("--query <text>", "FTS search over body + tags")
|
|
63
|
+
.option("--limit <n>", "Max items", "25")
|
|
64
|
+
.option("--since <cursor>", "Forward cursor from a prior pull")
|
|
65
|
+
.action(async (ref, opts) => {
|
|
66
|
+
const parsed = parseSkillRef(ref);
|
|
67
|
+
if (!parsed) {
|
|
68
|
+
fail(`invalid vendor/name: ${ref} (use <vendor>/<name>, e.g. opvs-ai/opvs-vibe)`);
|
|
69
|
+
}
|
|
70
|
+
const wsSlug = workspaceOf(memory);
|
|
71
|
+
try {
|
|
72
|
+
const client = createClient(wsSlug);
|
|
73
|
+
const params = {};
|
|
74
|
+
if (opts.query)
|
|
75
|
+
params.query = opts.query;
|
|
76
|
+
if (opts.limit)
|
|
77
|
+
params.limit = opts.limit;
|
|
78
|
+
if (opts.since)
|
|
79
|
+
params.since = opts.since;
|
|
80
|
+
const url = buildUrl(`/api/v1/skill-lessons/${parsed.vendor}/${parsed.name}`, params);
|
|
81
|
+
const res = await client.get(url);
|
|
82
|
+
printJson(await res.json());
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
const detail = err?.detail;
|
|
86
|
+
fail(detail || "Failed to pull skill lessons");
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
lessons
|
|
90
|
+
.command("add <vendor>/<name>")
|
|
91
|
+
.description("Contribute an anonymized lesson to a skill (→ pending)")
|
|
92
|
+
.requiredOption("--kind <kind>", "learning | pattern")
|
|
93
|
+
.option("--body <text>", "The lesson text (NO brand data)")
|
|
94
|
+
.option("-f, --body-file <path>", "Read the lesson text from a file")
|
|
95
|
+
.option("--tags <a,b>", "Comma-separated tags")
|
|
96
|
+
.action(async (ref, opts) => {
|
|
97
|
+
const parsed = parseSkillRef(ref);
|
|
98
|
+
if (!parsed) {
|
|
99
|
+
fail(`invalid vendor/name: ${ref} (use <vendor>/<name>, e.g. opvs-ai/opvs-vibe)`);
|
|
100
|
+
}
|
|
101
|
+
if (opts.kind !== "learning" && opts.kind !== "pattern") {
|
|
102
|
+
fail(`invalid --kind: ${opts.kind} (expected learning | pattern)`);
|
|
103
|
+
}
|
|
104
|
+
const wsSlug = workspaceOf(memory);
|
|
105
|
+
let body;
|
|
106
|
+
try {
|
|
107
|
+
body = readBody(opts.body, opts.bodyFile);
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
fail(err instanceof Error ? err.message : "Failed to read lesson body");
|
|
111
|
+
}
|
|
112
|
+
const payload = {
|
|
113
|
+
kind: opts.kind,
|
|
114
|
+
body,
|
|
115
|
+
tags: opts.tags
|
|
116
|
+
? opts.tags
|
|
117
|
+
.split(",")
|
|
118
|
+
.map((tag) => tag.trim())
|
|
119
|
+
.filter(Boolean)
|
|
120
|
+
: null,
|
|
121
|
+
source_engagement_hash: lessonProvenanceHash(wsSlug, body),
|
|
122
|
+
craft_id: null,
|
|
123
|
+
};
|
|
124
|
+
try {
|
|
125
|
+
const client = createClient(wsSlug);
|
|
126
|
+
const res = await client.post(`/api/v1/skill-lessons/${parsed.vendor}/${parsed.name}`, payload);
|
|
127
|
+
printJson(await res.json());
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
const detail = err?.detail;
|
|
131
|
+
fail(detail || "Failed to contribute skill lesson");
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=memory-lessons.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-lessons.js","sourceRoot":"","sources":["../../src/commands/memory-lessons.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* opvs memory — search typed repository memory and propose human curation.
|
|
3
|
+
*/
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
interface MemoryEntry {
|
|
6
|
+
id: string;
|
|
7
|
+
card?: string | null;
|
|
8
|
+
scopes?: string[];
|
|
9
|
+
anchors?: string[];
|
|
10
|
+
pinned?: boolean;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
}
|
|
13
|
+
interface ServicePattern {
|
|
14
|
+
service: string;
|
|
15
|
+
pattern: string;
|
|
16
|
+
}
|
|
17
|
+
/** Keep the API's relative order within the pinned and unpinned groups. */
|
|
18
|
+
export declare function pinnedFirst(entries: MemoryEntry[]): MemoryEntry[];
|
|
19
|
+
/** Read the service names and path patterns from the canonical registry. */
|
|
20
|
+
export declare function parseServicePatterns(registry: string): ServicePattern[];
|
|
21
|
+
/** Apply services.yaml's global "most specific wins" rule to a repository path. */
|
|
22
|
+
export declare function serviceForPath(path: string, patterns: ServicePattern[]): string | undefined;
|
|
23
|
+
export declare function registerMemoryCommands(program: Command): void;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* opvs memory — search typed repository memory and propose human curation.
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
5
|
+
import { dirname, join, resolve } from "node:path";
|
|
6
|
+
import { createClient } from "@opvs-ai/core";
|
|
7
|
+
import { buildUrl, printJson } from "../lib/format.js";
|
|
8
|
+
import { registerCaptureHooks } from "./memory-capture.js";
|
|
9
|
+
import { registerMemoryHooks } from "./memory-hook.js";
|
|
10
|
+
import { registerMemoryInstallCommands } from "./memory-install.js";
|
|
11
|
+
import { registerLessonCommands } from "./memory-lessons.js";
|
|
12
|
+
const SEARCH_PATH = "/api/v1/memory/entries/search";
|
|
13
|
+
/** Keep the API's relative order within the pinned and unpinned groups. */
|
|
14
|
+
export function pinnedFirst(entries) {
|
|
15
|
+
return entries
|
|
16
|
+
.map((entry, index) => ({ entry, index }))
|
|
17
|
+
.sort((a, b) => Number(Boolean(b.entry.pinned)) - Number(Boolean(a.entry.pinned)) || a.index - b.index)
|
|
18
|
+
.map(({ entry }) => entry);
|
|
19
|
+
}
|
|
20
|
+
function unquote(value) {
|
|
21
|
+
const trimmed = value.trim();
|
|
22
|
+
if (trimmed.length >= 2 &&
|
|
23
|
+
((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
|
|
24
|
+
(trimmed.startsWith("'") && trimmed.endsWith("'")))) {
|
|
25
|
+
return trimmed.slice(1, -1);
|
|
26
|
+
}
|
|
27
|
+
return trimmed;
|
|
28
|
+
}
|
|
29
|
+
/** Read the service names and path patterns from the canonical registry. */
|
|
30
|
+
export function parseServicePatterns(registry) {
|
|
31
|
+
const patterns = [];
|
|
32
|
+
let service;
|
|
33
|
+
let inCodePaths = false;
|
|
34
|
+
for (const line of registry.split(/\r?\n/)) {
|
|
35
|
+
const serviceMatch = line.match(/^ {2}- name:\s*(\S+)\s*$/);
|
|
36
|
+
if (serviceMatch) {
|
|
37
|
+
service = unquote(serviceMatch[1]);
|
|
38
|
+
inCodePaths = false;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (!service)
|
|
42
|
+
continue;
|
|
43
|
+
const planningMatch = line.match(/^ {4}planning_dir:\s*(.+?)\s*$/);
|
|
44
|
+
if (planningMatch) {
|
|
45
|
+
const planningDir = unquote(planningMatch[1].replace(/\s+#.*$/, ""));
|
|
46
|
+
if (planningDir !== "null")
|
|
47
|
+
patterns.push({ service, pattern: planningDir });
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (/^ {4}code_paths:\s*$/.test(line)) {
|
|
51
|
+
inCodePaths = true;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (/^ {4}\S/.test(line)) {
|
|
55
|
+
inCodePaths = false;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (inCodePaths) {
|
|
59
|
+
const pathMatch = line.match(/^ {6}-\s+(.+?)\s*$/);
|
|
60
|
+
if (pathMatch) {
|
|
61
|
+
const pattern = unquote(pathMatch[1].replace(/\s+#.*$/, ""));
|
|
62
|
+
if (pattern)
|
|
63
|
+
patterns.push({ service, pattern });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return patterns;
|
|
68
|
+
}
|
|
69
|
+
function globRegex(pattern) {
|
|
70
|
+
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*+/g, ".*");
|
|
71
|
+
return new RegExp(`^${escaped}${pattern.endsWith("/") ? "" : "$"}`);
|
|
72
|
+
}
|
|
73
|
+
function matchesPattern(path, pattern) {
|
|
74
|
+
if (pattern.includes("*"))
|
|
75
|
+
return globRegex(pattern).test(path);
|
|
76
|
+
return pattern.endsWith("/") ? path.startsWith(pattern) : path === pattern;
|
|
77
|
+
}
|
|
78
|
+
function specificity(pattern) {
|
|
79
|
+
const wildcard = pattern.indexOf("*");
|
|
80
|
+
return [wildcard === -1 ? pattern.length : wildcard, pattern.length];
|
|
81
|
+
}
|
|
82
|
+
/** Apply services.yaml's global "most specific wins" rule to a repository path. */
|
|
83
|
+
export function serviceForPath(path, patterns) {
|
|
84
|
+
const normalized = path.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
85
|
+
let best;
|
|
86
|
+
for (const candidate of patterns) {
|
|
87
|
+
if (!matchesPattern(normalized, candidate.pattern))
|
|
88
|
+
continue;
|
|
89
|
+
if (!best) {
|
|
90
|
+
best = candidate;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const [candidatePrefix, candidateLength] = specificity(candidate.pattern);
|
|
94
|
+
const [bestPrefix, bestLength] = specificity(best.pattern);
|
|
95
|
+
if (candidatePrefix > bestPrefix || (candidatePrefix === bestPrefix && candidateLength > bestLength)) {
|
|
96
|
+
best = candidate;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return best?.service;
|
|
100
|
+
}
|
|
101
|
+
function findServicesRegistry(start) {
|
|
102
|
+
let directory = resolve(start);
|
|
103
|
+
while (true) {
|
|
104
|
+
const candidate = join(directory, "memory", "services.yaml");
|
|
105
|
+
if (existsSync(candidate))
|
|
106
|
+
return candidate;
|
|
107
|
+
const parent = dirname(directory);
|
|
108
|
+
if (parent === directory)
|
|
109
|
+
throw new Error("memory/services.yaml was not found in this repository");
|
|
110
|
+
directory = parent;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function entriesFrom(payload) {
|
|
114
|
+
if (!payload || typeof payload !== "object")
|
|
115
|
+
return [];
|
|
116
|
+
const items = payload.items;
|
|
117
|
+
return Array.isArray(items) ? items : [];
|
|
118
|
+
}
|
|
119
|
+
function uniqueEntries(entries) {
|
|
120
|
+
const seen = new Set();
|
|
121
|
+
return entries.filter((entry) => {
|
|
122
|
+
if (seen.has(entry.id))
|
|
123
|
+
return false;
|
|
124
|
+
seen.add(entry.id);
|
|
125
|
+
return true;
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
async function searchEntries(client, params) {
|
|
129
|
+
const response = await client.get(buildUrl(SEARCH_PATH, params));
|
|
130
|
+
return (await response.json());
|
|
131
|
+
}
|
|
132
|
+
function contextResult(entries) {
|
|
133
|
+
const sorted = pinnedFirst(uniqueEntries(entries));
|
|
134
|
+
return { total: sorted.length, items: sorted };
|
|
135
|
+
}
|
|
136
|
+
function fail(error, fallback) {
|
|
137
|
+
const detail = error?.detail;
|
|
138
|
+
console.error(`Error: ${detail || (error instanceof Error ? error.message : fallback)}`);
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
export function registerMemoryCommands(program) {
|
|
142
|
+
const memory = program.command("memory").description("Search typed repository memory and request curation");
|
|
143
|
+
registerMemoryHooks(memory);
|
|
144
|
+
registerCaptureHooks(memory);
|
|
145
|
+
registerMemoryInstallCommands(memory);
|
|
146
|
+
registerLessonCommands(memory);
|
|
147
|
+
memory
|
|
148
|
+
.command("search <query>")
|
|
149
|
+
.description("Search typed memory, with pinned entries first")
|
|
150
|
+
.option("--service <name>", "Restrict to the service scope from memory/services.yaml")
|
|
151
|
+
.option("--type <type>", "Restrict to an entry type")
|
|
152
|
+
.action(async (query, opts) => {
|
|
153
|
+
const wsSlug = program.opts().workspace;
|
|
154
|
+
try {
|
|
155
|
+
const client = createClient(wsSlug);
|
|
156
|
+
const params = { q: query, pinned_first: "true" };
|
|
157
|
+
if (opts.service)
|
|
158
|
+
params.scope = `service:${opts.service}`;
|
|
159
|
+
if (opts.type)
|
|
160
|
+
params.type = opts.type;
|
|
161
|
+
const result = await searchEntries(client, params);
|
|
162
|
+
const items = pinnedFirst(entriesFrom(result));
|
|
163
|
+
printJson({ ...result, total: items.length, items });
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
fail(error, "Failed to search memory");
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
memory
|
|
170
|
+
.command("context")
|
|
171
|
+
.description("Show memory relevant to repository files or a card, with pinned entries first")
|
|
172
|
+
.option("--files <paths>", "Comma-separated repository paths")
|
|
173
|
+
.option("--card <ref>", "Card ref whose memory should be shown")
|
|
174
|
+
.action(async (opts) => {
|
|
175
|
+
if (Boolean(opts.files) === Boolean(opts.card)) {
|
|
176
|
+
console.error("Error: provide exactly one of --files <paths> or --card <ref>");
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
const wsSlug = program.opts().workspace;
|
|
180
|
+
try {
|
|
181
|
+
const client = createClient(wsSlug);
|
|
182
|
+
if (opts.card) {
|
|
183
|
+
const result = await searchEntries(client, {
|
|
184
|
+
card: opts.card,
|
|
185
|
+
pinned_first: "true",
|
|
186
|
+
});
|
|
187
|
+
printJson(contextResult(entriesFrom(result).filter((entry) => entry.card === opts.card)));
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const files = (opts.files ?? "")
|
|
191
|
+
.split(",")
|
|
192
|
+
.map((path) => path.trim().replace(/\\/g, "/").replace(/^\.\//, ""))
|
|
193
|
+
.filter(Boolean);
|
|
194
|
+
const registry = readFileSync(findServicesRegistry(process.cwd()), "utf8");
|
|
195
|
+
const patterns = parseServicePatterns(registry);
|
|
196
|
+
const services = new Set(files.map((path) => serviceForPath(path, patterns)).filter(Boolean));
|
|
197
|
+
const entries = [];
|
|
198
|
+
for (const anchor of files) {
|
|
199
|
+
const result = await searchEntries(client, { anchor, pinned_first: "true" });
|
|
200
|
+
entries.push(...entriesFrom(result));
|
|
201
|
+
}
|
|
202
|
+
for (const service of services) {
|
|
203
|
+
const result = await searchEntries(client, {
|
|
204
|
+
scope: `service:${service}`,
|
|
205
|
+
pinned_first: "true",
|
|
206
|
+
});
|
|
207
|
+
entries.push(...entriesFrom(result));
|
|
208
|
+
}
|
|
209
|
+
printJson(contextResult(entries));
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
fail(error, "Failed to load memory context");
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
memory
|
|
216
|
+
.command("pin-request <id>")
|
|
217
|
+
.description("Propose a pin by commenting on the entry's card; this never pins the entry")
|
|
218
|
+
.requiredOption("--reason <text>", "Why a human should pin this entry")
|
|
219
|
+
.action(async (id, opts) => {
|
|
220
|
+
const wsSlug = program.opts().workspace;
|
|
221
|
+
try {
|
|
222
|
+
const client = createClient(wsSlug);
|
|
223
|
+
const response = await client.get(`/api/v1/memory/entries/${encodeURIComponent(id)}`);
|
|
224
|
+
const entry = (await response.json());
|
|
225
|
+
if (!entry.card) {
|
|
226
|
+
console.error("Error: this memory entry has no card on which to record a pin request");
|
|
227
|
+
process.exit(1);
|
|
228
|
+
}
|
|
229
|
+
const comment = await client.post(`/api/v1/board/tasks/${encodeURIComponent(entry.card)}/comments`, {
|
|
230
|
+
content: `Pin request for memory entry \`${entry.id}\`: ${opts.reason}`,
|
|
231
|
+
});
|
|
232
|
+
printJson(await comment.json());
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
fail(error, "Failed to request a memory pin");
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
//# sourceMappingURL=memory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.js","sourceRoot":"","sources":["../../src/commands/memory.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { createClient, getEffectiveConfig } from "@opvs-ai/core";
|
|
3
|
+
import { Option } from "commander";
|
|
4
|
+
import { buildUrl, printJson, printResponse } from "../lib/format.js";
|
|
5
|
+
function fail(err, fallback) {
|
|
6
|
+
const error = err;
|
|
7
|
+
console.error(`Error: ${error.detail || error.message || fallback}`);
|
|
8
|
+
return process.exit(1);
|
|
9
|
+
}
|
|
10
|
+
function isNotFound(err) {
|
|
11
|
+
const error = err;
|
|
12
|
+
return error.status === 404 || error.statusCode === 404 || error.detail === "Not found";
|
|
13
|
+
}
|
|
14
|
+
function rejectRefused(result) {
|
|
15
|
+
if (result.status === "rejected") {
|
|
16
|
+
console.error(`Error: ${result.reason || "Message rejected"}`);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
/** Register commands for the current person's PAT-backed OPVS mailbox. */
|
|
23
|
+
export function registerMyMailCommands(program) {
|
|
24
|
+
const myMail = program
|
|
25
|
+
.command("my-mail")
|
|
26
|
+
.description("Read and write your OPVS mailbox");
|
|
27
|
+
myMail
|
|
28
|
+
.command("inbox")
|
|
29
|
+
.description("List messages in your OPVS mailbox")
|
|
30
|
+
.option("--status <status>", "Filter by message status")
|
|
31
|
+
.option("--limit <n>", "Maximum messages to return", "50")
|
|
32
|
+
.action(async (opts) => {
|
|
33
|
+
const workspace = program.opts().workspace;
|
|
34
|
+
const config = getEffectiveConfig(workspace);
|
|
35
|
+
const params = {};
|
|
36
|
+
if (opts.status)
|
|
37
|
+
params.status = opts.status;
|
|
38
|
+
if (opts.limit)
|
|
39
|
+
params.limit = opts.limit;
|
|
40
|
+
try {
|
|
41
|
+
const response = await createClient(workspace).get(buildUrl("/api/v1/me/mail", params, config.format));
|
|
42
|
+
await printResponse(response);
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
if (isNotFound(err)) {
|
|
46
|
+
console.error("You have no OPVS mailbox yet.");
|
|
47
|
+
process.exit(1);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
fail(err, "Failed to fetch your mailbox");
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
myMail
|
|
54
|
+
.command("thread <thread_id>")
|
|
55
|
+
.description("Read a thread in your OPVS mailbox")
|
|
56
|
+
.action(async (threadId) => {
|
|
57
|
+
const workspace = program.opts().workspace;
|
|
58
|
+
const config = getEffectiveConfig(workspace);
|
|
59
|
+
try {
|
|
60
|
+
const response = await createClient(workspace).get(buildUrl(`/api/v1/me/mail/threads/${encodeURIComponent(threadId)}`, {}, config.format));
|
|
61
|
+
await printResponse(response);
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
fail(err, "Failed to read thread");
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
myMail
|
|
68
|
+
.command("send <to> <body>")
|
|
69
|
+
.description("Send a message from your OPVS mailbox")
|
|
70
|
+
.addOption(new Option("--type <type>", "Message type: ? ! ~ ^ . > <")
|
|
71
|
+
.choices(["?", "!", "~", "^", ".", ">", "<"])
|
|
72
|
+
.default("."))
|
|
73
|
+
.action(async (to, body, opts) => {
|
|
74
|
+
const workspace = program.opts().workspace;
|
|
75
|
+
// Create this once per command invocation. createClient may retry the
|
|
76
|
+
// request, and each attempt must carry the same idempotency key.
|
|
77
|
+
const idempotencyKey = randomUUID();
|
|
78
|
+
try {
|
|
79
|
+
const response = await createClient(workspace).post("/api/v1/me/mail/send", { to, body, type: opts.type }, { "Idempotency-Key": idempotencyKey });
|
|
80
|
+
const result = (await response.json());
|
|
81
|
+
if (rejectRefused(result))
|
|
82
|
+
return;
|
|
83
|
+
printJson(result);
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
fail(err, "Failed to send message");
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
myMail
|
|
90
|
+
.command("reply <message_id> <body>")
|
|
91
|
+
.description("Reply to a message in your OPVS mailbox")
|
|
92
|
+
.action(async (messageId, body) => {
|
|
93
|
+
const workspace = program.opts().workspace;
|
|
94
|
+
try {
|
|
95
|
+
const response = await createClient(workspace).post(`/api/v1/me/mail/messages/${encodeURIComponent(messageId)}/reply`, { body });
|
|
96
|
+
const result = (await response.json());
|
|
97
|
+
if (rejectRefused(result))
|
|
98
|
+
return;
|
|
99
|
+
printJson(result);
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
fail(err, "Failed to reply");
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
myMail
|
|
106
|
+
.command("status <message_id> <status>")
|
|
107
|
+
.description("Update a message status in your OPVS mailbox")
|
|
108
|
+
.action(async (messageId, status) => {
|
|
109
|
+
const workspace = program.opts().workspace;
|
|
110
|
+
try {
|
|
111
|
+
const response = await createClient(workspace).patch(`/api/v1/me/mail/messages/${encodeURIComponent(messageId)}/status`, { status });
|
|
112
|
+
await printResponse(response);
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
fail(err, "Failed to update message status");
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=myMail.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"myMail.js","sourceRoot":"","sources":["../../src/commands/myMail.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { createClient } from "@opvs-ai/core";
|
|
2
|
+
import { Option } from "commander";
|
|
3
|
+
import { buildUrl, printResponse } from "../lib/format.js";
|
|
4
|
+
function fail(err, fallback) {
|
|
5
|
+
const e = err;
|
|
6
|
+
const status = e.status ?? e.statusCode;
|
|
7
|
+
if (status === 404) {
|
|
8
|
+
console.error("Not found");
|
|
9
|
+
}
|
|
10
|
+
else if (status === 422) {
|
|
11
|
+
console.error(e.detail || e.message || fallback);
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
console.error(`Error: ${e.detail || e.message || fallback}`);
|
|
15
|
+
}
|
|
16
|
+
return process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
/** Repeatable commander argParser that turns `--manager <id>` into an array. */
|
|
19
|
+
const collectManagers = (value, acc) => {
|
|
20
|
+
acc.push(value);
|
|
21
|
+
return acc;
|
|
22
|
+
};
|
|
23
|
+
/** Register commands for the brand's AI Office org chart. */
|
|
24
|
+
export function registerOrgChartCommands(program) {
|
|
25
|
+
const orgChart = program
|
|
26
|
+
.command("org-chart")
|
|
27
|
+
.description("Read and write the brand's AI Office org chart");
|
|
28
|
+
orgChart
|
|
29
|
+
.command("show")
|
|
30
|
+
.description("Show the brand's AI Office org chart")
|
|
31
|
+
.addOption(new Option("--format <format>", "Output format (default text; json|yaml pass through)")
|
|
32
|
+
.choices(["text", "json", "yaml"])
|
|
33
|
+
.default("text"))
|
|
34
|
+
.action(async (opts) => {
|
|
35
|
+
const workspace = program.opts().workspace;
|
|
36
|
+
try {
|
|
37
|
+
// text → no hint, CLI parses the JSON and prints lines.
|
|
38
|
+
// json|yaml → pass the format to the server, print whatever comes back.
|
|
39
|
+
const requestFormat = opts.format === "text" ? undefined : opts.format;
|
|
40
|
+
const response = await createClient(workspace).get(buildUrl("/api/v1/org-chart", {}, requestFormat));
|
|
41
|
+
if (opts.format === "text") {
|
|
42
|
+
const data = (await response.json());
|
|
43
|
+
const namesByHire = new Map((data.nodes ?? []).map((n) => [n.hire_id, n.name]));
|
|
44
|
+
for (const node of data.nodes ?? []) {
|
|
45
|
+
const managerNames = node.manager_hire_ids.map((id) => namesByHire.get(id) ?? id);
|
|
46
|
+
const tail = managerNames.length ? managerNames.join(", ") : "nobody";
|
|
47
|
+
console.log(`${node.name} (${node.role}) → reports to: ${tail}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
await printResponse(response);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
fail(err, "Failed to fetch org chart");
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
orgChart
|
|
59
|
+
.command("set-manager <hire_id>")
|
|
60
|
+
.description("Replace the manager list for a hire (--none clears it)")
|
|
61
|
+
.option("--manager <hire_id>", "Manager hire id (repeatable)", collectManagers, [])
|
|
62
|
+
.option("--none", "Clear all managers")
|
|
63
|
+
.action(async (hireId, opts) => {
|
|
64
|
+
// Bare `set-manager <hire>` would silently send `manager_hire_ids: []`
|
|
65
|
+
// and clear the hire's managers — refuse it before any request.
|
|
66
|
+
if (!opts.none && opts.manager.length === 0) {
|
|
67
|
+
console.error("Error: give --manager <hire_id> (repeatable) or --none");
|
|
68
|
+
process.exit(1);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
// Both intents are about the same wire field — picking one silently would
|
|
72
|
+
// send a bundle the operator did not ask for.
|
|
73
|
+
if (opts.none && opts.manager.length > 0) {
|
|
74
|
+
console.error("Error: --none cannot be combined with --manager");
|
|
75
|
+
process.exit(1);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const workspace = program.opts().workspace;
|
|
79
|
+
try {
|
|
80
|
+
const response = await createClient(workspace).put(`/api/v1/org-chart/agents/${encodeURIComponent(hireId)}/managers`, { manager_hire_ids: opts.none ? [] : opts.manager });
|
|
81
|
+
await printResponse(response);
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
fail(err, "Failed to set managers");
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=orgChart.js.map
|