@pellux/goodvibes-agent 0.1.107 → 0.1.108

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.
Files changed (79) hide show
  1. package/CHANGELOG.md +38 -13
  2. package/README.md +27 -11
  3. package/docs/README.md +5 -5
  4. package/docs/{runtime-connection.md → connected-services.md} +8 -8
  5. package/docs/getting-started.md +28 -12
  6. package/docs/release-and-publishing.md +4 -4
  7. package/package.json +2 -12
  8. package/src/agent/reminder-schedule-format.ts +75 -0
  9. package/src/agent/reminder-schedule.ts +494 -0
  10. package/src/agent/routine-schedule-format.ts +7 -7
  11. package/src/agent/routine-schedule-promotion.ts +1 -1
  12. package/src/agent/routine-schedule-receipts.ts +1 -1
  13. package/src/agent/skill-discovery.ts +2 -0
  14. package/src/cli/agent-knowledge-args.ts +93 -0
  15. package/src/cli/agent-knowledge-command.ts +200 -369
  16. package/src/cli/agent-knowledge-format.ts +58 -7
  17. package/src/cli/agent-knowledge-methods.ts +84 -0
  18. package/src/cli/agent-knowledge-runtime.ts +240 -0
  19. package/src/cli/completion.ts +0 -2
  20. package/src/cli/config-overrides.ts +2 -2
  21. package/src/cli/help.ts +34 -15
  22. package/src/cli/management-commands.ts +2 -2
  23. package/src/cli/management.ts +8 -8
  24. package/src/cli/package-verification.ts +7 -3
  25. package/src/cli/parser.ts +10 -4
  26. package/src/cli/service-posture.ts +6 -6
  27. package/src/cli/status.ts +32 -32
  28. package/src/input/agent-workspace-activation.ts +24 -13
  29. package/src/input/agent-workspace-basic-command-editors.ts +448 -0
  30. package/src/input/agent-workspace-categories.ts +42 -34
  31. package/src/input/agent-workspace-channels.ts +3 -3
  32. package/src/input/agent-workspace-command-editor.ts +65 -0
  33. package/src/input/agent-workspace-editors.ts +17 -2
  34. package/src/input/agent-workspace-knowledge-query-editor.ts +74 -0
  35. package/src/input/agent-workspace-knowledge-url-editor.ts +95 -0
  36. package/src/input/agent-workspace-reminder-schedule-editor.ts +125 -0
  37. package/src/input/agent-workspace-routine-schedule-editor.ts +127 -0
  38. package/src/input/agent-workspace-setup.ts +2 -2
  39. package/src/input/agent-workspace-types.ts +20 -2
  40. package/src/input/agent-workspace-voice-media.ts +5 -5
  41. package/src/input/agent-workspace.ts +39 -2
  42. package/src/input/commands/agent-runtime-profile-runtime.ts +1 -1
  43. package/src/input/commands/agent-skills-runtime.ts +94 -2
  44. package/src/input/commands/brief-runtime.ts +126 -0
  45. package/src/input/commands/channels-runtime.ts +47 -0
  46. package/src/input/commands/health-runtime.ts +10 -10
  47. package/src/input/commands/knowledge.ts +176 -1
  48. package/src/input/commands/planning-runtime.ts +1 -1
  49. package/src/input/commands/platform-access-runtime.ts +10 -10
  50. package/src/input/commands/policy-dispatch.ts +1 -1
  51. package/src/input/commands/schedule-runtime.ts +42 -5
  52. package/src/input/commands/security-runtime.ts +1 -1
  53. package/src/input/commands/session-content.ts +1 -1
  54. package/src/input/commands/session-workflow.ts +1 -1
  55. package/src/input/commands/shell-core.ts +4 -2
  56. package/src/input/commands/tasks-runtime.ts +3 -3
  57. package/src/input/commands.ts +4 -0
  58. package/src/input/handler-onboarding.ts +4 -4
  59. package/src/input/handler.ts +3 -2
  60. package/src/input/onboarding/onboarding-wizard-helpers.ts +1 -1
  61. package/src/input/onboarding/onboarding-wizard-operator-steps.ts +13 -13
  62. package/src/input/onboarding/onboarding-wizard-steps.ts +8 -8
  63. package/src/input/settings-modal-agent-policy.ts +1 -1
  64. package/src/input/slash-command-parser.ts +60 -0
  65. package/src/panels/builtin/agent.ts +1 -1
  66. package/src/panels/builtin/operations.ts +2 -2
  67. package/src/panels/provider-account-snapshot.ts +1 -1
  68. package/src/panels/provider-health-domains.ts +6 -6
  69. package/src/panels/subscription-panel.ts +1 -1
  70. package/src/panels/tasks-panel.ts +3 -3
  71. package/src/renderer/agent-workspace.ts +43 -30
  72. package/src/renderer/help-overlay.ts +1 -1
  73. package/src/renderer/settings-modal.ts +13 -13
  74. package/src/runtime/bootstrap-hook-bridge.ts +1 -1
  75. package/src/runtime/bootstrap.ts +8 -8
  76. package/src/runtime/index.ts +2 -2
  77. package/src/runtime/onboarding/derivation.ts +6 -6
  78. package/src/shell/service-settings-sync.ts +1 -1
  79. package/src/version.ts +1 -1
