@interchained/portal-agent 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.
@@ -1,41 +1,45 @@
1
1
  /**
2
2
  * Page / component generation.
3
3
  *
4
- * Reads the app contract, calls the Runner to generate a React component,
5
- * passes it through the Sentinel for safety review, then creates a Patch
6
- * for human approval (if required by policy).
4
+ * Flow when Sentinel is present:
5
+ * Runner generates Sentinel.reviewAndApply() applied or blocked
6
+ *
7
+ * Flow without Sentinel (--no-sentinel or API key absent):
8
+ * Runner generates → patch saved as "pending" → human approves via CLI
7
9
  */
8
10
  import type { AppContract, PageContract } from "@interchained/portal-contract";
9
11
  import { Runner } from "./runner.js";
10
- import { Sentinel } from "./sentinel.js";
11
- import { type Patch } from "./patch.js";
12
+ import { Sentinel, type SentinelReview } from "./sentinel.js";
13
+ import { PatchStore, type Patch } from "./patch.js";
12
14
  export interface GenerateOptions {
13
15
  runner: Runner;
14
16
  sentinel?: Sentinel;
15
17
  contract: AppContract;
16
18
  projectRoot: string;
17
- /** Override the policy — useful for interactive approve-later flow */
19
+ route?: string;
20
+ /** Only consulted when NO sentinel is present */
18
21
  requiresApproval?: boolean;
19
22
  }
20
23
  export interface GenerateResult {
21
24
  patch: Patch;
22
- sentinelReview?: {
23
- approved: boolean;
24
- summary: string;
25
- violations: string[];
26
- };
25
+ /** Set whenever a Sentinel ran (approved or not) */
26
+ sentinelReview?: SentinelReview;
27
+ /**
28
+ * True when the Sentinel approved and applied the patch to disk.
29
+ * False when sentinel rejected, or no sentinel ran.
30
+ * The CLI uses this to skip / show the human approval prompt.
31
+ */
32
+ sentinelApplied: boolean;
27
33
  }
28
34
  /**
29
- * Generate a new page component from a PageContract or an ad-hoc prompt.
35
+ * Generate a new page component from a PageContract.
30
36
  */
31
37
  export declare function generatePage(page: PageContract & {
32
38
  description?: string;
33
- }, opts: GenerateOptions): Promise<GenerateResult>;
39
+ }, opts: GenerateOptions, store: PatchStore): Promise<GenerateResult>;
34
40
  /**
35
- * Generate a page from a freeform description (no pre-existing PageContract).
41
+ * Generate a page from a freeform description.
36
42
  * `portal generate page "Father's Day promo"` style.
37
43
  */
38
- export declare function generateFromPrompt(prompt: string, opts: GenerateOptions & {
39
- route?: string;
40
- }): Promise<GenerateResult>;
44
+ export declare function generateFromPrompt(prompt: string, opts: GenerateOptions, store?: PatchStore): Promise<GenerateResult>;
41
45
  //# sourceMappingURL=generate.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../src/generate.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAC/E,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAe,KAAK,KAAK,EAAE,MAAM,YAAY,CAAC;AAErD,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,QAAQ,EAAE,WAAW,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,KAAK,CAAC;IACb,cAAc,CAAC,EAAE;QACf,QAAQ,EAAE,OAAO,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,MAAM,EAAE,CAAC;KACtB,CAAC;CACH;AAED;;GAEG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,YAAY,GAAG;IAAE,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,EAC7C,IAAI,EAAE,eAAe,GACpB,OAAO,CAAC,cAAc,CAAC,CAwDzB;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,eAAe,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GACzC,OAAO,CAAC,cAAc,CAAC,CASzB"}
