@ekanos/cli 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/README.md +25 -8
  2. package/dist/agents-api.d.ts +11 -2
  3. package/dist/agents-api.js +39 -3
  4. package/dist/agents-api.js.map +1 -1
  5. package/dist/apps-api.js +2 -2
  6. package/dist/apps-api.js.map +1 -1
  7. package/dist/auth/ekanos-api.d.ts +16 -2
  8. package/dist/auth/ekanos-api.js +101 -15
  9. package/dist/auth/ekanos-api.js.map +1 -1
  10. package/dist/auth/host-trust.d.ts +46 -0
  11. package/dist/auth/host-trust.js +79 -0
  12. package/dist/auth/host-trust.js.map +1 -0
  13. package/dist/auth/session.d.ts +10 -1
  14. package/dist/auth/session.js +28 -28
  15. package/dist/auth/session.js.map +1 -1
  16. package/dist/commands/agents-edit.d.ts +6 -2
  17. package/dist/commands/agents-edit.js +57 -26
  18. package/dist/commands/agents-edit.js.map +1 -1
  19. package/dist/commands/agents.d.ts +13 -2
  20. package/dist/commands/agents.js +140 -35
  21. package/dist/commands/agents.js.map +1 -1
  22. package/dist/commands/apps.d.ts +2 -0
  23. package/dist/commands/apps.js +16 -12
  24. package/dist/commands/apps.js.map +1 -1
  25. package/dist/commands/catalog.js +7 -6
  26. package/dist/commands/catalog.js.map +1 -1
  27. package/dist/commands/knowledge.d.ts +19 -0
  28. package/dist/commands/knowledge.js +119 -27
  29. package/dist/commands/knowledge.js.map +1 -1
  30. package/dist/commands/login.d.ts +4 -0
  31. package/dist/commands/login.js +30 -2
  32. package/dist/commands/login.js.map +1 -1
  33. package/dist/commands/logout.d.ts +9 -1
  34. package/dist/commands/logout.js +56 -7
  35. package/dist/commands/logout.js.map +1 -1
  36. package/dist/commands/publish.js +3 -3
  37. package/dist/commands/publish.js.map +1 -1
  38. package/dist/commands/reviewers.d.ts +2 -0
  39. package/dist/commands/reviewers.js +7 -3
  40. package/dist/commands/reviewers.js.map +1 -1
  41. package/dist/commands/routes.d.ts +2 -0
  42. package/dist/commands/routes.js +42 -9
  43. package/dist/commands/routes.js.map +1 -1
  44. package/dist/commands/runs.d.ts +13 -2
  45. package/dist/commands/runs.js +99 -16
  46. package/dist/commands/runs.js.map +1 -1
  47. package/dist/commands/scope-notice.d.ts +21 -0
  48. package/dist/commands/scope-notice.js +35 -0
  49. package/dist/commands/scope-notice.js.map +1 -0
  50. package/dist/commands/skills.d.ts +7 -1
  51. package/dist/commands/skills.js +80 -24
  52. package/dist/commands/skills.js.map +1 -1
  53. package/dist/commands/sources.d.ts +1 -1
  54. package/dist/commands/sources.js +126 -12
  55. package/dist/commands/sources.js.map +1 -1
  56. package/dist/commands/status.js +5 -4
  57. package/dist/commands/status.js.map +1 -1
  58. package/dist/commands/team-agent-readiness.d.ts +49 -0
  59. package/dist/commands/team-agent-readiness.js +308 -0
  60. package/dist/commands/team-agent-readiness.js.map +1 -0
  61. package/dist/commands/team-agents-agents.d.ts +24 -3
  62. package/dist/commands/team-agents-agents.js +314 -35
  63. package/dist/commands/team-agents-agents.js.map +1 -1
  64. package/dist/commands/team-agents-mcp.js +32 -3
  65. package/dist/commands/team-agents-mcp.js.map +1 -1
  66. package/dist/commands/team-agents-triggers.js +230 -55
  67. package/dist/commands/team-agents-triggers.js.map +1 -1
  68. package/dist/commands/team-agents.d.ts +3 -1
  69. package/dist/commands/team-agents.js +27 -7
  70. package/dist/commands/team-agents.js.map +1 -1
  71. package/dist/commands/teams-channel-install.d.ts +24 -0
  72. package/dist/commands/teams-channel-install.js +94 -0
  73. package/dist/commands/teams-channel-install.js.map +1 -0
  74. package/dist/commands/teams-connect.d.ts +41 -0
  75. package/dist/commands/teams-connect.js +212 -57
  76. package/dist/commands/teams-connect.js.map +1 -1
  77. package/dist/commands/teams-events.d.ts +12 -0
  78. package/dist/commands/teams-events.js +138 -0
  79. package/dist/commands/teams-events.js.map +1 -0
  80. package/dist/commands/teams-onboard.d.ts +11 -3
  81. package/dist/commands/teams-onboard.js +60 -7
  82. package/dist/commands/teams-onboard.js.map +1 -1
  83. package/dist/commands/teams-overview.d.ts +11 -0
  84. package/dist/commands/teams-overview.js +160 -0
  85. package/dist/commands/teams-overview.js.map +1 -0
  86. package/dist/commands/teams-scope.d.ts +10 -0
  87. package/dist/commands/teams-scope.js +109 -0
  88. package/dist/commands/teams-scope.js.map +1 -0
  89. package/dist/commands/teams.d.ts +4 -2
  90. package/dist/commands/teams.js +113 -14
  91. package/dist/commands/teams.js.map +1 -1
  92. package/dist/commands/upgrade.js +12 -2
  93. package/dist/commands/upgrade.js.map +1 -1
  94. package/dist/commands/use.js +2 -2
  95. package/dist/commands/use.js.map +1 -1
  96. package/dist/commands/whoami.js +1 -1
  97. package/dist/commands/whoami.js.map +1 -1
  98. package/dist/connections-api.js +1 -1
  99. package/dist/connections-api.js.map +1 -1
  100. package/dist/delegate.js +11 -4
  101. package/dist/delegate.js.map +1 -1
  102. package/dist/harness-scaffold.js +12 -0
  103. package/dist/harness-scaffold.js.map +1 -1
  104. package/dist/index.d.ts +6 -0
  105. package/dist/index.js +682 -167
  106. package/dist/index.js.map +1 -1
  107. package/dist/knowledge-api.d.ts +46 -0
  108. package/dist/knowledge-api.js +32 -1
  109. package/dist/knowledge-api.js.map +1 -1
  110. package/dist/open-url.d.ts +16 -0
  111. package/dist/open-url.js +73 -0
  112. package/dist/open-url.js.map +1 -0
  113. package/dist/parse-argv.d.ts +2 -0
  114. package/dist/parse-argv.js.map +1 -1
  115. package/dist/publish-api.d.ts +2 -1
  116. package/dist/publish-api.js +4 -3
  117. package/dist/publish-api.js.map +1 -1
  118. package/dist/pull-write.d.ts +27 -0
  119. package/dist/pull-write.js +60 -0
  120. package/dist/pull-write.js.map +1 -0
  121. package/dist/routes-api.d.ts +3 -0
  122. package/dist/routes-api.js +9 -4
  123. package/dist/routes-api.js.map +1 -1
  124. package/dist/runs-api.d.ts +7 -1
  125. package/dist/runs-api.js +21 -6
  126. package/dist/runs-api.js.map +1 -1
  127. package/dist/safe-fs.d.ts +2 -0
  128. package/dist/safe-fs.js +12 -6
  129. package/dist/safe-fs.js.map +1 -1
  130. package/dist/secret-file.d.ts +17 -0
  131. package/dist/secret-file.js +69 -0
  132. package/dist/secret-file.js.map +1 -0
  133. package/dist/skills-api.d.ts +10 -2
  134. package/dist/skills-api.js +37 -2
  135. package/dist/skills-api.js.map +1 -1
  136. package/dist/source-products-api.d.ts +4 -0
  137. package/dist/source-products-api.js +14 -4
  138. package/dist/source-products-api.js.map +1 -1
  139. package/dist/source-target.js +2 -1
  140. package/dist/source-target.js.map +1 -1
  141. package/dist/sources-api.d.ts +2 -1
  142. package/dist/sources-api.js +3 -2
  143. package/dist/sources-api.js.map +1 -1
  144. package/dist/team-connect-api.d.ts +44 -2
  145. package/dist/team-connect-api.js +19 -7
  146. package/dist/team-connect-api.js.map +1 -1
  147. package/dist/teams-api.d.ts +41 -1
  148. package/dist/teams-api.js +95 -19
  149. package/dist/teams-api.js.map +1 -1
  150. package/dist/toolchain-api.d.ts +9 -0
  151. package/dist/toolchain-api.js +17 -2
  152. package/dist/toolchain-api.js.map +1 -1
  153. package/dist/toolchain-resolve.d.ts +3 -0
  154. package/dist/toolchain-resolve.js +15 -1
  155. package/dist/toolchain-resolve.js.map +1 -1
  156. package/dist/toolchain.d.ts +6 -0
  157. package/dist/toolchain.js +19 -0
  158. package/dist/toolchain.js.map +1 -1
  159. package/dist/update-notice.js +2 -1
  160. package/dist/update-notice.js.map +1 -1
  161. package/package.json +2 -2
  162. package/templates/AGENTS.md.tmpl +6 -1
  163. package/templates/claude-skill.md.tmpl +5 -0
  164. package/templates/package.json.tmpl +4 -4
  165. package/templates/skills/build-agent/SKILL.md +104 -17
  166. package/templates/skills/build-agent/references/agent-json.md +4 -2
  167. package/templates/skills/build-agent/references/rollout.md +23 -4
  168. package/templates/skills/build-agent/references/set-up-an-integration.md +55 -7
  169. package/templates/skills/build-agent/references/skills.md +20 -1
  170. package/templates/skills/build-agent/references/triggers.md +47 -11
  171. package/templates/skills/build-integration/references/widget-design.md +3 -3
  172. package/templates/skills/ekanos-platform/SKILL.md +45 -16
  173. package/templates/skills/polish-widgets/references/patterns.md +2 -2
@@ -11,19 +11,22 @@ var __rest = (this && this.__rest) || function (s, e) {
11
11
  };
12
12
  import * as fs from 'node:fs';
13
13
  import * as path from 'node:path';
14
+ import { loginHint } from '../auth/host-trust.js';
14
15
  import { refreshStoredSession, requireSession, resolveAuthEnvironment, } from '../auth/session.js';
