@skrr-ai/cli 0.1.120 → 0.1.121

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 (65) hide show
  1. package/dist/base-command.js +11 -2
  2. package/dist/commands/agent/message/context.d.ts +1 -0
  3. package/dist/commands/agent/message/context.js +3 -2
  4. package/dist/commands/search/agents.js +5 -1
  5. package/dist/commands/search/all.js +74 -22
  6. package/dist/commands/search/skills.d.ts +1 -0
  7. package/dist/commands/search/skills.js +12 -1
  8. package/dist/commands/search/spaces.d.ts +8 -1
  9. package/dist/commands/search/spaces.js +50 -12
  10. package/dist/commands/search/tasks.d.ts +13 -2
  11. package/dist/commands/search/tasks.js +78 -14
  12. package/dist/commands/search/teams.js +5 -1
  13. package/dist/commands/search/workspace.d.ts +19 -0
  14. package/dist/commands/search/workspace.js +126 -0
  15. package/dist/commands/skills/browse.d.ts +1 -0
  16. package/dist/commands/skills/browse.js +8 -1
  17. package/dist/commands/skills/search.d.ts +1 -0
  18. package/dist/commands/skills/search.js +12 -1
  19. package/dist/commands/spaces/list.d.ts +1 -0
  20. package/dist/commands/spaces/list.js +12 -3
  21. package/dist/commands/spaces/search.d.ts +1 -0
  22. package/dist/commands/spaces/search.js +9 -0
  23. package/dist/commands/tasks/search.d.ts +1 -0
  24. package/dist/commands/tasks/search.js +9 -0
  25. package/dist/lib/api-error-reason.d.ts +2 -0
  26. package/dist/lib/api-error-reason.js +14 -0
  27. package/dist/lib/api-fetch.js +3 -0
  28. package/dist/lib/search-display.d.ts +7 -0
  29. package/dist/lib/search-display.js +31 -0
  30. package/dist/lib/skill-store-pagination.d.ts +8 -0
  31. package/dist/lib/skill-store-pagination.js +35 -0
  32. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.d.ts +10 -0
  33. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.js +147 -0
  34. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonConsentProof.d.ts +20 -0
  35. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonConsentProof.js +72 -0
  36. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/desktopDaemonProof.d.ts +21 -0
  37. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/desktopDaemonProof.js +96 -0
  38. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.d.ts +2 -3
  39. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +102 -20
  40. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/independentMachineReservation.d.ts +27 -0
  41. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/independentMachineReservation.js +161 -0
  42. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/independentMachineScopes.d.ts +26 -0
  43. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/independentMachineScopes.js +41 -0
  44. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +2 -1
  45. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +15 -0
  46. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/kek/macos.js +20 -4
  47. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.d.ts +10 -0
  48. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.js +172 -1
  49. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonConsentProof.d.ts +20 -0
  50. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonConsentProof.js +43 -0
  51. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/desktopDaemonProof.d.ts +21 -0
  52. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/desktopDaemonProof.js +62 -0
  53. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.d.ts +2 -3
  54. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +133 -25
  55. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/independentMachineReservation.d.ts +27 -0
  56. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/independentMachineReservation.js +132 -0
  57. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/independentMachineScopes.d.ts +26 -0
  58. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/independentMachineScopes.js +15 -0
  59. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +2 -1
  60. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +2 -1
  61. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/kek/macos.js +28 -15
  62. package/dist/node_modules/@skrr-ai/auth-core/package.json +31 -1
  63. package/dist/node_modules/@skrr-ai/data-provider/index.js +7627 -7574
  64. package/oclif.manifest.json +634 -21
  65. package/package.json +1 -1
@@ -33,6 +33,7 @@ const json_mode_1 = require("./lib/json-mode");
33
33
  const failed_lookup_1 = require("./lib/failed-lookup");
34
34
  const internal_id_1 = require("./lib/internal-id");
35
35
  const http_error_1 = require("./lib/http-error");
36
+ const api_error_reason_1 = require("./lib/api-error-reason");
36
37
  const guide_pointer_1 = require("./lib/guide-pointer");
37
38
  // Re-exported from its home in `lib/keychain`, beside the other profile
38
39
  // functions. It lived here while `init()` was its only caller; a lib module
@@ -1722,7 +1723,7 @@ class BaseCommand extends core_1.Command {
1722
1723
  code: bodyCode || 'FORBIDDEN',
1723
1724
  status: e.status,
1724
1725
  exit: 3,
1725
- retryable: edgeRefusal,
1726
+ retryable: typeof body?.retryable === 'boolean' ? body.retryable : edgeRefusal,
1726
1727
  });
1727
1728
  }