1
+ {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../src/generate.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAC/E,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAC9D,OAAO,EAAe,UAAU,EAAE,KAAK,KAAK,EAAE,MAAM,YAAY,CAAC;AAEjE,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,QAAQ,EAAE,WAAW,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iDAAiD;IACjD,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,KAAK,CAAC;IACb,oDAAoD;IACpD,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC;;;;OAIG;IACH,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED;;GAEG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,YAAY,GAAG;IAAE,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,EAC7C,IAAI,EAAE,eAAe,EACrB,KAAK,EAAE,UAAU,GAChB,OAAO,CAAC,cAAc,CAAC,CAsDzB;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,eAAe,EACrB,KAAK,CAAC,EAAE,UAAU,GACjB,OAAO,CAAC,cAAc,CAAC,CAUzB"}
package/dist/generate.js CHANGED
@@ -1,19 +1,20 @@
1
1
  /**
2
2
  * Page / component generation.
3
3
  *
4
- * Reads the app contract, calls the Runner to generate a React component,
5
- * passes it through the Sentinel for safety review, then creates a Patch
6
- * for human approval (if required by policy).
4
+ * Flow when Sentinel is present:
5
+ * Runner generates Sentinel.reviewAndApply() applied or blocked
6
+ *
7
+ * Flow without Sentinel (--no-sentinel or API key absent):
8
+ * Runner generates → patch saved as "pending" → human approves via CLI
7
9
  */
8
10
  import { readFile } from "node:fs/promises";
9
11
  import { join } from "node:path";
10
- import { createPatch } from "./patch.js";
12
+ import { createPatch, PatchStore } from "./patch.js";
11
13
  /**
12
- * Generate a new page component from a PageContract or an ad-hoc prompt.
14
+ * Generate a new page component from a PageContract.
13
15
  */
14
- export async function generatePage(page, opts) {
16
+ export async function generatePage(page, opts, store) {
15
17
  const { runner, sentinel, contract } = opts;
16
- // Check if the file already exists — use as context
17
18
  const fileName = routeToFileName(page.route);
18
19
  const filePath = join(opts.projectRoot, "routes", fileName);
19
20
  let original = "";
@@ -21,7 +22,7 @@ export async function generatePage(page, opts) {
21
22
  original = await readFile(filePath, "utf-8");
22
23
  }
23
24
  catch {
24
- // New file — original is empty
25
+ // New file
25
26
  }
26
27
  const proposed = await runner.generateComponent({
27
28
  route: page.route,
@@ -31,23 +32,7 @@ export async function generatePage(page, opts) {
31
32
  brandVoice: contract.brand?.voice,
32
33
  colors: contract.brand?.colors,
33
34
  });
34
- const requiresApproval = opts.requiresApproval ??
35
- contract.policies?.publishing === "human_review";
36
- let sentinelResult;
37
- if (sentinel) {
38
- const review = await sentinel.review({
39
- contract,
40
- filePath,
41
- original,
42
- proposed,
43
- agentTask: `Generate page for route "${page.route}": ${page.purpose}`,
44
- });
45
- sentinelResult = {
46
- approved: review.approved,
47
- summary: review.summary,
48
- violations: review.violations,
49
- };
50
- }
35
+ const requiresApproval = opts.requiresApproval ?? contract.policies?.publishing === "human_review";
51
36
  const patch = createPatch({
52
37
  agent: "portal-generate",
53
38
  file: join("routes", fileName),
@@ -55,17 +40,32 @@ export async function generatePage(page, opts) {
55
40
  proposed,
56
41
  reason: `Generate page: ${page.purpose}`,
57
42
  requiresApproval,
58
- sentinelApproved: sentinelResult?.approved,
59
- sentinelSummary: sentinelResult?.summary,
60
- sentinelViolations: sentinelResult?.violations,
61
43
  });
62
- return { patch, sentinelReview: sentinelResult };
44
+ await store.save(patch);
45
+ // ── Sentinel is the gate + applier ─────────────────────────────────────────
46
+ if (sentinel) {
47
+ const result = await sentinel.reviewAndApply({
48
+ patch,
49
+ projectRoot: opts.projectRoot,
50
+ store,
51
+ contract,
52
+ agentTask: `Generate page for route "${page.route}": ${page.purpose}`,
53
+ });
54
+ return {
55
+ patch,
56
+ sentinelReview: result.review,
57
+ sentinelApplied: result.applied,
58
+ };
59
+ }
60
+ // ── No sentinel — patch stays "pending", human decides ────────────────────
61
+ return { patch, sentinelApplied: false };
63
62
  }
64
63
  /**
65
- * Generate a page from a freeform description (no pre-existing PageContract).
64
+ * Generate a page from a freeform description.
66
65
  * `portal generate page "Father's Day promo"` style.
67
66
  */
68
- export async function generateFromPrompt(prompt, opts) {
67
+ export async function generateFromPrompt(prompt, opts, store) {
68
+ const resolvedStore = store ?? new PatchStore(opts.projectRoot);
69
69
  const route = opts.route ?? promptToRoute(prompt);
70
70
  const page = {
71
71
  route,
@@ -73,7 +73,7 @@ export async function generateFromPrompt(prompt, opts) {
73
73
  audience: undefined,
74
74
  primaryAction: undefined,
75
75
  };
76
- return generatePage(page, opts);
76
+ return generatePage(page, opts, resolvedStore);
77
77
  }
78
78
  // ── Helpers ───────────────────────────────────────────────────────────────────
79
79
  function routeToFileName(route) {
@@ -1 +1 @@
1
- {"version":3,"file":"generate.js","sourceRoot":"","sources":["../src/generate.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"generate.js","sourceRoot":"","sources":["../src/generate.ts"],"names":[],"mappings":"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"}
@@ -1,14 +1,17 @@
1
1
  /**
2
- * Sentinel — reviews Runner output before it touches the codebase.
2
+ * Sentinel — reviews Runner output and, if approved, applies the patch itself.
3
3
  *
4
- * The sentinel is a stronger/slower model that:
5
- * 1. Checks that the patch actually fixes what it claims to fix
6
- * 2. Verifies no forbidden claims or brand violations were introduced
7
- * 3. Confirms the change is faithful to the app contract
8
- * 4. Produces a human-readable review summary for the approval UI
4
+ * The Sentinel is both the gate and the final patch auditor:
5
+ * - APPROVED Sentinel writes the file, marks the patch "applied"
6
+ * - REJECTED → Sentinel blocks, surfaces violations, human must override
7
+ *
8
+ * Uses a stronger/slower model than the Runner so it can catch what the
9
+ * runner missed: brand violations, hallucinated data, forbidden claims,
10
+ * regressions, and contract drift.
9
11
  */
10
12
  import { type AiAssistConfig } from "./aiassist.js";
11
13
  import type { AppContract } from "@interchained/portal-contract";
14
+ import { type Patch, type PatchStore } from "./patch.js";
12
15
  export interface SentinelReview {
13
16
  approved: boolean;
14
17
  summary: string;
@@ -20,10 +23,14 @@ export interface SentinelReview {
20
23
  note: string;
21
24
  }>;
22
25
  }
26
+ export interface SentinelResult {
27
+ review: SentinelReview;
28
+ /** True when the Sentinel both approved AND applied the patch to disk */
29
+ applied: boolean;
30
+ }
23
31
  export declare class Sentinel {
24
32
  private client;
25
33
  constructor(config?: Partial<AiAssistConfig>);
26
- /** Review a generated patch against the app contract */
27
34
  review(opts: {
28
35
  contract: AppContract;
29
36
  filePath: string;
@@ -31,5 +38,23 @@ export declare class Sentinel {
31
38
  proposed: string;
32
39
  agentTask: string;
33
40
  }): Promise<SentinelReview>;
41
+ /**
42
+ * Review the patch and, if approved, apply it to disk.
43
+ *
44
+ * This is the primary entry point. The Sentinel is both judge and executor:
45
+ * - Approved → patch is written to disk, status set to "applied"
46
+ * - Rejected → nothing is written, violations are surfaced to the caller
47
+ *
48
+ * Pass `overrideApply: true` to apply even if the Sentinel rejects
49
+ * (e.g. explicit human "apply anyway" action in the CLI).
50
+ */
51
+ reviewAndApply(opts: {
52
+ patch: Patch;
53
+ projectRoot: string;
54
+ store: PatchStore;
55
+ contract: AppContract;
56
+ agentTask: string;
57
+ overrideApply?: boolean;
58
+ }): Promise<SentinelResult>;
34
59
  }
35
60
  //# sourceMappingURL=sentinel.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sentinel.d.ts","sourceRoot":"","sources":["../src/sentinel.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAkB,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AACpE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAEjE,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,qCAAqC;IACrC,WAAW,EAAE,KAAK,CAAC;QACjB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,IAAI,GAAG,SAAS,GAAG,WAAW,CAAC;QACrC,IAAI,EAAE,MAAM,CAAC;KACd,CAAC,CAAC;CACJ;AAED,qBAAa,QAAQ;IACnB,OAAO,CAAC,MAAM,CAAiB;gBAEnB,MAAM,GAAE,OAAO,CAAC,cAAc,CAAM;IAShD,wDAAwD;IAClD,MAAM,CAAC,IAAI,EAAE;QACjB,QAAQ,EAAE,WAAW,CAAC;QACtB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;KACnB,GAAG,OAAO,CAAC,cAAc,CAAC;CAsD5B"}
1
+ {"version":3,"file":"sentinel.d.ts","sourceRoot":"","sources":["../src/sentinel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAkB,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AACpE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAc,KAAK,KAAK,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAErE,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,qCAAqC;IACrC,WAAW,EAAE,KAAK,CAAC;QACjB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,IAAI,GAAG,SAAS,GAAG,WAAW,CAAC;QACrC,IAAI,EAAE,MAAM,CAAC;KACd,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,cAAc,CAAC;IACvB,yEAAyE;IACzE,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,qBAAa,QAAQ;IACnB,OAAO,CAAC,MAAM,CAAiB;gBAEnB,MAAM,GAAE,OAAO,CAAC,cAAc,CAAM;IAe1C,MAAM,CAAC,IAAI,EAAE;QACjB,QAAQ,EAAE,WAAW,CAAC;QACtB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;KACnB,GAAG,OAAO,CAAC,cAAc,CAAC;IAgE3B;;;;;;;;;OASG;IACG,cAAc,CAAC,IAAI,EAAE;QACzB,KAAK,EAAE,KAAK,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,KAAK,EAAE,UAAU,CAAC;QAClB,QAAQ,EAAE,WAAW,CAAC;QACtB,SAAS,EAAE,MAAM,CAAC;QAClB,aAAa,CAAC,EAAE,OAAO,CAAC;KACzB,GAAG,OAAO,CAAC,cAAc,CAAC;CA8B5B"}
package/dist/sentinel.js CHANGED
@@ -1,24 +1,30 @@
1
1
  /**
2
- * Sentinel — reviews Runner output before it touches the codebase.
2
+ * Sentinel — reviews Runner output and, if approved, applies the patch itself.
3
3
  *
4
- * The sentinel is a stronger/slower model that:
5
- * 1. Checks that the patch actually fixes what it claims to fix
6
- * 2. Verifies no forbidden claims or brand violations were introduced
7
- * 3. Confirms the change is faithful to the app contract
8
- * 4. Produces a human-readable review summary for the approval UI
4
+ * The Sentinel is both the gate and the final patch auditor:
5
+ * - APPROVED Sentinel writes the file, marks the patch "applied"
6
+ * - REJECTED → Sentinel blocks, surfaces violations, human must override
7
+ *
8
+ * Uses a stronger/slower model than the Runner so it can catch what the
9
+ * runner missed: brand violations, hallucinated data, forbidden claims,
10
+ * regressions, and contract drift.
9
11
  */
10
12
  import { AiAssistClient } from "./aiassist.js";
13
+ import { applyPatch } from "./patch.js";
11
14
  export class Sentinel {
12
15
  client;
13
16
  constructor(config = {}) {
14
17
  this.client = new AiAssistClient({
15
- apiKey: config.apiKey ?? process.env["AIASSIST_API_KEY"] ?? process.env["VITE_AIAS_API_KEY"] ?? "",
18
+ apiKey: config.apiKey ??
19
+ process.env["AIASSIST_API_KEY"] ??
20
+ process.env["VITE_AIAS_API_KEY"] ??
21
+ "",
16
22
  baseUrl: config.baseUrl,
17
23
  model: config.model ?? "gpt-4o",
18
24
  timeoutMs: config.timeoutMs ?? 90_000,
19
25
  });
20
26
  }
21
- /** Review a generated patch against the app contract */
27
+ // ── Core review ─────────────────────────────────────────────────────────────
22
28
  async review(opts) {
23
29
  const contractStr = JSON.stringify(opts.contract, null, 2);
24
30
  const prompt = `You are a code sentinel reviewing an AI-generated patch.
@@ -49,17 +55,24 @@ Check for:
49
55
  4. Hallucinated data (phone numbers, prices, names not in data sources)
50
56
  5. Whether the patch faithfully serves the stated goals
51
57
  6. Whether it actually fixes the agent task
58
+ 7. Any obvious regressions or broken imports
52
59
 
53
60
  Respond as JSON:
54
61
  {
55
62
  "approved": true/false,
56
63
  "summary": "one sentence summary of what the patch does",
57
- "violations": ["list of issues if any"],
64
+ "violations": ["list of issues if any — empty array if clean"],
58
65
  "annotations": [{"line": null, "type": "ok|warning|violation", "note": "..."}]
59
66
  }`;
60
- const raw = await this.client.complete(prompt, undefined, { temperature: 0.1, maxTokens: 2_000 });
67
+ const raw = await this.client.complete(prompt, undefined, {
68
+ temperature: 0.1,
69
+ maxTokens: 2_000,
70
+ });
61
71
  try {
62
- const jsonStr = raw.replace(/^```json\s*/i, "").replace(/\s*```$/, "").trim();
72
+ const jsonStr = raw
73
+ .replace(/^```json\s*/i, "")
74
+ .replace(/\s*```$/, "")
75
+ .trim();
63
76
  return JSON.parse(jsonStr);
64
77
  }
65
78
  catch {
@@ -71,5 +84,38 @@ Respond as JSON:
71
84
  };
72
85
  }
73
86
  }
87
+ // ── Gate + apply ─────────────────────────────────────────────────────────────
88
+ /**
89
+ * Review the patch and, if approved, apply it to disk.
90
+ *
91
+ * This is the primary entry point. The Sentinel is both judge and executor:
92
+ * - Approved → patch is written to disk, status set to "applied"
93
+ * - Rejected → nothing is written, violations are surfaced to the caller
94
+ *
95
+ * Pass `overrideApply: true` to apply even if the Sentinel rejects
96
+ * (e.g. explicit human "apply anyway" action in the CLI).
97
+ */
98
+ async reviewAndApply(opts) {
99
+ const review = await this.review({
100
+ contract: opts.contract,
101
+ filePath: opts.patch.file,
102
+ original: opts.patch.original,
103
+ proposed: opts.patch.proposed,
104
+ agentTask: opts.agentTask,
105
+ });
106
+ // Persist sentinel verdict onto the patch record
107
+ await opts.store.update(opts.patch.id, {
108
+ sentinelApproved: review.approved,
109
+ sentinelSummary: review.summary,
110
+ sentinelViolations: review.violations,
111
+ });
112
+ const shouldApply = review.approved || opts.overrideApply;
113
+ if (shouldApply) {
114
+ await opts.store.update(opts.patch.id, { status: "approved" });
115
+ await applyPatch({ ...opts.patch, status: "approved" }, opts.projectRoot, opts.store);
116
+ return { review, applied: true };
117
+ }
118
+ return { review, applied: false };
119
+ }
74
120
  }
75
121
  //# sourceMappingURL=sentinel.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sentinel.js","sourceRoot":"","sources":["../src/sentinel.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,cAAc,EAAuB,MAAM,eAAe,CAAC;AAepE,MAAM,OAAO,QAAQ;IACX,MAAM,CAAiB;IAE/B,YAAY,SAAkC,EAAE;QAC9C,IAAI,CAAC,MAAM,GAAG,IAAI,cAAc,CAAC;YAC/B,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,IAAI,EAAE;YAClG,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,QAAQ;YAC/B,SAAS,EAAE,MAAM,CAAC,SAAS,IAAI,MAAM;SACtC,CAAC,CAAC;IACL,CAAC;IAED,wDAAwD;IACxD,KAAK,CAAC,MAAM,CAAC,IAMZ;QACC,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QAE3D,MAAM,MAAM,GAAG;;;;;EAKjB,WAAW;;;cAGC,IAAI,CAAC,SAAS;QACpB,IAAI,CAAC,QAAQ;;;;EAInB,IAAI,CAAC,QAAQ;;;;;EAKb,IAAI,CAAC,QAAQ;;;;;;;;;;;;;;;;;EAiBb,CAAC;QAEC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,WAAW,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;QAElG,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;YAC9E,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAmB,CAAC;QAC/C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO;gBACL,QAAQ,EAAE,KAAK;gBACf,OAAO,EAAE,mEAAmE;gBAC5E,UAAU,EAAE,CAAC,+BAA+B,CAAC;gBAC7C,WAAW,EAAE,EAAE;aAChB,CAAC;QACJ,CAAC;IACH,CAAC;CACF"}
1
+ {"version":3,"file":"sentinel.js","sourceRoot":"","sources":["../src/sentinel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,cAAc,EAAuB,MAAM,eAAe,CAAC;AAEpE,OAAO,EAAE,UAAU,EAA+B,MAAM,YAAY,CAAC;AAoBrE,MAAM,OAAO,QAAQ;IACX,MAAM,CAAiB;IAE/B,YAAY,SAAkC,EAAE;QAC9C,IAAI,CAAC,MAAM,GAAG,IAAI,cAAc,CAAC;YAC/B,MAAM,EACJ,MAAM,CAAC,MAAM;gBACb,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;gBAC/B,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC;gBAChC,EAAE;YACJ,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,QAAQ;YAC/B,SAAS,EAAE,MAAM,CAAC,SAAS,IAAI,MAAM;SACtC,CAAC,CAAC;IACL,CAAC;IAED,+EAA+E;IAE/E,KAAK,CAAC,MAAM,CAAC,IAMZ;QACC,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QAE3D,MAAM,MAAM,GAAG;;;;;EAKjB,WAAW;;;cAGC,IAAI,CAAC,SAAS;QACpB,IAAI,CAAC,QAAQ;;;;EAInB,IAAI,CAAC,QAAQ;;;;;EAKb,IAAI,CAAC,QAAQ;;;;;;;;;;;;;;;;;;EAkBb,CAAC;QAEC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,SAAS,EAAE;YACxD,WAAW,EAAE,GAAG;YAChB,SAAS,EAAE,KAAK;SACjB,CAAC,CAAC;QAEH,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,GAAG;iBAChB,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC;iBAC3B,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC;iBACtB,IAAI,EAAE,CAAC;YACV,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAmB,CAAC;QAC/C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO;gBACL,QAAQ,EAAE,KAAK;gBACf,OAAO,EAAE,mEAAmE;gBAC5E,UAAU,EAAE,CAAC,+BAA+B,CAAC;gBAC7C,WAAW,EAAE,EAAE;aAChB,CAAC;QACJ,CAAC;IACH,CAAC;IAED,gFAAgF;IAEhF;;;;;;;;;OASG;IACH,KAAK,CAAC,cAAc,CAAC,IAOpB;QACC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC;YAC/B,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI;YACzB,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ;YAC7B,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ;YAC7B,SAAS,EAAE,IAAI,CAAC,SAAS;SAC1B,CAAC,CAAC;QAEH,iDAAiD;QACjD,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE;YACrC,gBAAgB,EAAE,MAAM,CAAC,QAAQ;YACjC,eAAe,EAAE,MAAM,CAAC,OAAO;YAC/B,kBAAkB,EAAE,MAAM,CAAC,UAAU;SACtC,CAAC,CAAC;QAEH,MAAM,WAAW,GAAG,MAAM,CAAC,QAAQ,IAAI,IAAI,CAAC,aAAa,CAAC;QAE1D,IAAI,WAAW,EAAE,CAAC;YAChB,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;YAC/D,MAAM,UAAU,CACd,EAAE,GAAG,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,EACrC,IAAI,CAAC,WAAW,EAChB,IAAI,CAAC,KAAK,CACX,CAAC;YACF,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QACnC,CAAC;QAED,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IACpC,CAAC;CACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@interchained/portal-agent",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Agent runtime for Portal — AiAssist.net runner/sentinel, audit, generate, improve, guard",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "author": "Interchained <dev@interchained.org>",
@@ -18,10 +18,17 @@
18
18
  "README.md",
19
19
  "LICENSE"
20
20
  ],
21
+ "scripts": {
22
+ "build": "tsc",
23
+ "dev": "tsc --watch",
24
+ "typecheck": "tsc --noEmit",
25
+ "clean": "rm -rf dist",
26
+ "prepublishOnly": "npm run typecheck && npm run build"
27
+ },
21
28
  "dependencies": {
29
+ "@interchained/portal-contract": "workspace:*",
22
30
  "picocolors": "^1.0.1",
23
- "ora": "^8.0.1",
24
- "@interchained/portal-contract": "0.1.0"
31
+ "ora": "^8.0.1"
25
32
  },
26
33
  "devDependencies": {
27
34
  "@types/node": "^20.12.7",
@@ -32,11 +39,5 @@
32
39
  },
33
40
  "publishConfig": {
34
41
  "access": "public"
35
- },
36
- "scripts": {
37
- "build": "tsc",
38
- "dev": "tsc --watch",
39
- "typecheck": "tsc --noEmit",
40
- "clean": "rm -rf dist"
41
42
  }
42
- }
43
+ }