@factorialco/fcode-cli 1.1.1 → 1.3.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.
package/README.md CHANGED
@@ -20,7 +20,7 @@ $ npm install -g @factorialco/fcode-cli
20
20
  $ fcode COMMAND
21
21
  running command...
22
22
  $ fcode (--version)
23
- @factorialco/fcode-cli/1.1.1 linux-x64 node-v22.22.3
23
+ @factorialco/fcode-cli/1.3.0 linux-x64 node-v22.22.3
24
24
  $ fcode --help [COMMAND]
25
25
  USAGE
26
26
  $ fcode COMMAND
@@ -100,12 +100,14 @@ Clone team workspace from cloud
100
100
 
101
101
  ```
102
102
  USAGE
103
- $ fcode clone [TEAM] [--skipSkillsSetup]
103
+ $ fcode clone [TEAM] [--skipSkillsSetup] [--autoSkillsSetup]
104
104
 
105
105
  ARGUMENTS
106
106
  [TEAM] Team name
107
107
 
108
108
  FLAGS
109
+ --autoSkillsSetup Install the Factorial Code agent skills non-interactively (defaults, project scope) instead of
110
+ prompting
109
111
  --skipSkillsSetup Skip installation of the Factorial Code agent skills
110
112
 
111
113
  DESCRIPTION
package/bin/run CHANGED
@@ -1,6 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const { run, handle, flush } = require('@oclif/core')
3
+ const { run, handle, flush, settings } = require('@oclif/core')
4
+
5
+ // This is the production runner: it always executes the compiled `dist/` output,
6
+ // so oclif must never try to auto-transpile TypeScript sources via ts-node.
7
+ // Otherwise, when the caller's environment has NODE_ENV=development or =test,
8
+ // oclif attempts ts-node and prints "Could not find typescript ... Falling back
9
+ // to compiled source" because typescript is (correctly) not installed in the
10
+ // production dependency tree. Auto-transpile stays enabled for local
11
+ // development through `bin/dev`.
12
+ settings.enableAutoTranspile = false
4
13
 
5
14
  run()
6
15
  .catch(async (error) => handle(error))
@@ -8,17 +8,18 @@ export default class Clone extends Command {
8
8
  static examples: string[];
9
9
  static flags: {
10
10
  skipSkillsSetup: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
11
+ autoSkillsSetup: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
11
12
  };
12
13
  checkWorkspaceAlreadyCloned(slug: string): Promise<void>;
13
14
  checkWorkspaceIsEmpty(slug: string): Promise<void>;
14
- cloneTeam(origin: TeamOrigin, skipSkillsSetup?: boolean): Promise<void>;
15
+ cloneTeam(origin: TeamOrigin, skipSkillsSetup?: boolean, autoSkillsSetup?: boolean): Promise<void>;
15
16
  createWorkspaceConfig(origin: TeamOrigin): Promise<void>;
16
17
  createWorkspaceDatastore(slug: string): void;
17
18
  createWorkspaceStorage(slug: string): void;
18
19
  createWorkspaceGitIgnore(slug: string): Promise<void>;
19
20
  createWorkspaceLocalVariables(slug: string): Promise<void>;
20
21
  private skillsInstallHint;
21
- setupSkills(): Promise<void>;
22
+ setupSkills(autoSkillsSetup?: boolean): Promise<void>;
22
23
  catch(error: any): Promise<void>;
23
24
  init(): Promise<void>;
24
25
  mkdirWorkspace(slug: string): Promise<void>;
@@ -41,7 +41,7 @@ class Clone extends core_1.Command {
41
41
  });
42
42
  }
43
43
  }
44
- async cloneTeam(origin, skipSkillsSetup = false) {
44
+ async cloneTeam(origin, skipSkillsSetup = false, autoSkillsSetup = false) {
45
45
  await this.mkdirWorkspace(origin.slug);
46
46
  await this.createWorkspaceConfig(origin);
47
47
  await this.createWorkspaceGitIgnore(origin.slug);
@@ -51,7 +51,7 @@ class Clone extends core_1.Command {
51
51
  process.chdir(origin.slug);
52
52
  await pull_1.default.run([]);
53
53
  if (!skipSkillsSetup) {
54
- await this.setupSkills();
54
+ await this.setupSkills(autoSkillsSetup);
55
55
  }
56
56
  }
57
57
  async createWorkspaceConfig(origin) {
@@ -79,12 +79,18 @@ class Clone extends core_1.Command {
79
79
  cli_1.default.log('šŸ’” Install the Factorial Code agent skills later with:');
80
80
  cli_1.default.log(` npx skills add ${SKILLS_REPO} -s '*'`);
81
81
  }
82
- async setupSkills() {
82
+ async setupSkills(autoSkillsSetup = false) {
83
83
  cli_1.default.log('');
84
84
  // `npx --yes skills add <repo> -s '*'` — preselect all skills; the skills CLI
85
85
  // then handles scope (project/global) and assistant selection interactively.
86
+ // With autoSkillsSetup, add the skills CLI's `-y` flag so it installs
87
+ // non-interactively using its defaults, project-scoped (no `-g`), without prompting.
88
+ const skillsArgs = ['--yes', 'skills', 'add', SKILLS_REPO, '-s', '*'];
89
+ if (autoSkillsSetup) {
90
+ skillsArgs.push('-y');
91
+ }
86
92
  await new Promise((resolve) => {
87
- const child = (0, node_child_process_1.spawn)('npx', ['--yes', 'skills', 'add', SKILLS_REPO, '-s', '*'], { stdio: 'inherit' });
93
+ const child = (0, node_child_process_1.spawn)('npx', skillsArgs, { stdio: 'inherit' });
88
94
  child.on('error', () => {
89
95
  // npx not available (or failed to launch) — don't fail the clone, just tell the user how to do it.
90
96
  cli_1.default.log("\nāš ļø Couldn't run npx automatically (is Node.js / npx installed?).");
@@ -161,7 +167,11 @@ class Clone extends core_1.Command {
161
167
  if (skipSkillsSetup === undefined) {
162
168
  skipSkillsSetup = (await new global_1.default().getSetting(settings_1.SettingKeys.SKIP_SKILLS_SETUP)) === 'true';
163
169
  }
164
- await this.cloneTeam({ url, slug }, skipSkillsSetup);
170
+ let autoSkillsSetup = flags.autoSkillsSetup;
171
+ if (autoSkillsSetup === undefined) {
172
+ autoSkillsSetup = (await new global_1.default().getSetting(settings_1.SettingKeys.AUTO_SKILLS_SETUP)) === 'true';
173
+ }
174
+ await this.cloneTeam({ url, slug }, skipSkillsSetup, autoSkillsSetup);
165
175
  cli_1.default.log(`\nšŸŽ‰ ${args.team} team workspace cloned successfully!\n`);
166
176
  cli_1.default.log(`$ cd ${slug}\n`);
167
177
  }
@@ -173,5 +183,8 @@ Clone.flags = {
173
183
  skipSkillsSetup: core_1.Flags.boolean({
174
184
  description: 'Skip installation of the Factorial Code agent skills',
175
185
  }),
186
+ autoSkillsSetup: core_1.Flags.boolean({
187
+ description: 'Install the Factorial Code agent skills non-interactively (defaults, project scope) instead of prompting',
188
+ }),
176
189
  };
177
190
  exports.default = Clone;
@@ -6,6 +6,7 @@ const color_1 = require("@oclif/color");
6
6
  const workspace_1 = require("../../config/workspace");
7
7
  class RemoteList extends base_command_1.default {
8
8
  async init() {
9
+ await this.parse(RemoteList);
9
10
  this.workspaceConfig = new workspace_1.default();
10
11
  }
11
12
  async run() {
@@ -4,6 +4,7 @@ export interface SettingDefinition {
4
4
  description?: string;
5
5
  }
6
6
  export declare enum SettingKeys {
7
+ AUTO_SKILLS_SETUP = "autoSkillsSetup",
7
8
  DISABLE_UPDATE_NOTIFIER = "disableUpdateNotifier",
8
9
  IGNORE_PROCESS_VERSIONS = "ignoreProcessVersions",
9
10
  IGNORE_WORKSPACE_NOT_EMPTY = "ignoreWorkspaceNotEmpty",
@@ -4,6 +4,7 @@ exports.SETTINGS_DEFINITIONS = exports.SettingKeys = void 0;
4
4
  exports.getSettingDefault = getSettingDefault;
5
5
  var SettingKeys;
6
6
  (function (SettingKeys) {
7
+ SettingKeys["AUTO_SKILLS_SETUP"] = "autoSkillsSetup";
7
8
  SettingKeys["DISABLE_UPDATE_NOTIFIER"] = "disableUpdateNotifier";
8
9
  SettingKeys["IGNORE_PROCESS_VERSIONS"] = "ignoreProcessVersions";
9
10
  SettingKeys["IGNORE_WORKSPACE_NOT_EMPTY"] = "ignoreWorkspaceNotEmpty";
@@ -28,6 +29,11 @@ exports.SETTINGS_DEFINITIONS = [
28
29
  default: 'false',
29
30
  description: 'Skip Factorial Code agent skills install on clone',
30
31
  },
32
+ {
33
+ key: SettingKeys.AUTO_SKILLS_SETUP,
34
+ default: 'false',
35
+ description: 'Install Factorial Code agent skills non-interactively (defaults, project scope) on clone',
36
+ },
31
37
  {
32
38
  key: SettingKeys.IGNORE_FCODE_RUN_PROCESSES,
33
39
  default: 'false',
@@ -587,7 +587,14 @@ const main = typeof _hooksModule['${fn}'] === 'function'
587
587
  console.log('[fcode] variables.delete removed ' + key + ' from variables.local.env (local run).');
588
588
  }
589
589
  };
590
- })()
590
+ })(),
591
+ sendMail: async ({ to, subject, text, html } = {}) => {
592
+ // No manager locally: log the email contents instead of sending, so
593
+ // devs can see exactly what would be delivered when run in the cloud.
594
+ console.log('[fcode] sendMail (local run) — not sent, logging contents below:');
595
+ console.log(JSON.stringify({ to, subject, text, html }, null, 2));
596
+ return { messageId: 'local-run', accepted: [].concat(to || []), rejected: [] };
597
+ }
591
598
  };
592
599
  ${Object.entries(this.getVariablesObject())
593
600
  .map(([key, value]) => `process.env['${key}'] = process.env['${key}'] ?? ${JSON.stringify(value)}`)
package/dist/env.d.ts ADDED
@@ -0,0 +1,43 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Validated environment schema for the fcode CLI. This is the single source of truth
4
+ * for the environment variables the CLI reads — add new vars here.
5
+ *
6
+ * Unlike the long-running services (storage-api, ai), a CLI reads config lazily and
7
+ * different commands need different vars, so there is currently NO universally required
8
+ * variable: every entry is optional or carries a default that preserves prior behavior.
9
+ * The fail-fast (āŒ + exit 1) path is kept for parity with the services and so that a
10
+ * genuinely-required var added later fails at process start listing every problem.
11
+ */
12
+ export declare const envSchema: z.ZodObject<{
13
+ FCODE_CLOUD_URL: z.ZodDefault<z.ZodString>;
14
+ FCODE_AUTH_URL: z.ZodOptional<z.ZodString>;
15
+ FCODE_CLIENT_ID: z.ZodDefault<z.ZodString>;
16
+ FCODE_CONFIG_PATH: z.ZodOptional<z.ZodString>;
17
+ IGNORE_WORKSPACE_NOT_EMPTY: z.ZodOptional<z.ZodEnum<{
18
+ true: "true";
19
+ false: "false";
20
+ }>>;
21
+ FCODE_DISABLE_UPDATE_NOTIFIER: z.ZodOptional<z.ZodEnum<{
22
+ true: "true";
23
+ false: "false";
24
+ }>>;
25
+ SILENT_MODE: z.ZodOptional<z.ZodEnum<{
26
+ true: "true";
27
+ false: "false";
28
+ }>>;
29
+ CLI_UX_SKIP_TTY_CHECK: z.ZodOptional<z.ZodString>;
30
+ YC_COMMAND: z.ZodOptional<z.ZodString>;
31
+ }, z.core.$strip>;
32
+ export declare const env: {
33
+ FCODE_CLOUD_URL: string;
34
+ FCODE_CLIENT_ID: string;
35
+ FCODE_AUTH_URL?: string | undefined;
36
+ FCODE_CONFIG_PATH?: string | undefined;
37
+ IGNORE_WORKSPACE_NOT_EMPTY?: "true" | "false" | undefined;
38
+ FCODE_DISABLE_UPDATE_NOTIFIER?: "true" | "false" | undefined;
39
+ SILENT_MODE?: "true" | "false" | undefined;
40
+ CLI_UX_SKIP_TTY_CHECK?: string | undefined;
41
+ YC_COMMAND?: string | undefined;
42
+ };
43
+ export default env;
package/dist/env.js ADDED
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.env = exports.envSchema = void 0;
4
+ const zod_1 = require("zod");
5
+ /**
6
+ * Validated environment schema for the fcode CLI. This is the single source of truth
7
+ * for the environment variables the CLI reads — add new vars here.
8
+ *
9
+ * Unlike the long-running services (storage-api, ai), a CLI reads config lazily and
10
+ * different commands need different vars, so there is currently NO universally required
11
+ * variable: every entry is optional or carries a default that preserves prior behavior.
12
+ * The fail-fast (āŒ + exit 1) path is kept for parity with the services and so that a
13
+ * genuinely-required var added later fails at process start listing every problem.
14
+ */
15
+ exports.envSchema = zod_1.z.object({
16
+ // --- Cloud / auth endpoints ---
17
+ FCODE_CLOUD_URL: zod_1.z.string().min(1).default('https://code.factorial.dev/platform'),
18
+ FCODE_AUTH_URL: zod_1.z.string().min(1).optional(),
19
+ FCODE_CLIENT_ID: zod_1.z.string().min(1).default('cli'),
20
+ // --- Local config location (defaults to os.homedir() at the call site) ---
21
+ FCODE_CONFIG_PATH: zod_1.z.string().min(1).optional(),
22
+ // --- Behavior flags (compared as the strings 'true' / 'false' at the call sites) ---
23
+ IGNORE_WORKSPACE_NOT_EMPTY: zod_1.z.enum(['true', 'false']).optional(),
24
+ FCODE_DISABLE_UPDATE_NOTIFIER: zod_1.z.enum(['true', 'false']).optional(),
25
+ SILENT_MODE: zod_1.z.enum(['true', 'false']).optional(),
26
+ CLI_UX_SKIP_TTY_CHECK: zod_1.z.string().min(1).optional(),
27
+ // --- Debug tooling ---
28
+ YC_COMMAND: zod_1.z.string().min(1).optional(),
29
+ });
30
+ /** Treat empty strings (e.g. an unset shell/dotenv var rendered as "") as missing. */
31
+ const raw = Object.fromEntries(Object.entries(process.env).filter(([, value]) => value !== ''));
32
+ /**
33
+ * Skip the hard fail-fast under test runners and when explicitly opted out, so importing
34
+ * this module never kills a test worker. Real invocations always validate.
35
+ */
36
+ const skipHardExit = Boolean(process.env.VITEST) || process.env.SKIP_ENV_VALIDATION === 'true';
37
+ const result = exports.envSchema.safeParse(raw);
38
+ let parsed;
39
+ if (result.success) {
40
+ parsed = result.data;
41
+ }
42
+ else if (skipHardExit) {
43
+ // No universally-required vars today, so parsing `raw` alone succeeds; this branch stays
44
+ // for parity with the services if a required var is ever added.
45
+ parsed = exports.envSchema.parse(raw);
46
+ }
47
+ else {
48
+ const issues = result.error.issues.map((issue) => ` - ${issue.path.join('.') || '(root)'}: ${issue.message}`).join('\n');
49
+ process.stderr.write(`āŒ Invalid environment variables:\n${issues}\n`);
50
+ process.exit(1);
51
+ }
52
+ exports.env = parsed;
53
+ exports.default = exports.env;
@@ -20,14 +20,12 @@ const updateNotifier = async function ({ config, id }) {
20
20
  await globalConfig.set('versionCheckTime', now);
21
21
  if (config.version !== lastestVersion) {
22
22
  console.log(`
