kollab 0.1.4 → 0.1.5

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 (3) hide show
  1. package/README.md +2 -0
  2. package/dist/index.js +247 -7
  3. package/package.json +2 -1
package/README.md CHANGED
@@ -69,6 +69,8 @@ The CLI opens a browser flow against Kollab auth and stores credentials locally.
69
69
  kollab logout
70
70
  ```
71
71
 
72
+ By default, login uses `https://kollab.im`. Use `kollab login --api-url <site>` or `KOLLAB_API_URL` only when you intentionally target test or alpha.
73
+
72
74
  ## What You Can Do
73
75
 
74
76
  ```bash
package/dist/index.js CHANGED
@@ -32,6 +32,14 @@ var import_commander = require("commander");
32
32
  var fs = __toESM(require("fs"));
33
33
  var path = __toESM(require("path"));
34
34
  var os = __toESM(require("os"));
35
+
36
+ // src/config/api-url.ts
37
+ var DEFAULT_KOLLAB_API_URL = "https://kollab.im";
38
+ function readEnvApiUrlOrDefault() {
39
+ return process.env.KOLLAB_API_URL || DEFAULT_KOLLAB_API_URL;
40
+ }
41
+
42
+ // src/auth/credential-store.ts
35
43
  var KOLLAB_DIR = path.join(os.homedir(), ".kollab");
36
44
  var CREDENTIALS_FILE = path.join(KOLLAB_DIR, "credentials.json");
37
45
  function ensureDir() {
@@ -50,7 +58,7 @@ function loadCredentials() {
50
58
  const envToken = (process.env.KOLLAB_API_TOKEN ?? process.env.KOLLAB_TOKEN ?? "").trim();
51
59
  if (envToken) {
52
60
  return {
53
- api_url: process.env.KOLLAB_API_URL || "https://test.flowus.work",
61
+ api_url: readEnvApiUrlOrDefault(),
54
62
  token: envToken,
55
63
  space_id: process.env.KOLLAB_SPACE_ID,
56
64
  created_at: (/* @__PURE__ */ new Date()).toISOString()
@@ -92,7 +100,7 @@ function resolveApiUrl() {
92
100
  if (envUrl) return envUrl;
93
101
  const creds = loadCredentials();
94
102
  if (creds?.api_url) return creds.api_url;
95
- return "https://test.flowus.work";
103
+ return DEFAULT_KOLLAB_API_URL;
96
104
  }
97
105
  function requireCredentials() {
98
106
  const creds = loadCredentials();
@@ -375,7 +383,7 @@ function registerAuthCommands(program2) {
375
383
  program2.command("login").description("Authenticate with Kollab workspace").option("--token <token>", "Workspace token (skip browser login)").option(
376
384
  "--api-url <url>",
377
385
  "API base URL",
378
- process.env.KOLLAB_API_URL || "https://test.flowus.work"
386
+ readEnvApiUrlOrDefault()
379
387
  ).action(async (opts) => {
380
388
  if (opts.token) {
381
389
  await loginWithToken(opts.apiUrl, opts.token);
@@ -1704,6 +1712,186 @@ function parseQuestionnaireAnswers(raw) {
1704
1712
  return normalized;
1705
1713
  }
1706
1714
 
1715
+ // src/commands/task-long-task.ts
1716
+ var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["succeeded", "failed", "cancelled"]);
1717
+ function sleep3(ms) {
1718
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
1719
+ }
1720
+ function resolveConversationId(optValue) {
1721
+ const envValue = process.env.CURRENT_CONVERSATION_ID?.trim();
1722
+ const result = optValue?.trim() || envValue || "";
1723
+ if (!result) {
1724
+ console.error(
1725
+ JSON.stringify({
1726
+ ok: false,
1727
+ error: "--conversation-id is required, or set CURRENT_CONVERSATION_ID in the environment."
1728
+ })
1729
+ );
1730
+ process.exit(1);
1731
+ }
1732
+ return result;
1733
+ }
1734
+ function registerTaskLongTaskCommand(task) {
1735
+ const longTask = task.command("long-task").description(
1736
+ "Generic long-task polling: list, check status, or watch a job to completion"
1737
+ );
1738
+ longTask.command("list").description(
1739
+ "List long-running jobs for a conversation (video, tts, asr, ...)"
1740
+ ).option(
1741
+ "--conversation-id <uuid>",
1742
+ "Conversation ID (defaults to $CURRENT_CONVERSATION_ID)"
1743
+ ).option("--limit <number>", "Maximum number of jobs to return", "20").option(
1744
+ "--kind <kind>",
1745
+ "Client-side filter: video | tts | asr | all (default: all)",
1746
+ "all"
1747
+ ).action(
1748
+ async (opts) => {
1749
+ const creds = requireCredentials();
1750
+ const conversationId = resolveConversationId(opts.conversationId);
1751
+ const params = new URLSearchParams({
1752
+ conversationId,
1753
+ limit: opts.limit
1754
+ });
1755
+ const result = await api(
1756
+ creds,
1757
+ "GET",
1758
+ `/skills/tools/long-tasks/list?${params}`
1759
+ );
1760
+ if (result.ok && opts.kind !== "all") {
1761
+ const raw = result.data;
1762
+ const jobs = raw?.jobs ?? raw?.data;
1763
+ if (Array.isArray(jobs)) {
1764
+ const filtered = jobs.filter(
1765
+ (j) => j.kind === opts.kind
1766
+ );
1767
+ const key = raw?.jobs !== void 0 ? "jobs" : "data";
1768
+ result.data = { ...raw, [key]: filtered };
1769
+ }
1770
+ }
1771
+ printResult(result);
1772
+ }
1773
+ );
1774
+ longTask.command("status").description("Get the current snapshot of a single long-running job").option(
1775
+ "--conversation-id <uuid>",
1776
+ "Conversation ID (defaults to $CURRENT_CONVERSATION_ID)"
1777
+ ).requiredOption("--job-id <id>", "Job ID to query").action(async (opts) => {
1778
+ const creds = requireCredentials();
1779
+ const conversationId = resolveConversationId(opts.conversationId);
1780
+ const params = new URLSearchParams({
1781
+ conversationId,
1782
+ jobId: opts.jobId
1783
+ });
1784
+ const result = await api(
1785
+ creds,
1786
+ "GET",
1787
+ `/skills/tools/long-tasks/status?${params}`
1788
+ );
1789
+ printResult(result);
1790
+ });
1791
+ longTask.command("watch").description(
1792
+ "Poll a long-running job until it reaches a terminal state or timeout"
1793
+ ).option(
1794
+ "--conversation-id <uuid>",
1795
+ "Conversation ID (defaults to $CURRENT_CONVERSATION_ID)"
1796
+ ).requiredOption("--job-id <id>", "Job ID to watch").option("--timeout <seconds>", "Maximum seconds to wait", "300").option("--interval <seconds>", "Polling interval in seconds", "5").action(
1797
+ async (opts) => {
1798
+ const creds = requireCredentials();
1799
+ const conversationId = resolveConversationId(opts.conversationId);
1800
+ const timeoutMs = Number.parseFloat(opts.timeout) * 1e3;
1801
+ const intervalMs = Number.parseFloat(opts.interval) * 1e3;
1802
+ const isTTY = process.stdout.isTTY === true;
1803
+ let interrupted = false;
1804
+ process.on("SIGINT", () => {
1805
+ interrupted = true;
1806
+ console.log(
1807
+ JSON.stringify(
1808
+ {
1809
+ ok: true,
1810
+ data: {
1811
+ watch_result: "interrupted",
1812
+ job_id: opts.jobId,
1813
+ conversation_id: conversationId
1814
+ }
1815
+ },
1816
+ null,
1817
+ 2
1818
+ )
1819
+ );
1820
+ process.exit(130);
1821
+ });
1822
+ const params = new URLSearchParams({
1823
+ conversationId,
1824
+ jobId: opts.jobId
1825
+ });
1826
+ const startMs = Date.now();
1827
+ let lastSnapshot = null;
1828
+ let lastStatus;
1829
+ while (!interrupted) {
1830
+ const elapsed = Date.now() - startMs;
1831
+ if (elapsed >= timeoutMs) {
1832
+ console.log(
1833
+ JSON.stringify(
1834
+ {
1835
+ ok: true,
1836
+ data: {
1837
+ watch_result: "timeout",
1838
+ job_id: opts.jobId,
1839
+ conversation_id: conversationId,
1840
+ elapsed_seconds: Math.round(elapsed / 1e3),
1841
+ last_status: lastStatus,
1842
+ last_snapshot: lastSnapshot
1843
+ }
1844
+ },
1845
+ null,
1846
+ 2
1847
+ )
1848
+ );
1849
+ process.exit(0);
1850
+ }
1851
+ const result = await api(
1852
+ creds,
1853
+ "GET",
1854
+ `/skills/tools/long-tasks/status?${params}`
1855
+ );
1856
+ if (!result.ok) {
1857
+ printResult(result);
1858
+ process.exit(1);
1859
+ }
1860
+ lastSnapshot = result.data;
1861
+ const jobData = result.data;
1862
+ lastStatus = jobData?.status ?? jobData?.data?.status ?? void 0;
1863
+ if (isTTY) {
1864
+ const progress = jobData?.progress ?? jobData?.data?.progress;
1865
+ process.stdout.write(
1866
+ `[watch] status=${lastStatus ?? "unknown"} progress=${progress ?? "-"} elapsed=${Math.round((Date.now() - startMs) / 1e3)}s
1867
+ `
1868
+ );
1869
+ }
1870
+ if (lastStatus && TERMINAL_STATUSES.has(lastStatus)) {
1871
+ console.log(
1872
+ JSON.stringify(
1873
+ {
1874
+ ok: true,
1875
+ data: {
1876
+ watch_result: lastStatus === "succeeded" ? "succeeded" : "failed",
1877
+ job_id: opts.jobId,
1878
+ conversation_id: conversationId,
1879
+ elapsed_seconds: Math.round((Date.now() - startMs) / 1e3),
1880
+ ...jobData ?? {}
1881
+ }
1882
+ },
1883
+ null,
1884
+ 2
1885
+ )
1886
+ );
1887
+ process.exit(lastStatus === "succeeded" ? 0 : 2);
1888
+ }
1889
+ await sleep3(intervalMs);
1890
+ }
1891
+ }
1892
+ );
1893
+ }
1894
+
1707
1895
  // src/commands/task.ts
1708
1896
  function registerTaskCommands(program2) {
1709
1897
  const task = program2.command("task").description("Task (conversation) management");
@@ -1728,6 +1916,7 @@ function registerTaskCommands(program2) {
1728
1916
  registerTaskCreateCommand(task);
1729
1917
  registerTaskAskCommand(task);
1730
1918
  registerTaskAnswerQuestionCommand(task);
1919
+ registerTaskLongTaskCommand(task);
1731
1920
  task.command("rename").requiredOption("--conversation-id <uuid>", "Conversation ID").requiredOption("--title <title>", "New title").action(async (opts) => {
1732
1921
  const creds = requireCredentials();
1733
1922
  const result = await api(
@@ -1971,16 +2160,67 @@ function registerSkillCommands(program2) {
1971
2160
  );
1972
2161
  printResult(result);
1973
2162
  });
1974
- skill.command("install").requiredOption("--id <uuid>", "Skill ID to install").action(async (opts) => {
2163
+ skill.command("install").description("Permanently install an official skill by ID or import a GitHub skill URL into the current space").option("--id <uuid>", "Official skill ID to install").option("--github-url <url>", "GitHub skill directory or SKILL.md URL to import and install").action(async (opts) => {
2164
+ if (opts.id && opts.githubUrl) {
2165
+ console.error(JSON.stringify({ ok: false, error: "--id and --github-url are mutually exclusive; provide exactly one" }, null, 2));
2166
+ process.exit(1);
2167
+ }
2168
+ if (!opts.id && !opts.githubUrl) {
2169
+ console.error(JSON.stringify({ ok: false, error: "Either --id or --github-url is required" }, null, 2));
2170
+ process.exit(1);
2171
+ }
1975
2172
  const creds = requireCredentials();
1976
2173
  const spaceId = requireSpaceId(creds);
1977
- const result = await api(
2174
+ const result = opts.githubUrl ? await api(
2175
+ creds,
2176
+ "POST",
2177
+ "/skills/skills/market/install",
2178
+ {
2179
+ github_url: opts.githubUrl,
2180
+ space_id: spaceId
2181
+ }
2182
+ ) : await api(
1978
2183
  creds,
1979
2184
  "POST",
1980
2185
  `/skills/skills/${opts.id}/add-to-space?spaceId=${spaceId}`
1981
2186
  );
1982
2187
  printResult(result);
1983
2188
  });
2189
+ skill.command("search").description("Paginated keyword search of the official skill catalog").option("-q, --query <query>", "Case-insensitive substring filter on name + description").option("--page <number>", "1-indexed page number (default: 1)", "1").option("--limit <number>", "Page size, 1\u201350 (default: 20)", "20").action(async (opts) => {
2190
+ const creds = requireCredentials();
2191
+ const spaceId = requireSpaceId(creds);
2192
+ const page = Math.max(1, parseInt(opts.page, 10) || 1);
2193
+ const limit = Math.min(50, Math.max(1, parseInt(opts.limit, 10) || 20));
2194
+ const params = new URLSearchParams({
2195
+ spaceId,
2196
+ page: String(page),
2197
+ limit: String(limit)
2198
+ });
2199
+ if (opts.query) {
2200
+ params.set("q", opts.query);
2201
+ }
2202
+ const result = await api(creds, "GET", `/skills/recommended/search?${params.toString()}`);
2203
+ printResult(result);
2204
+ });
2205
+ skill.command("try").description("Session-scoped hydration of an official skill into the current workspace (no space ACL write)").option("--id <uuid>", "Official skill ID (mutually exclusive with --name)").option("--name <name>", "Skill folder or display name (mutually exclusive with --id)").option("--expiration <seconds>", "Presigned URL TTL in seconds, 60\u201386400 (default: 3600)").action(async (opts) => {
2206
+ if (opts.id && opts.name) {
2207
+ console.error(JSON.stringify({ ok: false, error: "--id and --name are mutually exclusive; provide exactly one" }, null, 2));
2208
+ process.exit(1);
2209
+ }
2210
+ if (!opts.id && !opts.name) {
2211
+ console.error(JSON.stringify({ ok: false, error: "Either --id or --name is required" }, null, 2));
2212
+ process.exit(1);
2213
+ }
2214
+ const creds = requireCredentials();
2215
+ const body = {};
2216
+ if (opts.id) body.skill_id = opts.id;
2217
+ if (opts.name) body.skill_name = opts.name;
2218
+ if (opts.expiration) {
2219
+ body.expiration = Math.min(86400, Math.max(60, parseInt(opts.expiration, 10) || 3600));
2220
+ }
2221
+ const result = await api(creds, "POST", "/tools/skills/hydrate-from-catalog", body);
2222
+ printResult(result);
2223
+ });
1984
2224
  }
1985
2225
 
1986
2226
  // src/commands/artifact.ts
@@ -2887,7 +3127,7 @@ function registerImagineCommands(program2) {
2887
3127
  imagine.command("search").description("Search the prompt gallery; results are pre-sorted by like_count.").option("-q, --query <text>", "Free-text search across title and prompts (1-3 keywords work best).").option("-t, --tag <tag>", "Filter by one canonical tag. Run `kollab imagine tags` to list.").option(
2888
3128
  "-l, --lan <code>",
2889
3129
  "Output language (en, zh-CN, zh-TW, ja, ko, fr, de, ru, pt-BR). Defaults to env locale."
2890
- ).option("--limit <n>", `Max items to return (1-${DEFAULT_LIMIT}, default ${DEFAULT_LIMIT}).`, String(DEFAULT_LIMIT)).option("--cursor <c>", "Pagination cursor from a previous response (advanced).").option("--top", "Print only items[0].prompt text \u2014 useful for piping into gptimg / seedance2.").action(runSearch);
3130
+ ).option("--limit <n>", `Max items to return (1-${DEFAULT_LIMIT}, default ${DEFAULT_LIMIT}).`, String(DEFAULT_LIMIT)).option("--cursor <c>", "Pagination cursor from a previous response (advanced).").option("--top", "Print only items[0].prompt text \u2014 useful for piping into gptimg / seedance2 / klingv3.").action(runSearch);
2891
3131
  imagine.command("tags").description("List the canonical tags currently available in the gallery.").option("-l, --lan <code>", "Output language (only affects how related metadata may surface later).").action(runTags);
2892
3132
  }
2893
3133
 
@@ -2974,7 +3214,7 @@ function resolveCliVersion() {
2974
3214
  }
2975
3215
  } catch {
2976
3216
  }
2977
- return "0.1.4";
3217
+ return "0.1.5";
2978
3218
  }
2979
3219
  program.name("kollab").description("Kollab workspace CLI").version(resolveCliVersion());
2980
3220
  registerAuthCommands(program);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kollab",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Kollab workspace CLI",
5
5
  "bin": {
6
6
  "kollab": "dist/index.js"
@@ -22,6 +22,7 @@
22
22
  "build": "tsup",
23
23
  "dev": "tsup --watch",
24
24
  "lint": "eslint .",
25
+ "test:public-defaults": "node ./scripts/check-public-defaults.mjs",
25
26
  "release:canary": "node ./scripts/release-package.mjs --channel canary --publish",
26
27
  "release:next": "node ./scripts/release-package.mjs --channel next --publish",
27
28
  "release:latest": "node ./scripts/release-package.mjs --channel latest --publish"