@kontourai/flow-agents 0.1.1 → 0.1.2

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.
@@ -0,0 +1,254 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { flagBool, flagString, parseArgs } from "../lib/args.js";
4
+
5
+ // ---------------------------------------------------------------------------
6
+ // Output types
7
+ // ---------------------------------------------------------------------------
8
+
9
+ interface StatementResult {
10
+ excerpt: string;
11
+ badge: string;
12
+ target: { subjectType: string; subjectId: string; fieldOrBehavior: string };
13
+ span?: { start: number; end: number };
14
+ }
15
+
16
+ interface UtteranceReport {
17
+ status: "ok" | "not_configured" | "error";
18
+ agent_id: string;
19
+ utterance_excerpt: string;
20
+ statements: StatementResult[];
21
+ summary: string;
22
+ }
23
+
24
+ // ---------------------------------------------------------------------------
25
+ // Survey module interface (mirrors @kontourai/survey exported shapes)
26
+ // ---------------------------------------------------------------------------
27
+
28
+ interface SurveyExtractedItem {
29
+ target: { subjectType: string; subjectId: string; fieldOrBehavior: string };
30
+ value?: unknown;
31
+ excerpt: string;
32
+ span?: { start: number; end: number };
33
+ confidence: number;
34
+ }
35
+
36
+ interface SurveyExtractor {
37
+ name: string;
38
+ extract(utterance: string): SurveyExtractedItem[] | Promise<SurveyExtractedItem[]>;
39
+ }
40
+
41
+ interface SurveyStatementItem {
42
+ excerpt: string;
43
+ badge: string;
44
+ target: { subjectType: string; subjectId: string; fieldOrBehavior: string };
45
+ span?: { start: number; end: number };
46
+ inquiryRecord: Record<string, unknown>;
47
+ }
48
+
49
+ interface SurveyTrustReport {
50
+ source: Record<string, unknown>;
51
+ statements: SurveyStatementItem[];
52
+ }
53
+
54
+ interface SurveyMod {
55
+ surveyAgentUtterance: (
56
+ utterance: string,
57
+ extractor: SurveyExtractor,
58
+ context: { bundle: Record<string, unknown>; agentId: string; now?: Date }
59
+ ) => Promise<SurveyTrustReport>;
60
+ referenceUtteranceExtractor: SurveyExtractor;
61
+ }
62
+
63
+ // ---------------------------------------------------------------------------
64
+ // Helpers
65
+ // ---------------------------------------------------------------------------
66
+
67
+ function usage(): void {
68
+ console.error(
69
+ [
70
+ "usage: flow-agents utterance-check check [options]",
71
+ "",
72
+ "Check an agent utterance for evidence coverage using @kontourai/survey.",
73
+ "Requires @kontourai/survey to be installed in the target workspace.",
74
+ "",
75
+ "Options:",
76
+ " --utterance TEXT Utterance text to check (required unless --not-configured).",
77
+ " --bundle-path FILE Trust bundle JSON file. Omit for an empty bundle (all unsupported).",
78
+ " --agent-id ID Agent identifier for provenance (default: flow-agents-utterance-check).",
79
+ " --not-configured Skip survey call; output not_configured without error.",
80
+ " --strict Exit non-zero when any badge is disputed, rejected, or unsupported.",
81
+ " --help Show this help.",
82
+ ].join("\n")
83
+ );
84
+ }
85
+
86
+ function excerptText(text: string, maxLen = 200): string {
87
+ const trimmed = text.trim().replace(/\s+/g, " ");
88
+ return trimmed.length > maxLen ? `${trimmed.slice(0, maxLen - 3)}...` : trimmed;
89
+ }
90
+
91
+ function badgeSummary(statements: StatementResult[]): string {
92
+ if (statements.length === 0) return "no factual statements extracted";
93
+ const counts: Record<string, number> = {};
94
+ for (const s of statements) {
95
+ counts[s.badge] = (counts[s.badge] ?? 0) + 1;
96
+ }
97
+ return Object.entries(counts)
98
+ .sort((a, b) => b[1] - a[1])
99
+ .map(([badge, n]) => `${badge}:${n}`)
100
+ .join(", ");
101
+ }
102
+
103
+ function hasConcerningBadge(badge: string): boolean {
104
+ return badge === "disputed" || badge === "rejected" || badge === "unsupported";
105
+ }
106
+
107
+ async function loadSurvey(): Promise<SurveyMod | undefined> {
108
+ try {
109
+ const pkg = "@kontourai/survey";
110
+ // Dynamic import avoids a static dependency on @kontourai/survey —
111
+ // the same pattern survey/src/anthropic.ts uses for @anthropic-ai/sdk.
112
+ const mod = await (Function("m", "return import(m)")(pkg) as Promise<unknown>);
113
+ return mod as SurveyMod;
114
+ } catch {
115
+ return undefined;
116
+ }
117
+ }
118
+
119
+ // ---------------------------------------------------------------------------
120
+ // Core check logic
121
+ // ---------------------------------------------------------------------------
122
+
123
+ async function runCheck(argv: string[]): Promise<number> {
124
+ const { flags } = parseArgs(argv);
125
+
126
+ if (flagBool(flags, "help")) {
127
+ usage();
128
+ return 0;
129
+ }
130
+
131
+ const agentId = flagString(flags, "agent-id") ?? "flow-agents-utterance-check";
132
+ const notConfigured = flagBool(flags, "not-configured");
133
+ const strict = flagBool(flags, "strict");
134
+
135
+ if (notConfigured) {
136
+ const report: UtteranceReport = {
137
+ status: "not_configured",
138
+ agent_id: agentId,
139
+ utterance_excerpt: "",
140
+ statements: [],
141
+ summary: "@kontourai/survey is not configured for this workspace.",
142
+ };
143
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
144
+ return 0;
145
+ }
146
+
147
+ const utterance = flagString(flags, "utterance");
148
+ if (!utterance) {
149
+ usage();
150
+ return 3;
151
+ }
152
+
153
+ const bundlePath = flagString(flags, "bundle-path");
154
+ let bundle: Record<string, unknown> = { claims: [] };
155
+ if (bundlePath) {
156
+ const resolved = path.resolve(bundlePath);
157
+ try {
158
+ const raw = fs.readFileSync(resolved, "utf8");
159
+ bundle = JSON.parse(raw) as Record<string, unknown>;
160
+ } catch (err) {
161
+ const msg = err instanceof Error ? err.message : String(err);
162
+ process.stderr.write(`[UtteranceCheck] could not read bundle from ${resolved}: ${msg}\n`);
163
+ }
164
+ }
165
+
166
+ const survey = await loadSurvey();
167
+ if (!survey) {
168
+ const report: UtteranceReport = {
169
+ status: "not_configured",
170
+ agent_id: agentId,
171
+ utterance_excerpt: excerptText(utterance),
172
+ statements: [],
173
+ summary: "@kontourai/survey is not installed. Install it or run with --not-configured.",
174
+ };
175
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
176
+ process.stderr.write(
177
+ "[UtteranceCheck] not_configured: @kontourai/survey is not installed in this workspace.\n"
178
+ );
179
+ return 1;
180
+ }
181
+
182
+ const { surveyAgentUtterance, referenceUtteranceExtractor } = survey;
183
+
184
+ let trustReport: SurveyTrustReport;
185
+ try {
186
+ trustReport = await surveyAgentUtterance(utterance, referenceUtteranceExtractor, {
187
+ bundle,
188
+ agentId,
189
+ });
190
+ } catch (err) {
191
+ const msg = err instanceof Error ? err.message : String(err);
192
+ const report: UtteranceReport = {
193
+ status: "error",
194
+ agent_id: agentId,
195
+ utterance_excerpt: excerptText(utterance),
196
+ statements: [],
197
+ summary: `Survey call failed: ${msg}`,
198
+ };
199
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
200
+ process.stderr.write(`[UtteranceCheck] survey call failed: ${msg}\n`);
201
+ return 1;
202
+ }
203
+
204
+ const statements: StatementResult[] = trustReport.statements.map((s) => ({
205
+ excerpt: s.excerpt,
206
+ badge: s.badge,
207
+ target: s.target,
208
+ span: s.span,
209
+ }));
210
+
211
+ const summary = badgeSummary(statements);
212
+ const report: UtteranceReport = {
213
+ status: "ok",
214
+ agent_id: agentId,
215
+ utterance_excerpt: excerptText(utterance),
216
+ statements,
217
+ summary,
218
+ };
219
+
220
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
221
+
222
+ const concerning = statements.filter((s) => hasConcerningBadge(s.badge));
223
+ if (concerning.length > 0) {
224
+ process.stderr.write(
225
+ `[UtteranceCheck] ${concerning.length} statement(s) lack evidence coverage: ${summary}\n`
226
+ );
227
+ for (const s of concerning.slice(0, 4)) {
228
+ process.stderr.write(` - [${s.badge}] "${excerptText(s.excerpt, 100)}"\n`);
229
+ }
230
+ }
231
+
232
+ if (strict && concerning.length > 0) return 2;
233
+ return 0;
234
+ }
235
+
236
+ // ---------------------------------------------------------------------------
237
+ // Entry point
238
+ // ---------------------------------------------------------------------------
239
+
240
+ export async function main(argv = process.argv.slice(2)): Promise<number> {
241
+ const [subcommand, ...rest] = argv;
242
+ if (!subcommand || subcommand === "--help" || subcommand === "-h") {
243
+ usage();
244
+ return 0;
245
+ }
246
+ if (subcommand !== "check") {
247
+ console.error(`Unknown utterance-check subcommand: ${subcommand}`);
248
+ usage();
249
+ return 3;
250
+ }
251
+ return runCheck(rest);
252
+ }
253
+
254
+ if (import.meta.url === `file://${process.argv[1]}`) process.exit(await main());
package/src/cli.ts CHANGED
@@ -19,6 +19,7 @@ import { main as validateSource } from "./tools/validate-source-tree.js";
19
19
  import { main as validatePackage } from "./tools/validate-package.js";