23
- ${color_1.default.cyan(`__ __ _____ _
24
- \\ \\ / / / ____| | |
25
- \\ \\_/ /__ _ __ | | ___ __| | ___
26
- \\ / _ \\ '_ \\| | / _ \\ / _\` |/ _ \\
27
- | | __/ |_) | |___| (_) | (_| | __/
28
- |_|\\___| .__/ \\_____\\___/ \\__,_|\\___|
29
- | |
30
- |_|`)}
23
+ ${color_1.default.cyan(` ______ _ _ _ _____ _
24
+ | ____| | | (_) | |/ ____| | |
25
+ | |__ __ _ ___| |_ ___ _ __ _ __ _| | | ___ __| | ___
26
+ | __/ _\` |/ __| __/ _ \\| '__| |/ _\` | | | / _ \\ / _\` |/ _ \\
27
+ | | | (_| | (__| || (_) | | | | (_| | | |___| (_) | (_| | __/
28
+ |_| \\__,_|\\___|\\__\\___/|_| |_|\\__,_|_|\\_____\\___/ \\__,_|\\___|`)}
31
29
 
32
30
  Update available ${color_1.default.red(config.version)} → ${color_1.default.greenBright(lastestVersion)}
33
31
  Run ${color_1.default.bold('`npm i -g @factorialco/fcode-cli`')} to update