15
- import { authRequiredError, notFoundError, usageError, validationError, } from '../errors.js';
16
- import { KNOWLEDGE_PUSH_BATCH, KnowledgeListSchema, KnowledgePushSchema, KnowledgeSearchSchema, knowledgeCall, } from '../knowledge-api.js';
16
+ import { authRequiredError, invalidStateError, notFoundError, usageError, validationError, } from '../errors.js';
17
+ import { KNOWLEDGE_PUSH_BATCH, KnowledgeListSchema, KnowledgePruneSchema, KnowledgePushSchema, KnowledgeSearchSchema, isSafeKnowledgeSlug, knowledgeCall, } from '../knowledge-api.js';
18
+ import { writePulledFiles } from '../pull-write.js';
17
19
  import { classifyPath } from '../safe-fs.js';
18
20
  import { resolveSourceTarget } from '../source-target.js';
19
21
  import { pluralize } from '../ui.js';
22
+ import { promptYesNo } from './publish.js';
20
23
  export const KNOWLEDGE_SUBCOMMANDS = [
21
24
  'list',
22
25
  'push',
23
26
  'search',
24
27
  'pull',
25
28
  ];
26
- const USAGE = 'Run one of: ekanos knowledge list [--team <slug>] | push <dir> [--team <slug>] | search <query> --team <slug> | pull [--team <slug>] [--out <dir>].';
29
+ const USAGE = 'Run one of: ekanos knowledge list [--team <slug>] | push <dir> [--team <slug>] [--prune [--yes]] | search <query> --team <slug> | pull [--team <slug>] [--out <dir>] [--force].';
27
30
  /**
28
31
  * `knowledge` — a Source's Knowledge base (plan 244): the General collection
29
32
  * every team reads, and with `--team` one team's own collection. `push`
@@ -41,6 +44,14 @@ export async function runKnowledge(ctx, args) {
41
44
  const files = subcommand === 'push'
42
45
  ? readKnowledgeFolder(requireTarget(args.target, 'push'), args.dir)
43
46
  : null;
47
+ if (args.prune && subcommand !== 'push') {
48
+ throw usageError('--prune goes with knowledge push.', 'Run "ekanos knowledge push <dir> --prune".');
49
+ }
50
+ const origin = subcommand === 'push' ? knowledgeOrigin(args.target, args.dir) : null;
51
+ if (args.prune &&
52
+ classifyPath(path.resolve(args.dir, args.target)) !== 'directory') {
53
+ throw usageError('--prune needs a folder: it removes the articles whose files are gone from it.', 'Pass the folder you push, e.g. "ekanos knowledge push ./knowledge --prune".');
54
+ }
44
55
  if (subcommand === 'search') {
45
56
  requireTarget(args.target, 'search');
46
57
  if (!team) {
@@ -50,7 +61,7 @@ export async function runKnowledge(ctx, args) {
50
61
  const env = resolveAuthEnvironment(ctx, args.host, args.env, {
51
62
  projectDir: args.dir,
52
63
  });
53
- const resolved = await resolveSourceTarget(env, requireSession(env.store, env.host), args);
64
+ const resolved = await resolveSourceTarget(env, requireSession(env), args);
54
65
  const client = new KnowledgeClient(env, resolved.session);
55
66
  const source = resolved.source;
56
67
  const send = (schema, init) => client.call((token) => knowledgeCall(env.host, token, schema, init));
@@ -105,6 +116,23 @@ export async function runKnowledge(ctx, args) {
105
116
  }
106
117
  case 'push': {
107
118
  const articles = files;
119
+ const keep = articles.map((article) => article.slug);
120
+ const prune = (deleteSlugs) => send(KnowledgePruneSchema, {
121
+ method: 'POST',
122
+ path: '/prune',
123
+ query: {},
124
+ body: Object.assign(Object.assign(Object.assign({ source }, (team ? { team } : {})), { origin: origin, keep }), (deleteSlugs ? { delete: deleteSlugs } : {})),
125
+ });
126
+ // Ask (or refuse) before anything is written: what would go is known
127
+ // from the folder alone.
128
+ const preview = args.prune ? await prune() : null;
129
+ if (preview && preview.candidates.length > 0) {
130
+ await confirmPrune(ctx, args, {
131
+ candidates: preview.candidates,
132
+ where: where(preview.source.slug),
133
+ command: `ekanos knowledge push ${args.target}${team ? ` --team ${team}` : ''} --source ${source} --prune --yes`,
134
+ });
135
+ }
108
136
  const results = [];
109
137
  let pushed = null;
110
138
  for (let start = 0; start < articles.length; start += KNOWLEDGE_PUSH_BATCH) {
@@ -112,7 +140,7 @@ export async function runKnowledge(ctx, args) {
112
140
  const response = await send(KnowledgePushSchema, {
113
141
  method: 'POST',
114
142
  query: {},
115
- body: Object.assign(Object.assign({ source }, (team ? { team } : {})), { articles: batch.map((_a) => {
143
+ body: Object.assign(Object.assign({ source }, (team ? { team } : {})), { origin: origin, articles: batch.map((_a) => {
116
144
  var { file: _file } = _a, article = __rest(_a, ["file"]);
117
145
  return article;
118
146
  }) }),
@@ -120,9 +148,33 @@ export async function runKnowledge(ctx, args) {
120
148
  pushed = response;
121
149
  results.push(...response.results);
122
150
  }
151
+ const pruned = preview && preview.candidates.length > 0
152
+ ? await prune(preview.candidates.map((article) => article.slug))
153
+ : preview;
123
154
  const count = (status) => results.filter((result) => result.status === status).length;
124
155
  const refused = results.filter((result) => result.status === 'refused');
125
- return ctx.succeed({ host: env.host, team: team !== null && team !== void 0 ? team : null, results }, ctx.ui.result({
156
+ const pruneRows = pruned
157
+ ? [
158
+ [
159
+ 'Pruned',
160
+ pruned.deleted.length > 0
161
+ ? pruned.deleted.map((article) => article.slug)
162
+ : '0',
163
+ ],
164
+ ...(pruned.edited.length > 0
165
+ ? [
166
+ [
167
+ 'Kept',
168
+ pruned.edited.map((article) => `${article.slug}: its file is gone, but it was edited in the app since it was pushed`),
169
+ ],
170
+ ]
171
+ : []),
172
+ ]
173
+ : [];
174
+ return ctx.succeed(Object.assign({ host: env.host, team: team !== null && team !== void 0 ? team : null, origin,
175
+ results }, (pruned
176
+ ? { pruned: pruned.deleted, keptEdited: pruned.edited }
177
+ : {})), ctx.ui.result({
126
178
  title: `Pushed ${pluralize(results.length, 'article')} to ${where((_b = pushed === null || pushed === void 0 ? void 0 : pushed.source.slug) !== null && _b !== void 0 ? _b : source)}`,
127
179
  tone: refused.length > 0 ? 'warn' : 'ok',
128
180
  rows: [
@@ -137,11 +189,12 @@ export async function runKnowledge(ctx, args) {
137
189
  ],
138
190
  ]
139
191
  : []),
192
+ ...pruneRows,
140
193
  ],
141
194
  notes: [
142
195
  team
143
196
  ? `Team ${team}'s agents with search_knowledge find these now.`
144
- : 'Every team on the Source reads the General collection.',
197
+ : "Every team's agents granted search_knowledge find these now, beside their team's own articles.",
145
198
  ],
146
199
  }));
147
200
  }
@@ -151,7 +204,13 @@ export async function runKnowledge(ctx, args) {
151
204
  query: { source, team, body: '1' },
152
205
  });
153
206
  const outDir = path.resolve(args.dir, (_c = args.out) !== null && _c !== void 0 ? _c : path.join('knowledge', team !== null && team !== void 0 ? team : 'general'));
154
- const written = writeKnowledgeFolder(outDir, list.articles);
207
+ const written = writePulledFiles({
208
+ projectDir: args.dir,
209
+ outDir,
210
+ files: knowledgeFiles(list.articles),
211
+ force: args.force === true,
212
+ rerun: `ekanos knowledge pull${team ? ` --team ${team}` : ''}`,
213
+ });
155
214
  return ctx.succeed({ host: env.host, team: team !== null && team !== void 0 ? team : null, dir: outDir, files: written }, ctx.ui.result({
156
215
  title: `Pulled ${pluralize(written.length, 'article')} from ${where(list.source.slug)}`,
157
216
  rows: [['Into', outDir]],
@@ -162,6 +221,38 @@ export async function runKnowledge(ctx, args) {
162
221
  }
163
222
  }
164
223
  }
224
+ /**
225
+ * Which folder a push came from, as the host records it on each article
226
+ * (`synced_from`): the path as named from the project directory, so pushing
227
+ * the same folder from the same place is the same origin. `--prune` only
228
+ * ever removes articles of that origin.
229
+ */
230
+ export function knowledgeOrigin(target, cwd) {
231
+ const relative = path
232
+ .relative(cwd, path.resolve(cwd, target))
233
+ .split(path.sep)
234
+ .join('/');
235
+ return (relative || '.').slice(-300);
236
+ }
237
+ /**
238
+ * Pruning deletes articles. A person is asked, listing them; a script or
239
+ * agent run needs `--yes`, and is refused before anything is written.
240
+ */
241
+ async function confirmPrune(ctx, args, target) {
242
+ var _a, _b;
243
+ if (args.yes)
244
+ return;
245
+ const list = target.candidates.map((article) => article.slug);
246
+ const what = `--prune would delete ${pluralize(list.length, 'article')} from ${target.where} whose files are gone: ${list.join(', ')}`;
247
+ const canPrompt = !ctx.jsonMode && ((_a = args.stdinIsTTY) !== null && _a !== void 0 ? _a : process.stdin.isTTY === true);
248
+ if (!canPrompt) {
249
+ throw invalidStateError(`${what}. Nothing was pushed or deleted.`, `Re-run with --yes: ${target.command}`);
250
+ }
251
+ const sure = await ((_b = args.confirm) !== null && _b !== void 0 ? _b : promptYesNo)(`${what}. Delete them? [y/N]`);
252
+ if (!sure) {
253
+ throw validationError('Nothing was pushed or deleted.', 'Run it again without --prune to push only.');
254
+ }
255
+ }
165
256
  /** Every call refreshes the session once on a 401, then gives up. */
166
257
  class KnowledgeClient {
167
258
  constructor(env, session) {
@@ -176,7 +267,7 @@ class KnowledgeClient {
176
267
  }
177
268
  if (result.status === 'ok')
178
269
  return result.data;
179
- throw authRequiredError(`The session for ${this.env.host} could not authorize the knowledge call.`, `Run "ekanos login --host ${this.env.host}" and retry.`);
270
+ throw authRequiredError(`The session for ${this.env.host} could not authorize the knowledge call.`, loginHint(this.env));
180
271
  }
181
272
  }
182
273
  /** Mirrors the host's `knowledgeSlug`: a file's path without `.md`. */