20
20
  import { main as validateHookInfluence } from "./cli/validate-hook-influence.js";
21
21
  import { main as runtimeAdapter } from "./cli/runtime-adapter.js";
22
+ import { main as utteranceCheck } from "./cli/utterance-check.js";
22
23
 
23
24
  const availableCommands = new Map<string, (argv: string[]) => number | Promise<number>>([
24
25
  ["build-bundles", () => buildBundles()],
@@ -33,6 +34,7 @@ const availableCommands = new Map<string, (argv: string[]) => number | Promise<n
33
34
  ["publish-change", publishChange],
34
35
  ["pull-work-provider", pullWorkProvider],
35
36
  ["runtime-adapter", runtimeAdapter],
37
+ ["utterance-check", utteranceCheck],
36
38
  ["telemetry-doctor", telemetryDoctor],
37
39
  ["usage-feedback", usageFeedback],
38
40
  ["veritas-governance", veritasGovernance],
@@ -58,6 +60,7 @@ const aliases = new Map<string, string>([
58
60
  ["flow-agents-usage-feedback", "usage-feedback"],
59
61
  ["flow-agents-veritas-governance", "veritas-governance"],
60
62
  ["flow-agents-validate-hook-influence", "validate-hook-influence"],
63
+ ["flow-agents-utterance-check", "utterance-check"],
61
64
  ["flow-agents-validate-source", "validate-source"],
62
65
  ["flow-agents-workflow-artifact-cleanup-audit", "workflow-artifact-cleanup-audit"],
63
66
  ]);
@@ -68,6 +68,7 @@ const hookFilePolicies = new Map<string, { category: string; requiredNeedles: st
68
68
  ["scripts/hooks/report-only-guard.js", { category: "policy hook", requiredNeedles: ["Report-Only Guard Hook"] }],
69
69
  ["scripts/hooks/stop-format-typecheck.js", { category: "policy hook", requiredNeedles: ["Stop Hook", "typecheck"] }],
70
70
  ["scripts/hooks/stop-goal-fit.js", { category: "policy hook", requiredNeedles: ["Stop Hook", "Goal Fit"] }],
71
+ ["scripts/hooks/utterance-check.js", { category: "policy hook", requiredNeedles: ["Utterance Check Hook", "FLOW_AGENTS_UTTERANCE_CHECK_ENABLED"] }],
71
72
  ["scripts/hooks/workflow-steering.js", { category: "policy hook", requiredNeedles: ["Workflow Steering Hook"] }],
72
73
  ["scripts/hooks/desktop-notify.sh", { category: "local notification helper", requiredNeedles: ["desktop-notify.sh", "osascript"] }],
73
74
  ["scripts/hooks/lib/audit-transport.sh", { category: "shared hook library", requiredNeedles: ["audit_emit"] }],
@@ -1,39 +0,0 @@
1
- import * as fs from "node:fs";
2
- import * as path from "node:path";
3
- function esc(value) {
4
- return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
5
- }
6
- function titleFor(file, text) {
7
- return text.match(/^title:\s*(.+)$/m)?.[1].trim() ?? text.split(/\r?\n/).find((line) => line.startsWith("# "))?.slice(2).trim() ?? path.basename(file, ".md").replace(/-/g, " ");
8
- }
9
- function render(text) {
10
- return text.replace(/^---\n.*?\n---\n/s, "").split(/\r?\n/).map((line) => {
11
- if (line.startsWith("# "))
12
- return `<h1>${esc(line.slice(2).trim())}</h1>`;
13
- if (line.startsWith("## "))
14
- return `<h2>${esc(line.slice(3).trim())}</h2>`;
15
- if (line.startsWith("- "))
16
- return `<li>${esc(line.slice(2).trim())}</li>`;
17
- return line.trim() ? `<p>${esc(line.trim())}</p>` : "";
18
- }).join("\n");
19
- }
20
- export function main() {
21
- const root = path.resolve(path.dirname(process.argv[1]), "..");
22
- const docs = path.join(root, "docs");
23
- const out = path.join(root, "_site");
24
- fs.rmSync(out, { recursive: true, force: true });
25
- fs.mkdirSync(out, { recursive: true });
26
- const assets = path.join(docs, "assets");
27
- if (fs.existsSync(assets))
28
- fs.cpSync(assets, path.join(out, "assets"), { recursive: true });
29
- for (const file of fs.readdirSync(docs).filter((name) => name.endsWith(".md")).sort()) {
30
- const source = path.join(docs, file);
31
- const text = fs.readFileSync(source, "utf8");
32
- const title = titleFor(file, text);
33
- fs.writeFileSync(path.join(out, `${path.basename(file, ".md")}.html`), `<!doctype html><html><head><meta charset="utf-8"><title>${esc(title)}</title><link rel="stylesheet" href="assets/site.css"></head><body>${render(text)}<script src="assets/site.js" defer></script></body></html>`, "utf8");
34
- }
35
- console.log(`Built local docs preview: ${out}`);
36
- return 0;
37
- }
38
- if (import.meta.url === `file://${process.argv[1]}`)
39
- process.exit(main());
@@ -1,38 +0,0 @@
1
- import * as fs from "node:fs";
2
- import * as os from "node:os";
3
- import * as path from "node:path";
4
- function esc(value) {
5
- return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
6
- }
7
- export function main(argv = process.argv.slice(2)) {
8
- if (argv.length < 1) {
9
- console.log("Usage: export-bookmarks <bookmarks.md> [output.html]");
10
- return 1;
11
- }
12
- const input = argv[0];
13
- const output = argv[1] ?? path.join(os.homedir(), "Downloads", "bookmarks_organized.html");
14
- if (!fs.existsSync(input)) {
15
- console.log(`Error: ${input} not found`);
16
- return 1;
17
- }
18
- const links = Array.from(fs.readFileSync(input, "utf8").matchAll(/^- \*\*[\d-]+\*\* \[(\w+)\]: (.+?) — (https?:\/\/\S+)(?: — (.+))?$/gm), (m) => ({ tag: m[1], title: m[2], url: m[3] }));
19
- const timestamp = Math.floor(Date.now() / 1000);
20
- const byTag = new Map();
21
- for (const link of links)
22
- byTag.set(link.tag, [...(byTag.get(link.tag) ?? []), link]);
23
- const lines = ["<!DOCTYPE NETSCAPE-Bookmark-file-1>", '<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">', "<TITLE>Bookmarks</TITLE>", "<H1>Bookmarks</H1>", "<DL><p>"];
24
- for (const tag of Array.from(byTag.keys()).sort()) {
25
- lines.push(` <DT><H3 ADD_DATE="${timestamp}" LAST_MODIFIED="${timestamp}">${esc(tag)}</H3>`, " <DL><p>");
26
- for (const link of (byTag.get(tag) ?? []).sort((a, b) => a.title.localeCompare(b.title)))
27
- lines.push(` <DT><A HREF="${esc(link.url)}" ADD_DATE="${timestamp}">${esc(link.title)}</A>`);
28
- lines.push(" </DL><p>");
29
- }
30
- lines.push("</DL><p>");
31
- fs.writeFileSync(output, lines.join("\n"), "utf8");
32
- console.log("Bookmarks exported");
33
- console.log(`Source: ${input} (${links.length} links)`);
34
- console.log(`Output: ${output}`);
35
- return 0;
36
- }
37
- if (import.meta.url === `file://${process.argv[1]}`)
38
- process.exit(main());
@@ -1,50 +0,0 @@
1
- import * as fs from "node:fs";
2
- const SKIP = [/mail\.google\.com/i, /outlook\.(office|live)\.com/i, /calendar\.google\.com/i, /linkedin\.com\/in\//i, /slack\.com\/archives/i, /zoom\.us\/j\//i, /teams\.microsoft\.com/i];
3
- const TAGS = [[/github\.com|gitlab\.com|codecommit|code\.amazon/i, "repo"], [/docs\.|documentation|\/docs\/|readme|userguide|developer-guide/i, "docs"], [/wiki\.|\/wiki\/|confluence|runbook/i, "wiki"], [/dashboard|console\.|portal|admin\.|grafana|cloudwatch/i, "tool"], [/learn\.|training\.|course|certification|workshop|tutorial/i, "training"], [/template|boilerplate|starter|scaffold/i, "template"], [/spec\.|standard|architecture|rfc|design-doc/i, "reference"]];
4
- function inferTag(url) {
5
- return TAGS.find(([pattern]) => pattern.test(url))?.[1] ?? "external";
6
- }
7
- function existingUrls(file) {
8
- if (!fs.existsSync(file))
9
- return new Set();
10
- return new Set(Array.from(fs.readFileSync(file, "utf8").matchAll(/-->\s*(https?:\/\/\S+)|—\s+(https?:\/\/\S+)/g), (match) => match[1] ?? match[2]));
11
- }
12
- export function main(argv = process.argv.slice(2)) {
13
- if (argv.length < 2) {
14
- console.log("Usage: import-bookmarks <bookmarks.html> <bookmarks.md>");
15
- return 1;
16
- }
17
- const [htmlPath, mdPath] = argv;
18
- if (!fs.existsSync(htmlPath)) {
19
- console.log(`Error: ${htmlPath} not found`);
20
- return 1;
21
- }
22
- const html = fs.readFileSync(htmlPath, "utf8");
23
- const bookmarks = Array.from(html.matchAll(/<A\b[^>]*HREF=["']([^"']+)["'][^>]*>(.*?)<\/A>/gi), (match) => ({ url: match[1], title: match[2].replace(/<[^>]+>/g, "").trim() }));
24
- console.log(`Found ${bookmarks.length} bookmarks in HTML`);
25
- const seen = existingUrls(mdPath);
26
- console.log(`Found ${seen.size} existing entries in bookmarks.md`);
27
- const today = new Date().toISOString().slice(0, 10);
28
- const entries = [];
29
- let skipped = 0;
30
- let preserved = 0;
31
- for (const bookmark of bookmarks) {
32
- if (!/^https?:\/\//.test(bookmark.url) || SKIP.some((pattern) => pattern.test(bookmark.url))) {
33
- skipped += 1;
34
- continue;
35
- }
36
- if (seen.has(bookmark.url)) {
37
- preserved += 1;
38
- continue;
39
- }
40
- entries.push(`- **${today}** [${inferTag(bookmark.url)}]: ${bookmark.title} — ${bookmark.url} — (imported from browser)`);
41
- }
42
- if (entries.length)
43
- fs.appendFileSync(mdPath, `${fs.existsSync(mdPath) ? "\n" : ""}${entries.join("\n")}\n`, "utf8");
44
- console.log(`Added ${entries.length} new bookmarks`);
45
- console.log(`Preserved ${preserved} existing bookmarks`);
46
- console.log(`Skipped ${skipped} routine/non-http bookmarks`);
47
- return 0;
48
- }
49
- if (import.meta.url === `file://${process.argv[1]}`)
50
- process.exit(main());
@@ -1,93 +0,0 @@
1
- import * as fs from "node:fs";
2
- import * as os from "node:os";
3
- import * as path from "node:path";
4
- const SOUL_PATH = process.env.SOUL_PATH ?? path.join(os.homedir(), ".soul");
5
- const INSTINCTS_DIR = path.join(SOUL_PATH, "knowledge", "instincts");
6
- function parse(file) {
7
- const text = fs.readFileSync(file, "utf8");
8
- if (!text.startsWith("---"))
9
- return null;
10
- const [, fm, body] = text.split("---", 3);
11
- const meta = {};
12
- for (const line of fm.trim().split(/\r?\n/)) {
13
- const idx = line.indexOf(":");
14
- if (idx === -1)
15
- continue;
16
- const key = line.slice(0, idx).trim();
17
- const raw = line.slice(idx + 1).trim().replace(/^["']|["']$/g, "");
18
- meta[key] = key === "confidence" ? Number(raw) : key === "observation_count" ? Number.parseInt(raw, 10) : raw;
19
- }
20
- meta._body = body.trim();
21
- meta._path = file;
22
- return meta;
23
- }
24
- function files() {
25
- const roots = [path.join(INSTINCTS_DIR, "global"), path.join(INSTINCTS_DIR, "projects")];
26
- const out = [];
27
- for (const root of roots) {
28
- if (!fs.existsSync(root))
29
- continue;
30
- const stack = [root];
31
- while (stack.length) {
32
- const dir = stack.pop();
33
- for (const name of fs.readdirSync(dir)) {
34
- const file = path.join(dir, name);
35
- if (fs.statSync(file).isDirectory())
36
- stack.push(file);
37
- else if (file.endsWith(".md"))
38
- out.push(file);
39
- }
40
- }
41
- }
42
- return out;
43
- }
44
- function write(inst, file) {
45
- const body = String(inst._body ?? "");
46
- const lines = ["---"];
47
- for (const [key, value] of Object.entries(inst))
48
- if (!key.startsWith("_"))
49
- lines.push(`${key}: ${typeof value === "number" ? value : String(value)}`);
50
- lines.push("---", "", body);
51
- fs.mkdirSync(path.dirname(file), { recursive: true });
52
- fs.writeFileSync(file, `${lines.join("\n").trimEnd()}\n`, "utf8");
53
- }
54
- export function main(argv = process.argv.slice(2)) {
55
- const [command, ...rest] = argv;
56
- const instincts = files().map(parse).filter((item) => Boolean(item));
57
- if (command === "status") {
58
- const global = instincts.filter((item) => item.scope === "global").length;
59
- console.log(`Total instincts: ${instincts.length}`);
60
- console.log(` Global: ${global}, Project: ${instincts.length - global}`);
61
- return 0;
62
- }
63
- if (command === "list") {
64
- for (const inst of instincts)
65
- console.log(` [${Number(inst.confidence ?? 0).toFixed(2)}] ${inst.id ?? "?"} - ${inst.trigger ?? "?"} (${inst.scope ?? "?"})`);
66
- return 0;
67
- }
68
- if (command === "show") {
69
- const found = instincts.find((item) => item.id === rest[0]);
70
- if (!found) {
71
- console.error(`Instinct '${rest[0]}' not found.`);
72
- return 1;
73
- }
74
- console.log(fs.readFileSync(String(found._path), "utf8"));
75
- return 0;
76
- }
77
- if (command === "create") {
78
- const id = rest[rest.indexOf("--id") + 1];
79
- const trigger = rest[rest.indexOf("--trigger") + 1];
80
- const domain = rest[rest.indexOf("--domain") + 1];
81
- const scope = rest.includes("--scope") ? rest[rest.indexOf("--scope") + 1] : "project";
82
- if (!id || !trigger || !domain)
83
- return 2;
84
- const file = scope === "global" ? path.join(INSTINCTS_DIR, "global", `${id}.md`) : path.join(INSTINCTS_DIR, "projects", rest[rest.indexOf("--project-id") + 1] ?? "default", "instincts", `${id}.md`);
85
- write({ id, trigger, confidence: 0.3, domain, source: "session-observation", scope, created: new Date().toISOString().slice(0, 10), last_observed: new Date().toISOString().slice(0, 10), observation_count: 1, _body: `# ${id}\n\n## Action\nTBD` }, file);
86
- console.log(`Created ${file}`);
87
- return 0;
88
- }
89
- console.error("usage: instinct-cli <status|list|show|create>");
90
- return 2;
91
- }
92
- if (import.meta.url === `file://${process.argv[1]}`)
93
- process.exit(main());