@@ -90,6 +90,25 @@ class FcodeGlobal:
90
90
  self.variables = variables
91
91
  self.processes = Processes()
92
92
 
93
+ def send_mail(self, to=None, subject=None, text=None, html=None):
94
+ # No manager locally: log the email contents instead of sending, so devs
95
+ # can see exactly what would be delivered when run in the cloud.
96
+ import json
97
+ import logging
98
+
99
+ logger = logging.getLogger("fcode_logger")
100
+ logger.info(
101
+ "[fcode] send_mail (local run) — not sent, logging contents below:"
102
+ )
103
+ logger.info(
104
+ json.dumps(
105
+ {"to": to, "subject": subject, "text": text, "html": html},
106
+ indent=2,
107
+ )
108
+ )
109
+ accepted = to if isinstance(to, list) else ([to] if to else [])
110
+ return {"messageId": "local-run", "accepted": accepted, "rejected": []}
111
+
93
112
  def import_module(self, module_name, version=None):
94
113
  module_path = module_name
95
114
  if version:
@@ -60,6 +60,9 @@ exports.default = {
60
60
  case types_1.ResourceStatus.OUT_OF_DATE:
61
61
  outOfDateResources.push(remoteResource);
62
62
  break;
63
+ case types_1.ResourceStatus.SENSITIVITY_CHANGED:
64
+ outOfDateResources.push(remoteResource);
65
+ break;
63
66
  case types_1.ResourceStatus.REMOTE_REMOVED:
64
67
  remoteRemovedResources.push(localResource);
65
68
  break;
@@ -41,6 +41,12 @@
41
41
  "name": "skipSkillsSetup",
42
42
  "allowNo": false,
43
43
  "type": "boolean"
44
+ },
45
+ "autoSkillsSetup": {
46
+ "description": "Install the Factorial Code agent skills non-interactively (defaults, project scope) instead of prompting",
47
+ "name": "autoSkillsSetup",
48
+ "allowNo": false,
49
+ "type": "boolean"
44
50
  }
45
51
  },
46
52
  "hasDynamicHelp": false,
@@ -1779,5 +1785,5 @@
1779
1785
  ]
1780
1786
  }
1781
1787
  },
1782
- "version": "1.1.1"
1788
+ "version": "1.3.0"
1783
1789
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@factorialco/fcode-cli",
3
- "version": "1.1.1",
3
+ "version": "1.3.0",
4
4
  "packageManager": "pnpm@11.7.0",
5
5
  "description": "Factorial Code Command Line Interface",
6
6
  "homepage": "https://code.factorialhr.com/docs/cli/",
@@ -40,7 +40,8 @@
40
40
  "open": "^8.4.2",
41
41
  "openid-client": "^5.6.5",
42
42
  "string-width": "^4.2.3",
43
- "strip-comments": "^2.0.1"
43
+ "strip-comments": "^2.0.1",
44
+ "zod": "^4.4.3"
44
45
  },
45
46
  "peerDependencies": {
46
47
  "graphql-request": "^7.4.0",