@@ -225,8 +316,13 @@ export function readKnowledgeFolder(target, cwd) {
225
316
  found.push(full);
226
317
  }
227
318
  };
228
- if (kind === 'file')
319
+ if (kind === 'file') {
320
+ const name = path.basename(root);
321
+ if (name.startsWith('.') || !MARKDOWN.test(name)) {
322
+ throw validationError(`${root} is not a markdown file, so it was not pushed.`, 'Pass a .md, .markdown or .mdx file (not a dotfile), or a folder of them.');
323
+ }
229
324
  found.push(root);
325
+ }
230
326
  else
231
327
  walk(root);
232
328
  if (found.length === 0) {
@@ -263,18 +359,14 @@ export function readKnowledgeFolder(target, cwd) {
263
359
  slug, title: title || slug, body: text }, (tags.length > 0 ? { tags: tags.slice(0, 30) } : {}));
264
360
  });
265
361
  }
266
- function writeKnowledgeFolder(outDir, articles) {
267
- var _a;
268
- const written = [];
269
- for (const article of articles) {
270
- const target = path.resolve(outDir, ...`${article.slug}.md`.split('/'));
271
- if (!target.startsWith(outDir + path.sep)) {
272
- throw validationError(`Refusing to write ${article.slug} outside ${outDir}.`, 'The host sent a slug that leaves the folder.');
273
- }
274
- const kind = classifyPath(target);
275
- if (kind !== 'missing' && kind !== 'file') {
276
- throw validationError(`Refusing to write through ${target} (${kind}).`, 'Remove it, or pass --out <dir>.');
277
- }
362
+ /** One `.md` per article: its title and tags as frontmatter, then the body. */
363
+ function knowledgeFiles(articles) {
364
+ const unsafe = articles.filter((article) => !isSafeKnowledgeSlug(article.slug));
365
+ if (unsafe.length > 0) {
366
+ throw validationError(`Refusing to pull: the host sent ${unsafe.length === 1 ? 'a slug' : 'slugs'} that cannot be a file path (${unsafe.map((article) => JSON.stringify(article.slug)).join(', ')}).`, 'Nothing was written. A knowledge slug is lowercase letters, digits, ".", "_", "-" and "/" folders, with no "." or ".." folder.');
367
+ }
368
+ return articles.map((article) => {
369
+ var _a;
278
370
  const frontmatter = [
279
371
  '---',
280
372
  `title: ${JSON.stringify(article.title)}`,
@@ -286,11 +378,11 @@ function writeKnowledgeFolder(outDir, articles) {
286
378
  '---',
287
379
  '',
288
380
  ].join('\n');
289
- fs.mkdirSync(path.dirname(target), { recursive: true });
290
- fs.writeFileSync(target, frontmatter + ((_a = article.body) !== null && _a !== void 0 ? _a : ''));
291
- written.push(target);
292
- }
293
- return written;
381
+ return {
382
+ path: `${article.slug}.md`,
383
+ contents: frontmatter + ((_a = article.body) !== null && _a !== void 0 ? _a : ''),
384
+ };
385
+ });
294
386
  }
295
387
  const FRONTMATTER = /^?---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)/;
296
388
  /** Top-level `title`, `slug` and `tags` only. */
@@ -1 +1 @@
1
- {"version":3,"file":"knowledge.js","sourceRoot":"","sources":["../../src/commands/knowledge.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport type { z } from 'zod';\n\nimport type { StoredSession } from '../auth/credential-store';\nimport {\n type AuthEnvironment,\n refreshStoredSession,\n requireSession,\n resolveAuthEnvironment,\n} from '../auth/session';\nimport type { CliContext } from '../context';\nimport {\n authRequiredError,\n notFoundError,\n usageError,\n validationError,\n} from '../errors';\nimport type { ExitCode } from '../exit-codes';\nimport {\n KNOWLEDGE_PUSH_BATCH,\n type KnowledgeApiResult,\n type KnowledgeArticle,\n KnowledgeListSchema,\n type KnowledgePushArticle,\n type KnowledgePushResult,\n KnowledgePushSchema,\n KnowledgeSearchSchema,\n knowledgeCall,\n} from '../knowledge-api';\nimport { classifyPath } from '../safe-fs';\nimport { resolveSourceTarget } from '../source-target';\nimport { pluralize } from '../ui';\n\nexport const KNOWLEDGE_SUBCOMMANDS = [\n 'list',\n 'push',\n 'search',\n 'pull',\n] as const;\ntype KnowledgeSubcommand = (typeof KNOWLEDGE_SUBCOMMANDS)[number];\n\nexport interface KnowledgeArgs {\n host?: string;\n source?: string;\n subcommand?: string;\n /** `push <dir>`, `search <query>`. */\n target?: string;\n /** A team's slug: that team's collection instead of the Source's General one. */\n team?: string;\n /** `pull`: where to write (default `knowledge/<team|general>`). */\n out?: string;\n dir: string;\n env: Record<string, string | undefined>;\n}\n\nconst USAGE =\n 'Run one of: ekanos knowledge list [--team <slug>] | push <dir> [--team <slug>] | search <query> --team <slug> | pull [--team <slug>] [--out <dir>].';\n\n/**\n * `knowledge` — a Source's Knowledge base (plan 244): the General collection\n * every team reads, and with `--team` one team's own collection. `push`\n * turns a folder of markdown files into articles — one per `.md`, keyed by\n * its path, so pushing the same folder again only changes what changed —\n * and `pull` writes them back the same way. `search` runs as a team's\n * agents do: that team's articles plus General.\n */\nexport async function runKnowledge(\n ctx: CliContext,\n args: KnowledgeArgs,\n): Promise<ExitCode> {\n const subcommand = parseSubcommand(args.subcommand);\n const team = args.team?.trim() || undefined;\n\n // Read the folder before any network call, so a bad one fails fast.\n const files =\n subcommand === 'push'\n ? readKnowledgeFolder(requireTarget(args.target, 'push'), args.dir)\n : null;\n if (subcommand === 'search') {\n requireTarget(args.target, 'search');\n if (!team) {\n throw usageError(\n 'knowledge search needs --team <slug>.',\n 'Search runs as that team’s agents see it: its articles plus the General collection.',\n );\n }\n }\n\n const env = resolveAuthEnvironment(ctx, args.host, args.env, {\n projectDir: args.dir,\n });\n const resolved = await resolveSourceTarget(\n env,\n requireSession(env.store, env.host),\n args,\n );\n const client = new KnowledgeClient(env, resolved.session);\n const source = resolved.source;\n const send = <T>(\n schema: z.ZodType<T>,\n init: Parameters<typeof knowledgeCall>[3],\n ) => client.call((token) => knowledgeCall(env.host, token, schema, init));\n const where = (slug: string) => (team ? `team ${team}` : `${slug} General`);\n\n switch (subcommand) {\n case 'list': {\n const list = await send(KnowledgeListSchema, {\n method: 'GET',\n query: { source, team },\n });\n if (list.articles.length === 0) {\n return ctx.succeed(\n { host: env.host, ...list },\n ctx.ui.result({\n title: `No Knowledge articles in ${where(list.source.slug)} yet`,\n tone: 'warn',\n notes: [\n `\"ekanos knowledge push <dir>${team ? ` --team ${team}` : ''}\" stores a folder of .md files.`,\n ],\n }),\n );\n }\n return ctx.succeed(\n { host: env.host, ...list },\n [\n ctx.ui.bold(\n `${pluralize(list.articles.length, 'article')} in ${where(list.source.slug)}`,\n ),\n ctx.ui.table(\n ['Slug', 'Title', 'Tags', 'Version', 'Updated'],\n list.articles.map((article) => [\n article.slug,\n article.title,\n article.tags.join(', '),\n `v${article.version}`,\n article.updatedAt.slice(0, 10),\n ]),\n ),\n ].join('\\n'),\n );\n }\n\n case 'search': {\n const query = args.target!;\n const found = await send(KnowledgeSearchSchema, {\n method: 'GET',\n path: '/search',\n query: { source, team, q: query },\n });\n if (found.results.length === 0) {\n return ctx.succeed(\n { host: env.host, ...found },\n ctx.ui.result({\n title: `Nothing in team ${team}'s Knowledge matches \"${query}\"`,\n tone: 'warn',\n notes: ['Try fewer or different words.'],\n }),\n );\n }\n return ctx.succeed(\n { host: env.host, ...found },\n ctx.ui.result({\n title: `${found.results.length === 1 ? '1 match' : `${found.results.length} matches`} for \"${query}\" in team ${team}`,\n rows: found.results.map((hit) => [\n `${hit.title} (${hit.collection === 'general' ? 'General' : 'team'})`,\n [hit.snippet.replace(/\\s+/g, ' '), ctx.ui.dim(hit.slug)],\n ]),\n }),\n );\n }\n\n case 'push': {\n const articles = files!;\n const results: KnowledgePushResult[] = [];\n let pushed: { source: { slug: string } } | null = null;\n for (\n let start = 0;\n start < articles.length;\n start += KNOWLEDGE_PUSH_BATCH\n ) {\n const batch = articles.slice(start, start + KNOWLEDGE_PUSH_BATCH);\n const response = await send(KnowledgePushSchema, {\n method: 'POST',\n query: {},\n body: {\n source,\n ...(team ? { team } : {}),\n articles: batch.map(({ file: _file, ...article }) => article),\n },\n });\n pushed = response;\n results.push(...response.results);\n }\n const count = (status: KnowledgePushResult['status']) =>\n results.filter((result) => result.status === status).length;\n const refused = results.filter((result) => result.status === 'refused');\n return ctx.succeed(\n { host: env.host, team: team ?? null, results },\n ctx.ui.result({\n title: `Pushed ${pluralize(results.length, 'article')} to ${where(pushed?.source.slug ?? source)}`,\n tone: refused.length > 0 ? 'warn' : 'ok',\n rows: [\n ['Created', String(count('created'))],\n ['Updated', String(count('updated'))],\n ['Unchanged', String(count('unchanged'))],\n ...(refused.length > 0\n ? ([\n [\n 'Refused',\n refused.map(\n (result) => `${result.slug}: ${result.message ?? ''}`,\n ),\n ],\n ] as const)\n : []),\n ],\n notes: [\n team\n ? `Team ${team}'s agents with search_knowledge find these now.`\n : 'Every team on the Source reads the General collection.',\n ],\n }),\n );\n }\n\n case 'pull': {\n const list = await send(KnowledgeListSchema, {\n method: 'GET',\n query: { source, team, body: '1' },\n });\n const outDir = path.resolve(\n args.dir,\n args.out ?? path.join('knowledge', team ?? 'general'),\n );\n const written = writeKnowledgeFolder(outDir, list.articles);\n return ctx.succeed(\n { host: env.host, team: team ?? null, dir: outDir, files: written },\n ctx.ui.result({\n title: `Pulled ${pluralize(written.length, 'article')} from ${where(list.source.slug)}`,\n rows: [['Into', outDir]],\n notes: [\n `Edit, then \"ekanos knowledge push ${path.relative(args.dir, outDir) || '.'}${team ? ` --team ${team}` : ''}\".`,\n ],\n }),\n );\n }\n }\n}\n\n/** Every call refreshes the session once on a 401, then gives up. */\nclass KnowledgeClient {\n constructor(\n private readonly env: AuthEnvironment,\n private session: StoredSession,\n ) {}\n\n async call<T>(\n send: (accessToken: string) => Promise<KnowledgeApiResult<T>>,\n ): Promise<T> {\n let result = await send(this.session.accessToken);\n if (result.status === 'unauthorized') {\n this.session = await refreshStoredSession(this.env, this.session);\n result = await send(this.session.accessToken);\n }\n if (result.status === 'ok') return result.data;\n throw authRequiredError(\n `The session for ${this.env.host} could not authorize the knowledge call.`,\n `Run \"ekanos login --host ${this.env.host}\" and retry.`,\n );\n }\n}\n\n/** Mirrors the host's `knowledgeSlug`: a file's path without `.md`. */\nexport function knowledgeSlugFromPath(relative: string): string {\n return relative\n .trim()\n .toLowerCase()\n .replace(/\\\\/g, '/')\n .replace(/\\.(md|markdown|mdx)$/, '')\n .normalize('NFKD')\n .replace(/[̀-ͯ]/g, '')\n .replace(/[^a-z0-9._/-]+/g, '-')\n .replace(/\\/+/g, '/')\n .replace(/-{2,}/g, '-')\n .split('/')\n .map((part) => part.replace(/^[-.]+|[-.]+$/g, ''))\n .filter(Boolean)\n .join('/')\n .slice(0, 200);\n}\n\nconst IGNORED = new Set(['node_modules', '.git']);\nconst MARKDOWN = /\\.(md|markdown|mdx)$/i;\nconst BODY_MAX = 200_000;\n\n/**\n * Every markdown file under `target`, as articles. The title is the\n * frontmatter `title:`, else the first `# heading` (taken out of the body so\n * it is not shown twice), else the file name; tags are the frontmatter\n * `tags:` (`[a, b]`, `a, b` or a `- a` list).\n */\nexport function readKnowledgeFolder(\n target: string,\n cwd: string,\n): (KnowledgePushArticle & { file: string })[] {\n const root = path.resolve(cwd, target);\n const kind = classifyPath(root);\n if (kind !== 'directory' && kind !== 'file') {\n throw notFoundError(\n `No folder or file at ${root}.`,\n 'Pass a folder of .md files, e.g. \"ekanos knowledge push ./knowledge\".',\n );\n }\n\n const found: string[] = [];\n const walk = (dir: string) => {\n for (const name of fs.readdirSync(dir).sort()) {\n if (IGNORED.has(name) || name.startsWith('.')) continue;\n const full = path.join(dir, name);\n const entry = classifyPath(full);\n if (entry === 'directory') walk(full);\n else if (entry === 'file' && MARKDOWN.test(name)) found.push(full);\n }\n };\n if (kind === 'file') found.push(root);\n else walk(root);\n\n if (found.length === 0) {\n throw validationError(\n `No .md files under ${root}.`,\n 'Each markdown file becomes one article.',\n );\n }\n\n const base = kind === 'file' ? path.dirname(root) : root;\n const seen = new Map<string, string>();\n return found.map((file) => {\n const relative = path.relative(base, file).split(path.sep).join('/');\n const markdown = fs.readFileSync(file, 'utf8');\n const { fields, tags, body } = splitFrontmatter(markdown);\n const slug = knowledgeSlugFromPath(fields.slug || relative);\n if (!slug) {\n throw validationError(\n `${file}: no usable slug.`,\n 'Rename the file with letters or digits.',\n );\n }\n const clash = seen.get(slug);\n if (clash) {\n throw validationError(\n `${file} and ${clash} both become \"${slug}\".`,\n 'Rename one of them.',\n );\n }\n seen.set(slug, file);\n\n const heading = fields.title\n ? null\n : body.match(/^[ \\t]*#[ \\t]+(.+?)[ \\t]*#*[ \\t]*$/m);\n const title = (\n fields.title ||\n heading?.[1] ||\n path.basename(file).replace(MARKDOWN, '').replace(/[-_]+/g, ' ')\n )\n .trim()\n .slice(0, 300);\n const text = (heading ? body.replace(heading[0], '') : body).replace(\n /^\\s*\\n/,\n '',\n );\n if (text.length > BODY_MAX) {\n throw validationError(\n `${file} is longer than ${BODY_MAX.toLocaleString()} characters.`,\n 'Split it into smaller articles.',\n );\n }\n return {\n file,\n slug,\n title: title || slug,\n body: text,\n ...(tags.length > 0 ? { tags: tags.slice(0, 30) } : {}),\n };\n });\n}\n\nfunction writeKnowledgeFolder(\n outDir: string,\n articles: KnowledgeArticle[],\n): string[] {\n const written: string[] = [];\n for (const article of articles) {\n const target = path.resolve(outDir, ...`${article.slug}.md`.split('/'));\n if (!target.startsWith(outDir + path.sep)) {\n throw validationError(\n `Refusing to write ${article.slug} outside ${outDir}.`,\n 'The host sent a slug that leaves the folder.',\n );\n }\n const kind = classifyPath(target);\n if (kind !== 'missing' && kind !== 'file') {\n throw validationError(\n `Refusing to write through ${target} (${kind}).`,\n 'Remove it, or pass --out <dir>.',\n );\n }\n const frontmatter = [\n '---',\n `title: ${JSON.stringify(article.title)}`,\n ...(article.tags.length > 0\n ? [\n `tags: [${article.tags.map((tag) => JSON.stringify(tag)).join(', ')}]`,\n ]\n : []),\n '---',\n '',\n ].join('\\n');\n fs.mkdirSync(path.dirname(target), { recursive: true });\n fs.writeFileSync(target, frontmatter + (article.body ?? ''));\n written.push(target);\n }\n return written;\n}\n\nconst FRONTMATTER = /^?---[ \\t]*\\r?\\n([\\s\\S]*?)\\r?\\n---[ \\t]*(?:\\r?\\n|$)/;\n\n/** Top-level `title`, `slug` and `tags` only. */\nfunction splitFrontmatter(markdown: string): {\n fields: Record<string, string>;\n tags: string[];\n body: string;\n} {\n const match = markdown.match(FRONTMATTER);\n if (!match) return { fields: {}, tags: [], body: markdown };\n const fields: Record<string, string> = {};\n const tags: string[] = [];\n const lines = match[1]!.split(/\\r?\\n/);\n for (let index = 0; index < lines.length; index += 1) {\n const pair = lines[index]!.match(/^([A-Za-z0-9_-]+):[ \\t]*(.*)$/);\n if (!pair) continue;\n const key = pair[1]!.toLowerCase();\n const value = pair[2]!.trim();\n if (key === 'tags') {\n if (value) {\n tags.push(...value.replace(/^\\[|\\]$/g, '').split(','));\n }\n while (\n index + 1 < lines.length &&\n /^[ \\t]*-[ \\t]+/.test(lines[index + 1]!)\n ) {\n tags.push(lines[index + 1]!.replace(/^[ \\t]*-[ \\t]+/, ''));\n index += 1;\n }\n continue;\n }\n fields[key] = unquote(value);\n }\n return {\n fields,\n tags: [...new Set(tags.map((tag) => unquote(tag.trim())).filter(Boolean))],\n body: markdown.slice(match[0].length),\n };\n}\n\nfunction unquote(value: string): string {\n if (value.length >= 2 && value.startsWith('\"') && value.endsWith('\"')) {\n try {\n const parsed: unknown = JSON.parse(value);\n if (typeof parsed === 'string') return parsed;\n } catch {\n // Not JSON-escaped: strip the quotes below.\n }\n }\n if (\n value.length >= 2 &&\n ((value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\")))\n ) {\n return value.slice(1, -1);\n }\n return value;\n}\n\nfunction parseSubcommand(value: string | undefined): KnowledgeSubcommand {\n const match = KNOWLEDGE_SUBCOMMANDS.find((name) => name === value);\n if (match) return match;\n throw usageError(\n value\n ? `Unknown knowledge subcommand \"${value}\".`\n : 'knowledge needs a subcommand.',\n USAGE,\n );\n}\n\nfunction requireTarget(value: string | undefined, verb: 'push' | 'search') {\n if (value?.trim()) return value;\n throw usageError(\n verb === 'push'\n ? 'knowledge push needs a folder of .md files.'\n : 'knowledge search needs a query.',\n verb === 'push'\n ? 'e.g. \"ekanos knowledge push ./knowledge --team first-bank\".'\n : 'e.g. \"ekanos knowledge search \\\\\"wire cutoff\\\\\" --team first-bank\".',\n );\n}\n"]}
1
+ {"version":3,"file":"knowledge.js","sourceRoot":"","sources":["../../src/commands/knowledge.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport type { z } from 'zod';\n\nimport type { StoredSession } from '../auth/credential-store';\nimport { loginHint } from '../auth/host-trust';\nimport {\n type AuthEnvironment,\n refreshStoredSession,\n requireSession,\n resolveAuthEnvironment,\n} from '../auth/session';\nimport type { CliContext } from '../context';\nimport {\n authRequiredError,\n invalidStateError,\n notFoundError,\n usageError,\n validationError,\n} from '../errors';\nimport type { ExitCode } from '../exit-codes';\nimport {\n KNOWLEDGE_PUSH_BATCH,\n type KnowledgeApiResult,\n type KnowledgeArticle,\n KnowledgeListSchema,\n type KnowledgePruneArticle,\n KnowledgePruneSchema,\n type KnowledgePushArticle,\n type KnowledgePushResult,\n KnowledgePushSchema,\n KnowledgeSearchSchema,\n isSafeKnowledgeSlug,\n knowledgeCall,\n} from '../knowledge-api';\nimport { type PulledFile, writePulledFiles } from '../pull-write';\nimport { classifyPath } from '../safe-fs';\nimport { resolveSourceTarget } from '../source-target';\nimport { type ResultRow, pluralize } from '../ui';\nimport { promptYesNo } from './publish';\n\nexport const KNOWLEDGE_SUBCOMMANDS = [\n 'list',\n 'push',\n 'search',\n 'pull',\n] as const;\ntype KnowledgeSubcommand = (typeof KNOWLEDGE_SUBCOMMANDS)[number];\n\nexport interface KnowledgeArgs {\n host?: string;\n source?: string;\n subcommand?: string;\n /** `push <dir>`, `search <query>`. */\n target?: string;\n /** A team's slug: that team's collection instead of the Source's General one. */\n team?: string;\n /** `pull`: where to write (default `knowledge/<team|general>`). */\n out?: string;\n /** `pull`: overwrite local files that differ from the host's. */\n force?: boolean;\n /**\n * `push --prune`: also delete the articles an earlier push of this folder\n * made whose files are gone. Never an article written in the app, and\n * never one edited there since the push.\n */\n prune?: boolean;\n /** `push --prune`: delete without asking (a script or agent run). */\n yes?: boolean;\n dir: string;\n env: Record<string, string | undefined>;\n stdinIsTTY?: boolean;\n confirm?: (question: string) => Promise<boolean>;\n}\n\nconst USAGE =\n 'Run one of: ekanos knowledge list [--team <slug>] | push <dir> [--team <slug>] [--prune [--yes]] | search <query> --team <slug> | pull [--team <slug>] [--out <dir>] [--force].';\n\n/**\n * `knowledge` — a Source's Knowledge base (plan 244): the General collection\n * every team reads, and with `--team` one team's own collection. `push`\n * turns a folder of markdown files into articles — one per `.md`, keyed by\n * its path, so pushing the same folder again only changes what changed —\n * and `pull` writes them back the same way. `search` runs as a team's\n * agents do: that team's articles plus General.\n */\nexport async function runKnowledge(\n ctx: CliContext,\n args: KnowledgeArgs,\n): Promise<ExitCode> {\n const subcommand = parseSubcommand(args.subcommand);\n const team = args.team?.trim() || undefined;\n\n // Read the folder before any network call, so a bad one fails fast.\n const files =\n subcommand === 'push'\n ? readKnowledgeFolder(requireTarget(args.target, 'push'), args.dir)\n : null;\n if (args.prune && subcommand !== 'push') {\n throw usageError(\n '--prune goes with knowledge push.',\n 'Run \"ekanos knowledge push <dir> --prune\".',\n );\n }\n const origin =\n subcommand === 'push' ? knowledgeOrigin(args.target!, args.dir) : null;\n if (\n args.prune &&\n classifyPath(path.resolve(args.dir, args.target!)) !== 'directory'\n ) {\n throw usageError(\n '--prune needs a folder: it removes the articles whose files are gone from it.',\n 'Pass the folder you push, e.g. \"ekanos knowledge push ./knowledge --prune\".',\n );\n }\n if (subcommand === 'search') {\n requireTarget(args.target, 'search');\n if (!team) {\n throw usageError(\n 'knowledge search needs --team <slug>.',\n 'Search runs as that team’s agents see it: its articles plus the General collection.',\n );\n }\n }\n\n const env = resolveAuthEnvironment(ctx, args.host, args.env, {\n projectDir: args.dir,\n });\n const resolved = await resolveSourceTarget(env, requireSession(env), args);\n const client = new KnowledgeClient(env, resolved.session);\n const source = resolved.source;\n const send = <T>(\n schema: z.ZodType<T>,\n init: Parameters<typeof knowledgeCall>[3],\n ) => client.call((token) => knowledgeCall(env.host, token, schema, init));\n const where = (slug: string) => (team ? `team ${team}` : `${slug} General`);\n\n switch (subcommand) {\n case 'list': {\n const list = await send(KnowledgeListSchema, {\n method: 'GET',\n query: { source, team },\n });\n if (list.articles.length === 0) {\n return ctx.succeed(\n { host: env.host, ...list },\n ctx.ui.result({\n title: `No Knowledge articles in ${where(list.source.slug)} yet`,\n tone: 'warn',\n notes: [\n `\"ekanos knowledge push <dir>${team ? ` --team ${team}` : ''}\" stores a folder of .md files.`,\n ],\n }),\n );\n }\n return ctx.succeed(\n { host: env.host, ...list },\n [\n ctx.ui.bold(\n `${pluralize(list.articles.length, 'article')} in ${where(list.source.slug)}`,\n ),\n ctx.ui.table(\n ['Slug', 'Title', 'Tags', 'Version', 'Updated'],\n list.articles.map((article) => [\n article.slug,\n article.title,\n article.tags.join(', '),\n `v${article.version}`,\n article.updatedAt.slice(0, 10),\n ]),\n ),\n ].join('\\n'),\n );\n }\n\n case 'search': {\n const query = args.target!;\n const found = await send(KnowledgeSearchSchema, {\n method: 'GET',\n path: '/search',\n query: { source, team, q: query },\n });\n if (found.results.length === 0) {\n return ctx.succeed(\n { host: env.host, ...found },\n ctx.ui.result({\n title: `Nothing in team ${team}'s Knowledge matches \"${query}\"`,\n tone: 'warn',\n notes: ['Try fewer or different words.'],\n }),\n );\n }\n return ctx.succeed(\n { host: env.host, ...found },\n ctx.ui.result({\n title: `${found.results.length === 1 ? '1 match' : `${found.results.length} matches`} for \"${query}\" in team ${team}`,\n rows: found.results.map((hit) => [\n `${hit.title} (${hit.collection === 'general' ? 'General' : 'team'})`,\n [hit.snippet.replace(/\\s+/g, ' '), ctx.ui.dim(hit.slug)],\n ]),\n }),\n );\n }\n\n case 'push': {\n const articles = files!;\n const keep = articles.map((article) => article.slug);\n const prune = (deleteSlugs?: string[]) =>\n send(KnowledgePruneSchema, {\n method: 'POST',\n path: '/prune',\n query: {},\n body: {\n source,\n ...(team ? { team } : {}),\n origin: origin!,\n keep,\n ...(deleteSlugs ? { delete: deleteSlugs } : {}),\n },\n });\n // Ask (or refuse) before anything is written: what would go is known\n // from the folder alone.\n const preview = args.prune ? await prune() : null;\n if (preview && preview.candidates.length > 0) {\n await confirmPrune(ctx, args, {\n candidates: preview.candidates,\n where: where(preview.source.slug),\n command: `ekanos knowledge push ${args.target}${team ? ` --team ${team}` : ''} --source ${source} --prune --yes`,\n });\n }\n\n const results: KnowledgePushResult[] = [];\n let pushed: { source: { slug: string } } | null = null;\n for (\n let start = 0;\n start < articles.length;\n start += KNOWLEDGE_PUSH_BATCH\n ) {\n const batch = articles.slice(start, start + KNOWLEDGE_PUSH_BATCH);\n const response = await send(KnowledgePushSchema, {\n method: 'POST',\n query: {},\n body: {\n source,\n ...(team ? { team } : {}),\n origin: origin!,\n articles: batch.map(({ file: _file, ...article }) => article),\n },\n });\n pushed = response;\n results.push(...response.results);\n }\n const pruned =\n preview && preview.candidates.length > 0\n ? await prune(preview.candidates.map((article) => article.slug))\n : preview;\n const count = (status: KnowledgePushResult['status']) =>\n results.filter((result) => result.status === status).length;\n const refused = results.filter((result) => result.status === 'refused');\n const pruneRows: ResultRow[] = pruned\n ? [\n [\n 'Pruned',\n pruned.deleted.length > 0\n ? pruned.deleted.map((article) => article.slug)\n : '0',\n ],\n ...(pruned.edited.length > 0\n ? ([\n [\n 'Kept',\n pruned.edited.map(\n (article) =>\n `${article.slug}: its file is gone, but it was edited in the app since it was pushed`,\n ),\n ],\n ] as const)\n : []),\n ]\n : [];\n return ctx.succeed(\n {\n host: env.host,\n team: team ?? null,\n origin,\n results,\n ...(pruned\n ? { pruned: pruned.deleted, keptEdited: pruned.edited }\n : {}),\n },\n ctx.ui.result({\n title: `Pushed ${pluralize(results.length, 'article')} to ${where(pushed?.source.slug ?? source)}`,\n tone: refused.length > 0 ? 'warn' : 'ok',\n rows: [\n ['Created', String(count('created'))],\n ['Updated', String(count('updated'))],\n ['Unchanged', String(count('unchanged'))],\n ...(refused.length > 0\n ? ([\n [\n 'Refused',\n refused.map(\n (result) => `${result.slug}: ${result.message ?? ''}`,\n ),\n ],\n ] as const)\n : []),\n ...pruneRows,\n ],\n notes: [\n team\n ? `Team ${team}'s agents with search_knowledge find these now.`\n : \"Every team's agents granted search_knowledge find these now, beside their team's own articles.\",\n ],\n }),\n );\n }\n\n case 'pull': {\n const list = await send(KnowledgeListSchema, {\n method: 'GET',\n query: { source, team, body: '1' },\n });\n const outDir = path.resolve(\n args.dir,\n args.out ?? path.join('knowledge', team ?? 'general'),\n );\n const written = writePulledFiles({\n projectDir: args.dir,\n outDir,\n files: knowledgeFiles(list.articles),\n force: args.force === true,\n rerun: `ekanos knowledge pull${team ? ` --team ${team}` : ''}`,\n });\n return ctx.succeed(\n { host: env.host, team: team ?? null, dir: outDir, files: written },\n ctx.ui.result({\n title: `Pulled ${pluralize(written.length, 'article')} from ${where(list.source.slug)}`,\n rows: [['Into', outDir]],\n notes: [\n `Edit, then \"ekanos knowledge push ${path.relative(args.dir, outDir) || '.'}${team ? ` --team ${team}` : ''}\".`,\n ],\n }),\n );\n }\n }\n}\n\n/**\n * Which folder a push came from, as the host records it on each article\n * (`synced_from`): the path as named from the project directory, so pushing\n * the same folder from the same place is the same origin. `--prune` only\n * ever removes articles of that origin.\n */\nexport function knowledgeOrigin(target: string, cwd: string): string {\n const relative = path\n .relative(cwd, path.resolve(cwd, target))\n .split(path.sep)\n .join('/');\n return (relative || '.').slice(-300);\n}\n\n/**\n * Pruning deletes articles. A person is asked, listing them; a script or\n * agent run needs `--yes`, and is refused before anything is written.\n */\nasync function confirmPrune(\n ctx: CliContext,\n args: KnowledgeArgs,\n target: {\n candidates: KnowledgePruneArticle[];\n where: string;\n command: string;\n },\n) {\n if (args.yes) return;\n const list = target.candidates.map((article) => article.slug);\n const what = `--prune would delete ${pluralize(list.length, 'article')} from ${target.where} whose files are gone: ${list.join(', ')}`;\n const canPrompt =\n !ctx.jsonMode && (args.stdinIsTTY ?? process.stdin.isTTY === true);\n if (!canPrompt) {\n throw invalidStateError(\n `${what}. Nothing was pushed or deleted.`,\n `Re-run with --yes: ${target.command}`,\n );\n }\n const sure = await (args.confirm ?? promptYesNo)(\n `${what}. Delete them? [y/N]`,\n );\n if (!sure) {\n throw validationError(\n 'Nothing was pushed or deleted.',\n 'Run it again without --prune to push only.',\n );\n }\n}\n\n/** Every call refreshes the session once on a 401, then gives up. */\nclass KnowledgeClient {\n constructor(\n private readonly env: AuthEnvironment,\n private session: StoredSession,\n ) {}\n\n async call<T>(\n send: (accessToken: string) => Promise<KnowledgeApiResult<T>>,\n ): Promise<T> {\n let result = await send(this.session.accessToken);\n if (result.status === 'unauthorized') {\n this.session = await refreshStoredSession(this.env, this.session);\n result = await send(this.session.accessToken);\n }\n if (result.status === 'ok') return result.data;\n throw authRequiredError(\n `The session for ${this.env.host} could not authorize the knowledge call.`,\n loginHint(this.env),\n );\n }\n}\n\n/** Mirrors the host's `knowledgeSlug`: a file's path without `.md`. */\nexport function knowledgeSlugFromPath(relative: string): string {\n return relative\n .trim()\n .toLowerCase()\n .replace(/\\\\/g, '/')\n .replace(/\\.(md|markdown|mdx)$/, '')\n .normalize('NFKD')\n .replace(/[̀-ͯ]/g, '')\n .replace(/[^a-z0-9._/-]+/g, '-')\n .replace(/\\/+/g, '/')\n .replace(/-{2,}/g, '-')\n .split('/')\n .map((part) => part.replace(/^[-.]+|[-.]+$/g, ''))\n .filter(Boolean)\n .join('/')\n .slice(0, 200);\n}\n\nconst IGNORED = new Set(['node_modules', '.git']);\nconst MARKDOWN = /\\.(md|markdown|mdx)$/i;\nconst BODY_MAX = 200_000;\n\n/**\n * Every markdown file under `target`, as articles. The title is the\n * frontmatter `title:`, else the first `# heading` (taken out of the body so\n * it is not shown twice), else the file name; tags are the frontmatter\n * `tags:` (`[a, b]`, `a, b` or a `- a` list).\n */\nexport function readKnowledgeFolder(\n target: string,\n cwd: string,\n): (KnowledgePushArticle & { file: string })[] {\n const root = path.resolve(cwd, target);\n const kind = classifyPath(root);\n if (kind !== 'directory' && kind !== 'file') {\n throw notFoundError(\n `No folder or file at ${root}.`,\n 'Pass a folder of .md files, e.g. \"ekanos knowledge push ./knowledge\".',\n );\n }\n\n const found: string[] = [];\n const walk = (dir: string) => {\n for (const name of fs.readdirSync(dir).sort()) {\n if (IGNORED.has(name) || name.startsWith('.')) continue;\n const full = path.join(dir, name);\n const entry = classifyPath(full);\n if (entry === 'directory') walk(full);\n else if (entry === 'file' && MARKDOWN.test(name)) found.push(full);\n }\n };\n if (kind === 'file') {\n const name = path.basename(root);\n if (name.startsWith('.') || !MARKDOWN.test(name)) {\n throw validationError(\n `${root} is not a markdown file, so it was not pushed.`,\n 'Pass a .md, .markdown or .mdx file (not a dotfile), or a folder of them.',\n );\n }\n found.push(root);\n } else walk(root);\n\n if (found.length === 0) {\n throw validationError(\n `No .md files under ${root}.`,\n 'Each markdown file becomes one article.',\n );\n }\n\n const base = kind === 'file' ? path.dirname(root) : root;\n const seen = new Map<string, string>();\n return found.map((file) => {\n const relative = path.relative(base, file).split(path.sep).join('/');\n const markdown = fs.readFileSync(file, 'utf8');\n const { fields, tags, body } = splitFrontmatter(markdown);\n const slug = knowledgeSlugFromPath(fields.slug || relative);\n if (!slug) {\n throw validationError(\n `${file}: no usable slug.`,\n 'Rename the file with letters or digits.',\n );\n }\n const clash = seen.get(slug);\n if (clash) {\n throw validationError(\n `${file} and ${clash} both become \"${slug}\".`,\n 'Rename one of them.',\n );\n }\n seen.set(slug, file);\n\n const heading = fields.title\n ? null\n : body.match(/^[ \\t]*#[ \\t]+(.+?)[ \\t]*#*[ \\t]*$/m);\n const title = (\n fields.title ||\n heading?.[1] ||\n path.basename(file).replace(MARKDOWN, '').replace(/[-_]+/g, ' ')\n )\n .trim()\n .slice(0, 300);\n const text = (heading ? body.replace(heading[0], '') : body).replace(\n /^\\s*\\n/,\n '',\n );\n if (text.length > BODY_MAX) {\n throw validationError(\n `${file} is longer than ${BODY_MAX.toLocaleString()} characters.`,\n 'Split it into smaller articles.',\n );\n }\n return {\n file,\n slug,\n title: title || slug,\n body: text,\n ...(tags.length > 0 ? { tags: tags.slice(0, 30) } : {}),\n };\n });\n}\n\n/** One `.md` per article: its title and tags as frontmatter, then the body. */\nfunction knowledgeFiles(articles: KnowledgeArticle[]): PulledFile[] {\n const unsafe = articles.filter(\n (article) => !isSafeKnowledgeSlug(article.slug),\n );\n if (unsafe.length > 0) {\n throw validationError(\n `Refusing to pull: the host sent ${unsafe.length === 1 ? 'a slug' : 'slugs'} that cannot be a file path (${unsafe.map((article) => JSON.stringify(article.slug)).join(', ')}).`,\n 'Nothing was written. A knowledge slug is lowercase letters, digits, \".\", \"_\", \"-\" and \"/\" folders, with no \".\" or \"..\" folder.',\n );\n }\n return articles.map((article) => {\n const frontmatter = [\n '---',\n `title: ${JSON.stringify(article.title)}`,\n ...(article.tags.length > 0\n ? [\n `tags: [${article.tags.map((tag) => JSON.stringify(tag)).join(', ')}]`,\n ]\n : []),\n '---',\n '',\n ].join('\\n');\n return {\n path: `${article.slug}.md`,\n contents: frontmatter + (article.body ?? ''),\n };\n });\n}\n\nconst FRONTMATTER = /^?---[ \\t]*\\r?\\n([\\s\\S]*?)\\r?\\n---[ \\t]*(?:\\r?\\n|$)/;\n\n/** Top-level `title`, `slug` and `tags` only. */\nfunction splitFrontmatter(markdown: string): {\n fields: Record<string, string>;\n tags: string[];\n body: string;\n} {\n const match = markdown.match(FRONTMATTER);\n if (!match) return { fields: {}, tags: [], body: markdown };\n const fields: Record<string, string> = {};\n const tags: string[] = [];\n const lines = match[1]!.split(/\\r?\\n/);\n for (let index = 0; index < lines.length; index += 1) {\n const pair = lines[index]!.match(/^([A-Za-z0-9_-]+):[ \\t]*(.*)$/);\n if (!pair) continue;\n const key = pair[1]!.toLowerCase();\n const value = pair[2]!.trim();\n if (key === 'tags') {\n if (value) {\n tags.push(...value.replace(/^\\[|\\]$/g, '').split(','));\n }\n while (\n index + 1 < lines.length &&\n /^[ \\t]*-[ \\t]+/.test(lines[index + 1]!)\n ) {\n tags.push(lines[index + 1]!.replace(/^[ \\t]*-[ \\t]+/, ''));\n index += 1;\n }\n continue;\n }\n fields[key] = unquote(value);\n }\n return {\n fields,\n tags: [...new Set(tags.map((tag) => unquote(tag.trim())).filter(Boolean))],\n body: markdown.slice(match[0].length),\n };\n}\n\nfunction unquote(value: string): string {\n if (value.length >= 2 && value.startsWith('\"') && value.endsWith('\"')) {\n try {\n const parsed: unknown = JSON.parse(value);\n if (typeof parsed === 'string') return parsed;\n } catch {\n // Not JSON-escaped: strip the quotes below.\n }\n }\n if (\n value.length >= 2 &&\n ((value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\")))\n ) {\n return value.slice(1, -1);\n }\n return value;\n}\n\nfunction parseSubcommand(value: string | undefined): KnowledgeSubcommand {\n const match = KNOWLEDGE_SUBCOMMANDS.find((name) => name === value);\n if (match) return match;\n throw usageError(\n value\n ? `Unknown knowledge subcommand \"${value}\".`\n : 'knowledge needs a subcommand.',\n USAGE,\n );\n}\n\nfunction requireTarget(value: string | undefined, verb: 'push' | 'search') {\n if (value?.trim()) return value;\n throw usageError(\n verb === 'push'\n ? 'knowledge push needs a folder of .md files.'\n : 'knowledge search needs a query.',\n verb === 'push'\n ? 'e.g. \"ekanos knowledge push ./knowledge --team first-bank\".'\n : 'e.g. \"ekanos knowledge search \\\\\"wire cutoff\\\\\" --team first-bank\".',\n );\n}\n"]}
@@ -17,6 +17,10 @@ export interface LoginArgs {
17
17
  claude?: boolean;
18
18
  /** Test seam for the Claude Code offer's question. */
19
19
  confirm?: (question: string) => Promise<boolean>;
20
+ /** `--trust-host`: sign in to a host you have never used without the question. */
21
+ trustHost?: boolean;
22
+ /** Test seam for the new-host question. */
23
+ confirmHost?: (question: string) => Promise<boolean>;
20
24
  }
21
25
  /**
22
26
  * `login` — authenticate this machine against an Ekanos deployment via the
@@ -1,9 +1,10 @@
1
1
  import { pollForDeviceTokens, startDeviceAuthorization, } from '../auth/device-flow.js';
2
2
  import { createEkanosSession, fetchCliAuthConfig } from '../auth/ekanos-api.js';
3
- import { resolveAuthEnvironment, resolveIdentity } from '../auth/session.js';
3
+ import { isKnownHost } from '../auth/host-trust.js';
4
+ import { resolveAuthEnvironment, resolveIdentity, } from '../auth/session.js';
4
5
  import { printBanner } from '../banner.js';
5
6
  import { claudeDocsRow, offerDocsToClaude } from '../claude-docs.js';
6
- import { CliError } from '../errors.js';
7
+ import { CliError, invalidStateError, validationError } from '../errors.js';
7
8
  import { ERROR_CODES } from '../exit-codes.js';
8
9
  import { shortHost } from '../ui.js';
9
10
  import { canWizard, wizard } from '../wizard.js';
@@ -69,6 +70,7 @@ export async function runLogin(ctx, args) {
69
70
  }
70
71
  }
71
72
  const config = await fetchCliAuthConfig(env.host);
73
+ await confirmNewHost(ctx, args, env, config.auth0Domain);
72
74
  const authorization = await startDeviceAuthorization(config);
73
75
  const openUrl = (_b = authorization.verificationUriComplete) !== null && _b !== void 0 ? _b : authorization.verificationUri;
74
76
  if (useWizard) {
@@ -133,6 +135,32 @@ export async function runLogin(ctx, args) {
133
135
  ],
134
136
  }));
135
137
  }
138
+ /**
139
+ * A login sends the Auth0 access token, and later the refresh token, to the
140
+ * host. A hostile host can return Ekanos's real Auth0 domain and client id,
141
+ * so the browser step looks genuine and proves nothing about the host. So a
142
+ * host that is neither stored nor a known Ekanos host is shown, with the
143
+ * Auth0 domain it named, and asked about; with nobody to ask (JSON mode, an
144
+ * agent, no TTY) it needs `--trust-host`.
145
+ */
146
+ async function confirmNewHost(ctx, args, env, auth0Domain) {
147
+ var _a, _b;
148
+ if (args.trustHost || isKnownHost(env.store, env.host))
149
+ return;
150
+ const canPrompt = !ctx.jsonMode && ((_a = args.stdinIsTTY) !== null && _a !== void 0 ? _a : process.stdin.isTTY === true);
151
+ if (!canPrompt) {
152
+ throw invalidStateError(`${env.host} is not a host you have logged in to before, and it ` +
153
+ `signs in through ${auth0Domain}. A login sends your credentials there.`, `Check with the person you work for that ${env.host} is their Ekanos ` +
154
+ `deployment. If it is, re-run: ekanos login --host ${env.host} --trust-host`);
155
+ }
156
+ ctx.log(` Host: ${env.host}`);
157
+ ctx.log(` Sign-in: ${auth0Domain}`);
158
+ const sure = await ((_b = args.confirmHost) !== null && _b !== void 0 ? _b : promptYesNo)(`You have never logged in to ${env.host}. Send your credentials there? [y/N]`);
159
+ if (!sure) {
160
+ throw validationError('Nothing was changed.', `Check the host is the Ekanos deployment you meant, then run ` +
161
+ `"ekanos login --host <url>" again.`);
162
+ }
163
+ }
136
164
  /**
137
165
  * Probe a stored session. Returns null when it cannot be revived, so `login`
138
166
  * falls through to a fresh device flow rather than failing — "log in again"
@@ -1 +1 @@
1
- {"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"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","sourcesContent":["import type { StoredSession } from '../auth/credential-store';\nimport {\n pollForDeviceTokens,\n startDeviceAuthorization,\n} from '../auth/device-flow';\nimport { createEkanosSession, fetchCliAuthConfig } from '../auth/ekanos-api';\nimport { resolveAuthEnvironment, resolveIdentity } from '../auth/session';\nimport { printBanner } from '../banner';\nimport { claudeDocsRow, offerDocsToClaude } from '../claude-docs';\nimport type { CliContext } from '../context';\nimport { CliError } from '../errors';\nimport { ERROR_CODES } from '../exit-codes';\nimport type { ExitCode } from '../exit-codes';\nimport { shortHost } from '../ui';\nimport { canWizard, wizard } from '../wizard';\nimport { promptYesNo } from './publish';\n\nexport interface LoginArgs {\n host?: string;\n /** Start a new browser login even if a working session is already stored. */\n force: boolean;\n /** Where a project's ekanos.json may sit — read-only here, never written. */\n cwd: string;\n env: Record<string, string | undefined>;\n /**\n * Test-only override for the clack wizard gate (`canWizard` in\n * `wizard.ts`) — never set from real CLI wiring. See `RunOptions` in\n * `index.ts`.\n */\n stdinIsTTY?: boolean;\n /** `--claude` adds the Ekanos docs to Claude Code without asking; `--no-claude` skips the offer. */\n claude?: boolean;\n /** Test seam for the Claude Code offer's question. */\n confirm?: (question: string) => Promise<boolean>;\n}\n\n/**\n * `login` — authenticate this machine against an Ekanos deployment via the\n * Auth0 device authorization flow.\n *\n * Idempotent: with a stored session that still authenticates, it reports that\n * and exits 0 without opening a browser. `--force` skips the check.\n *\n * The user code and verification URL go to STDERR. They have to be visible\n * even when stdout is a pipe into an agent, and stdout in this CLI carries\n * exactly one JSON object.\n *\n * In human mode at a real TTY (`canWizard`), this runs behind a clack\n * `intro`/`outro` flow with a spinner over the approval wait — see\n * `wizard.ts`. The device code and URL themselves stay plain, copy-pasteable\n * text (a clack `note`, not a prompt) either way: a partner needs to select\n * and open them, not interact with them through clack. JSON mode and human\n * non-TTY mode are byte-for-byte what they were before this existed.\n */\nexport async function runLogin(\n ctx: CliContext,\n args: LoginArgs,\n): Promise<ExitCode> {\n // No `projectDir`: login is the one verb that CREATES credentials, so its\n // host must come from something the developer chose (--host, EKANOS_HOST)\n // or already trusts (the sole stored login) — never from a committed\n // ekanos.json, which an attacker can set in any cloned repo to point the\n // device flow at their own tenant. Every other verb may read the project\n // host because their bearer tokens are looked up per-host from the store:\n // an unfamiliar host simply has no session to present.\n const env = resolveAuthEnvironment(ctx, args.host, args.env);\n\n const useWizard = canWizard(ctx, { stdinIsTTY: args.stdinIsTTY });\n if (useWizard) wizard.intro('ekanos login');\n\n if (!args.force) {\n const existing = env.store.read(env.host);\n\n if (existing) {\n const reusable = await tryReuse(env, existing);\n\n if (reusable) {\n printBanner(ctx);\n\n const email = reusable.user.email ?? reusable.user.id;\n if (useWizard) {\n wizard.outro(\n `Already signed in as ${email} on ${shortHost(env.host)}.`,\n );\n }\n\n return ctx.succeed(\n {\n host: env.host,\n status: 'already-authenticated',\n user: reusable.user,\n expiresAt: reusable.expiresAt,\n credentialsPath: env.store.filePath,\n },\n useWizard\n ? ''\n : ctx.ui.result({\n title: 'Already signed in',\n rows: [\n ['Account', email],\n ['Host', shortHost(env.host)],\n ],\n }),\n );\n }\n\n ctx.log(\n ctx.ui.dim('The stored session has expired. Starting a new login…'),\n );\n }\n }\n\n const config = await fetchCliAuthConfig(env.host);\n const authorization = await startDeviceAuthorization(config);\n const openUrl =\n authorization.verificationUriComplete ?? authorization.verificationUri;\n\n if (useWizard) {\n const noteLines = [`Open: ${openUrl}`, `Code: ${authorization.userCode}`];\n if (authorization.verificationUriComplete) {\n noteLines.push(\n `(without the pre-filled code: ${authorization.verificationUri})`,\n );\n }\n wizard.note(noteLines.join('\\n'), 'Device login');\n } else {\n ctx.log('');\n ctx.log(` Open: ${openUrl}`);\n ctx.log(` Code: ${authorization.userCode}`);\n\n if (authorization.verificationUriComplete) {\n ctx.log(\n ` (without the pre-filled code: ${authorization.verificationUri})`,\n );\n }\n\n ctx.log('');\n ctx.log('Waiting for approval…');\n }\n\n const spinner = useWizard ? wizard.spinner() : null;\n spinner?.start('Waiting for approval…');\n\n const tokens = await pollForDeviceTokens(config, authorization);\n const session = await createEkanosSession(env.host, tokens.accessToken);\n\n spinner?.stop(`✓ authenticated as ${session.email ?? session.userId}`);\n\n const stored: StoredSession = {\n auth0RefreshToken: tokens.refreshToken,\n accessToken: session.accessToken,\n expiresAt: session.expiresAt,\n userId: session.userId,\n email: session.email,\n updatedAt: new Date().toISOString(),\n };\n\n env.store.write(env.host, stored);\n\n // A first sign-in is when a developer is setting up: offer the docs to\n // Claude Code once, so it can look Ekanos up instead of guessing.\n const claudeDocs = await offerDocsToClaude({\n choice: args.claude,\n canPrompt:\n !ctx.jsonMode && (args.stdinIsTTY ?? process.stdin.isTTY === true),\n confirm: args.confirm ?? promptYesNo,\n env: args.env,\n });\n const claudeRow = claudeDocsRow(claudeDocs);\n\n printBanner(ctx);\n\n if (useWizard) {\n wizard.outro(\n `Signed in as ${session.email ?? session.userId} on ${shortHost(env.host)}.`,\n );\n }\n\n return ctx.succeed(\n {\n host: env.host,\n status: 'authenticated',\n user: { id: session.userId, email: session.email },\n expiresAt: session.expiresAt,\n credentialsPath: env.store.filePath,\n claudeDocs,\n },\n useWizard\n ? ''\n : ctx.ui.result({\n title: 'Signed in',\n rows: [\n ['Account', session.email ?? session.userId],\n ['Host', shortHost(env.host)],\n ...(claudeRow ? [claudeRow] : []),\n ],\n }),\n );\n}\n\n/**\n * Probe a stored session. Returns null when it cannot be revived, so `login`\n * falls through to a fresh device flow rather than failing — \"log in again\"\n * is the whole point of the command being run.\n *\n * Only auth failures are absorbed. A network or configuration error still\n * propagates: silently starting a browser login because the host was\n * unreachable would be the wrong recovery, and would hide the real fault.\n */\nasync function tryReuse(\n env: ReturnType<typeof resolveAuthEnvironment>,\n existing: StoredSession,\n): Promise<{\n user: { id: string; email: string | null };\n expiresAt: string;\n} | null> {\n try {\n const { identity, session } = await resolveIdentity(env, existing);\n\n return {\n user: { id: identity.id, email: identity.email },\n expiresAt: session.expiresAt,\n };\n } catch (error) {\n if (isAuthFailure(error)) {\n return null;\n }\n\n throw error;\n }\n}\n\nfunction isAuthFailure(error: unknown): boolean {\n return error instanceof CliError && error.code === ERROR_CODES.AUTH_REQUIRED;\n}\n"]}
1
+ {"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"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","sourcesContent":["import type { StoredSession } from '../auth/credential-store';\nimport {\n pollForDeviceTokens,\n startDeviceAuthorization,\n} from '../auth/device-flow';\nimport { createEkanosSession, fetchCliAuthConfig } from '../auth/ekanos-api';\nimport { isKnownHost } from '../auth/host-trust';\nimport {\n type AuthEnvironment,\n resolveAuthEnvironment,\n resolveIdentity,\n} from '../auth/session';\nimport { printBanner } from '../banner';\nimport { claudeDocsRow, offerDocsToClaude } from '../claude-docs';\nimport type { CliContext } from '../context';\nimport { CliError, invalidStateError, validationError } from '../errors';\nimport { ERROR_CODES } from '../exit-codes';\nimport type { ExitCode } from '../exit-codes';\nimport { shortHost } from '../ui';\nimport { canWizard, wizard } from '../wizard';\nimport { promptYesNo } from './publish';\n\nexport interface LoginArgs {\n host?: string;\n /** Start a new browser login even if a working session is already stored. */\n force: boolean;\n /** Where a project's ekanos.json may sit — read-only here, never written. */\n cwd: string;\n env: Record<string, string | undefined>;\n /**\n * Test-only override for the clack wizard gate (`canWizard` in\n * `wizard.ts`) — never set from real CLI wiring. See `RunOptions` in\n * `index.ts`.\n */\n stdinIsTTY?: boolean;\n /** `--claude` adds the Ekanos docs to Claude Code without asking; `--no-claude` skips the offer. */\n claude?: boolean;\n /** Test seam for the Claude Code offer's question. */\n confirm?: (question: string) => Promise<boolean>;\n /** `--trust-host`: sign in to a host you have never used without the question. */\n trustHost?: boolean;\n /** Test seam for the new-host question. */\n confirmHost?: (question: string) => Promise<boolean>;\n}\n\n/**\n * `login` — authenticate this machine against an Ekanos deployment via the\n * Auth0 device authorization flow.\n *\n * Idempotent: with a stored session that still authenticates, it reports that\n * and exits 0 without opening a browser. `--force` skips the check.\n *\n * The user code and verification URL go to STDERR. They have to be visible\n * even when stdout is a pipe into an agent, and stdout in this CLI carries\n * exactly one JSON object.\n *\n * In human mode at a real TTY (`canWizard`), this runs behind a clack\n * `intro`/`outro` flow with a spinner over the approval wait — see\n * `wizard.ts`. The device code and URL themselves stay plain, copy-pasteable\n * text (a clack `note`, not a prompt) either way: a partner needs to select\n * and open them, not interact with them through clack. JSON mode and human\n * non-TTY mode are byte-for-byte what they were before this existed.\n */\nexport async function runLogin(\n ctx: CliContext,\n args: LoginArgs,\n): Promise<ExitCode> {\n // No `projectDir`: login is the one verb that CREATES credentials, so its\n // host must come from something the developer chose (--host, EKANOS_HOST)\n // or already trusts (the sole stored login) — never from a committed\n // ekanos.json, which an attacker can set in any cloned repo to point the\n // device flow at their own tenant. Every other verb may read the project\n // host because their bearer tokens are looked up per-host from the store:\n // an unfamiliar host simply has no session to present.\n const env = resolveAuthEnvironment(ctx, args.host, args.env);\n\n const useWizard = canWizard(ctx, { stdinIsTTY: args.stdinIsTTY });\n if (useWizard) wizard.intro('ekanos login');\n\n if (!args.force) {\n const existing = env.store.read(env.host);\n\n if (existing) {\n const reusable = await tryReuse(env, existing);\n\n if (reusable) {\n printBanner(ctx);\n\n const email = reusable.user.email ?? reusable.user.id;\n if (useWizard) {\n wizard.outro(\n `Already signed in as ${email} on ${shortHost(env.host)}.`,\n );\n }\n\n return ctx.succeed(\n {\n host: env.host,\n status: 'already-authenticated',\n user: reusable.user,\n expiresAt: reusable.expiresAt,\n credentialsPath: env.store.filePath,\n },\n useWizard\n ? ''\n : ctx.ui.result({\n title: 'Already signed in',\n rows: [\n ['Account', email],\n ['Host', shortHost(env.host)],\n ],\n }),\n );\n }\n\n ctx.log(\n ctx.ui.dim('The stored session has expired. Starting a new login…'),\n );\n }\n }\n\n const config = await fetchCliAuthConfig(env.host);\n await confirmNewHost(ctx, args, env, config.auth0Domain);\n const authorization = await startDeviceAuthorization(config);\n const openUrl =\n authorization.verificationUriComplete ?? authorization.verificationUri;\n\n if (useWizard) {\n const noteLines = [`Open: ${openUrl}`, `Code: ${authorization.userCode}`];\n if (authorization.verificationUriComplete) {\n noteLines.push(\n `(without the pre-filled code: ${authorization.verificationUri})`,\n );\n }\n wizard.note(noteLines.join('\\n'), 'Device login');\n } else {\n ctx.log('');\n ctx.log(` Open: ${openUrl}`);\n ctx.log(` Code: ${authorization.userCode}`);\n\n if (authorization.verificationUriComplete) {\n ctx.log(\n ` (without the pre-filled code: ${authorization.verificationUri})`,\n );\n }\n\n ctx.log('');\n ctx.log('Waiting for approval…');\n }\n\n const spinner = useWizard ? wizard.spinner() : null;\n spinner?.start('Waiting for approval…');\n\n const tokens = await pollForDeviceTokens(config, authorization);\n const session = await createEkanosSession(env.host, tokens.accessToken);\n\n spinner?.stop(`✓ authenticated as ${session.email ?? session.userId}`);\n\n const stored: StoredSession = {\n auth0RefreshToken: tokens.refreshToken,\n accessToken: session.accessToken,\n expiresAt: session.expiresAt,\n userId: session.userId,\n email: session.email,\n updatedAt: new Date().toISOString(),\n };\n\n env.store.write(env.host, stored);\n\n // A first sign-in is when a developer is setting up: offer the docs to\n // Claude Code once, so it can look Ekanos up instead of guessing.\n const claudeDocs = await offerDocsToClaude({\n choice: args.claude,\n canPrompt:\n !ctx.jsonMode && (args.stdinIsTTY ?? process.stdin.isTTY === true),\n confirm: args.confirm ?? promptYesNo,\n env: args.env,\n });\n const claudeRow = claudeDocsRow(claudeDocs);\n\n printBanner(ctx);\n\n if (useWizard) {\n wizard.outro(\n `Signed in as ${session.email ?? session.userId} on ${shortHost(env.host)}.`,\n );\n }\n\n return ctx.succeed(\n {\n host: env.host,\n status: 'authenticated',\n user: { id: session.userId, email: session.email },\n expiresAt: session.expiresAt,\n credentialsPath: env.store.filePath,\n claudeDocs,\n },\n useWizard\n ? ''\n : ctx.ui.result({\n title: 'Signed in',\n rows: [\n ['Account', session.email ?? session.userId],\n ['Host', shortHost(env.host)],\n ...(claudeRow ? [claudeRow] : []),\n ],\n }),\n );\n}\n\n/**\n * A login sends the Auth0 access token, and later the refresh token, to the\n * host. A hostile host can return Ekanos's real Auth0 domain and client id,\n * so the browser step looks genuine and proves nothing about the host. So a\n * host that is neither stored nor a known Ekanos host is shown, with the\n * Auth0 domain it named, and asked about; with nobody to ask (JSON mode, an\n * agent, no TTY) it needs `--trust-host`.\n */\nasync function confirmNewHost(\n ctx: CliContext,\n args: LoginArgs,\n env: AuthEnvironment,\n auth0Domain: string,\n): Promise<void> {\n if (args.trustHost || isKnownHost(env.store, env.host)) return;\n\n const canPrompt =\n !ctx.jsonMode && (args.stdinIsTTY ?? process.stdin.isTTY === true);\n\n if (!canPrompt) {\n throw invalidStateError(\n `${env.host} is not a host you have logged in to before, and it ` +\n `signs in through ${auth0Domain}. A login sends your credentials there.`,\n `Check with the person you work for that ${env.host} is their Ekanos ` +\n `deployment. If it is, re-run: ekanos login --host ${env.host} --trust-host`,\n );\n }\n\n ctx.log(` Host: ${env.host}`);\n ctx.log(` Sign-in: ${auth0Domain}`);\n\n const sure = await (args.confirmHost ?? promptYesNo)(\n `You have never logged in to ${env.host}. Send your credentials there? [y/N]`,\n );\n\n if (!sure) {\n throw validationError(\n 'Nothing was changed.',\n `Check the host is the Ekanos deployment you meant, then run ` +\n `\"ekanos login --host <url>\" again.`,\n );\n }\n}\n\n/**\n * Probe a stored session. Returns null when it cannot be revived, so `login`\n * falls through to a fresh device flow rather than failing — \"log in again\"\n * is the whole point of the command being run.\n *\n * Only auth failures are absorbed. A network or configuration error still\n * propagates: silently starting a browser login because the host was\n * unreachable would be the wrong recovery, and would hide the real fault.\n */\nasync function tryReuse(\n env: ReturnType<typeof resolveAuthEnvironment>,\n existing: StoredSession,\n): Promise<{\n user: { id: string; email: string | null };\n expiresAt: string;\n} | null> {\n try {\n const { identity, session } = await resolveIdentity(env, existing);\n\n return {\n user: { id: identity.id, email: identity.email },\n expiresAt: session.expiresAt,\n };\n } catch (error) {\n if (isAuthFailure(error)) {\n return null;\n }\n\n throw error;\n }\n}\n\nfunction isAuthFailure(error: unknown): boolean {\n return error instanceof CliError && error.code === ERROR_CODES.AUTH_REQUIRED;\n}\n"]}
@@ -9,7 +9,15 @@ export interface LogoutArgs {
9
9
  env: Record<string, string | undefined>;
10
10
  }
11
11
  /**
12
- * `logout` — revoke the stored refresh token at Auth0, then delete it locally.
12
+ * `logout` — end the Ekanos session on the server, revoke the stored refresh
13
+ * token at Auth0, then delete both locally.
14
+ *
15
+ * Two revocations, because they stop different things. The Ekanos one
16
+ * (`/api/auth/cli-logout`, `sessionRevoked` in the envelope) marks the
17
+ * server-side session revoked, so no copy of these credentials can be
18
+ * refreshed again — even one already copied off this machine. The Auth0 one
19
+ * (`revoked`) kills the refresh token itself. Neither invalidates the last
20
+ * minted access token before its expiry (an hour at most).
13
21
  *
14
22
  * Idempotent: logging out twice exits 0. `cleared: false` in the envelope says
15
23
  * there was nothing to remove; it is information, not a failure.