1728
1729
  else if (e.status === 404) {
@@ -2229,6 +2230,12 @@ function refusalCodeOf(body) {
2229
2230
  const direct = stringProp(body ?? null, 'code') || stringProp(body ?? null, 'errorType');
2230
2231
  if (direct)
2231
2232
  return direct;
2233
+ // Session-scoped Task routes return `{ok:false, reason}`. This is an
2234
+ // application refusal, not a bodyless edge error. Preserve its spelling so
2235
+ // an Agent can distinguish a stale session from a Task authorization stop.
2236
+ const reason = (0, api_error_reason_1.reasonOnlyCode)(body);
2237
+ if (reason)
2238
+ return reason;
2232
2239
  const legacy = stringProp(body ?? null, 'error');
2233
2240
  return legacy && LEGACY_ERROR_FIELD_CODE.test(legacy) ? legacy.toUpperCase() : undefined;
2234
2241
  }
@@ -2696,7 +2703,9 @@ function errorBodyMessage(body) {
2696
2703
  const nested = record.error && typeof record.error === 'object' && !Array.isArray(record.error)
2697
2704
  ? record.error.message
2698
2705
  : undefined;
2699
- return text(record.message) ?? text(record.error) ?? text(nested);
2706
+ const reason = (0, api_error_reason_1.reasonOnlyCode)(record);
2707
+ return text(record.message) ?? text(record.error) ?? text(nested) ??
2708
+ (reason ? `Request refused: ${reason}` : undefined);
2700
2709
  }
2701
2710
  /**
2702
2711
  * The `details` of a CLI error envelope, in ONE shape. The server's refusal body becomes the
@@ -14,6 +14,7 @@ export default class AgentMessageContext extends BaseCommand {
14
14
  agent: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
15
  task: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
16
  run: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
17
+ json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
17
18
  };
18
19
  run(): Promise<void>;
19
20
  }
@@ -26,8 +26,9 @@ class AgentMessageContext extends base_command_1.BaseCommand {
26
26
  run: core_1.Flags.string({
27
27
  description: 'Optional run context to narrow recent updates (refused inside a daemon session — the session path is already bound to its own run)',
28
28
  }),
29
- // No `--json` flag: these observations exist to be parsed, so JSON is the
30
- // only output. A flag that cannot be turned off is a lie in `--help`.
29
+ json: core_1.Flags.boolean({
30
+ description: 'Accepted for consistency; this command always outputs JSON',
31
+ }),
31
32
  };
32
33
  async run() {
33
34
  const { args, flags } = await this.parse(AgentMessageContext);
@@ -83,8 +83,12 @@ class SearchAgents extends base_command_1.BaseCommand {
83
83
  const items = Array.isArray(r)
84
84
  ? r
85
85
  : (r?.data ?? []);
86
+ const meta = response;
87
+ const partial = Boolean(meta?.partial || meta?.semanticUnavailable);
88
+ if (partial)
89
+ this.warn('Agent store search may be incomplete. Try again or narrow your query.');
86
90
  if (items.length === 0) {
87
- this.log('No agents found.');
91
+ this.log(partial ? 'No agents confirmed in the available search results.' : 'No agents found.');
88
92
  return;
89
93
  }
90
94
  const rows = items.map((item) => ({
@@ -7,6 +7,8 @@ const data_provider_1 = require("@skrr-ai/data-provider");
7
7
  const base_command_1 = require("../../base-command");
8
8
  const format_1 = require("../../lib/format");
9
9
  const web_url_1 = require("../../lib/web-url");
10
+ const skill_store_pagination_1 = require("../../lib/skill-store-pagination");
11
+ const search_display_1 = require("../../lib/search-display");
10
12
  /**
11
13
  * One store document → one row of the merged agent/skill/team table.
12
14
  *
@@ -47,9 +49,9 @@ function storeSearchRow(type, doc) {
47
49
  // `OSK-S-12`) — that is what a person types back into `tasks show` /
48
50
  // `spaces show`, and the UUID is still one `--json` away. Spaces created
49
51
  // before refs existed fall through to the UUID until backfilled.
50
- id: (0, format_1.formatCell)(doc.identifier ?? doc.id ?? doc._id ?? doc.slug),
51
- name: (0, format_1.formatCell)(doc.name ?? doc.title),
52
- description: plainDescription(doc.description),
52
+ id: (0, search_display_1.searchDisplayText)((0, format_1.formatCell)(doc.identifier ?? doc.id ?? doc._id ?? doc.slug)),
53
+ name: (0, search_display_1.searchDisplayText)((0, format_1.formatCell)(doc.name ?? doc.title)),
54
+ description: (0, search_display_1.searchDisplayText)(plainDescription(doc.description)),
53
55
  };
54
56
  }
55
57
  // No single `getStoreAll` exists in data-service — compose by fanning out to
@@ -73,24 +75,44 @@ class SearchAll extends base_command_1.BaseCommand {
73
75
  async run() {
74
76
  this.requireAuth();
75
77
  const { flags } = await this.parse(SearchAll);
78
+ const validBucket = (value) => {
79
+ const rows = Array.isArray(value) ? value
80
+ : value && typeof value === 'object' ? value.data : undefined;
81
+ return Array.isArray(rows) && rows.every((row) => row && typeof row === 'object' && !Array.isArray(row));
82
+ };
76
83
  const items = (b) => Array.isArray(b) ? b : (b?.data ?? []);
77
- let agents;
78
- let skills;
79
- let teams;
80
- let tasks;
81
- let spaces;
82
- try {
83
- [agents, skills, teams, tasks, spaces] = await Promise.all([
84
- data_provider_1.dataService.getStoreAgents({ q: flags.query, limit: flags.limit }),
85
- data_provider_1.dataService.getStoreSkills({ q: flags.query, limit: flags.limit }),
86
- data_provider_1.dataService.getStoreTeams({ q: flags.query, limit: flags.limit }),
87
- data_provider_1.dataService.listTasks({ q: flags.query, limit: flags.limit }),
88
- data_provider_1.dataService.listSpaces({ q: flags.query, limit: flags.limit }),
89
- ]);
90
- }
91
- catch (err) {
92
- this.handleApiError(err);
93
- }
84
+ const names = ['agents', 'skills', 'teams', 'tasks', 'spaces'];
85
+ const outcomes = await Promise.allSettled([
86
+ data_provider_1.dataService.getStoreAgents({ q: flags.query, limit: flags.limit }),
87
+ data_provider_1.dataService.getStoreSkills({ q: flags.query, limit: flags.limit }),
88
+ data_provider_1.dataService.getStoreTeams({ q: flags.query, limit: flags.limit }),
89
+ data_provider_1.dataService.listTasks({ q: flags.query, limit: flags.limit }),
90
+ data_provider_1.dataService.listSpaces({ q: flags.query, limit: flags.limit }),
91
+ ]);
92
+ const [agents, skills, teams, tasks, spaces] = outcomes.map((outcome) => outcome.status === 'fulfilled' && validBucket(outcome.value) ? outcome.value : undefined);
93
+ const failed = names.filter((_, index) => outcomes[index].status === 'rejected' ||
94
+ (outcomes[index].status === 'fulfilled' && !validBucket(outcomes[index].value)));
95
+ const pageInfo = Object.fromEntries(names.map((name, index) => {
96
+ const outcome = outcomes[index];
97
+ const page = outcome.status === 'fulfilled' && !Array.isArray(outcome.value)
98
+ ? outcome.value
99
+ : undefined;
100
+ const skillPage = name === 'skills' && outcome.status === 'fulfilled'
101
+ ? (0, skill_store_pagination_1.skillStorePageState)(outcome.value)
102
+ : undefined;
103
+ return [name, failed.includes(name) ? { status: 'failed', ...(outcome.status === 'fulfilled' ? { reason: 'invalid_response' } : {}) } : {
104
+ status: 'ok', ...(page?.has_more !== undefined ? { has_more: page.has_more } : {}),
105
+ ...(skillPage?.nextCursor ? { next_cursor: skillPage.nextCursor } : page?.next_cursor ? { next_cursor: page.next_cursor } : {}),
106
+ ...(skillPage?.invalidCursor ? { incomplete: true } : {}),
107
+ ...(page?.warnings ? { warnings: page.warnings } : {}),
108
+ ...(page?.degraded ? { degraded: true } : {}),
109
+ ...(page?.incomplete ? { incomplete: true } : {}),
110
+ ...(page?.truncated ? { truncated: true } : {}),
111
+ ...(page?.partial ? { partial: true } : {}),
112
+ ...(page?.semanticUnavailable ? { semanticUnavailable: true } : {}),
113
+ ...(page?.search ? { search: page.search } : {}),
114
+ }];
115
+ }));
94
116
  if (flags.json) {
95
117
  this.log(JSON.stringify({
96
118
  agents: items(agents).map((agent) => ({
@@ -107,9 +129,30 @@ class SearchAll extends base_command_1.BaseCommand {
107
129
  ...space,
108
130
  url: (0, web_url_1.spaceUrlFrom)(this.cliConfig.baseURL, space),
109
131
  })),
132
+ sources: pageInfo,
110
133
  }, null, 2));
134
+ if (failed.length)
135
+ this.exit(1);
111
136
  return;
112
137
  }
138
+ if (failed.length)
139
+ this.warn(`Some sources failed: ${failed.join(', ')}. Results are incomplete; retry those searches.`);
140
+ for (const [name, info] of Object.entries(pageInfo)) {
141
+ if (('degraded' in info && info.degraded) || ('incomplete' in info && info.incomplete) ||
142
+ ('truncated' in info && info.truncated) || ('partial' in info && info.partial) ||
143
+ ('semanticUnavailable' in info && info.semanticUnavailable) || ('warnings' in info && info.warnings?.length) ||
144
+ ('search' in info && (info.search?.candidates_may_be_truncated || info.search?.candidate_window_unknown || info.search?.comment_search_incomplete))) {
145
+ this.warn(`${name} search may be incomplete; inspect --json diagnostics.`);
146
+ }
147
+ if ('has_more' in info && info.has_more) {
148
+ if (name === 'skills' && 'next_cursor' in info && info.next_cursor) {
149
+ this.warn(`skills has more candidates. Repeat the query with search skills --cursor ${info.next_cursor}.`);
150
+ }
151
+ else {
152
+ this.warn(`${name} has more results. Use its dedicated search command to page through them.`);
153
+ }
154
+ }
155
+ }
113
156
  // Your own work first — when both a task and a store agent mention the
114
157
  // query, the task is almost always what you were looking for.
115
158
  const rows = [
@@ -129,7 +172,14 @@ class SearchAll extends base_command_1.BaseCommand {
129
172
  ...items(teams).map((t) => storeSearchRow('team', t)),
130
173
  ];
131
174
  if (rows.length === 0) {
132
- this.log('No results.');
175
+ const hasMore = Object.values(pageInfo).some((info) => 'has_more' in info && info.has_more);
176
+ const incomplete = Object.values(pageInfo).some((info) => ('degraded' in info && info.degraded) || ('incomplete' in info && info.incomplete) ||
177
+ ('truncated' in info && info.truncated) || ('partial' in info && info.partial) ||
178
+ ('semanticUnavailable' in info && info.semanticUnavailable) || ('warnings' in info && info.warnings?.length) ||
179
+ ('search' in info && (info.search?.candidates_may_be_truncated || info.search?.candidate_window_unknown || info.search?.comment_search_incomplete)));
180
+ this.log(failed.length ? 'No results from the sources that answered.' : hasMore ? 'No results on these pages. More candidates remain.' : incomplete ? 'No confirmed results. Some sources may be incomplete.' : 'No results.');
181
+ if (failed.length)
182
+ this.exit(1);
133
183
  return;
134
184
  }
135
185
  (0, format_1.renderTable)(rows, [
@@ -137,8 +187,10 @@ class SearchAll extends base_command_1.BaseCommand {
137
187
  { key: 'id', header: 'ID', verbatim: true },
138
188
  { key: 'name', header: 'NAME', maxWidth: 30 },
139
189
  { key: 'description', header: 'DESCRIPTION', maxWidth: 60 },
140
- { key: 'url', header: 'URL', maxWidth: 72 },
190
+ { key: 'url', header: 'URL', verbatim: true },
141
191
  ], (line) => this.log(line));
192
+ if (failed.length)
193
+ this.exit(1);
142
194
  }
143
195
  }
144
196
  exports.default = SearchAll;
@@ -17,6 +17,7 @@ export default class SearchSkills extends BaseCommand {
17
17
  query: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
18
18
  category: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
19
19
  limit: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
20
+ cursor: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
20
21
  };
21
22
  run(): Promise<void>;
22
23
  }
@@ -5,6 +5,7 @@ const data_provider_1 = require("@skrr-ai/data-provider");
5
5
  const base_command_1 = require("../../base-command");
6
6
  const format_1 = require("../../lib/format");
7
7
  const search_query_1 = require("../../lib/search-query");
8
+ const skill_store_pagination_1 = require("../../lib/skill-store-pagination");
8
9
  class SearchSkills extends base_command_1.BaseCommand {
9
10
  static description = 'Search skills in the skrr store by keyword (same as `skills search`)';
10
11
  static examples = [
@@ -43,6 +44,7 @@ class SearchSkills extends base_command_1.BaseCommand {
43
44
  description: 'Maximum number of results',
44
45
  default: 20,
45
46
  }),
47
+ cursor: core_1.Flags.string({ description: 'Continue from a previous page' }),
46
48
  };
47
49
  async run() {
48
50
  this.requireAuth();
@@ -54,6 +56,7 @@ class SearchSkills extends base_command_1.BaseCommand {
54
56
  q: query,
55
57
  category: flags.category,
56
58
  limit: flags.limit,
59
+ cursor: flags.cursor,
57
60
  });
58
61
  }
59
62
  catch (err) {
@@ -65,8 +68,15 @@ class SearchSkills extends base_command_1.BaseCommand {
65
68
  }
66
69
  const r = response;
67
70
  const items = Array.isArray(r) ? r : (r?.data ?? []);
71
+ const incomplete = (0, skill_store_pagination_1.skillStoreSearchIncomplete)(response);
72
+ if (incomplete)
73
+ this.warn('Skill store search may be incomplete. Try again or narrow your query.');
68
74
  if (items.length === 0) {
69
- this.log(`No skills found matching '${query}'.`);
75
+ this.log((0, skill_store_pagination_1.skillStorePageState)(response, flags.cursor).hasMore
76
+ ? 'No skills matched on this page. More candidates remain.'
77
+ : incomplete ? 'No skills confirmed in the available search results.'
78
+ : `No skills found matching '${query}'.`);
79
+ (0, skill_store_pagination_1.reportSkillStorePage)(response, flags.cursor, (message) => this.log(message), (message) => this.warn(message));
70
80
  return;
71
81
  }
72
82
  const rows = items.map((s) => ({
@@ -85,6 +95,7 @@ class SearchSkills extends base_command_1.BaseCommand {
85
95
  { key: 'installs', header: 'INSTALLS' },
86
96
  { key: 'description', header: 'DESCRIPTION', maxWidth: 60 },
87
97
  ], (line) => this.log(line));
98
+ (0, skill_store_pagination_1.reportSkillStorePage)(response, flags.cursor, (message) => this.log(message), (message) => this.warn(message));
88
99
  }
89
100
  }
90
101
  exports.default = SearchSkills;
@@ -16,12 +16,19 @@ export declare function spaceSearchRow(s: Record<string, unknown>): {
16
16
  export default class SearchSpaces extends BaseCommand {
17
17
  static description: string;
18
18
  static examples: string[];
19
+ static args: {
20
+ query: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
21
+ };
19
22
  static flags: {
20
23
  json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
21
- query: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
24
+ query: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
22
25
  status: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
26
+ label: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
23
27
  'include-archived': import("@oclif/core/interfaces").BooleanFlag<boolean>;
28
+ 'archived-only': import("@oclif/core/interfaces").BooleanFlag<boolean>;
24
29
  limit: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
30
+ cursor: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
31
+ match: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
25
32
  };
26
33
  run(): Promise<void>;
27
34
  }
@@ -2,10 +2,12 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.spaceSearchRow = spaceSearchRow;
4
4
  const core_1 = require("@oclif/core");
5
+ const search_query_1 = require("../../lib/search-query");
5
6
  const data_provider_1 = require("@skrr-ai/data-provider");
6
7
  const base_command_1 = require("../../base-command");
7
8
  const format_1 = require("../../lib/format");
8
9
  const web_url_1 = require("../../lib/web-url");
10
+ const search_display_1 = require("../../lib/search-display");
9
11
  /**
10
12
  * One space document → one search-result row.
11
13
  *
@@ -15,12 +17,12 @@ const web_url_1 = require("../../lib/web-url");
15
17
  */
16
18
  function spaceSearchRow(s) {
17
19
  return {
18
- id: (0, format_1.formatCell)(s.id ?? s._id),
20
+ id: (0, search_display_1.searchDisplayText)((0, format_1.formatCell)(s.id ?? s._id)),
19
21
  // Absent on spaces created before identifiers existed and not yet
20
22
  // backfilled, so this renders "-" rather than "undefined".
21
- ref: (0, format_1.formatCell)(s.identifier),
22
- title: (0, format_1.formatCell)(s.title),
23
- status: (0, format_1.formatCell)(s.status),
23
+ ref: (0, search_display_1.searchDisplayText)((0, format_1.formatCell)(s.identifier)),
24
+ title: (0, search_display_1.searchDisplayText)((0, format_1.formatCell)(s.title)),
25
+ status: (0, search_display_1.searchDisplayText)((0, format_1.formatCell)(s.status)),
24
26
  archived: s.archivedAt ? 'yes' : 'no',
25
27
  };
26
28
  }
@@ -29,42 +31,74 @@ class SearchSpaces extends base_command_1.BaseCommand {
29
31
  // `--space` here would be resolved as a space reference by the BaseCommand
30
32
  // parse hook, so this command deliberately uses `--query` like its siblings.
31
33
  static examples = [
34
+ '<%= config.bin %> spaces search "always on" --match phrase',
32
35
  '<%= config.bin %> search spaces --query dogfood',
33
36
  '<%= config.bin %> search spaces --query "always on" --json',
34
37
  '<%= config.bin %> search spaces --query retired --include-archived',
38
+ '<%= config.bin %> spaces search retired --archived-only',
35
39
  ];
40
+ static args = { query: core_1.Args.string({ description: 'Search term', ignoreStdin: true }) };
36
41
  static flags = {
37
42
  json: core_1.Flags.boolean({ description: 'Output as JSON' }),
38
- query: core_1.Flags.string({ char: 'q', description: 'Search query', required: true }),
39
- status: core_1.Flags.string({ description: 'Filter by space status' }),
40
- 'include-archived': core_1.Flags.boolean({ description: 'Include archived spaces' }),
43
+ query: core_1.Flags.string({ char: 'q', description: 'Search query' }),
44
+ status: core_1.Flags.string({ description: 'Filter by space status', options: [...data_provider_1.SPACE_STATUSES] }),
45
+ label: core_1.Flags.string({ description: 'Filter by label ID; repeat to require all labels', multiple: true }),
46
+ 'include-archived': core_1.Flags.boolean({ description: 'Include archived spaces', exclusive: ['archived-only'] }),
47
+ 'archived-only': core_1.Flags.boolean({ description: 'Search only archived spaces', exclusive: ['include-archived'] }),
41
48
  limit: core_1.Flags.integer({ description: 'Max results', default: 20 }),
49
+ cursor: core_1.Flags.string({ description: 'Continue from a previous page' }),
50
+ match: core_1.Flags.string({ description: 'Matching policy', options: ['fuzzy', 'phrase', 'exact'] }),
42
51
  };
43
52
  async run() {
44
53
  this.requireAuth();
45
- const { flags } = await this.parse(SearchSpaces);
54
+ const { args, flags } = await this.parse(SearchSpaces);
55
+ const query = (0, search_query_1.resolveSearchQuery)(this, args.query, flags.query, 'search spaces');
46
56
  let response;
47
57
  try {
48
58
  response = await data_provider_1.dataService.listSpaces({
49
- q: flags.query,
59
+ q: query,
50
60
  ...(flags.status ? { status: flags.status } : {}),
61
+ ...(flags.label?.length ? { labelIds: flags.label } : {}),
51
62
  includeArchived: flags['include-archived'],
63
+ archivedOnly: flags['archived-only'],
52
64
  limit: flags.limit,
65
+ cursor: flags.cursor,
66
+ ...(flags.match ? { match: flags.match } : {}),
53
67
  });
54
68
  }
55
69
  catch (err) {
56
70
  this.handleApiError(err);
57
71
  }
72
+ if (!(0, search_display_1.isSearchListPage)(response)) {
73
+ this.failWithCliError({ message: 'The server returned an invalid Space search response. Retry or check server compatibility.',
74
+ code: 'SEARCH_INVALID_RESPONSE', exit: 1, retryable: true });
75
+ }
76
+ if (flags.match && response?.search?.mode !== flags.match) {
77
+ this.error('The server did not confirm the requested matching policy. Upgrade the server before relying on this result.');
78
+ }
58
79
  if (flags.json) {
59
80
  this.log(JSON.stringify((0, web_url_1.withDataUrls)(this.cliConfig.baseURL, response, web_url_1.spaceUrlFrom), null, 2));
60
81
  return;
61
82
  }
62
83
  const items = (response?.data ?? []);
84
+ const meta = response;
85
+ if (meta.degraded || meta.warnings?.length)
86
+ this.warn('Search may be incomplete. Inspect --json diagnostics.');
87
+ if (meta.search?.candidates_may_be_truncated)
88
+ this.warn('Search index candidates may be truncated; results may be incomplete.');
89
+ if (meta.search?.candidate_window_unknown)
90
+ this.warn('The search index result window could not be checked; results may be incomplete.');
91
+ if (meta.search?.backend === 'database' && (!flags.match || flags.match === 'fuzzy'))
92
+ this.warn('Search is using literal database matching; typo tolerance may be unavailable.');
63
93
  if (items.length === 0) {
64
- const hint = flags['include-archived']
94
+ const hint = flags['include-archived'] || flags['archived-only']
65
95
  ? ''
66
96
  : ' (archived spaces are excluded — add --include-archived)';
67
- this.log(`No spaces matching ${JSON.stringify(flags.query)}${hint}.`);
97
+ this.log(`No spaces matching ${JSON.stringify(query)} on this page${hint}. Try a distinctive word or broaden the filters.`);
98
+ if (meta.next_cursor)
99
+ this.log(`Next page: --cursor ${(0, search_display_1.shellSearchCursor)(meta.next_cursor)}`);
100
+ else if (meta.has_more)
101
+ this.warn('More results exist, but the server did not provide a cursor.');
68
102
  return;
69
103
  }
70
104
  const rows = items.map((s) => ({
@@ -77,8 +111,12 @@ class SearchSpaces extends base_command_1.BaseCommand {
77
111
  { key: 'status', header: 'STATUS' },
78
112
  { key: 'archived', header: 'ARCHIVED' },
79
113
  { key: 'id', header: 'ID' },
80
- { key: 'url', header: 'URL', maxWidth: 72 },
114
+ { key: 'url', header: 'URL', verbatim: true },
81
115
  ], (line) => this.log(line));
116
+ if (meta.next_cursor)
117
+ this.log(`Next page: --cursor ${(0, search_display_1.shellSearchCursor)(meta.next_cursor)}`);
118
+ else if (meta.has_more)
119
+ this.warn('More results exist, but the server did not provide a cursor.');
82
120
  }
83
121
  }
84
122
  exports.default = SearchSpaces;
@@ -21,13 +21,24 @@ export declare function taskSearchRow(t: Record<string, unknown>): {
21
21
  export default class SearchTasks extends BaseCommand {
22
22
  static description: string;
23
23
  static examples: string[];
24
+ static args: {
25
+ query: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
26
+ };
24
27
  static flags: {
25
28
  json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
26
- query: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
29
+ query: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
27
30
  space: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
28
- status: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
31
+ goal: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
32
+ project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
33
+ status: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
34
+ 'status-type': import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
35
+ open: import("@oclif/core/interfaces").BooleanFlag<boolean>;
36
+ priority: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
37
+ tag: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
38
+ 'label-id': import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
29
39
  'include-archived': import("@oclif/core/interfaces").BooleanFlag<boolean>;
30
40
  limit: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
41
+ cursor: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
31
42
  };
32
43
  run(): Promise<void>;
33
44
  }
@@ -2,10 +2,13 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.taskSearchRow = taskSearchRow;
4
4
  const core_1 = require("@oclif/core");
5
+ const search_query_1 = require("../../lib/search-query");
5
6
  const data_provider_1 = require("@skrr-ai/data-provider");
6
7
  const base_command_1 = require("../../base-command");
7
8
  const format_1 = require("../../lib/format");
8
9
  const web_url_1 = require("../../lib/web-url");
10
+ const tasks_1 = require("../../lib/tasks");
11
+ const search_display_1 = require("../../lib/search-display");
9
12
  /**
10
13
  * One task document → one search-result row.
11
14
  *
@@ -19,57 +22,114 @@ const web_url_1 = require("../../lib/web-url");
19
22
  */
20
23
  function taskSearchRow(t) {
21
24
  return {
22
- ref: (0, format_1.formatCell)(t.identifier ?? (t.taskNumber ? `#${t.taskNumber}` : undefined)),
23
- title: (0, format_1.formatCell)(t.title),
24
- status: (0, format_1.formatCell)(t.status),
25
- priority: (0, format_1.formatCell)(t.priority),
25
+ ref: (0, search_display_1.searchDisplayText)((0, format_1.formatCell)(t.identifier ?? (t.taskNumber ? `#${t.taskNumber}` : undefined))),
26
+ title: (0, search_display_1.searchDisplayText)((0, format_1.formatCell)(t.title)),
27
+ status: (0, search_display_1.searchDisplayText)((0, format_1.formatCell)(t.status)),
28
+ priority: (0, search_display_1.searchDisplayText)((0, format_1.formatCell)(t.priority)),
26
29
  // Why this row matched — ref / title / description / comment. The search
27
30
  // covers descriptions and comment bodies, so without it a hit whose title
28
31
  // has nothing to do with the query reads like a broken search.
29
- match: (0, format_1.formatCell)(t._matchField),
30
- id: (0, format_1.formatCell)(t.id ?? t._id),
32
+ match: (0, search_display_1.searchDisplayText)((0, format_1.formatCell)(t._matchField)),
33
+ id: (0, search_display_1.searchDisplayText)((0, format_1.formatCell)(t.id ?? t._id)),
31
34
  };
32
35
  }
33
36
  class SearchTasks extends base_command_1.BaseCommand {
34
- static description = 'Search your tasks by title, ref, description, or comment text';
37
+ static description = 'Search visible tasks by title, ref, or description';
35
38
  static examples = [
39
+ '<%= config.bin %> tasks search "login bug"',
40
+ '<%= config.bin %> tasks search login --status backlog --status review --priority high',
36
41
  '<%= config.bin %> search tasks --query "login bug"',
37
42
  '<%= config.bin %> search tasks --query watcher --status backlog',
38
43
  '<%= config.bin %> search tasks --query "cloud coding" --space "Dogfood Lab" --json',
39
44
  ];
45
+ static args = { query: core_1.Args.string({ description: 'Search term', ignoreStdin: true }) };
40
46
  static flags = {
41
47
  json: core_1.Flags.boolean({ description: 'Output as JSON' }),
42
- query: core_1.Flags.string({ char: 'q', description: 'Search query', required: true }),
48
+ query: core_1.Flags.string({ char: 'q', description: 'Search query' }),
43
49
  // Resolved by name via the BaseCommand parse hook, so `--space "Dogfood
44
50
  // Lab"` works here exactly as it does on `tasks list`.
45
51
  space: core_1.Flags.string({ description: 'Restrict to one space (id or title)' }),
46
- status: core_1.Flags.string({ description: 'Filter by task status' }),
52
+ goal: core_1.Flags.string({ description: 'Filter by goal ID' }),
53
+ project: core_1.Flags.string({ description: 'Filter by project ID' }),
54
+ status: core_1.Flags.string({ description: 'Filter by task status (repeatable, OR)', multiple: true }),
55
+ 'status-type': core_1.Flags.string({
56
+ description: 'Filter by workflow type (repeatable, OR)',
57
+ options: ['backlog', 'unstarted', 'started', 'review', 'completed', 'cancelled', 'triage', 'failed', 'blocked', 'hidden'],
58
+ multiple: true,
59
+ }),
60
+ open: core_1.Flags.boolean({ description: 'Only open workflow types', exclusive: ['status-type', 'status'] }),
61
+ priority: core_1.Flags.string({ description: 'Filter by task priority', options: [...tasks_1.TASK_PRIORITIES] }),
62
+ tag: core_1.Flags.string({ description: 'Filter by tag (repeatable, AND)', multiple: true }),
63
+ 'label-id': core_1.Flags.string({ description: 'Filter by label ID (repeatable, AND)', multiple: true }),
47
64
  'include-archived': core_1.Flags.boolean({ description: 'Include archived tasks' }),
48
65
  limit: core_1.Flags.integer({ description: 'Max results', default: 20 }),
66
+ cursor: core_1.Flags.string({ description: 'Continue from a previous page' }),
49
67
  };
50
68
  async run() {
51
69
  this.requireAuth();
52
- const { flags } = await this.parse(SearchTasks);
70
+ const { args, flags } = await this.parse(SearchTasks);
71
+ const query = (0, search_query_1.resolveSearchQuery)(this, args.query, flags.query, 'search tasks');
53
72
  let response;
54
73
  try {
55
74
  response = await data_provider_1.dataService.listTasks({
56
- q: flags.query,
75
+ q: query,
57
76
  ...(flags.space ? { spaceId: flags.space } : {}),
58
- ...(flags.status ? { status: flags.status } : {}),
77
+ ...(flags.goal ? { goalId: flags.goal } : {}),
78
+ ...(flags.project ? { projectId: flags.project } : {}),
79
+ ...(flags.status?.length ? { status: flags.status.length === 1 ? flags.status[0] : flags.status } : {}),
80
+ ...(flags.open
81
+ ? { statusType: [...data_provider_1.TASK_WORKFLOW_TYPE_BANDS.open] }
82
+ : flags['status-type']?.length
83
+ ? { statusType: (flags['status-type'].length === 1 ? flags['status-type'][0] : flags['status-type']) }
84
+ : {}),
85
+ ...(flags.priority ? { priority: flags.priority } : {}),
86
+ ...(flags.tag?.length ? { tags: flags.tag } : {}),
87
+ ...(flags['label-id']?.length ? { labelIds: flags['label-id'] } : {}),
59
88
  ...(flags['include-archived'] ? { includeArchived: true } : {}),
60
89
  limit: flags.limit,
90
+ cursor: flags.cursor,
61
91
  });
62
92
  }
63
93
  catch (err) {
64
94
  this.handleApiError(err);
65
95
  }
96
+ if (!(0, search_display_1.isSearchListPage)(response)) {
97
+ this.failWithCliError({ message: 'The server returned an invalid Task search response. Retry or check server compatibility.',
98
+ code: 'SEARCH_INVALID_RESPONSE', exit: 1, retryable: true });
99
+ }
66
100
  if (flags.json) {
67
101
  this.log(JSON.stringify((0, web_url_1.withDataUrls)(this.cliConfig.baseURL, response, web_url_1.taskUrlFrom), null, 2));
68
102
  return;
69
103
  }
70
104
  const items = (response?.data ?? []);
105
+ const meta = response;
106
+ if (meta.degraded || meta.incomplete || meta.truncated || meta.warnings?.length)
107
+ this.warn('Search may be incomplete. Inspect --json diagnostics.');
108
+ if (meta.search?.candidates_may_be_truncated)
109
+ this.warn('Search index candidates may be truncated; results may be incomplete.');
110
+ if (meta.search?.candidate_window_unknown)
111
+ this.warn('The search index result window could not be checked; results may be incomplete.');
112
+ if (meta.search?.comment_search_incomplete)
113
+ this.warn('Comment search was limited or unavailable; results may be incomplete.');
114
+ if (meta.search?.semantics === 'case_insensitive_substring')
115
+ this.warn('Search is using literal database matching; typo tolerance may be unavailable.');
71
116
  if (items.length === 0) {
72
- this.log(`No tasks matching ${JSON.stringify(flags.query)}.`);
117
+ if (meta.search?.backend === 'not_run' && meta.search.semantics === 'filters_exclude_all') {
118
+ this.log('No tasks are eligible under the current access and filters. Broaden the filters to search more tasks.');
119
+ }
120
+ else {
121
+ this.log(`No tasks matching ${JSON.stringify(query)} on this page. Try a distinctive word or broaden the filters.`);
122
+ }
123
+ if (meta.search?.semantics === 'exact_identifier') {
124
+ this.log('This query was treated as an exact Task reference. Search a phrase to find text mentioning it.');
125
+ }
126
+ if (meta.search?.backend === 'meilisearch' && meta.search.semantics !== 'exact_identifier') {
127
+ this.log('This index does not search Task comments. A comment-only match may not appear.');
128
+ }
129
+ if (meta.next_cursor)
130
+ this.log(`Next page: --cursor ${(0, search_display_1.shellSearchCursor)(meta.next_cursor)}`);
131
+ else if (meta.has_more)
132
+ this.warn('More results exist, but the server did not provide a cursor.');
73
133
  return;
74
134
  }
75
135
  const rows = items.map((t) => ({
@@ -83,8 +143,12 @@ class SearchTasks extends base_command_1.BaseCommand {
83
143
  { key: 'priority', header: 'PRIORITY' },
84
144
  { key: 'match', header: 'MATCH' },
85
145
  { key: 'id', header: 'ID' },
86
- { key: 'url', header: 'URL', maxWidth: 72 },
146
+ { key: 'url', header: 'URL', verbatim: true },
87
147
  ], (line) => this.log(line));
148
+ if (meta.next_cursor)
149
+ this.log(`Next page: --cursor ${(0, search_display_1.shellSearchCursor)(meta.next_cursor)}`);
150
+ else if (meta.has_more)
151
+ this.warn('More results exist, but the server did not provide a cursor.');
88
152
  }
89
153
  }
90
154
  exports.default = SearchTasks;
@@ -44,8 +44,12 @@ class SearchTeams extends base_command_1.BaseCommand {
44
44
  const items = Array.isArray(r)
45
45
  ? r
46
46
  : (r?.data ?? []);
47
+ const meta = response;
48
+ const partial = Boolean(meta?.partial || meta?.semanticUnavailable);
49
+ if (partial)
50
+ this.warn('Team store search may be incomplete. Try again or narrow your query.');
47
51
  if (items.length === 0) {
48
- this.log('No teams found.');
52
+ this.log(partial ? 'No teams confirmed in the available search results.' : 'No teams found.');
49
53
  return;
50
54
  }
51
55
  const rows = items.map((t) => ({