@kimchi-dev/kimchictl 0.0.8 → 0.0.9

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.
@@ -2,13 +2,6 @@ import { readFileSync } from "node:fs"
2
2
  import { resolve } from "node:path"
3
3
  import { resolveWorkspace } from "../api/resolver.js"
4
4
  import { formatAge, formatBytes, formatMillicores } from "../api/resources.js"
5
- import {
6
- AmbiguousTemplateError,
7
- getWorkspaceTemplate,
8
- listWorkspaceTemplates,
9
- TemplateNotFoundError,
10
- type WorkspaceTemplate,
11
- } from "../api/templates.js"
12
5
  import { waitForWorkspaceActive } from "../api/wait.js"
13
6
  import {
14
7
  type CreateWorkspaceSpec,
@@ -43,11 +36,10 @@ const GROUP_USAGE = `Usage:
43
36
  kimchictl workspace <verb> [options]
44
37
 
45
38
  Verbs:
46
- create Create a workspace (server generates the alias)
47
- list List workspaces (alias: ls)
48
- get Show workspace details
49
- delete Delete a workspace (alias: rm)
50
- templates Browse workspace templates (list | get)
39
+ create Create a workspace (server generates the alias)
40
+ list List workspaces (alias: ls)
41
+ get Show workspace details
42
+ delete Delete a workspace (alias: rm)
51
43
 
52
44
  Create options:
53
45
  --desc <text> Description
@@ -81,9 +73,6 @@ export async function runWorkspace(args: string[], deps: WorkspaceDeps = {}): Pr
81
73
  case "delete":
82
74
  case "rm":
83
75
  return runDelete(rest, deps)
84
- case "templates":
85
- case "template":
86
- return runTemplates(rest, deps)
87
76
  case "help":
88
77
  case "--help":
89
78
  case "-h":
@@ -274,13 +263,14 @@ async function runList(args: string[], deps: WorkspaceDeps): Promise<number> {
274
263
  w.alias,
275
264
  w.status,
276
265
  w.cluster,
266
+ w.templateName || "-",
277
267
  formatAge(w.createdAt, now),
278
268
  formatMillicores(w.cpuMillicores),
279
269
  formatBytes(w.ramBytes),
280
270
  formatBytes(w.pvcSizeBytes),
281
271
  w.uri ?? "",
282
272
  ])
283
- console.log(formatTable(["NAME", "STATUS", "CLUSTER", "AGE", "CPU", "RAM", "PVC", "URI"], rows))
273
+ console.log(formatTable(["NAME", "STATUS", "CLUSTER", "TEMPLATE", "AGE", "CPU", "RAM", "PVC", "URI"], rows))
284
274
  return 0
285
275
  }
286
276
 
@@ -349,184 +339,6 @@ async function runDelete(args: string[], deps: WorkspaceDeps): Promise<number> {
349
339
  return 0
350
340
  }
351
341
 
352
- // ---------------------------------------------------------------- templates
353
-
354
- async function runTemplates(args: string[], deps: WorkspaceDeps): Promise<number> {
355
- const [verb, ...rest] = args
356
- switch (verb) {
357
- case "list":
358
- case "ls":
359
- return runTemplatesList(rest, deps)
360
- case "get":
361
- return runTemplatesGet(rest, deps)
362
- case "help":
363
- case "--help":
364
- case "-h":
365
- console.log(TEMPLATES_USAGE)
366
- return 0
367
- case undefined:
368
- console.error(TEMPLATES_USAGE)
369
- return 2
370
- default:
371
- console.error(`kimchictl workspace templates: unknown verb "${verb}"`)
372
- console.error(TEMPLATES_USAGE)
373
- return 2
374
- }
375
- }
376
-
377
- const TEMPLATES_USAGE = `Usage:
378
- kimchictl workspace templates <verb> [options]
379
-
380
- Verbs:
381
- list List workspace templates (alias: ls)
382
- get Show template details
383
-
384
- Options:
385
- --help Show this help`
386
-
387
- async function runTemplatesList(args: string[], deps: WorkspaceDeps): Promise<number> {
388
- const { values, positionals } = parseFlags("workspace templates list", args, {
389
- output: { type: "string", short: "o", default: "table" },
390
- })
391
- if (positionals.length > 0) {
392
- throw new UsageError("kimchictl workspace templates list: unexpected positional argument")
393
- }
394
- if (values.output !== "json" && values.output !== "table") {
395
- throw new UsageError(
396
- `kimchictl workspace templates list: unsupported --output "${String(values.output)}" (expected table|json)`,
397
- )
398
- }
399
-
400
- const { key } = requireApiKey(deps.env)
401
- const templates = await listWorkspaceTemplates(key, { fetch: deps.fetch })
402
-
403
- if (values.output === "json") {
404
- console.log(JSON.stringify(templates.map(serializeTemplate), null, 2))
405
- return 0
406
- }
407
-
408
- const now = deps.now?.() ?? new Date()
409
- const rows = templates.map((t) => [
410
- t.name || t.id.slice(0, 12),
411
- t.description.length > 40 ? `${t.description.slice(0, 37)}…` : t.description || "-",
412
- formatMillicores(t.spec?.resources?.cpu ? parseQuantity(t.spec.resources.cpu) : undefined),
413
- formatBytes(t.spec?.resources?.memory ? parseByteQuantity(t.spec.resources.memory) : undefined),
414
- formatBytes(t.spec?.resources?.pvcSize ? parseByteQuantity(t.spec.resources.pvcSize) : undefined),
415
- formatAge(t.createTime, now),
416
- ])
417
- console.log(formatTable(["NAME", "DESCRIPTION", "CPU", "MEMORY", "STORAGE", "AGE"], rows))
418
- return 0
419
- }
420
-
421
- async function runTemplatesGet(args: string[], deps: WorkspaceDeps): Promise<number> {
422
- const { values, positionals } = parseFlags("workspace templates get", args, {
423
- output: { type: "string", short: "o", default: "text" },
424
- })
425
- if (positionals.length !== 1) {
426
- throw new UsageError("kimchictl workspace templates get: expected exactly one template name or ID")
427
- }
428
- if (values.output !== "json" && values.output !== "text") {
429
- throw new UsageError(
430
- `kimchictl workspace templates get: unsupported --output "${String(values.output)}" (expected text|json)`,
431
- )
432
- }
433
-
434
- const { key } = requireApiKey(deps.env)
435
- const ref = positionals[0] ?? ""
436
-
437
- // Templates are addressed by ID in the API; when the ref is a name,
438
- // list-and-match (the API has no by-name lookup).
439
- let template: WorkspaceTemplate | undefined
440
- if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(ref)) {
441
- template = await getWorkspaceTemplate(key, ref, { fetch: deps.fetch })
442
- } else {
443
- const all = await listWorkspaceTemplates(key, { fetch: deps.fetch })
444
- const matches = all.filter((t) => t.name === ref || t.name.startsWith(ref))
445
- if (matches.length === 0) throw new TemplateNotFoundError(ref)
446
- if (matches.length > 1)
447
- throw new AmbiguousTemplateError(
448
- ref,
449
- matches.map((t) => t.name),
450
- )
451
- template = matches[0]
452
- }
453
- if (!template) throw new TemplateNotFoundError(ref)
454
-
455
- if (values.output === "json") {
456
- console.log(JSON.stringify(serializeTemplate(template), null, 2))
457
- return 0
458
- }
459
-
460
- for (const line of renderTemplateCard(template, deps.color ?? false, deps.now?.() ?? new Date())) {
461
- console.log(line)
462
- }
463
- return 0
464
- }
465
-
466
- function renderTemplateCard(template: WorkspaceTemplate, color: boolean, now: Date): string[] {
467
- const lines: string[] = [`${template.name || template.id.slice(0, 12)} ${dim("template", color)}`]
468
- if (template.description) lines.push(dim(` ${template.description}`, color))
469
-
470
- const parts: string[] = []
471
- const res = template.spec?.resources
472
- if (res) {
473
- if (res.cpu) parts.push(formatMillicores(parseQuantity(res.cpu)))
474
- if (res.memory) parts.push(formatBytes(parseByteQuantity(res.memory)))
475
- }
476
- if (parts.length > 0) {
477
- lines.push(dim(` ${parts.join(" · ")}`, color))
478
- }
479
-
480
- const deps = template.spec?.dependencies
481
- if (deps && deps.length > 0) {
482
- lines.push(` deps ${deps.join(", ")}`)
483
- }
484
-
485
- const eg = template.spec?.egressPolicy
486
- if (eg) {
487
- const egParts: string[] = []
488
- if (eg.denyByDefault === false) egParts.push("default-allow")
489
- if (eg.allowed && eg.allowed.length > 0) egParts.push(`allow: ${eg.allowed.join(", ")}`)
490
- if (eg.denied && eg.denied.length > 0) egParts.push(`deny: ${eg.denied.join(", ")}`)
491
- if (egParts.length > 0) lines.push(dim(` egress ${egParts.join(" · ")}`, color))
492
- }
493
-
494
- lines.push(dim(` updated ${formatAge(template.updateTime, now)} ago`, color))
495
- return lines
496
- }
497
-
498
- function serializeTemplate(t: WorkspaceTemplate): Record<string, unknown> {
499
- return {
500
- id: t.id,
501
- name: t.name,
502
- ...(t.description ? { description: t.description } : {}),
503
- spec: t.spec,
504
- ...(t.createdBy ? { createdBy: t.createdBy } : {}),
505
- ...(t.updateTime.getTime() > 0 ? { updateTime: t.updateTime.toISOString() } : {}),
506
- }
507
- }
508
-
509
- /** Parse a k8s CPU quantity string ("500m", "2") to millicores. */
510
- function parseQuantity(q: string): number | undefined {
511
- const value = Number.parseFloat(q.replace(/m$/, ""))
512
- if (Number.isNaN(value)) return undefined
513
- return q.endsWith("m") ? value : value * 1000
514
- }
515
-
516
- /** Parse a k8s memory/storage quantity string ("1Gi", "512Mi") to bytes. */
517
- function parseByteQuantity(q: string): number | undefined {
518
- const units: Record<string, number> = { Ki: 1024, Mi: 1024 ** 2, Gi: 1024 ** 3, Ti: 1024 ** 4 }
519
- for (const [suffix, mult] of Object.entries(units)) {
520
- if (q.endsWith(suffix)) {
521
- const value = Number.parseFloat(q.slice(0, -suffix.length))
522
- if (!Number.isNaN(value)) return value * mult
523
- }
524
- }
525
- const value = Number.parseFloat(q)
526
- if (!Number.isNaN(value)) return value
527
- return undefined
528
- }
529
-
530
342
  // ---------------------------------------------------------------- shared
531
343
 
532
344
  /** Tinted status dot: green=active, yellow=transitional/suspended, dim=terminated. */
@@ -560,6 +372,7 @@ function renderWorkspaceCard(
560
372
  const meta = [
561
373
  workspace.cluster ? `cluster ${workspace.cluster}` : "",
562
374
  workspace.clientType,
375
+ workspace.templateName ? `from ${workspace.templateName}` : "",
563
376
  `created ${formatAge(workspace.createdAt, now)} ago`,
564
377
  ].filter(Boolean)
565
378
  if (meta.length > 0) lines.push(dim(` ${meta.join(" · ")}`, color))
@@ -593,6 +406,7 @@ function serializeWorkspace(w: Workspace): Record<string, unknown> {
593
406
  status: w.status,
594
407
  ...(w.cluster ? { cluster: w.cluster } : {}),
595
408
  ...(w.clientType ? { clientType: w.clientType } : {}),
409
+ ...(w.templateName ? { templateName: w.templateName } : {}),
596
410
  ...(w.uri ? { uri: w.uri } : {}),
597
411
  createdAt: w.createdAt.getTime() > 0 ? w.createdAt.toISOString() : undefined,
598
412
  resources: {
package/src/version.ts CHANGED
@@ -3,4 +3,4 @@
3
3
  * version over this constant at build time; the committed value stays a
4
4
  * sentinel so source checkouts are recognizable.
5
5
  */
6
- export const VERSION = "0.0.8"
6
+ export const VERSION = "0.0.9"