@@ -1,13 +1,22 @@
1
- import { existsSync, readFileSync } from 'node:fs';
2
- import { join } from 'node:path';
3
- import { createBrowserAgentSdk } from '@pellux/goodvibes-sdk/browser/agent';
4
- import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
5
- import { SDK_VERSION, VERSION } from '../version.ts';
6
1
  import type { CliCommandOutput } from './types.ts';
7
2
  import type { CliCommandRuntime } from './management.ts';
3
+ import {
4
+ commandValues,
5
+ delegationTaskValues,
6
+ hasFlag,
7
+ parseConnectorInput,
8
+ readFirstStringList,
9
+ readOptionValue,
10
+ readPositiveInt,
11
+ readSinceMs,
12
+ readStringList,
13
+ stripCommandFlag,
14
+ } from './agent-knowledge-args.ts';
8
15
  import {
9
16
  formatAsk,
10
17
  formatBatchIngest,
18
+ formatConnector,
19
+ formatConnectorDoctor,
11
20
  formatConnectors,
12
21
  formatEntityList,
13
22
  formatFailure,
@@ -18,100 +27,22 @@ import {
18
27
  formatSearch,
19
28
  formatStatus,
20
29
  } from './agent-knowledge-format.ts';
30
+ import { AGENT_KNOWLEDGE_METHODS, DELEGATION_METHOD } from './agent-knowledge-methods.ts';
31
+ import {
32
+ createAgentSdk,
33
+ fetchDaemonStatus,
34
+ findDisallowedKnowledgeScopeFlag,
35
+ formatScopeFlagRejection,
36
+ getAgentKnowledgeJson,
37
+ isRecord,
38
+ postAgentKnowledgeJson,
39
+ readPackageMetadata,
40
+ readString,
41
+ resolveDaemonConnection,
42
+ runKnowledgeCall,
43
+ } from './agent-knowledge-runtime.ts';
21
44
  import { formatJsonOrText, yesNo } from './management.ts';
22
45
 
23
- type JsonRecord = Record<string, unknown>;
24
-
25
- interface AgentDaemonConnection {
26
- readonly baseUrl: string;
27
- readonly token: string | null;
28
- readonly tokenPath: string;
29
- }
30
-
31
- interface AgentKnowledgeFailure {
32
- readonly ok: false;
33
- readonly kind: 'daemon_unavailable' | 'auth_required' | 'version_mismatch' | 'daemon_route_unavailable' | 'daemon_error';
34
- readonly error: string;
35
- readonly baseUrl: string;
36
- readonly route: string;
37
- readonly daemonVersion?: string;
38
- readonly expectedSdkVersion?: string;
39
- }
40
-
41
- interface AgentKnowledgeSuccess<TData> {
42
- readonly ok: true;
43
- readonly kind: string;
44
- readonly route: string;
45
- readonly data: TData;
46
- }
47
-
48
- type AgentKnowledgeResult<TData> = AgentKnowledgeSuccess<TData> | AgentKnowledgeFailure;
49
-
50
- interface DaemonCallMethod {
51
- readonly kind: string;
52
- readonly route: string;
53
- }
54
-
55
- const AGENT_KNOWLEDGE_METHODS = {
56
- status: {
57
- kind: 'agentKnowledge.status',
58
- route: '/api/goodvibes-agent/knowledge/status',
59
- },
60
- ask: {
61
- kind: 'agentKnowledge.ask',
62
- route: '/api/goodvibes-agent/knowledge/ask',
63
- },
64
- search: {
65
- kind: 'agentKnowledge.search',
66
- route: '/api/goodvibes-agent/knowledge/search',
67
- },
68
- sourcesList: {
69
- kind: 'agentKnowledge.sources.list',
70
- route: '/api/goodvibes-agent/knowledge/sources',
71
- },
72
- nodesList: {
73
- kind: 'agentKnowledge.nodes.list',
74
- route: '/api/goodvibes-agent/knowledge/nodes',
75
- },
76
- issuesList: {
77
- kind: 'agentKnowledge.issues.list',
78
- route: '/api/goodvibes-agent/knowledge/issues',
79
- },
80
- itemGet: {
81
- kind: 'agentKnowledge.item.get',
82
- route: '/api/goodvibes-agent/knowledge/items/{id}',
83
- },
84
- map: {
85
- kind: 'agentKnowledge.map',
86
- route: '/api/goodvibes-agent/knowledge/map',
87
- },
88
- connectorsList: {
89
- kind: 'agentKnowledge.connectors.list',
90
- route: '/api/goodvibes-agent/knowledge/connectors',
91
- },
92
- ingestUrl: {
93
- kind: 'agentKnowledge.ingest.url',
94
- route: '/api/goodvibes-agent/knowledge/ingest/url',
95
- },
96
- ingestUrls: {
97
- kind: 'agentKnowledge.ingest.urls',
98
- route: '/api/goodvibes-agent/knowledge/ingest/urls',
99
- },
100
- ingestBookmarks: {
101
- kind: 'agentKnowledge.ingest.bookmarks',
102
- route: '/api/goodvibes-agent/knowledge/ingest/bookmarks',
103
- },
104
- reindex: {
105
- kind: 'agentKnowledge.reindex',
106
- route: '/api/goodvibes-agent/knowledge/reindex',
107
- },
108
- } as const;
109
-
110
- const DELEGATION_METHOD = {
111
- kind: 'sessions.messages.create',
112
- route: 'sessions.messages.create',
113
- } as const;
114
-
115
46
  interface DelegationResult {
116
47
  readonly sessionId: string;
117
48
  readonly message: unknown;
@@ -119,253 +50,6 @@ interface DelegationResult {
119
50
  readonly wrfcRequested: boolean;
120
51
  }
121
52
 
122
- function isRecord(value: unknown): value is JsonRecord {
123
- return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
124
- }
125
-
126
- function readString(record: JsonRecord | null, key: string): string | null {
127
- const value = record?.[key];
128
- return typeof value === 'string' ? value : null;
129
- }
130
-
131
- function commandValues(args: readonly string[]): string[] {
132
- const values: string[] = [];
133
- for (let index = 0; index < args.length; index += 1) {
134
- const token = args[index]!;
135
- if (!token.startsWith('--')) {
136
- values.push(token);
137
- continue;
138
- }
139
- if (!token.includes('=') && args[index + 1] && !args[index + 1]!.startsWith('--')) index += 1;
140
- }
141
- return values;
142
- }
143
-
144
- function delegationTaskValues(args: readonly string[]): string[] {
145
- const values: string[] = [];
146
- for (const token of args) {
147
- if (token === '--wrfc') continue;
148
- if (!token.startsWith('--')) values.push(token);
149
- }
150
- return values;
151
- }
152
-
153
- function readOptionValue(args: readonly string[], name: string): string | undefined {
154
- for (let index = 0; index < args.length; index += 1) {
155
- const token = args[index]!;
156
- if (token === name) {
157
- const next = args[index + 1];
158
- return next && !next.startsWith('--') ? next : undefined;
159
- }
160
- if (token.startsWith(`${name}=`)) return token.slice(name.length + 1);
161
- }
162
- return undefined;
163
- }
164
-
165
- function readPositiveInt(args: readonly string[], name: string, fallback: number): number {
166
- const raw = readOptionValue(args, name);
167
- if (!raw) return fallback;
168
- const parsed = Number.parseInt(raw, 10);
169
- return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
170
- }
171
-
172
- function readStringList(args: readonly string[], name: string): readonly string[] {
173
- const raw = readOptionValue(args, name);
174
- if (!raw) return [];
175
- return raw.split(',').map((entry) => entry.trim()).filter(Boolean);
176
- }
177
-
178
- function hasFlag(args: readonly string[], flag: string): boolean {
179
- return args.includes(flag);
180
- }
181
-
182
- function stripCommandFlag(args: readonly string[], flag: string): { readonly rest: readonly string[]; readonly present: boolean } {
183
- const rest: string[] = [];
184
- let present = false;
185
- for (const arg of args) {
186
- if (arg === flag) {
187
- present = true;
188
- continue;
189
- }
190
- rest.push(arg);
191
- }
192
- return { rest, present };
193
- }
194
-
195
- function readPackageMetadata(): { readonly version: string; readonly sdkVersion: string } {
196
- return { version: VERSION, sdkVersion: SDK_VERSION };
197
- }
198
-
199
- function resolveDaemonConnection(runtime: CliCommandRuntime): AgentDaemonConnection {
200
- const host = String(runtime.configManager.get('controlPlane.host') ?? '127.0.0.1');
201
- const port = Number(runtime.configManager.get('controlPlane.port') ?? 3421);
202
- const baseUrl = `http://${host}:${Number.isFinite(port) ? port : 3421}`;
203
- const tokenPath = join(runtime.homeDirectory, '.goodvibes', 'daemon', 'operator-tokens.json');
204
- if (!existsSync(tokenPath)) return { baseUrl, token: null, tokenPath };
205
- try {
206
- const parsed = JSON.parse(readFileSync(tokenPath, 'utf-8')) as unknown;
207
- const token = isRecord(parsed) && typeof parsed.token === 'string' ? parsed.token : null;
208
- return { baseUrl, token, tokenPath };
209
- } catch {
210
- return { baseUrl, token: null, tokenPath };
211
- }
212
- }
213
-
214
- async function fetchDaemonStatus(connection: AgentDaemonConnection): Promise<{ readonly ok: boolean; readonly status: number; readonly body: unknown }> {
215
- try {
216
- const response = await fetch(`${connection.baseUrl}/status`, {
217
- headers: connection.token ? { authorization: `Bearer ${connection.token}` } : undefined,
218
- });
219
- const text = await response.text();
220
- let body: unknown = text;
221
- try {
222
- body = JSON.parse(text) as unknown;
223
- } catch {
224
- body = text;
225
- }
226
- return { ok: response.ok, status: response.status, body };
227
- } catch (error) {
228
- return { ok: false, status: 0, body: summarizeError(error) };
229
- }
230
- }
231
-
232
- async function classifyKnowledgeError(error: unknown, connection: AgentDaemonConnection, route: string): Promise<AgentKnowledgeFailure> {
233
- const message = summarizeError(error);
234
- const lower = message.toLowerCase();
235
- if (lower.includes('401') || lower.includes('unauthorized') || lower.includes('auth')) {
236
- return { ok: false, kind: 'auth_required', error: message, baseUrl: connection.baseUrl, route };
237
- }
238
- if (lower.includes('404') || lower.includes('not found')) {
239
- const metadata = readPackageMetadata();
240
- const daemon = await fetchDaemonStatus(connection);
241
- const daemonRecord = isRecord(daemon.body) ? daemon.body : {};
242
- const daemonVersion = readString(daemonRecord, 'version') ?? 'unknown';
243
- if (daemon.ok && daemonVersion !== metadata.sdkVersion) {
244
- return {
245
- ok: false,
246
- kind: 'version_mismatch',
247
- error: `External runtime SDK version ${daemonVersion} does not match Agent SDK pin ${metadata.sdkVersion}; Agent Knowledge route is unavailable.`,
248
- baseUrl: connection.baseUrl,
249
- route,
250
- daemonVersion,
251
- expectedSdkVersion: metadata.sdkVersion,
252
- };
253
- }
254
- return { ok: false, kind: 'daemon_route_unavailable', error: message, baseUrl: connection.baseUrl, route };
255
- }
256
- if (lower.includes('fetch') || lower.includes('connect') || lower.includes('econnrefused')) {
257
- return { ok: false, kind: 'daemon_unavailable', error: message, baseUrl: connection.baseUrl, route };
258
- }
259
- return { ok: false, kind: 'daemon_error', error: message, baseUrl: connection.baseUrl, route };
260
- }
261
-
262
- function createAgentSdk(connection: AgentDaemonConnection) {
263
- return createBrowserAgentSdk({
264
- baseUrl: connection.baseUrl,
265
- authToken: connection.token,
266
- });
267
- }
268
-
269
- async function postAgentKnowledgeJson<TData>(
270
- connection: AgentDaemonConnection,
271
- route: string,
272
- body: JsonRecord,
273
- ): Promise<TData> {
274
- const response = await fetch(`${connection.baseUrl}${route}`, {
275
- method: 'POST',
276
- headers: {
277
- authorization: `Bearer ${connection.token ?? ''}`,
278
- 'content-type': 'application/json',
279
- },
280
- body: JSON.stringify(body),
281
- });
282
- const text = await response.text();
283
- let parsed: unknown = text;
284
- if (text.trim()) {
285
- try {
286
- parsed = JSON.parse(text) as unknown;
287
- } catch {
288
- parsed = text;
289
- }
290
- }
291
- if (!response.ok) {
292
- const detail = isRecord(parsed) && typeof parsed.error === 'string' ? parsed.error : text;
293
- throw new Error(`HTTP ${response.status} ${response.statusText}${detail ? `: ${detail}` : ''}`);
294
- }
295
- return parsed as TData;
296
- }
297
-
298
- function queryRoute(route: string, query: JsonRecord): string {
299
- const params = new URLSearchParams();
300
- for (const [key, value] of Object.entries(query)) {
301
- if (value === undefined || value === null || value === '') continue;
302
- if (Array.isArray(value)) {
303
- for (const item of value) {
304
- if (typeof item === 'string' && item.trim().length > 0) params.append(key, item);
305
- }
306
- continue;
307
- }
308
- if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
309
- params.set(key, String(value));
310
- }
311
- }
312
- const suffix = params.toString();
313
- return suffix ? `${route}?${suffix}` : route;
314
- }
315
-
316
- async function getAgentKnowledgeJson<TData>(
317
- connection: AgentDaemonConnection,
318
- route: string,
319
- query: JsonRecord = {},
320
- ): Promise<TData> {
321
- const response = await fetch(`${connection.baseUrl}${queryRoute(route, query)}`, {
322
- headers: {
323
- authorization: `Bearer ${connection.token ?? ''}`,
324
- },
325
- });
326
- const text = await response.text();
327
- let parsed: unknown = text;
328
- if (text.trim()) {
329
- try {
330
- parsed = JSON.parse(text) as unknown;
331
- } catch {
332
- parsed = text;
333
- }
334
- }
335
- if (!response.ok) {
336
- const detail = isRecord(parsed) && typeof parsed.error === 'string' ? parsed.error : text;
337
- throw new Error(`HTTP ${response.status} ${response.statusText}${detail ? `: ${detail}` : ''}`);
338
- }
339
- return parsed as TData;
340
- }
341
-
342
- function findDisallowedKnowledgeScopeFlag(args: readonly string[]): string | null {
343
- const disallowed = [
344
- '--space',
345
- '--knowledge-space',
346
- '--knowledge-space-id',
347
- ['--knowledge', 'SpaceId'].join(''),
348
- '--include-all-spaces',
349
- ['--include', 'AllSpaces'].join(''),
350
- ['--home', 'graph'].join(''),
351
- ['--home', '-graph'].join(''),
352
- ];
353
- for (const token of args) {
354
- for (const flag of disallowed) {
355
- if (token === flag || token.startsWith(`${flag}=`)) return flag;
356
- }
357
- }
358
- return null;
359
- }
360
-
361
- function formatScopeFlagRejection(flag: string): string {
362
- return [
363
- `Agent Knowledge is isolated; ${flag} is not accepted.`,
364
- 'GoodVibes Agent must not use default Knowledge/Wiki or non-Agent product spaces.',
365
- 'Use only /api/goodvibes-agent/knowledge/* Agent-owned routes.',
366
- ].join('\n');
367
- }
368
-
369
53
  function buildDelegationBody(task: string, wrfcRequested: boolean): string {
370
54
  return [
371
55
  'GoodVibes Agent explicit build delegation.',
@@ -383,29 +67,6 @@ function buildDelegationBody(task: string, wrfcRequested: boolean): string {
383
67
  ].join('\n');
384
68
  }
385
69
 
386
- async function runKnowledgeCall<TData>(
387
- runtime: CliCommandRuntime,
388
- method: DaemonCallMethod,
389
- call: (connection: AgentDaemonConnection) => Promise<TData>,
390
- ): Promise<AgentKnowledgeResult<TData>> {
391
- const connection = resolveDaemonConnection(runtime);
392
- if (!connection.token) {
393
- return {
394
- ok: false,
395
- kind: 'auth_required',
396
- error: `No runtime operator token found at ${connection.tokenPath}`,
397
- baseUrl: connection.baseUrl,
398
- route: method.route,
399
- };
400
- }
401
- try {
402
- const data = await call(connection);
403
- return { ok: true, kind: method.kind, route: method.route, data };
404
- } catch (error) {
405
- return classifyKnowledgeError(error, connection, method.route);
406
- }
407
- }
408
-
409
70
  export async function handleAgentKnowledgeCommand(runtime: CliCommandRuntime): Promise<CliCommandOutput> {
410
71
  const [sub = 'status', ...rawRest] = runtime.cli.commandArgs;
411
72
  const confirmation = stripCommandFlag(rawRest, '--yes');
@@ -521,6 +182,32 @@ export async function handleAgentKnowledgeCommand(runtime: CliCommandRuntime): P
521
182
  }
522
183
 
523
184
  if (normalized === 'connectors') {
185
+ const values = commandValues(rest);
186
+ if (values[0] === 'doctor') {
187
+ const id = values[1];
188
+ if (!id) return { output: 'Usage: goodvibes-agent knowledge connectors doctor <connectorId>', exitCode: 2 };
189
+ const route = `/api/goodvibes-agent/knowledge/connectors/${encodeURIComponent(id)}/doctor`;
190
+ const result = await runKnowledgeCall(runtime, AGENT_KNOWLEDGE_METHODS.connectorDoctor, async (connection) => (
191
+ await getAgentKnowledgeJson(connection, route)
192
+ ));
193
+ if (!result.ok) return { output: formatFailure(result, json), exitCode: 1 };
194
+ return {
195
+ output: formatJsonOrText(runtime.cli)(result, formatConnectorDoctor(result.data, id)),
196
+ exitCode: 0,
197
+ };
198
+ }
199
+ const id = values[0];
200
+ if (id) {
201
+ const route = `/api/goodvibes-agent/knowledge/connectors/${encodeURIComponent(id)}`;
202
+ const result = await runKnowledgeCall(runtime, AGENT_KNOWLEDGE_METHODS.connectorGet, async (connection) => (
203
+ await getAgentKnowledgeJson(connection, route)
204
+ ));
205
+ if (!result.ok) return { output: formatFailure(result, json), exitCode: 1 };
206
+ return {
207
+ output: formatJsonOrText(runtime.cli)(result, formatConnector(result.data, id)),
208
+ exitCode: 0,
209
+ };
210
+ }
524
211
  const result = await runKnowledgeCall(runtime, AGENT_KNOWLEDGE_METHODS.connectorsList, async (connection) => (
525
212
  await getAgentKnowledgeJson(connection, AGENT_KNOWLEDGE_METHODS.connectorsList.route)
526
213
  ));
@@ -531,6 +218,34 @@ export async function handleAgentKnowledgeCommand(runtime: CliCommandRuntime): P
531
218
  };
532
219
  }
533
220
 
221
+ if (normalized === 'connector' || normalized === 'connector-get') {
222
+ const [id] = commandValues(rest);
223
+ if (!id) return { output: 'Usage: goodvibes-agent knowledge connector <connectorId>', exitCode: 2 };
224
+ const route = `/api/goodvibes-agent/knowledge/connectors/${encodeURIComponent(id)}`;
225
+ const result = await runKnowledgeCall(runtime, AGENT_KNOWLEDGE_METHODS.connectorGet, async (connection) => (
226
+ await getAgentKnowledgeJson(connection, route)
227
+ ));
228
+ if (!result.ok) return { output: formatFailure(result, json), exitCode: 1 };
229
+ return {
230
+ output: formatJsonOrText(runtime.cli)(result, formatConnector(result.data, id)),
231
+ exitCode: 0,
232
+ };
233
+ }
234
+
235
+ if (normalized === 'connector-doctor' || normalized === 'doctor-connector') {
236
+ const [id] = commandValues(rest);
237
+ if (!id) return { output: 'Usage: goodvibes-agent knowledge connector-doctor <connectorId>', exitCode: 2 };
238
+ const route = `/api/goodvibes-agent/knowledge/connectors/${encodeURIComponent(id)}/doctor`;
239
+ const result = await runKnowledgeCall(runtime, AGENT_KNOWLEDGE_METHODS.connectorDoctor, async (connection) => (
240
+ await getAgentKnowledgeJson(connection, route)
241
+ ));
242
+ if (!result.ok) return { output: formatFailure(result, json), exitCode: 1 };
243
+ return {
244
+ output: formatJsonOrText(runtime.cli)(result, formatConnectorDoctor(result.data, id)),
245
+ exitCode: 0,
246
+ };
247
+ }
248
+
534
249
  if (normalized === 'ingest-url') {
535
250
  const values = commandValues(rest);
536
251
  const url = values[0];
@@ -565,6 +280,50 @@ export async function handleAgentKnowledgeCommand(runtime: CliCommandRuntime): P
565
280
  };
566
281
  }
567
282
 
283
+ if (normalized === 'ingest-file' || normalized === 'ingest-artifact') {
284
+ const values = commandValues(rest);
285
+ const path = values[0];
286
+ if (!path) return { output: 'Usage: goodvibes-agent knowledge ingest-file <path> [--title <title>] [--tags a,b] [--folder <path>] --yes', exitCode: 2 };
287
+ if (!confirmation.present) {
288
+ const failure = {
289
+ ok: false,
290
+ kind: 'confirmation_required',
291
+ error: `Refusing to ingest file into Agent Knowledge ${path} without --yes.`,
292
+ route: AGENT_KNOWLEDGE_METHODS.ingestArtifact.route,
293
+ };
294
+ return {
295
+ output: json ? JSON.stringify(failure, null, 2) : `${failure.error}\nUsage: goodvibes-agent knowledge ingest-file <path> [--title <title>] [--tags a,b] [--folder <path>] --yes`,
296
+ exitCode: 2,
297
+ };
298
+ }
299
+ const title = readOptionValue(rest, '--title');
300
+ const tags = readStringList(rest, '--tags');
301
+ const folderPath = readOptionValue(rest, '--folder');
302
+ const connectorId = readOptionValue(rest, '--connector') ?? 'goodvibes-agent-file';
303
+ const result = await runKnowledgeCall(runtime, AGENT_KNOWLEDGE_METHODS.ingestArtifact, async (connection) => (
304
+ await postAgentKnowledgeJson(connection, AGENT_KNOWLEDGE_METHODS.ingestArtifact.route, {
305
+ path,
306
+ title,
307
+ tags,
308
+ folderPath,
309
+ connectorId,
310
+ allowPrivateHosts: hasFlag(rest, '--allow-private-hosts'),
311
+ metadata: { originSurface: 'goodvibes-agent-cli' },
312
+ })
313
+ ));
314
+ if (!result.ok) return { output: formatFailure(result, json), exitCode: 1 };
315
+ return {
316
+ output: formatJsonOrText(runtime.cli)(result, formatIngest(
317
+ result.data,
318
+ path,
319
+ 'ingest-file',
320
+ '/api/goodvibes-agent/knowledge/ingest/artifact',
321
+ 'file',
322
+ )),
323
+ exitCode: 0,
324
+ };
325
+ }
326
+
568
327
  if (normalized === 'import-urls' || normalized === 'import-bookmarks') {
569
328
  const values = commandValues(rest);
570
329
  const path = values[0];
@@ -603,6 +362,78 @@ export async function handleAgentKnowledgeCommand(runtime: CliCommandRuntime): P
603
362
  };
604
363
  }
605
364
 
365
+ if (normalized === 'import-browser-history' || normalized === 'sync-browser-history') {
366
+ if (!confirmation.present) {
367
+ const failure = {
368
+ ok: false,
369
+ kind: 'confirmation_required',
370
+ error: 'Refusing to import browser history into Agent Knowledge without --yes.',
371
+ route: AGENT_KNOWLEDGE_METHODS.ingestBrowserHistory.route,
372
+ };
373
+ return {
374
+ output: json ? JSON.stringify(failure, null, 2) : `${failure.error}\nUsage: goodvibes-agent knowledge import-browser-history [--browsers chrome,firefox] [--sources history,bookmark] [--limit <n>] [--since-days <n>] --yes`,
375
+ exitCode: 2,
376
+ };
377
+ }
378
+ const browsers = readFirstStringList(rest, ['--browsers', '--browser']);
379
+ const sourceKinds = readFirstStringList(rest, ['--sources', '--source-kinds', '--source-kind']);
380
+ const homeOverride = readOptionValue(rest, '--home');
381
+ const result = await runKnowledgeCall(runtime, AGENT_KNOWLEDGE_METHODS.ingestBrowserHistory, async (connection) => (
382
+ await postAgentKnowledgeJson(connection, AGENT_KNOWLEDGE_METHODS.ingestBrowserHistory.route, {
383
+ browsers,
384
+ sourceKinds,
385
+ homeOverride,
386
+ limit: readPositiveInt(rest, '--limit', 250),
387
+ sinceMs: readSinceMs(rest),
388
+ connectorId: 'goodvibes-agent-browser-history',
389
+ })
390
+ ));
391
+ if (!result.ok) return { output: formatFailure(result, json), exitCode: 1 };
392
+ return {
393
+ output: formatJsonOrText(runtime.cli)(result, formatBatchIngest(result.data, 'browser-history')),
394
+ exitCode: 0,
395
+ };
396
+ }
397
+
398
+ if (normalized === 'ingest-connector') {
399
+ const values = commandValues(rest);
400
+ const connectorId = values[0];
401
+ if (!connectorId) return { output: 'Usage: goodvibes-agent knowledge ingest-connector <connectorId> [--input <json-or-text>|--path <path>|--content <text>] --yes', exitCode: 2 };
402
+ const input = parseConnectorInput(readOptionValue(rest, '--input'));
403
+ const path = readOptionValue(rest, '--path');
404
+ const content = readOptionValue(rest, '--content');
405
+ if (input === undefined && !path && !content) {
406
+ return { output: 'Usage: goodvibes-agent knowledge ingest-connector <connectorId> [--input <json-or-text>|--path <path>|--content <text>] --yes', exitCode: 2 };
407
+ }
408
+ if (!confirmation.present) {
409
+ const failure = {
410
+ ok: false,
411
+ kind: 'confirmation_required',
412
+ error: `Refusing to ingest connector input into Agent Knowledge for ${connectorId} without --yes.`,
413
+ route: AGENT_KNOWLEDGE_METHODS.ingestConnector.route,
414
+ };
415
+ return {
416
+ output: json ? JSON.stringify(failure, null, 2) : `${failure.error}\nUsage: goodvibes-agent knowledge ingest-connector <connectorId> [--input <json-or-text>|--path <path>|--content <text>] --yes`,
417
+ exitCode: 2,
418
+ };
419
+ }
420
+ const result = await runKnowledgeCall(runtime, AGENT_KNOWLEDGE_METHODS.ingestConnector, async (connection) => (
421
+ await postAgentKnowledgeJson(connection, AGENT_KNOWLEDGE_METHODS.ingestConnector.route, {
422
+ connectorId,
423
+ input,
424
+ path,
425
+ content,
426
+ allowPrivateHosts: hasFlag(rest, '--allow-private-hosts'),
427
+ sessionId: 'goodvibes-agent-cli',
428
+ })
429
+ ));
430
+ if (!result.ok) return { output: formatFailure(result, json), exitCode: 1 };
431
+ return {
432
+ output: formatJsonOrText(runtime.cli)(result, formatBatchIngest(result.data, `connector ${connectorId}`)),
433
+ exitCode: 0,
434
+ };
435
+ }
436
+
606
437
  if (normalized === 'reindex') {
607
438
  if (!confirmation.present) {
608
439
  const failure = {
@@ -627,7 +458,7 @@ export async function handleAgentKnowledgeCommand(runtime: CliCommandRuntime): P
627
458
  }
628
459
 
629
460
  return {
630
- output: 'Usage: goodvibes-agent knowledge [status|ask <question>|search <query>|list|sources|nodes|issues|get <id>|map|connectors|ingest-url <url> --yes|import-urls <path> --yes|import-bookmarks <path> --yes|reindex --yes]',
461
+ output: 'Usage: goodvibes-agent knowledge [status|ask <question>|search <query>|list|sources|nodes|issues|get <id>|map|connectors|ingest-url <url> --yes|ingest-file <path> --yes|ingest-connector <id> --yes|import-urls <path> --yes|import-bookmarks <path> --yes|import-browser-history --yes|reindex --yes]',
631
462
  exitCode: 2,
632
463
  };
633
464
  }
@@ -687,7 +518,7 @@ export async function handleCompatCommand(runtime: CliCommandRuntime): Promise<C
687
518
  ` version compatible: ${yesNo(versionCompatible)}`,
688
519
  ` operator token: ${connection.token ? 'present' : 'missing'} (${connection.tokenPath})`,
689
520
  ` Agent knowledge route: ${knowledgeRouteReady ? 'ready' : `not ready (${knowledgeRoute.ok ? 'unknown' : knowledgeRoute.kind})`}`,
690
- ...(versionCompatible ? [] : [' next: update/restart the external GoodVibes runtime so /status matches the Agent SDK pin.']),
521
+ ...(versionCompatible ? [] : [' next: update connected GoodVibes services so /status matches the Agent SDK pin.']),
691
522
  ].join('\n');
692
523
  return {
693
524
  output: runtime.cli.flags.outputFormat === 'json' ? JSON.stringify(value, null, 2) : text,